Files
MentOS/mentos/src/fs/ext2.c
T
2021-12-23 17:36:23 +01:00

2615 lines
105 KiB
C

/// @file ext2.c
/// @author Enrico Fraccaroli (enry.frak@gmail.com)
/// @brief EXT2 driver.
/// @version 0.1
/// @date 2021-12-13
/// @copyright (c) 2014-2021 This file is distributed under the MIT License.
/// See LICENSE.md for details.
#include "fs/ext2.h"
// Include the kernel log levels.
#include "sys/kernel_levels.h"
// Change the header.
#define __DEBUG_HEADER__ "[EXT2 ]"
// Set the log level.
#define __DEBUG_LEVEL__ LOGLEVEL_DEBUG
#include "process/scheduler.h"
#include "process/process.h"
#include "klib/spinlock.h"
#include "fs/vfs_types.h"
#include "sys/errno.h"
#include "io/debug.h"
#include "fs/vfs.h"
#include "assert.h"
#include "libgen.h"
#include "string.h"
#include "stdio.h"
#include "fcntl.h"
#define EXT2_SUPERBLOCK_MAGIC 0xEF53 ///< Magic value used to identify an ext2 filesystem.
#define EXT2_INDIRECT_BLOCKS 12 ///< Amount of indirect blocks in an inode.
#define EXT2_PATH_MAX 4096 ///< Maximum length of a pathname.
#define EXT2_MAX_SYMLINK_COUNT 8 ///< Maximum nesting of symlinks, used to prevent a loop.
#define EXT2_NAME_LEN 255 ///< The lenght of names inside directory entries.
// File types.
#define EXT2_S_IFMT 0xF000 ///< Format mask
#define EXT2_S_IFSOCK 0xC000 ///< Socket
#define EXT2_S_IFLNK 0xA000 ///< Symbolic link
#define EXT2_S_IFREG 0x8000 ///< Regular file
#define EXT2_S_IFBLK 0x6000 ///< Block device
#define EXT2_S_IFDIR 0x4000 ///< Directory
#define EXT2_S_IFCHR 0x2000 ///< Character device
#define EXT2_S_IFIFO 0x1000 ///< Fifo
// Permissions bit.
#define EXT2_S_ISUID 0x0800 ///< SUID
#define EXT2_S_ISGID 0x0400 ///< SGID
#define EXT2_S_ISVTX 0x0200 ///< Sticky Bit
#define EXT2_S_IRWXU 0x01C0 ///< rwx------- : User can read/write/execute
#define EXT2_S_IRUSR 0x0100 ///< -r-------- : User can read
#define EXT2_S_IWUSR 0x0080 ///< --w------- : User can write
#define EXT2_S_IXUSR 0x0040 ///< ---x------ : User can execute
#define EXT2_S_IRWXG 0x0038 ///< ----rwx--- : Group can read/write/execute
#define EXT2_S_IRGRP 0x0020 ///< ----r----- : Group can read
#define EXT2_S_IWGRP 0x0010 ///< -----w---- : Group can write
#define EXT2_S_IXGRP 0x0008 ///< ------x--- : Group can execute
#define EXT2_S_IRWXO 0x0007 ///< -------rwx : Others can read/write/execute
#define EXT2_S_IROTH 0x0004 ///< -------r-- : Others can read
#define EXT2_S_IWOTH 0x0002 ///< --------w- : Others can write
#define EXT2_S_IXOTH 0x0001 ///< ---------x : Others can execute
// ============================================================================
// Data Structures
// ============================================================================
typedef enum ext2_file_type_t {
ext2_file_type_unknown, ///< Unknown type.
ext2_file_type_regular_file, ///< Regular file.
ext2_file_type_directory, ///< Directory.
ext2_file_type_character_device, ///< Character device.
ext2_file_type_block_device, ///< Block device.
ext2_file_type_named_pipe, ///< Named pipe.
ext2_file_type_socket, ///< Socket
ext2_file_type_symbolic_link ///< Symbolic link.
} ext2_file_type_t;
typedef enum ext2_block_status_t {
ext2_block_status_free = 0, ///< The block is free.
ext2_block_status_occupied = 1 ///< The block is occupied.
} ext2_block_status_t;
/// @brief The superblock contains all the information about the configuration
/// of the filesystem.
/// @details The primary copy of the superblock is stored at an offset of 1024
/// bytes from the start of the device, and it is essential to mounting the
/// filesystem. Since it is so important, backup copies of the superblock are
/// stored in block groups throughout the filesystem.
typedef struct ext2_superblock_t {
/// @brief Total number of inodes in file system.
uint32_t inodes_count;
/// @brief Total number of blocks in file system
uint32_t blocks_count;
/// @brief Number of blocks reserved for superuser.
uint32_t r_blocks_count;
/// @brief Total number of unallocated blocks.
uint32_t free_blocks_count;
/// @brief Total number of unallocated inodes.
uint32_t free_inodes_count;
/// @brief Block number of the block containing the superblock.
uint32_t first_data_block;
/// @brief The number to shift 1024 to the left by to obtain the block size
/// (log2 (block size) - 10).
uint32_t log_block_size;
/// @brief The number to shift 1024 to the left by to obtain the fragment
/// size (log2 (fragment size) - 10).
uint32_t log_frag_size;
/// @brief Number of blocks in each block group.
uint32_t blocks_per_group;
/// @brief Number of fragments in each block group.
uint32_t frags_per_group;
/// @brief Number of inodes in each block group.
uint32_t inodes_per_group;
/// @brief Last mount time (in POSIX time).
uint32_t mtime;
/// @brief Last written time (in POSIX time).
uint32_t wtime;
/// @brief Number of times the volume has been mounted since its last
/// consistency check (fsck).
uint16_t mnt_count;
/// @brief Number of mounts allowed before a consistency check (fsck) must
/// be done.
uint16_t max_mnt_count;
/// @brief Ext2 signature (0xef53), used to help confirm the presence of
/// Ext2 on a volume.
uint16_t magic;
/// @brief File system state.
uint16_t state;
/// @brief What to do when an error is detected.
uint16_t errors;
/// @brief Minor portion of version (combine with Major portion below to
/// construct full version field).
uint16_t minor_rev_level;
/// @brief POSIX time of last consistency check (fsck).
uint32_t lastcheck;
/// @brief Interval (in POSIX time) between forced consistency checks
/// (fsck).
uint32_t checkinterval;
/// @brief Operating system ID from which the filesystem on this volume was
/// created.
uint32_t creator_os;
/// @brief Major portion of version (combine with Minor portion above to
/// construct full version field).
uint32_t rev_level;
/// @brief User ID that can use reserved blocks.
uint16_t def_resuid;
/// @brief Group ID that can use reserved blocks.
uint16_t def_resgid;
// == Extended Superblock Fields ==========================================
/// @brief First non-reserved inode in file system. (In versions < 1.0, this
/// is fixed as 11)
uint32_t first_ino;
/// @brief Size of each inode structure in bytes. (In versions < 1.0, this
/// is fixed as 128)
uint16_t inode_size;
/// @brief Block group that this superblock is part of (if backup copy).
uint16_t block_group_nr;
/// @brief Optional features present (features that are not required to read
/// or write, but usually result in a performance increase).
uint32_t feature_compat;
/// @brief Required features present (features that are required to be
/// supported to read or write)
uint32_t feature_incompat;
/// @brief Features that if not supported, the volume must be mounted
/// read-only).
uint32_t feature_ro_compat;
/// @brief File system ID (what is output by blkid).
uint8_t uuid[16];
/// @brief Volume name (C-style string: characters terminated by a 0 byte).
uint8_t volume_name[16];
/// @brief Path volume was last mounted to (C-style string: characters
/// terminated by a 0 byte).
uint8_t last_mounted[64];
/// @brief Compression algorithms used.
uint32_t algo_bitmap;
// == Performance Hints ===================================================
/// @brief Number of blocks to preallocate for files.
uint8_t prealloc_blocks;
/// @brief Number of blocks to preallocate for directories.
uint8_t prealloc_dir_blocks;
/// @brief (Unused)
uint16_t padding0;
// == Journaling Support ==================================================
/// @brief Journal ID
uint8_t journal_uuid[16];
/// @brief Inode number of journal file.
uint32_t journal_inum;
/// @brief Device number of journal file.
uint32_t jounral_dev;
/// @brief Start of list of inodes to delete.
uint32_t last_orphan;
// == Directory Indexing Support ==========================================
/// @brief HTree hash seed.
uint32_t hash_seed[4];
/// @brief Ddefault hash version to use.
uint8_t def_hash_version;
/// @brief Padding.
uint16_t padding1;
/// @brief Padding.
uint8_t padding2;
// == Other Options =======================================================
/// @brief The default mount options for the file system.
uint32_t default_mount_options;
/// @brief The ID of the first meta block group.
uint32_t first_meta_block_group_id;
/// @brief Reserved.
uint8_t reserved[760];
} ext2_superblock_t;
/// @brief
typedef struct ext2_group_descriptor_t {
/// @brief The block number of the block bitmap for this Block Group
uint32_t block_bitmap;
/// @brief The block number of the inode allocation bitmap for this Block Group.
uint32_t inode_bitmap;
/// @brief The block number of the starting block for the inode table for this Block Group.
uint32_t inode_table;
/// @brief Number of free blocks.
uint16_t free_blocks_count;
/// @brief Number of free inodes.
uint16_t free_inodes_count;
/// @brief Number of used directories.
uint16_t used_dirs_count;
/// @brief Padding.
uint16_t pad;
/// @brief Reserved.
uint32_t reserved[3];
} ext2_group_descriptor_t;
/// @brief The ext2 inode.
typedef struct ext2_inode_t {
/// @brief File mode
uint16_t mode;
/// @brief The user identifiers of the owners.
uint16_t uid;
/// @brief The size of the file in bytes.
uint32_t size;
/// @brief The time that the inode was accessed.
uint32_t atime;
/// @brief The time that the inode was created.
uint32_t ctime;
/// @brief The time that the inode was modified the last time.
uint32_t mtime;
/// @brief The time that the inode was deleted.
uint32_t dtime;
/// @brief The group identifiers of the owners.
uint16_t gid;
/// @brief Number of hard links.
uint16_t links_count;
/// @brief Blocks count.
uint32_t blocks_count;
/// @brief File flags.
uint32_t flags;
/// @brief OS dependant value.
uint32_t osd1;
/// @brief
union blocks_t {
/// [60 byte]
struct blocks_data_t {
/// [48 byte]
uint32_t dir_blocks[EXT2_INDIRECT_BLOCKS];
/// [ 4 byte]
uint32_t indir_block;
/// [ 4 byte]
uint32_t doubly_indir_block;
/// [ 4 byte]
uint32_t trebly_indir_block;
} blocks;
/// [60 byte]
char symlink[60];
} data;
/// @brief Value used to indicate the file version (used by NFS).
uint32_t generation;
/// @brief Value indicating the block number containing the extended attributes.
uint32_t file_acl;
/// @brief For regular files this 32bit value contains the high 32 bits of the 64bit file size.
uint32_t dir_acl;
/// @brief Value indicating the location of the file fragment.
uint32_t fragment_addr;
/// @brief OS dependant structure.
uint32_t osd2[3];
} ext2_inode_t;
/// @brief The header of an ext2 directory entry.
typedef struct ext2_dirent_t {
/// Number of the inode that this directory entry points to.
uint32_t inode;
/// Length of this directory entry. Must be a multiple of 4.
uint16_t rec_len;
/// Length of the file name.
uint8_t name_len;
/// File type code.
uint8_t file_type;
/// File name.
char name[EXT2_NAME_LEN];
} ext2_dirent_t;
/// @brief The details regarding the filesystem.
typedef struct ext2_filesystem_t {
/// Pointer to the block device.
vfs_file_t *block_device;
/// Device superblock, contains important information.
ext2_superblock_t superblock;
/// Block Group Descriptor / Block groups.
ext2_group_descriptor_t *block_groups;
/// EXT2 memory cache for buffers.
kmem_cache_t *ext2_buffer_cache;
/// Root FS node (attached to mountpoint).
vfs_file_t *root;
/// List of opened files.
list_head opened_files;
/// Size of one block.
uint32_t block_size;
/// Number of inodes that fit in a block.
uint32_t inodes_per_block_count;
/// Number of blocks that fit in a block.
uint32_t blocks_per_block_count;
/// Number of blocks groups.
uint32_t block_groups_count;
/// Number of block pointers per block.
uint32_t pointers_per_block;
/// Index in terms of blocks where the BGDT starts.
uint32_t bgdt_start_block;
/// Index in terms of blocks where the BGDT ends.
uint32_t bgdt_end_block;
/// The number of blocks containing the BGDT
uint32_t bgdt_length;
/// Index of indirect blocks.
uint32_t indirect_blocks_index;
/// Index of doubly-indirect blocks.
uint32_t doubly_indirect_blocks_index;
/// Index of trebly-indirect blocks.
uint32_t trebly_indirect_blocks_index;
/// Spinlock for protecting filesystem operations.
spinlock_t spinlock;
} ext2_filesystem_t;
/// @brief Structure used when searching for a directory entry.
typedef struct ext2_direntry_search_t {
/// Pointer to the direntry where we store the search results.
ext2_dirent_t *direntry;
/// The inode of the parent directory.
ino_t parent_inode;
/// The index of the block where the direntry resides.
uint32_t block_index;
/// The offest of the direntry inside the block.
uint32_t block_offset;
} ext2_direntry_search_t;
// ============================================================================
// Forward Declaration of Functions
// ============================================================================
static ext2_block_status_t ext2_check_bitmap_bit(uint8_t *buffer, uint32_t index);
static void ext2_set_bitmap_bit(uint8_t *buffer, uint32_t index, ext2_block_status_t status);
static int ext2_read_superblock(ext2_filesystem_t *fs);
static int ext2_write_superblock(ext2_filesystem_t *fs);
static int ext2_read_block(ext2_filesystem_t *fs, uint32_t block_index, uint8_t *buffer);
static int ext2_write_block(ext2_filesystem_t *fs, uint32_t block_index, uint8_t *buffer);
static int ext2_read_bgdt(ext2_filesystem_t *fs);
static int ext2_write_bgdt(ext2_filesystem_t *fs);
static int ext2_read_inode(ext2_filesystem_t *fs, ext2_inode_t *inode, uint32_t inode_index);
static int ext2_write_inode(ext2_filesystem_t *fs, ext2_inode_t *inode, uint32_t inode_index);
static vfs_file_t *ext2_open(const char *path, int flags, mode_t mode);
static int ext2_unlink(const char *path);
static int ext2_close(vfs_file_t *file);
static ssize_t ext2_read(vfs_file_t *file, char *buffer, off_t offset, size_t nbyte);
static ssize_t ext2_write(vfs_file_t *file, const void *buffer, off_t offset, size_t nbyte);
static off_t ext2_lseek(vfs_file_t *file, off_t offset, int whence);
static int ext2_fstat(vfs_file_t *file, stat_t *stat);
static int ext2_ioctl(vfs_file_t *file, int request, void *data);
static int ext2_getdents(vfs_file_t *file, dirent_t *dirp, off_t doff, size_t count);
static int ext2_mkdir(const char *path, mode_t mode);
static int ext2_rmdir(const char *path);
static int ext2_stat(const char *path, stat_t *stat);
static vfs_file_t *ext2_mount(vfs_file_t *block_device, const char *path);
// ============================================================================
// Virtual FileSystem (VFS) Operaions
// ============================================================================
/// Filesystem general operations.
static vfs_sys_operations_t ext2_sys_operations = {
.mkdir_f = ext2_mkdir,
.rmdir_f = ext2_rmdir,
.stat_f = ext2_stat
};
/// Filesystem file operations.
static vfs_file_operations_t ext2_fs_operations = {
.open_f = ext2_open,
.unlink_f = ext2_unlink,
.close_f = ext2_close,
.read_f = ext2_read,
.write_f = ext2_write,
.lseek_f = ext2_lseek,
.stat_f = ext2_fstat,
.ioctl_f = ext2_ioctl,
.getdents_f = ext2_getdents
};
// ============================================================================
// Debugging Support Functions
// ============================================================================
/// @brief Turns an UUID to string.
/// @param uuid the UUID to turn to string.
/// @return the string representing the UUID.
static const char *uuid_to_string(uint8_t uuid[16])
{
static char s[33] = { 0 };
sprintf(s, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
uuid[0], uuid[1], uuid[2], uuid[3], uuid[4], uuid[5], uuid[6], uuid[7],
uuid[8], uuid[9], uuid[10], uuid[11], uuid[12], uuid[13], uuid[14], uuid[15]);
return s;
}
static int ext2_file_type_to_vfs_file_type(int ext2_type)
{
if (ext2_type == ext2_file_type_regular_file)
return DT_REG;
if (ext2_type == ext2_file_type_directory)
return DT_DIR;
if (ext2_type == ext2_file_type_character_device)
return DT_CHR;
if (ext2_type == ext2_file_type_block_device)
return DT_BLK;
if (ext2_type == ext2_file_type_named_pipe)
return DT_FIFO;
if (ext2_type == ext2_file_type_socket)
return DT_SOCK;
if (ext2_type == ext2_file_type_symbolic_link)
return DT_LNK;
return DT_UNKNOWN;
}
/// @brief Turns the time to string.
/// @param time the UNIX time to turn to string.
/// @return time turned to string.
static const char *time_to_string(uint32_t time)
{
static char s[250] = { 0 };
tm_t *tm = localtime(&time);
sprintf(s, "%2d/%2d %2d:%2d", tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min);
return s;
}
/// @brief Dumps on debugging output the superblock.
/// @param sb the object to dump.
static void ext2_dump_superblock(ext2_superblock_t *sb)
{
pr_debug("inodes_count : %d\n", sb->inodes_count);
pr_debug("blocks_count : %d\n", sb->blocks_count);
pr_debug("r_blocks_count : %d\n", sb->r_blocks_count);
pr_debug("free_blocks_count : %d\n", sb->free_blocks_count);
pr_debug("free_inodes_count : %d\n", sb->free_inodes_count);
pr_debug("first_data_block : %d\n", sb->first_data_block);
pr_debug("log_block_size : %d\n", sb->log_block_size);
pr_debug("log_frag_size : %d\n", sb->log_frag_size);
pr_debug("blocks_per_group : %d\n", sb->blocks_per_group);
pr_debug("frags_per_group : %d\n", sb->frags_per_group);
pr_debug("inodes_per_group : %d\n", sb->inodes_per_group);
pr_debug("mtime : %s\n", time_to_string(sb->mtime));
pr_debug("wtime : %s\n", time_to_string(sb->wtime));
pr_debug("mnt_count : %d\n", sb->mnt_count);
pr_debug("max_mnt_count : %d\n", sb->max_mnt_count);
pr_debug("magic : 0x%0x\n", sb->magic);
pr_debug("state : %d\n", sb->state);
pr_debug("errors : %d\n", sb->errors);
pr_debug("minor_rev_level : %d\n", sb->minor_rev_level);
pr_debug("lastcheck : %s\n", time_to_string(sb->lastcheck));
pr_debug("checkinterval : %d\n", sb->checkinterval);
pr_debug("creator_os : %d\n", sb->creator_os);
pr_debug("rev_level : %d\n", sb->rev_level);
pr_debug("def_resuid : %d\n", sb->def_resuid);
pr_debug("def_resgid : %d\n", sb->def_resgid);
pr_debug("first_ino : %d\n", sb->first_ino);
pr_debug("inode_size : %d\n", sb->inode_size);
pr_debug("block_group_nr : %d\n", sb->block_group_nr);
pr_debug("feature_compat : %d\n", sb->feature_compat);
pr_debug("feature_incompat : %d\n", sb->feature_incompat);
pr_debug("feature_ro_compat : %d\n", sb->feature_ro_compat);
pr_debug("uuid : %s\n", uuid_to_string(sb->uuid));
pr_debug("volume_name : %s\n", (char *)sb->volume_name);
pr_debug("last_mounted : %s\n", (char *)sb->last_mounted);
pr_debug("algo_bitmap : %d\n", sb->algo_bitmap);
pr_debug("prealloc_blocks : %d\n", sb->prealloc_blocks);
pr_debug("prealloc_dir_blocks : %d\n", sb->prealloc_dir_blocks);
pr_debug("journal_uuid : %s\n", uuid_to_string(sb->journal_uuid));
pr_debug("journal_inum : %d\n", sb->journal_inum);
pr_debug("jounral_dev : %d\n", sb->jounral_dev);
pr_debug("last_orphan : %d\n", sb->last_orphan);
pr_debug("hash_seed : %u %u %u %u\n", sb->hash_seed[0], sb->hash_seed[1], sb->hash_seed[2], sb->hash_seed[3]);
pr_debug("def_hash_version : %d\n", sb->def_hash_version);
pr_debug("default_mount_options : %d\n", sb->default_mount_options);
pr_debug("first_meta_bg : %d\n", sb->first_meta_block_group_id);
}
/// @brief Dumps on debugging output the group descriptor.
/// @param gd the object to dump.
static void ext2_dump_group_descriptor(ext2_group_descriptor_t *gd)
{
pr_debug("block_bitmap : %d\n", gd->block_bitmap);
pr_debug("inode_bitmap : %d\n", gd->inode_bitmap);
pr_debug("inode_table : %d\n", gd->inode_table);
pr_debug("free_blocks_count : %d\n", gd->free_blocks_count);
pr_debug("free_inodes_count : %d\n", gd->free_inodes_count);
pr_debug("used_dirs_count : %d\n", gd->used_dirs_count);
}
/// @brief Dumps on debugging output the inode.
/// @param inode the object to dump.
static void ext2_dump_inode(ext2_inode_t *inode)
{
pr_debug("mode : %u\n", inode->mode);
pr_debug("uid : %u\n", inode->uid);
pr_debug("size : %u\n", inode->size);
pr_debug("atime : %u\n", inode->atime);
pr_debug("ctime : %u\n", inode->ctime);
pr_debug("mtime : %u\n", inode->mtime);
pr_debug("dtime : %u\n", inode->dtime);
pr_debug("gid : %u\n", inode->gid);
pr_debug("links_count : %u\n", inode->links_count);
pr_debug("blocks_count : %u\n", inode->blocks_count);
pr_debug("flags : %u\n", inode->flags);
pr_debug("osd1 : %u\n", inode->osd1);
pr_debug("data : {\n");
for (int i = 0; i < EXT2_INDIRECT_BLOCKS; ++i)
pr_debug(" data.blocks.D[%2d] : %u\n", i, inode->data.blocks.dir_blocks[i]);
pr_debug(" data.blocks.IND_B : %u\n", inode->data.blocks.indir_block);
pr_debug(" data.blocks.DBL_B : %u\n", inode->data.blocks.doubly_indir_block);
pr_debug(" data.blocks.TRB_B : %u\n", inode->data.blocks.trebly_indir_block);
pr_debug(" data.symblink : %s\n", inode->data.symlink);
pr_debug("data : }\n");
pr_debug("generation : %u\n", inode->generation);
pr_debug("file_acl : %u\n", inode->file_acl);
pr_debug("dir_acl : %u\n", inode->dir_acl);
pr_debug("osd2[0] : %u\n", inode->osd2[0]);
pr_debug("osd2[1] : %u\n", inode->osd2[1]);
pr_debug("osd2[2] : %u\n", inode->osd2[2]);
}
/// @brief Dumps on debugging output the BGDT.
/// @param fs the filesystem of which we print the BGDT.
static void ext2_dump_bgdt(ext2_filesystem_t *fs)
{
// Allocate the cache.
uint8_t *cache = kmem_cache_alloc(fs->ext2_buffer_cache, GFP_KERNEL);
// Clean the cache.
memset(cache, 0, fs->block_size);
for (uint32_t i = 0; i < fs->block_groups_count; ++i) {
// Get the pointer to the current group descriptor.
ext2_group_descriptor_t *gd = &(fs->block_groups[i]);
pr_debug("Block Group Descriptor [%d] @ %d:\n", i, fs->bgdt_start_block + i * fs->superblock.blocks_per_group);
pr_debug(" Free Blocks : %4d of %d\n", gd->free_blocks_count, fs->superblock.blocks_per_group);
pr_debug(" Free Inodes : %4d of %d\n", gd->free_inodes_count, fs->superblock.inodes_per_group);
// Dump the block bitmap.
ext2_read_block(fs, gd->block_bitmap, cache);
pr_debug(" Block Bitmap at %d\n", gd->block_bitmap);
for (uint32_t j = 0; j < fs->block_size; ++j) {
if ((j % 8) == 0)
pr_debug(" Block index: %4d, Bitmap: %s\n", j / 8, dec_to_binary(cache[j / 8], 8));
if (!ext2_check_bitmap_bit(cache, j)) {
pr_debug(" First free block in group is in block %d, the linear index is %d\n", j / 8, j);
break;
}
}
// Dump the block bitmap.
ext2_read_block(fs, gd->inode_bitmap, cache);
pr_debug(" Inode Bitmap at %d\n", gd->inode_bitmap);
for (uint32_t j = 0; j < fs->block_size; ++j) {
if ((j % 8) == 0)
pr_debug(" Block index: %4d, Bitmap: %s\n", j / 8, dec_to_binary(cache[j / 8], 8));
if (!ext2_check_bitmap_bit(cache, j)) {
pr_debug(" First free block in group is in block %d, the linear index is %d\n", j / 8, j);
break;
}
}
}
kmem_cache_free(cache);
}
/// @brief Dumps on debugging output the filesystem.
/// @param fs the object to dump.
static void ext2_dump_filesystem(ext2_filesystem_t *fs)
{
pr_debug("block_device : 0x%x\n", fs->block_device);
pr_debug("superblock : 0x%x\n", fs->superblock);
pr_debug("block_groups : 0x%x\n", fs->block_groups);
pr_debug("root : 0x%x\n", fs->root);
pr_debug("block_size : %d\n", fs->block_size);
pr_debug("inodes_per_block_count: %d\n", fs->inodes_per_block_count);
pr_debug("blocks_per_block_count: %d\n", fs->blocks_per_block_count);
pr_debug("block_groups_count : %d\n", fs->block_groups_count);
pr_debug("pointers_per_block : %d\n", fs->pointers_per_block);
pr_debug("bgdt_start_block : %d\n", fs->bgdt_start_block);
pr_debug("bgdt_end_block : %d\n", fs->bgdt_end_block);
pr_debug("bgdt_length : %d\n", fs->bgdt_length);
}
// ============================================================================
// EXT2 Core Functions
// ============================================================================
/// @brief Determining which block group contains an inode.
/// @param fs the ext2 filesystem structure.
/// @param inode_index the inode index.
/// @return the group index.
/// @details Remember that inode addressing starts from 1.
static uint32_t ext2_get_group_index_from_inode(ext2_filesystem_t *fs, uint32_t inode_index)
{
return (inode_index - 1) / fs->superblock.inodes_per_group;
}
/// @brief Determining the offest of the inode inside the block group.
/// @param fs the ext2 filesystem structure.
/// @param inode_index the inode index.
/// @return the offset of the inode inside the group.
/// @details Remember that inode addressing starts from 1.
static uint32_t ext2_get_inode_offest_in_group(ext2_filesystem_t *fs, uint32_t inode_index)
{
return (inode_index - 1) % fs->superblock.inodes_per_group;
}
/// @brief Determines which block contains our inode.
/// @param fs the ext2 filesystem structure.
/// @param inode_offset the inode offset inside the group.
/// @return which block contains our inode.
static uint32_t ext2_get_block_index_from_inode_offset(ext2_filesystem_t *fs, uint32_t inode_offset)
{
return (inode_offset * fs->superblock.inode_size) / fs->block_size;
}
/// @brief Cheks if the bit at the given linear index is free.
/// @param buffer the buffer containing the bitmap
/// @param linear_index the linear index we want to check.
/// @return if the bit is 0 or 1.
/// @details
/// How we access the specific bits inside the bitmap takes inspiration from the
/// mailman's algorithm.
static ext2_block_status_t ext2_check_bitmap_bit(uint8_t *buffer, uint32_t linear_index)
{
return (ext2_block_status_t)(bit_check(buffer[linear_index / 8], linear_index % 8) != 0);
}
/// @brief Sets the bit at the given linear index accordingly to `status`.
/// @param buffer the buffer containing the bitmap
/// @param linear_index the linear index we want to check.
/// @param status the new status of the block (free|occupied).
static void ext2_set_bitmap_bit(uint8_t *buffer, uint32_t linear_index, ext2_block_status_t status)
{
if (status == ext2_block_status_occupied)
bit_set_assign(buffer[linear_index / 8], linear_index % 8);
else
bit_clear_assign(buffer[linear_index / 8], linear_index % 8);
}
/// @brief Searches for a free inode inside the group data loaded inside the cache.
/// @param fs the ext2 filesystem structure.
/// @param cache the cache from which we read the bgdt data.
/// @param linear_index the output variable where we store the linear indes to the free inode.
/// @return true if we found a free inode, false otherwise.
static inline bool_t ext2_find_free_inode_in_group(
ext2_filesystem_t *fs,
uint8_t *cache,
uint32_t *linear_index,
bool_t skip_reserved)
{
for ((*linear_index) = 0; (*linear_index) < fs->superblock.inodes_per_group; ++(*linear_index)) {
// If we need to skip the reserved inodes, we skip the round if the
// index is that of a reserved inode (superblock.first_ino).
if (skip_reserved && ((*linear_index) < fs->superblock.first_ino))
continue;
// Check if the entry is free.
if (!ext2_check_bitmap_bit(cache, *linear_index))
return true;
}
return false;
}
/// @brief Searches for a free inode inside the Block Group Descriptor Table (BGDT).
/// @param fs the ext2 filesystem structure.
/// @param cache the cache from which we read the bgdt data.
/// @param group_index the output variable where we store the group index.
/// @param linear_index the output variable where we store the linear indes to the free inode.
/// @return true if we found a free inode, false otherwise.
static inline bool_t ext2_find_free_inode(
ext2_filesystem_t *fs,
uint8_t *cache,
uint32_t *group_index,
uint32_t *linear_index,
uint32_t preferred_group)
{
// If we received a preference, try to find a free inode in that specific group.
if (preferred_group != 0) {
// Set the group index to the preferred group.
(*group_index) = preferred_group;
// Find the first free inode. We need to ask to skip reserved inodes,
// only if we are in group 0.
if (ext2_find_free_inode_in_group(fs, cache, linear_index, (*group_index) == 0))
return true;
}
// Get the group and bit index of the first free block.
for ((*group_index) = 0; (*group_index) < fs->block_groups_count; ++(*group_index)) {
// Check if there are free inodes in this block group.
if (fs->block_groups[(*group_index)].free_inodes_count > 0) {
// Read the block bitmap.
if (ext2_read_block(fs, fs->block_groups[(*group_index)].inode_bitmap, cache) < 0) {
pr_err("Failed to read the inode bitmap for group `%d`.\n", (*group_index));
return false;
}
// Find the first free inode. We need to ask to skip reserved
// inodes, only if we are in group 0.
if (ext2_find_free_inode_in_group(fs, cache, linear_index, (*group_index) == 0))
return true;
}
}
return false;
}
/// @brief Searches for a free block inside the group data loaded inside the cache.
/// @param fs the ext2 filesystem structure.
/// @param cache the cache from which we read the bgdt data.
/// @param group_index the output variable where we store the group index.
/// @param linear_index the output variable where we store the linear indes to the free block.
/// @return true if we found a free block, false otherwise.
static inline bool_t ext2_find_free_block_in_group(ext2_filesystem_t *fs, uint8_t *cache, uint32_t *linear_index)
{
for ((*linear_index) = 0; (*linear_index) < fs->superblock.blocks_per_group; ++(*linear_index)) {
// Check if the entry is free.
if (!ext2_check_bitmap_bit(cache, *linear_index))
return true;
}
return false;
}
/// @brief Searches for a free block.
/// @param fs the ext2 filesystem structure.
/// @param cache the cache from which we read the bgdt data.
/// @param linear_index the output variable where we store the linear indes to the free block.
/// @return true if we found a free block, false otherwise.
static inline bool_t ext2_find_free_block(
ext2_filesystem_t *fs,
uint8_t *cache,
uint32_t *group_index,
uint32_t *linear_index)
{
// Get the group and bit index of the first free block.
for ((*group_index) = 0; (*group_index) < fs->block_groups_count; ++(*group_index)) {
// Check if there are free blocks in this block group.
if (fs->block_groups[(*group_index)].free_blocks_count > 0) {
// Read the block bitmap.
if (ext2_read_block(fs, fs->block_groups[(*group_index)].block_bitmap, cache) < 0) {
pr_err("Failed to read the block bitmap for group `%d`.\n", (*group_index));
return false;
}
// Find the first free block.
if (ext2_find_free_block_in_group(fs, cache, linear_index))
return true;
}
}
return false;
}
/// @brief Reads the superblock from the block device associated with this filesystem.
/// @param fs the ext2 filesystem structure.
/// @return the amount of data we read, or negative value for an error.
static int ext2_read_superblock(ext2_filesystem_t *fs)
{
pr_debug("Read superblock for EXT2 filesystem (0x%x)\n", fs);
return vfs_read(fs->block_device, &fs->superblock, 1024, sizeof(ext2_superblock_t));
}
/// @brief Writes the superblock on the block device associated with this filesystem.
/// @param fs the ext2 filesystem structure.
/// @return the amount of data we wrote, or negative value for an error.
static int ext2_write_superblock(ext2_filesystem_t *fs)
{
pr_debug("Write superblock for EXT2 filesystem (0x%x)\n", fs);
return vfs_write(fs->block_device, &fs->superblock, 1024, sizeof(ext2_superblock_t));
}
/// @brief Read a block from the block device associated with this filesystem.
/// @param fs the ext2 filesystem structure.
/// @param block_index the index of the block we want to read.
/// @param buffer the buffer where the content will be placed.
/// @return the amount of data we read, or negative value for an error.
static int ext2_read_block(ext2_filesystem_t *fs, uint32_t block_index, uint8_t *buffer)
{
//pr_debug("Read block %4d for EXT2 filesystem (0x%x)\n", block_index, fs);
if (block_index == 0) {
pr_err("You are trying to read an invalid block index (%d).\n", block_index);
return -1;
}
if (buffer == NULL) {
pr_err("You are trying to read with a NULL buffer.\n");
return -1;
}
return vfs_read(fs->block_device, buffer, block_index * fs->block_size, fs->block_size);
}
/// @brief Writes a block on the block device associated with this filesystem.
/// @param fs the ext2 filesystem structure.
/// @param block_index the index of the block we want to read.
/// @param buffer the buffer where the content will be placed.
/// @return the amount of data we wrote, or negative value for an error.
static int ext2_write_block(ext2_filesystem_t *fs, uint32_t block_index, uint8_t *buffer)
{
//pr_debug("Write block %4d for EXT2 filesystem (0x%x)\n", block_index, fs);
if (block_index == 0) {
pr_err("You are trying to write on an invalid block index (%d).\n", block_index);
return -1;
}
if (buffer == NULL) {
pr_err("You are trying to write with a NULL buffer.\n");
return -1;
}
return vfs_write(fs->block_device, buffer, block_index * fs->block_size, fs->block_size);
}
/// @brief Reads the Block Group Descriptor Table (BGDT) from the block device associated with this filesystem.
/// @param fs the ext2 filesystem structure.
/// @return 0 on success, -1 on failure.
static int ext2_read_bgdt(ext2_filesystem_t *fs)
{
pr_debug("Read BGDT for EXT2 filesystem (0x%x)\n", fs);
if (fs->block_groups) {
for (uint32_t i = 0; i < fs->bgdt_length; ++i)
ext2_read_block(fs, fs->bgdt_start_block + i, (uint8_t *)((uintptr_t)fs->block_groups + (fs->block_size * i)));
return 0;
}
pr_err("The `block_groups` list is not initialized.\n");
return -1;
}
/// @brief Writes the Block Group Descriptor Table (BGDT) to the block device associated with this filesystem.
/// @param fs the ext2 filesystem structure.
/// @return 0 on success, -1 on failure.
static int ext2_write_bgdt(ext2_filesystem_t *fs)
{
pr_debug("Write BGDT for EXT2 filesystem (0x%x)\n", fs);
if (fs->block_groups) {
for (uint32_t i = 0; i < fs->bgdt_length; ++i)
ext2_write_block(fs, fs->bgdt_start_block + i, (uint8_t *)((uintptr_t)fs->block_groups + (fs->block_size * i)));
return 0;
}
pr_err("The `block_groups` list is not initialized.\n");
return -1;
}
/// @brief Reads an inode.
/// @param fs the filesystem.
/// @param inode the inode which we are working with.
/// @param inode_index The index of the inode.
/// @return 0 on success, -1 on failure.
static int ext2_read_inode(ext2_filesystem_t *fs, ext2_inode_t *inode, uint32_t inode_index)
{
if (inode_index == 0) {
pr_err("You are trying to read an invalid inode index (%d).\n", inode_index);
return -1;
}
// Retrieve the group index.
uint32_t group_index = ext2_get_group_index_from_inode(fs, inode_index);
if (group_index > fs->block_groups_count) {
pr_err("Invalid group index computed from inode index `%d`.\n", inode_index);
return -1;
}
// Get the index of the inode inside the group.
uint32_t offset = ext2_get_inode_offest_in_group(fs, inode_index);
// Get the block offest.
uint32_t block = ext2_get_block_index_from_inode_offset(fs, offset);
// Get the real inode offset inside the block.
offset %= fs->inodes_per_block_count;
// Allocate the cache.
uint8_t *cache = kmem_cache_alloc(fs->ext2_buffer_cache, GFP_KERNEL);
// Clean the cache.
memset(cache, 0, fs->block_size);
// Read the block containing the inode table.
ext2_read_block(fs, fs->block_groups[group_index].inode_table + block, cache);
// Save the inode content.
memcpy(inode, (ext2_inode_t *)((uintptr_t)cache + (offset * fs->superblock.inode_size)), fs->superblock.inode_size);
// Free the cache.
kmem_cache_free(cache);
return 0;
}
/// @brief Writes the inode.
/// @param fs the filesystem.
/// @param inode the inode which we are working with.
/// @param inode_index The index of the inode.
/// @return 0 on success, -1 on failure.
static int ext2_write_inode(ext2_filesystem_t *fs, ext2_inode_t *inode, uint32_t inode_index)
{
if (inode_index == 0) {
pr_err("You are trying to read an invalid inode index (%d).\n", inode_index);
return -1;
}
// Retrieve the group index.
uint32_t group_index = ext2_get_group_index_from_inode(fs, inode_index);
if (group_index > fs->block_groups_count) {
pr_err("Invalid group index computed from inode index `%d`.\n", inode_index);
return -1;
}
// Get the offset of the inode inside the group.
uint32_t offset = ext2_get_inode_offest_in_group(fs, inode_index);
// Get the block offest.
uint32_t block = ext2_get_block_index_from_inode_offset(fs, offset);
// Get the real inode offset inside the block.
offset %= fs->inodes_per_block_count;
// Allocate the cache.
uint8_t *cache = kmem_cache_alloc(fs->ext2_buffer_cache, GFP_KERNEL);
// Clean the cache.
memset(cache, 0, fs->block_size);
// Read the block containing the inode table.
ext2_read_block(fs, fs->block_groups[group_index].inode_table + block, cache);
// Write the inode.
memcpy((ext2_inode_t *)((uintptr_t)cache + (offset * fs->superblock.inode_size)), inode, fs->superblock.inode_size);
// Write back the block.
ext2_write_block(fs, fs->block_groups[group_index].inode_table + block, cache);
// Free the cache.
kmem_cache_free(cache);
return 0;
}
/// @brief Allocate a new inode.
/// @param fs the filesystem.
/// @param preferred_group the preferred group.
/// @return index of the inode.
/// @details
/// Here are the rules used to allocate new inodes:
/// - the inode for a new file is allocated in the same group of the inode of
/// its parent directory.
/// - inodes are allocated equally between groups.
static int ext2_allocate_inode(ext2_filesystem_t *fs, unsigned preferred_group)
{
uint32_t group_index = 0, linear_index = 0, inode_index = 0;
// Lock the filesystem.
spinlock_lock(&fs->spinlock);
// Allocate the cache.
uint8_t *cache = kmem_cache_alloc(fs->ext2_buffer_cache, GFP_KERNEL);
// Clean the cache.
memset(cache, 0, fs->block_size);
// Search for a free inode.
if (!ext2_find_free_inode(fs, cache, &group_index, &linear_index, preferred_group)) {
pr_warning("Failed to find a free inode.\n");
// Unlock the filesystem.
spinlock_unlock(&fs->spinlock);
// Free the cache.
kmem_cache_free(cache);
return 0;
}
// Compute the inode index.
inode_index = (group_index * fs->superblock.inodes_per_group) + linear_index + 1U;
// Set the inode as occupied.
ext2_set_bitmap_bit(cache, linear_index, ext2_block_status_occupied);
// Write back the inode bitmap.
ext2_write_block(fs, fs->block_groups[group_index].inode_bitmap, cache);
// Free the cache.
kmem_cache_free(cache);
// Reduce the number of free inodes.
fs->block_groups[group_index].free_inodes_count -= 1;
// Update the bgdt.
ext2_write_bgdt(fs);
// Reduce the number of inodes inside the superblock.
fs->superblock.free_inodes_count -= 1;
// Update the superblock.
ext2_write_superblock(fs);
// Unlock the filesystem.
spinlock_unlock(&fs->spinlock);
// Return the inode.
return inode_index;
}
/// @brief Allocates a new block.
/// @param fs the filesystem.
/// @return 0 on failure, or the index of the new block on success.
static uint32_t ext2_allocate_block(ext2_filesystem_t *fs)
{
uint32_t group_index = 0, linear_index = 0, block_index = 0;
// Lock the filesystem.
spinlock_lock(&fs->spinlock);
// Allocate the cache.
uint8_t *cache = kmem_cache_alloc(fs->ext2_buffer_cache, GFP_KERNEL);
// Clean the cache.
memset(cache, 0, fs->block_size);
// Search for a free block.
if (!ext2_find_free_block(fs, cache, &group_index, &linear_index)) {
pr_warning("Failed to find a free block.\n");
// Unlock the filesystem.
spinlock_unlock(&fs->spinlock);
// Free the cache.
kmem_cache_free(cache);
return 0;
}
// Compute the block index.
block_index = (group_index * fs->superblock.blocks_per_group) + linear_index;
// Set the block as occupied.
ext2_set_bitmap_bit(cache, linear_index, ext2_block_status_occupied);
// Update the bitmap.
ext2_write_block(fs, fs->block_groups[group_index].block_bitmap, cache);
// Decrease the number of free blocks inside the BGDT entry.
fs->block_groups[group_index].free_blocks_count -= 1;
// Update the BGDT.
ext2_write_bgdt(fs);
// Decrease the number of free blocks inside the superblock.
fs->superblock.free_blocks_count -= 1;
// Update the superblock.
ext2_write_superblock(fs);
// Empty out the new block.
memset(cache, 0, fs->block_size);
ext2_write_block(fs, block_index, cache);
// Free the cache.
kmem_cache_free(cache);
// Unlock the spinlock.
spinlock_unlock(&fs->spinlock);
return block_index;
}
/// @brief Sets the real block index based on the block index inside an inode.
/// @param fs the filesystem.
/// @param inode the inode which we are working with.
/// @param block_index the block index inside the inode.
/// @param real_index the real block number.
/// @return 0 on success, a negative value on failure.
static int ext2_set_real_block_index(ext2_filesystem_t *fs, ext2_inode_t *inode, uint32_t inode_index, uint32_t block_index, uint32_t real_index)
{
// Set the direct block pointer.
if (block_index < EXT2_INDIRECT_BLOCKS) {
inode->data.blocks.dir_blocks[block_index] = real_index;
return 0;
}
// Check if the index is among the indirect blocks.
if (block_index < fs->indirect_blocks_index) {
// Compute the indirect indices.
uint32_t a = block_index - EXT2_INDIRECT_BLOCKS;
// Check that the indirect block points to a valid block.
if (!inode->data.blocks.indir_block) {
// Allocate a new block.
uint32_t new_block_index = ext2_allocate_block(fs);
if (new_block_index == 0)
return -1;
// Update the index.
inode->data.blocks.indir_block = new_block_index;
// Update the inode.
if (ext2_write_inode(fs, inode, inode_index) == -1)
return -1;
}
// Allocate the cache.
uint8_t *cache = kmem_cache_alloc(fs->ext2_buffer_cache, GFP_KERNEL);
// Clean the cache.
memset(cache, 0, fs->block_size);
// Read the indirect block (which contains pointers to the next set of blocks).
ext2_read_block(fs, inode->data.blocks.indir_block, cache);
// Write the index inside the final block.
((uint32_t *)cache)[a] = real_index;
// Write back the indirect block.
ext2_read_block(fs, inode->data.blocks.indir_block, cache);
// Free the cache.
kmem_cache_free(cache);
return 0;
}
// For simplicity.
uint32_t p1 = fs->pointers_per_block, p2 = fs->pointers_per_block * fs->pointers_per_block;
// Check if the index is among the doubly-indirect blocks.
if (block_index < fs->doubly_indirect_blocks_index) {
// Compute the indirect indices.
uint32_t a = block_index - EXT2_INDIRECT_BLOCKS;
uint32_t b = a - p1;
uint32_t c = b / p1;
uint32_t d = b - (c * p1);
// Check that the indirect block points to a valid block.
if (!inode->data.blocks.doubly_indir_block) {
// Allocate a new block.
uint32_t new_block_index = ext2_allocate_block(fs);
if (new_block_index == 0)
return -1;
// Update the index.
inode->data.blocks.doubly_indir_block = new_block_index;
// Update the inode.
if (ext2_write_inode(fs, inode, inode_index) == -1)
return -1;
}
// Allocate the cache.
uint8_t *cache = kmem_cache_alloc(fs->ext2_buffer_cache, GFP_KERNEL);
// Clean the cache.
memset(cache, 0, fs->block_size);
// Read the doubly-indirect block (which contains pointers to indirect blocks).
ext2_read_block(fs, inode->data.blocks.doubly_indir_block, cache);
// Check that the indirect block points to a valid block.
if (!((uint32_t *)cache)[c]) {
// Allocate a new block.
uint32_t new_block_index = ext2_allocate_block(fs);
if (new_block_index == 0) {
// Free the cache.
kmem_cache_free(cache);
return -1;
}
// Update the index.
((uint32_t *)cache)[c] = new_block_index;
// Write the doubly-indirect block back.
ext2_write_block(fs, inode->data.blocks.doubly_indir_block, cache);
}
// Compute the index inside the indirect block.
ext2_read_block(fs, ((uint32_t *)cache)[c], cache);
// Write the index inside the final block.
((uint32_t *)cache)[d] = real_index;
// Write back the indirect block.
ext2_read_block(fs, ((uint32_t *)cache)[c], cache);
// Free the cache.
kmem_cache_free(cache);
return 0;
}
// Check if the index is among the trebly-indirect blocks.
if (block_index < fs->trebly_indirect_blocks_index) {
// Compute the indirect indices.
uint32_t a = block_index - EXT2_INDIRECT_BLOCKS;
uint32_t b = a - p1;
uint32_t c = b - p2;
uint32_t d = c / p2;
uint32_t e = c - (d * p2);
uint32_t f = e / p1;
uint32_t g = e - (f * p1);
uint32_t block_index_save;
// Check that the indirect block points to a valid block.
if (!inode->data.blocks.trebly_indir_block) {
// Allocate a new block.
uint32_t new_block_index = ext2_allocate_block(fs);
if (new_block_index == 0)
return -1;
// Update the index.
inode->data.blocks.trebly_indir_block = new_block_index;
// Update the inode.
if (ext2_write_inode(fs, inode, inode_index) == -1)
return -1;
}
// Allocate the cache.
uint8_t *cache = kmem_cache_alloc(fs->ext2_buffer_cache, GFP_KERNEL);
// Clean the cache.
memset(cache, 0, fs->block_size);
// Read the trebly-indirect block (which contains pointers to doubly-indirect blocks).
ext2_read_block(fs, inode->data.blocks.trebly_indir_block, cache);
// Check that the indirect block points to a valid block.
if (!((uint32_t *)cache)[d]) {
// Allocate a new block.
uint32_t new_block_index = ext2_allocate_block(fs);
if (new_block_index == 0) {
// Free the cache.
kmem_cache_free(cache);
return -1;
}
// Update the index.
((uint32_t *)cache)[d] = new_block_index;
// Write the doubly-indirect block back.
ext2_write_block(fs, inode->data.blocks.trebly_indir_block, cache);
}
// Save the block index, otherwise with the next read we lose the block index list [d].
block_index_save = ((uint32_t *)cache)[d];
// Read the doubly-indirect block (which contains pointers to indirect blocks).
ext2_read_block(fs, block_index_save, cache);
// Check that the indirect block points to a valid block.
if (!((uint32_t *)cache)[f]) {
// Allocate a new block.
uint32_t new_block_index = ext2_allocate_block(fs);
if (new_block_index == 0) {
// Free the cache.
kmem_cache_free(cache);
return -1;
}
// Update the index.
((uint32_t *)cache)[f] = new_block_index;
// Write the doubly-indirect block back.
ext2_write_block(fs, block_index_save, cache);
}
// Get the next group index.
block_index_save = ((uint32_t *)cache)[f];
// Read the indirect block (which contains pointers to the next set of blocks).
ext2_read_block(fs, block_index_save, cache);
// Write the index inside the final block.
((uint32_t *)cache)[g] = real_index;
// Write back the indirect block.
ext2_read_block(fs, block_index_save, cache);
// Free the cache.
kmem_cache_free(cache);
return 0;
}
pr_err("We failed to write the real block number of the block with index `%d`\n", block_index);
return -1;
}
/// @brief Returns the real block index starting from a block index inside an inode.
/// @param fs the filesystem.
/// @param inode the inode which we are working with.
/// @param block_index the block index inside the inode.
/// @return the real block number.
static uint32_t ext2_get_real_block_index(ext2_filesystem_t *fs, ext2_inode_t *inode, uint32_t block_index)
{
// Return the direct block pointer.
if (block_index < EXT2_INDIRECT_BLOCKS) {
return inode->data.blocks.dir_blocks[block_index];
}
// Create a variable for the real index.
uint32_t real_index = 0;
// For simplicity.
uint32_t p1 = fs->pointers_per_block;
uint32_t p2 = fs->pointers_per_block * fs->pointers_per_block;
// Allocate the cache.
uint8_t *cache = kmem_cache_alloc(fs->ext2_buffer_cache, GFP_KERNEL);
// Clean the cache.
memset(cache, 0, fs->block_size);
// Check if the index is among the indirect blocks.
if (block_index < fs->indirect_blocks_index) {
// Compute the indirect indices.
uint32_t a = block_index - EXT2_INDIRECT_BLOCKS;
// Read the indirect block (which contains pointers to the next set of blocks).
ext2_read_block(fs, inode->data.blocks.indir_block, cache);
// Compute the index inside the final block.
real_index = ((uint32_t *)cache)[a];
} else if (block_index < fs->doubly_indirect_blocks_index) {
// The index is among the doubly-indirect blocks.
// Compute the indirect indices.
uint32_t a = block_index - EXT2_INDIRECT_BLOCKS;
uint32_t b = a - p1;
uint32_t c = b / p1;
uint32_t d = b - (c * p1);
// Read the doubly-indirect block (which contains pointers to indirect blocks).
ext2_read_block(fs, inode->data.blocks.doubly_indir_block, cache);
// Compute the index inside the indirect block.
ext2_read_block(fs, ((uint32_t *)cache)[c], cache);
// Compute the index inside the final block.
real_index = ((uint32_t *)cache)[d];
} else if (block_index < fs->trebly_indirect_blocks_index) {
// The index is among the trebly-indirect blocks.
// Compute the indirect indices.
uint32_t a = block_index - EXT2_INDIRECT_BLOCKS;
uint32_t b = a - p1;
uint32_t c = b - p2;
uint32_t d = c / p2;
uint32_t e = c - (d * p2);
uint32_t f = e / p1;
uint32_t g = e - (f * p1);
// Read the trebly-indirect block (which contains pointers to doubly-indirect blocks).
ext2_read_block(fs, inode->data.blocks.trebly_indir_block, cache);
// Read the doubly-indirect block (which contains pointers to indirect blocks).
ext2_read_block(fs, ((uint32_t *)cache)[d], cache);
// Read the indirect block (which contains pointers to the next set of blocks).
ext2_read_block(fs, ((uint32_t *)cache)[f], cache);
// Compute the index inside the final block.
real_index = ((uint32_t *)cache)[g];
} else {
pr_err("We failed to retrieve the real block number of the block with index `%d`\n", block_index);
}
// Free the cache.
kmem_cache_free(cache);
// Return the real index.
return real_index;
}
/// @brief Allocate a new block for an inode.
/// @param fs the filesystem.
/// @param inode the inode which we are working with.
/// @param inode_index The index of the inode.
/// @param block_index The index of the block within the inode.
/// @return 0 on success, -1 on failure.
static int ext2_allocate_inode_block(ext2_filesystem_t *fs, ext2_inode_t *inode, uint32_t inode_index, uint32_t block_index)
{
pr_debug("Allocating block with index `%d` for inode with index `%d`.\n", block_index, inode_index);
// Allocate the block.
int real_index = ext2_allocate_block(fs);
if (real_index == -1)
return -1;
// Associate the real index and the index inside the inode.
if (ext2_set_real_block_index(fs, inode, inode_index, block_index, real_index) == -1)
return -1;
// Compute the new blocks count.
uint32_t blocks_count = (block_index + 1) * fs->blocks_per_block_count;
if (inode->blocks_count < blocks_count) {
inode->blocks_count = blocks_count;
pr_debug("Setting the block count for inode to %d = (%d blocks)",
blocks_count, blocks_count / fs->blocks_per_block_count);
}
// Update the inode.
if (ext2_write_inode(fs, inode, inode_index) == -1)
return -1;
return 0;
}
/// @brief Reads the real block starting from an inode and the block index inside the inode.
/// @param fs the filesystem.
/// @param inode the inode which we are working with.
/// @param block_index the index of the block within the inode.
/// @param buffer the buffer where to put the data.
/// @return the amount of data we read, or negative value for an error.
static ssize_t ext2_read_inode_block(ext2_filesystem_t *fs, ext2_inode_t *inode, uint32_t block_index, uint8_t *buffer)
{
if (block_index >= (inode->blocks_count / fs->blocks_per_block_count)) {
pr_err("Tried to read an invalid block `%d`, but inode only has %d\n",
block_index, (inode->blocks_count / fs->blocks_per_block_count));
return -1;
}
// Get the real index.
uint32_t real_index = ext2_get_real_block_index(fs, inode, block_index);
if (real_index == 0)
return -1;
// Read the block.
return ext2_read_block(fs, real_index, buffer);
}
/// @brief Writes the real block starting from an inode and the block index inside the inode.
/// @param fs the filesystem.
/// @param inode the inode which we are working with.
/// @param inode_index The index of the inode.
/// @param block_index the index of the block within the inode.
/// @param buffer the buffer where to put the data.
/// @return the amount of data we wrote, or negative value for an error.
static ssize_t ext2_write_inode_block(ext2_filesystem_t *fs, ext2_inode_t *inode, uint32_t inode_index, uint32_t block_index, uint8_t *buffer)
{
while (block_index >= (inode->blocks_count / fs->blocks_per_block_count)) {
pr_err("Tried to read an invalid block `%d`, but inode only has %d!",
block_index, (inode->blocks_count / fs->blocks_per_block_count));
ext2_allocate_inode_block(fs, inode, inode_index, (inode->blocks_count / fs->blocks_per_block_count));
ext2_write_inode(fs, inode, inode_index);
}
// Get the real index.
uint32_t real_index = ext2_get_real_block_index(fs, inode, block_index);
if (real_index == 0)
return -1;
// Write the block.
return ext2_write_block(fs, real_index, buffer);
}
/// @brief Reads the data from the given inode.
/// @param fs the filesystem.
/// @param inode the inode which we are working with.
/// @param inode_index the index of the inode.
/// @param offset the offset from which we start reading the data.
/// @param nbyte the number of bytes to read.
/// @param buffer the buffer containing the data.
/// @return the amount we read.
static ssize_t ext2_read_inode_data(ext2_filesystem_t *fs, ext2_inode_t *inode, uint32_t inode_index, off_t offset, size_t nbyte, char *buffer)
{
// Check if the file is empty.
if (inode->size == 0)
return 0;
uint32_t end;
if ((offset + nbyte) > inode->size) {
end = inode->size;
} else {
end = offset + nbyte;
}
uint32_t start_block = offset / fs->block_size;
uint32_t end_block = end / fs->block_size;
uint32_t end_size = end - end_block * fs->block_size;
uint32_t size_to_read = end - offset;
// Allocate the cache.
uint8_t *cache = kmem_cache_alloc(fs->ext2_buffer_cache, GFP_KERNEL);
// Clean the cache.
memset(cache, 0, fs->block_size);
if (start_block == end_block) {
// Read the real block.
if (ext2_read_inode_block(fs, inode, start_block, cache) == -1) {
pr_err("Failed to read the inode block `%d`\n", start_block);
goto free_cache_return_error;
}
// Copy the content back to the buffer.
memcpy(buffer, (uint8_t *)(((uintptr_t)cache) + ((uintptr_t)offset % fs->block_size)), size_to_read);
} else {
uint32_t block_offset;
uint32_t blocks_read = 0;
for (block_offset = start_block; block_offset < end_block; block_offset++, blocks_read++) {
// Read the real block.
if (ext2_read_inode_block(fs, inode, block_offset, cache) == -1) {
pr_err("Failed to read the inode block `%d`\n", block_offset);
goto free_cache_return_error;
}
// Copy the content back to the buffer.
if (block_offset == start_block) {
memcpy(buffer, (uint8_t *)(((uintptr_t)cache) + ((uintptr_t)offset % fs->block_size)), fs->block_size - (offset % fs->block_size));
} else {
memcpy(buffer + fs->block_size * blocks_read - (offset % fs->block_size), cache, fs->block_size);
}
}
if (end_size) {
// Read the real block.
if (ext2_read_inode_block(fs, inode, end_block, cache) == -1) {
pr_err("Failed to read the inode block `%d`\n", block_offset);
goto free_cache_return_error;
}
// Copy the content back to the buffer.
memcpy(buffer + fs->block_size * blocks_read - (offset % fs->block_size), cache, end_size);
}
}
// Free the cache.
kmem_cache_free(cache);
return size_to_read;
free_cache_return_error:
// Free the cache.
kmem_cache_free(cache);
return -1;
}
/// @brief Writes the data on the given inode.
/// @param fs the filesystem.
/// @param inode the inode which we are working with.
/// @param inode_index the index of the inode.
/// @param offset the offset from which we start writing the data.
/// @param nbyte the number of bytes to write.
/// @param buffer the buffer containing the data.
/// @return the amount written.
static ssize_t ext2_write_inode_data(ext2_filesystem_t *fs, ext2_inode_t *inode, uint32_t inode_index, off_t offset, size_t nbyte, char *buffer)
{
uint32_t end = offset + nbyte;
if (end > inode->size) {
inode->size = end;
}
uint32_t start_block = offset / fs->block_size;
uint32_t end_block = end / fs->block_size;
uint32_t end_size = end - end_block * fs->block_size;
uint32_t size_to_write = end - offset;
// Allocate the cache.
uint8_t *cache = kmem_cache_alloc(fs->ext2_buffer_cache, GFP_KERNEL);
// Clean the cache.
memset(cache, 0, fs->block_size);
if (start_block == end_block) {
// Read the real block.
if (ext2_read_inode_block(fs, inode, start_block, cache) == -1) {
pr_err("Failed to read the inode block `%d`\n", start_block);
goto free_cache_return_error;
}
// Copy the content back to the buffer.
memcpy((uint8_t *)(((uintptr_t)cache) + ((uintptr_t)offset % fs->block_size)), buffer, size_to_write);
// Write the block back.
if (!ext2_write_inode_block(fs, inode, inode_index, start_block, cache)) {
pr_err("Failed to write the inode block `%d`\n", start_block);
goto free_cache_return_error;
}
} else {
uint32_t block_offset, blocks_read = 0;
for (block_offset = start_block; block_offset < end_block; ++block_offset, ++blocks_read) {
// Read the real block.
if (ext2_read_inode_block(fs, inode, block_offset, cache) == -1) {
pr_err("Failed to read the inode block `%d`\n", block_offset);
goto free_cache_return_error;
}
if (block_offset == start_block) {
// Copy the content back to the buffer.
memcpy((uint8_t *)(((uintptr_t)cache) + ((uintptr_t)offset % fs->block_size)), buffer, fs->block_size - (offset % fs->block_size));
} else {
// Copy the content back to the buffer.
memcpy(cache, buffer + fs->block_size * blocks_read - (offset % fs->block_size), fs->block_size);
}
// Write the block back.
if (!ext2_write_inode_block(fs, inode, inode_index, block_offset, cache)) {
pr_err("Failed to write the inode block `%d`\n", start_block);
goto free_cache_return_error;
}
}
if (end_size) {
// Read the real block.
if (ext2_read_inode_block(fs, inode, end_block, cache) == -1) {
pr_err("Failed to read the inode block `%d`\n", block_offset);
goto free_cache_return_error;
}
// Copy the content back to the buffer.
memcpy(cache, buffer + fs->block_size * blocks_read - (offset % fs->block_size), end_size);
// Write the block back.
if (ext2_write_inode_block(fs, inode, inode_index, end_block, cache)) {
pr_err("Failed to write the inode block `%d`\n", start_block);
goto free_cache_return_error;
}
}
}
return size_to_write;
free_cache_return_error:
// Free the cache.
kmem_cache_free(cache);
return -1;
}
// ============================================================================
// Directory Entry Iteration Functions
// ============================================================================
/// @brief Iterator for visiting the directory entries.
typedef struct ext2_direntry_iterator_t {
ext2_filesystem_t *fs; ///< A pointer to the filesystem.
uint8_t *cache; ///< Cache used for reading.
ext2_inode_t *inode; ///< A pointer to the directory inode.
uint32_t block_index; ///< The current block we are reading.
uint32_t total_offset; ///< The total amount of bytes we have read.
uint32_t block_offset; ///< The total amount of bytes we have read inside the current block.
ext2_dirent_t *direntry; ///< Pointer to the directory entry.
} ext2_direntry_iterator_t;
/// @brief Returns the ext2_dirent_t pointed by the iterator.
/// @param iterator the iterator.
/// @return pointer to the ext2_dirent_t
ext2_dirent_t *ext2_direntry_iterator_get(ext2_direntry_iterator_t *iterator)
{
return (ext2_dirent_t *)((uintptr_t)iterator->cache + iterator->block_offset);
}
/// @brief Check if the iterator is valid.
/// @param iterator the iterator to check.
/// @return true if valid, false otherwise.
bool_t ext2_direntry_iterator_valid(ext2_direntry_iterator_t *iterator)
{
return iterator->direntry != NULL;
}
/// @brief Initializes the iterator and reads the first block.
/// @param fs pointer to the filesystem.
/// @param cache used for reading.
/// @param inode pointer to the directory inode.
/// @return The initialized directory iterator.
ext2_direntry_iterator_t ext2_direntry_iterator_begin(ext2_filesystem_t *fs, uint8_t *cache, ext2_inode_t *inode)
{
ext2_direntry_iterator_t it = {
.fs = fs,
.cache = cache,
.inode = inode,
.block_index = 0,
.total_offset = 0,
.block_offset = 0,
.direntry = NULL
};
// Start by reading the first block of the inode.
if (ext2_read_inode_block(fs, inode, it.block_index, cache) == -1) {
pr_err("Failed to read the inode block `%d`\n", it.block_index);
} else {
// Initialize the directory entry.
it.direntry = ext2_direntry_iterator_get(&it);
}
return it;
}
/// @brief Moves to the next direntry, and moves to the next block if necessary.
/// @param iterator the iterator.
void ext2_direntry_iterator_next(ext2_direntry_iterator_t *iterator)
{
// Get the current rec_len.
uint32_t rec_len = ext2_direntry_iterator_get(iterator)->rec_len;
// Advance the offsets.
iterator->block_offset += rec_len;
iterator->total_offset += rec_len;
// If we reached the end of the inode, stop.
if (iterator->total_offset >= iterator->inode->size) {
// The iterator is not valid anymore.
iterator->direntry = NULL;
return;
}
// If we exceed the size of a block, move to the next block.
if (iterator->block_offset >= iterator->fs->block_size) {
// Increase the block index.
iterator->block_index += 1;
// Remove the exceeding size, so that we start correctly in the new block.
iterator->block_offset -= iterator->fs->block_size;
// Read the new block.
if (ext2_read_inode_block(iterator->fs, iterator->inode, iterator->block_index, iterator->cache) == -1) {
pr_err("Failed to read the inode block `%d`\n", iterator->block_index);
// The iterator is not valid anymore.
iterator->direntry = NULL;
return;
}
}
// Read the direntry.
iterator->direntry = ext2_direntry_iterator_get(iterator);
}
// ============================================================================
// Directory Entry Management Functions
// ============================================================================
static int ext2_allocate_direntry(ext2_filesystem_t *fs, vfs_file_t *parent, char *name, uint32_t inode_index)
{
pr_err("Not implemented yet.\n");
return -1;
}
/// @brief Finds the entry with the given `name` inside the `directory`.
/// @param directory the directory in which we perform the search.
/// @param name the name of the entry we are looking for.
/// @param search the output variable where we save the info about the entry.
/// @return 0 on success, -1 on failure.
static int ext2_find_direntry(ext2_filesystem_t *fs, ino_t ino, const char *name, ext2_direntry_search_t *search)
{
if (fs == NULL) {
pr_err("You provided a NULL filesystem.\n");
return -1;
}
if (name == NULL) {
pr_err("You provided a NULL name.\n");
return -1;
}
if (search == NULL) {
pr_err("You provided a NULL search.\n");
return -1;
}
if (search->direntry == NULL) {
pr_err("You provided a NULL direntry.\n");
return -1;
}
pr_debug("ext2_find_direntry(ino: %d, name: \"%s\")\n", ino, name);
// Allocate the cache.
uint8_t *cache = kmem_cache_alloc(fs->ext2_buffer_cache, GFP_KERNEL);
// Clean the cache.
memset(cache, 0, fs->block_size);
// Get the inode associated with the file.
ext2_inode_t inode;
if (ext2_read_inode(fs, &inode, ino) == -1) {
pr_err("Failed to read the inode (%d).\n", ino);
goto free_cache_return_error;
}
ext2_direntry_iterator_t it = ext2_direntry_iterator_begin(fs, cache, &inode);
for (; ext2_direntry_iterator_valid(&it); ext2_direntry_iterator_next(&it)) {
// Chehck the name.
if (!strcmp(it.direntry->name, ".") && !strcmp(name, "/")) {
break;
}
// Check if the entry has the same name.
if ((it.direntry->inode != 0) && (strlen(name) == it.direntry->name_len))
if (!strncmp(it.direntry->name, name, it.direntry->name_len))
break;
}
// Copy the inode of the parent, even if we did not find the entry.
search->parent_inode = ino;
// Check if we have found the entry.
if (it.direntry == NULL)
goto free_cache_return_error;
// Copy the direntry.
memcpy(search->direntry, it.direntry, sizeof(ext2_dirent_t));
// Close the name.
search->direntry->name[search->direntry->name_len] = 0;
// Copy the index of the block containing the direntry.
search->block_index = it.block_index;
// Copy the offset of the direntry inside the block.
search->block_offset = it.block_offset;
// Free the cache.
kmem_cache_free(cache);
pr_debug("ext2_find_direntry(ino: %d, name: \"%s\") -> (ino: %d, name: \"%s\")\n",
ino, name, search->direntry->inode, search->direntry->name);
return 0;
free_cache_return_error:
// Free the cache.
kmem_cache_free(cache);
return -1;
}
/// @brief Searches the entry specified in `path` starting from `directory`.
/// @param directory the directory from which we start performing the search.
/// @param path the path of the entry we are looking for, it cna be a relative path.
/// @param search the output variable where we save the entry information.
/// @return 0 on success, -1 on failure.
static int ext2_resolve_path(vfs_file_t *directory, char *path, ext2_direntry_search_t *search)
{
// Check the pointers.
if (directory == NULL) {
pr_err("You provided a NULL directory.\n");
return -1;
}
if (path == NULL) {
pr_err("You provided a NULL path.\n");
return -1;
}
if (search == NULL) {
pr_err("You provided a NULL search.\n");
return -1;
}
if (search->direntry == NULL) {
pr_err("You provided a NULL direntry.\n");
return -1;
}
pr_debug("ext2_resolve_path(directory: \"%s\", path: \"%s\")\n", directory->name, path);
// Get the filesystem.
ext2_filesystem_t *fs = (ext2_filesystem_t *)directory->device;
if (fs == NULL) {
pr_err("The file does not belong to an EXT2 filesystem `%s`.\n", directory->name);
return -1;
}
// If the path is `/`.
if (strcmp(path, "/") == 0)
return ext2_find_direntry(fs, directory->ino, path, search);
ino_t ino = directory->ino;
char *tmp_path = strdup(path);
char *token = strtok(tmp_path, "/");
while (token) {
if (!ext2_find_direntry(fs, ino, token, search)) {
ino = search->direntry->inode;
} else {
memset(search->direntry, 0, sizeof(ext2_dirent_t));
kfree(tmp_path);
return -1;
}
token = strtok(NULL, "/");
}
kfree(tmp_path);
pr_debug("ext2_resolve_path(directory: \"%s\", path: \"%s\") -> (ino: %d, name: \"%s\")\n",
directory->name, path, search->direntry->inode, search->direntry->name);
return 0;
}
/// @brief Searches the entry specified in `path` starting from `directory`.
/// @param directory the directory from which we start performing the search.
/// @param path the path of the entry we are looking for, it cna be a relative path.
/// @param direntry the output variable where we save the found entry.
/// @return 0 on success, -1 on failure.
static int ext2_resolve_path_direntry(vfs_file_t *directory, char *path, ext2_dirent_t *direntry)
{
pr_debug("ext2_resolve_path_direntry(%s, %s, %p)\n", directory->name, path, direntry);
// Check the pointers.
if (directory == NULL) {
pr_err("You provided a NULL directory.\n");
return -1;
}
if (path == NULL) {
pr_err("You provided a NULL path.\n");
return -1;
}
if (direntry == NULL) {
pr_err("You provided a NULL direntry.\n");
return -1;
}
// Prepare the structure for the search.
ext2_direntry_search_t search;
memset(&search, 0, sizeof(ext2_direntry_search_t));
// Initialize the search structure.
search.direntry = direntry;
search.block_index = 0;
search.block_offset = 0;
search.parent_inode = 0;
return ext2_resolve_path(directory, path, &search);
}
/// @brief Get the ext2 filesystem object starting from a path.
/// @param absolute_path the absolute path for which we want to find the associated EXT2 filesystem.
/// @return a pointer to the EXT2 filesystem, NULL otherwise.
static ext2_filesystem_t *get_ext2_filesystem(const char *absolute_path)
{
pr_debug("get_ext2_filesystem(%s)\n", absolute_path);
if (absolute_path == NULL) {
pr_err("We received a NULL absolute path.\n");
return NULL;
}
if (absolute_path[0] != '/') {
pr_err("We did not received an absolute path `%s`.\n", absolute_path);
return NULL;
}
super_block_t *sb = vfs_get_superblock(absolute_path);
if (sb == NULL) {
pr_err("Cannot find the superblock for the absolute path `%s`.\n", absolute_path);
return NULL;
}
vfs_file_t *sb_root = sb->root;
if (sb_root == NULL) {
pr_err("Cannot find the superblock root for the absolute path `%s`.\n", absolute_path);
return NULL;
}
// Get the filesystem.
ext2_filesystem_t *fs = (ext2_filesystem_t *)sb_root->device;
if (fs == NULL) {
pr_err("The file does not belong to an EXT2 filesystem `%s`.\n", sb_root->name);
return NULL;
}
// Check the magic number.
if (fs->superblock.magic != EXT2_SUPERBLOCK_MAGIC) {
pr_err("The file does not belong to an EXT2 filesystem `%s`.\n", sb_root->name);
return NULL;
}
return fs;
}
static int ext2_init_vfs_file(
ext2_filesystem_t *fs,
vfs_file_t *file,
ext2_inode_t *inode,
uint32_t inode_index,
const char *name,
size_t name_len)
{
// Information for root dir.
file->device = (void *)fs;
file->ino = inode_index;
// Copy the name
memcpy(file->name, name, name_len);
file->name[name_len] = 0;
// Information from the inode.
file->uid = inode->uid;
file->gid = inode->gid;
file->length = inode->size;
file->mask = inode->mode & 0xFFF;
file->nlink = inode->links_count;
// File flags.
file->flags = 0;
if ((inode->mode & EXT2_S_IFREG) == EXT2_S_IFREG) {
file->flags |= DT_REG;
}
if ((inode->mode & EXT2_S_IFDIR) == EXT2_S_IFDIR) {
file->flags |= DT_DIR;
}
if ((inode->mode & EXT2_S_IFBLK) == EXT2_S_IFBLK) {
file->flags |= DT_BLK;
}
if ((inode->mode & EXT2_S_IFCHR) == EXT2_S_IFCHR) {
file->flags |= DT_CHR;
}
if ((inode->mode & EXT2_S_IFIFO) == EXT2_S_IFIFO) {
file->flags |= DT_FIFO;
}
if ((inode->mode & EXT2_S_IFLNK) == EXT2_S_IFLNK) {
file->flags |= DT_LNK;
}
file->atime = inode->atime;
file->mtime = inode->mtime;
file->ctime = inode->ctime;
file->sys_operations = &ext2_sys_operations;
file->fs_operations = &ext2_fs_operations;
// Initialize the list of siblings.
list_head_init(&file->siblings);
pr_debug("ext2_init_vfs_file : [%d] `%s` (%s) (name len : %d)\n", file->ino, file->name, name, strlen(name));
return 0;
}
static vfs_file_t *ext2_find_vfs_file_with_inode(ext2_filesystem_t *fs, ino_t inode)
{
vfs_file_t *file = NULL;
if (!list_head_empty(&fs->opened_files)) {
list_for_each_decl(it, &fs->opened_files)
{
// Get the file structure.
file = list_entry(it, vfs_file_t, siblings);
if (file && (file->ino == inode))
return file;
}
}
return NULL;
}
// ============================================================================
// Virtual FileSystem (VFS) Functions
// ============================================================================
/// @brief Creates and initializes a new inode.
/// @param fs the filesystem.
/// @param inode the inode we use to initialize the root of the filesystem.
/// @param preferred_group the preferred group where the inode should be allocated.
/// @return true on success, false on failure.
static bool_t ext2_create_inode(ext2_filesystem_t *fs, ext2_inode_t *inode, uint32_t preferred_group)
{
if (fs == NULL) {
pr_err("Received a null EXT2 filesystem.\n");
return false;
}
if (inode == NULL) {
pr_err("Received a null EXT2 inode.\n");
return false;
}
task_struct *task = scheduler_get_current_process();
if (task == NULL) {
pr_err("Failed to get the current running process.\n");
return false;
}
// Allocate an inode, inside the preferred_group if possible.
int inode_index = ext2_allocate_inode(fs, preferred_group);
if (inode_index == 0) {
pr_err("Failed to allocate a new inode.\n");
return false;
}
// Clean the inode structure.
memset(inode, 0, sizeof(ext2_inode_t));
// Get the inode associated with the directory entry.
if (ext2_read_inode(fs, inode, inode_index) == -1) {
pr_err("Failed to read the newly created inode.\n");
return false;
}
// Set the user identifiers of the owners.
inode->uid = task->uid;
// Set the size of the file in bytes.
inode->size = 0;
// Set the time that the inode was accessed.
inode->atime = sys_time(NULL);
// Set the time that the inode was created.
inode->ctime = inode->atime;
// Set the time that the inode was modified the last time.
inode->mtime = inode->atime;
// Set the time that the inode was deleted.
inode->dtime = 0;
// Set the group identifiers of the owners.
inode->gid = task->gid;
// Set the number of hard links.
inode->links_count = 1;
// Set the blocks count.
inode->blocks_count = 0;
// Set the file flags.
inode->flags = 0;
// Set the OS dependant value.
inode->osd1 = 0;
// Set the blocks data.
memset(&inode->data, 0, sizeof(inode->data));
// Set the value used to indicate the file version (used by NFS).
inode->generation = 0;
// TODO: The value indicating the block number containing the extended attributes.
inode->file_acl = 0;
// TODO: For regular files this 32bit value contains the high 32 bits of the 64bit file size.
inode->dir_acl = 0;
// TODO:Value indicating the location of the file fragment.
inode->fragment_addr = 0;
// TODO: OS dependant structure.
memset(&inode->osd2, 0, sizeof(inode->osd2));
// Write the inode.
if (ext2_write_inode(fs, inode, inode_index) == -1) {
pr_err("Failed to write the newly created inode.\n");
return false;
}
return true;
}
static vfs_file_t *ext2_creat(vfs_file_t *parent, const char *name, mode_t mode)
{
// Get the filesystem.
ext2_filesystem_t *fs = (ext2_filesystem_t *)parent->device;
if (fs == NULL) {
pr_err("The parent does not belong to an EXT2 filesystem `%s`.\n", parent->name);
return NULL;
}
pr_err("Not implemented yet.\n");
#if 0
// Get the group index of the parent.
uint32_t group_index = ext2_get_group_index_from_inode(fs, parent->ino);
// Create and initialize the new inode.
ext2_inode_t inode;
if (!ext2_create_inode(fs, &inode, group_index)) {
pr_err("Failed to create a new inode inside `%s` (group index: %d).\n", parent->name, group_index);
return NULL;
}
// Initialize the file.
ext2_allocate_direntry(fs, parent, name, inode_index);
#endif
return NULL;
}
/// @brief Open the file at the given path and returns its file descriptor.
/// @param path The path to the file.
/// @param flags The flags used to determine the behavior of the function.
/// @param mode The mode with which we open the file.
/// @return The file descriptor of the opened file, otherwise returns -1.
static vfs_file_t *ext2_open(const char *path, int flags, mode_t mode)
{
pr_debug("ext2_open(path: \"%s\", flags: %d, mode: %d)\n", path, flags, mode);
// Get the absolute path.
char absolute_path[PATH_MAX];
// If the first character is not the '/' then get the absolute path.
if (!realpath(path, absolute_path)) {
pr_err("Cannot get the absolute path for path `%s`.\n", path);
return NULL;
}
// Get the EXT2 filesystem.
ext2_filesystem_t *fs = get_ext2_filesystem(absolute_path);
if (fs == NULL) {
pr_err("Failed to get the EXT2 filesystem for absolute path `%s`.\n", absolute_path);
return NULL;
}
// Prepare the structure for the direntry.
ext2_dirent_t direntry;
memset(&direntry, 0, sizeof(ext2_dirent_t));
// Prepare the structure for the search.
ext2_direntry_search_t search;
memset(&search, 0, sizeof(ext2_direntry_search_t));
// Initialize the search structure.
search.direntry = &direntry;
search.block_index = 0;
search.block_offset = 0;
search.parent_inode = 0;
// First check, if a file with the given name already exists.
if (!ext2_resolve_path(fs->root, absolute_path, &search)) {
if (bitmask_check(flags, O_CREAT | O_EXCL)) {
pr_err("A file at `%s` already exists (O_CREAT | O_EXCL).\n", absolute_path);
return NULL;
}
} else {
// If we need to create it, it's ok if it does not exist.
if (bitmask_check(flags, O_CREAT)) {
// Get the name of the directory.
char *parent_path = dirname(path);
if (strcmp(parent_path, path)) {
// Get the parent VFS node.
vfs_file_t *parent = ext2_open(parent_path, O_RDONLY, 0);
if (parent) {
// Create the file.
ext2_creat(parent, basename(path), mode);
// Close the parent directory.
ext2_close(parent);
}
}
return NULL;
} else {
pr_err("The file does not exist `%s`.\n", absolute_path);
return NULL;
}
}
// Prepare the structure for the inode.
ext2_inode_t inode;
memset(&inode, 0, sizeof(ext2_inode_t));
// Get the inode associated with the directory entry.
if (ext2_read_inode(fs, &inode, direntry.inode) == -1) {
pr_err("Failed to read the inode of `%s`.\n", direntry.name);
return NULL;
}
vfs_file_t *file = ext2_find_vfs_file_with_inode(fs, direntry.inode);
if (file == NULL) {
// Allocate the memory for the file.
file = kmem_cache_alloc(vfs_file_cache, GFP_KERNEL);
if (file == NULL) {
pr_err("Failed to allocate memory for the EXT2 file.\n");
return NULL;
}
if (ext2_init_vfs_file(fs, file, &inode, direntry.inode, direntry.name, direntry.name_len) == -1) {
pr_err("Failed to properly set the VFS file.\n");
return NULL;
}
// Add the vfs_file to the list of associated files.
list_head_add_tail(&file->siblings, &fs->opened_files);
}
pr_debug("ext2_open(path: \"%s\", flags: %d, mode: %d) -> file(ino: %d, name: \"%s\")\n",
path, flags, mode, file->ino, file->name);
return file;
}
static int ext2_unlink(const char *path)
{
pr_debug("ext2_unlink(%s)\n", path);
// Get the absolute path.
char absolute_path[PATH_MAX];
// If the first character is not the '/' then get the absolute path.
if (!realpath(path, absolute_path)) {
pr_err("Cannot get the absolute path for path `%s`.\n", path);
return -ENOENT;
}
// Get the name of the entry we want to unlink.
char *name = basename(absolute_path);
if (name == NULL) {
pr_err("Cannot get the basename from the absolute path `%s`.\n", absolute_path);
return -ENOENT;
}
// Get the EXT2 filesystem.
ext2_filesystem_t *fs = get_ext2_filesystem(absolute_path);
if (fs == NULL) {
pr_err("Failed to get the EXT2 filesystem for absolute path `%s`.\n", absolute_path);
return -ENOENT;
}
// Prepare the structure for the direntry.
ext2_dirent_t direntry;
memset(&direntry, 0, sizeof(ext2_dirent_t));
// Prepare the structure for the search.
ext2_direntry_search_t search;
memset(&search, 0, sizeof(ext2_direntry_search_t));
// Initialize the search structure.
search.direntry = &direntry;
search.block_index = 0;
search.block_offset = 0;
search.parent_inode = 0;
// Resolve the path to the directory entry.
if (ext2_resolve_path(fs->root, absolute_path, &search)) {
pr_err("Failed to resolve path `%s`.\n", absolute_path);
return -ENOENT;
}
// Get the inode associated with the parent directory entry.
ext2_inode_t parent_inode;
if (ext2_read_inode(fs, &parent_inode, search.parent_inode) == -1) {
pr_err("ext2_stat(%s): Failed to read the inode of parent of `%s`.\n", path, direntry.name);
return -ENOENT;
}
// Allocate the cache and clean it.
uint8_t *cache = kmem_cache_alloc(fs->ext2_buffer_cache, GFP_KERNEL);
memset(cache, 0, fs->block_size);
// Read the block where the direntry resides.
if (ext2_read_inode_block(fs, &parent_inode, search.block_index, cache) == -1) {
pr_err("Failed to read the parent inode block `%d`\n", search.block_index);
goto free_cache_return_error;
}
// Get a pointer to the direntry.
ext2_dirent_t *actual_dirent = (ext2_dirent_t *)((uintptr_t)cache + search.block_offset);
if (actual_dirent == NULL) {
pr_err("We found a NULL ext2_dirent_t\n");
goto free_cache_return_error;
}
// Set the inode to zero.
actual_dirent->inode = 0;
// Write back the parent directory block.
if (!ext2_write_inode_block(fs, &parent_inode, search.parent_inode, search.block_index, cache)) {
pr_err("Failed to write the inode block `%d`\n", search.block_index);
goto free_cache_return_error;
}
// Read the inode of the direntry we want to unlink.
ext2_inode_t inode;
if (ext2_read_inode(fs, &inode, direntry.inode) == -1) {
pr_err("Failed to read the inode of `%s`.\n", direntry.name);
goto free_cache_return_error;
}
if (inode.links_count > 0) {
inode.links_count--;
// Update the inode.
if (ext2_write_inode(fs, &inode, direntry.inode) == -1) {
pr_err("Failed to update the inode of `%s`.\n", direntry.name);
goto free_cache_return_error;
}
}
// Free the cache.
kmem_cache_free(cache);
return 0;
free_cache_return_error:
// Free the cache.
kmem_cache_free(cache);
return -1;
}
/// @brief Closes the given file.
/// @param file The file structure.
static int ext2_close(vfs_file_t *file)
{
// Get the filesystem.
ext2_filesystem_t *fs = (ext2_filesystem_t *)file->device;
if (fs == NULL) {
pr_err("The file does not belong to an EXT2 filesystem `%s`.\n", file->name);
return -1;
}
// We cannot close the root.
if (file == fs->root) {
return -1;
}
pr_debug("ext2_close(ino: %d, file: \"%s\")\n", file->ino, file->name);
// Remove the file from the list of opened files.
list_head_del(&file->siblings);
// Free the cache.
kmem_cache_free(file);
return 0;
}
/// @brief Reads from the file identified by the file descriptor.
/// @param file The file.
/// @param buffer Buffer where the read content must be placed.
/// @param offset Offset from which we start reading from the file.
/// @param nbyte The number of bytes to read.
/// @return The number of red bytes.
static ssize_t ext2_read(vfs_file_t *file, char *buffer, off_t offset, size_t nbyte)
{
//pr_debug("ext2_read(%s, %p, %d, %d)\n", file->name, buffer, offset, nbyte);
// Get the filesystem.
ext2_filesystem_t *fs = (ext2_filesystem_t *)file->device;
if (fs == NULL) {
pr_err("The file does not belong to an EXT2 filesystem `%s`.\n", file->name);
return -1;
}
// Get the inode associated with the file.
ext2_inode_t inode;
if (ext2_read_inode(fs, &inode, file->ino) == -1) {
pr_err("Failed to read the inode `%s`.\n", file->name);
return -1;
}
return ext2_read_inode_data(fs, &inode, file->ino, offset, nbyte, buffer);
}
/// @brief Writes the given content inside the file.
/// @param file The file descriptor of the file.
/// @param buffer The content to write.
/// @param offset Offset from which we start writing in the file.
/// @param nbyte The number of bytes to write.
/// @return The number of written bytes.
static ssize_t ext2_write(vfs_file_t *file, const void *buffer, off_t offset, size_t nbyte)
{
// Get the filesystem.
ext2_filesystem_t *fs = (ext2_filesystem_t *)file->device;
if (fs == NULL) {
pr_err("The file does not belong to an EXT2 filesystem `%s`.\n", file->name);
return -1;
}
// Get the inode associated with the file.
ext2_inode_t inode;
if (ext2_read_inode(fs, &inode, file->ino) == -1) {
pr_err("Failed to read the inode `%s`.\n", file->name);
return -1;
}
return ext2_write_inode_data(fs, &inode, file->ino, offset, nbyte, (char *)buffer);
}
/// @brief Repositions the file offset inside a file.
/// @param file the file we are working with.
/// @param offset the offest to use for the operation.
/// @param whence the type of operation.
/// @return Upon successful completion, returns the resulting offset
/// location as measured in bytes from the beginning of the file. On
/// error, the value (off_t) -1 is returned and errno is set to
/// indicate the error.
static off_t ext2_lseek(vfs_file_t *file, off_t offset, int whence)
{
// Get the filesystem.
ext2_filesystem_t *fs = (ext2_filesystem_t *)file->device;
if (fs == NULL) {
pr_err("The file does not belong to an EXT2 filesystem `%s`.\n", file->name);
return -EPERM;
}
// Get the inode associated with the file.
ext2_inode_t inode;
if (ext2_read_inode(fs, &inode, file->ino) == -1) {
pr_err("Failed to read the inode `%s`.\n", file->name);
return -ENOENT;
}
// Deal with the specific whence.
switch (whence) {
case SEEK_END:
offset += inode.size;
break;
case SEEK_CUR:
if (offset == 0) {
return file->f_pos;
}
offset += file->f_pos;
break;
case SEEK_SET:
break;
default:
return -EINVAL;
}
if (offset >= 0) {
if (offset != file->f_pos) {
file->f_pos = offset;
}
return offset;
}
return -EINVAL;
}
/// @brief Saves the information concerning the file.
/// @param inode The inode containing the data.
/// @param stat The structure where the information are stored.
/// @return 0 if success.
static int __ext2_stat(ext2_inode_t *inode, stat_t *stat)
{
stat->st_mode = inode->mode;
stat->st_uid = inode->uid;
stat->st_gid = inode->gid;
stat->st_size = inode->size;
stat->st_atime = inode->atime;
stat->st_mtime = inode->mtime;
stat->st_ctime = inode->ctime;
return 0;
}
/// @brief Retrieves information concerning the file at the given position.
/// @param file The file struct.
/// @param stat The structure where the information are stored.
/// @return 0 if success.
static int ext2_fstat(vfs_file_t *file, stat_t *stat)
{
// Get the filesystem.
ext2_filesystem_t *fs = (ext2_filesystem_t *)file->device;
if (fs == NULL) {
pr_err("The file does not belong to an EXT2 filesystem `%s`.\n", file->name);
return -EPERM;
}
// Get the inode associated with the file.
ext2_inode_t inode;
if (ext2_read_inode(fs, &inode, file->ino) == -1) {
pr_err("Failed to read the inode `%s`.\n", file->name);
return -ENOENT;
}
/// ID of device containing file.
stat->st_dev = fs->block_device->ino;
// Set the inode.
stat->st_ino = file->ino;
// Set the rest of the structure.
return __ext2_stat(&inode, stat);
}
static int ext2_ioctl(vfs_file_t *file, int request, void *data)
{
return -1;
}
/// @brief Reads contents of the directories to a dirent buffer, updating
/// the offset and returning the number of written bytes in the buffer,
/// it assumes that all paths are well-formed.
/// @param file The directory handler.
/// @param dirp The buffer where the data should be written.
/// @param doff The offset inside the buffer where the data should be written.
/// @param count The maximum length of the buffer.
/// @return The number of written bytes in the buffer.
static int ext2_getdents(vfs_file_t *file, dirent_t *dirp, off_t doff, size_t count)
{
pr_debug("ext2_getdents(%s, %p, %d, %d)\n", file->name, dirp, doff, count);
// Get the filesystem.
ext2_filesystem_t *fs = (ext2_filesystem_t *)file->device;
if (fs == NULL) {
pr_err("The file does not belong to an EXT2 filesystem `%s`.\n", file->name);
return -ENOENT;
}
// Get the inode associated with the file.
ext2_inode_t inode;
if (ext2_read_inode(fs, &inode, file->ino) == -1) {
pr_err("Failed to read the inode (%d).\n", file->ino);
return -ENOENT;
}
uint32_t current = 0, written = 0;
// Allocate the cache.
uint8_t *cache = kmem_cache_alloc(fs->ext2_buffer_cache, GFP_KERNEL);
// Clean the cache.
memset(cache, 0, fs->block_size);
// Initialize the iterator.
ext2_direntry_iterator_t it = ext2_direntry_iterator_begin(fs, cache, &inode);
for (; ext2_direntry_iterator_valid(&it) && (written < count); ext2_direntry_iterator_next(&it)) {
// Skip if already provided.
current += sizeof(dirent_t);
if (current <= doff)
continue;
// Write on current directory entry data.
dirp->d_ino = it.direntry->inode;
dirp->d_type = ext2_file_type_to_vfs_file_type(it.direntry->file_type);
memset(dirp->d_name, 0, NAME_MAX);
strncpy(dirp->d_name, it.direntry->name, it.direntry->name_len);
dirp->d_off = it.direntry->rec_len;
dirp->d_reclen = it.direntry->rec_len;
// Increment the amount written.
written += sizeof(dirent_t);
// Move to next writing position.
++dirp;
}
// Free the cache.
kmem_cache_free(cache);
return written;
}
static int ext2_mkdir(const char *path, mode_t mode)
{
return -1;
}
static int ext2_rmdir(const char *path)
{
return -1;
}
/// @brief Retrieves information concerning the file at the given position.
/// @param path The path where the file resides.
/// @param stat The structure where the information are stored.
/// @return 0 if success.
static int ext2_stat(const char *path, stat_t *stat)
{
pr_debug("ext2_stat(%s, %p)\n", path, stat);
// Get the absolute path.
char absolute_path[PATH_MAX];
// If the first character is not the '/' then get the absolute path.
if (!realpath(path, absolute_path)) {
pr_err("Cannot get the absolute path for path `%s`.\n", path);
return -ENOENT;
}
// Get the EXT2 filesystem.
ext2_filesystem_t *fs = get_ext2_filesystem(absolute_path);
if (fs == NULL) {
pr_err("Failed to get the EXT2 filesystem for absolute path `%s`.\n", absolute_path);
return -ENOENT;
}
// Prepare the structure for the direntry.
ext2_dirent_t direntry;
memset(&direntry, 0, sizeof(ext2_dirent_t));
// Resolve the path.
if (ext2_resolve_path_direntry(fs->root, absolute_path, &direntry)) {
pr_err("Failed to resolve path `%s`.\n", absolute_path);
return -ENOENT;
}
// Get the inode associated with the directory entry.
ext2_inode_t inode;
if (ext2_read_inode(fs, &inode, direntry.inode) == -1) {
pr_err("ext2_stat(%s): Failed to read the inode of `%s`.\n", path, direntry.name);
return -ENOENT;
}
/// ID of device containing file.
stat->st_dev = fs->block_device->ino;
// Set the inode.
stat->st_ino = direntry.inode;
// Set the rest of the structure.
return __ext2_stat(&inode, stat);
}
/// @brief Mounts the block device as an EXT2 filesystem.
/// @param block_device the block device formatted as EXT2.
/// @return the VFS root node of the EXT2 filesystem.
static vfs_file_t *ext2_mount(vfs_file_t *block_device, const char *path)
{
// Create the ext2 filesystem.
ext2_filesystem_t *fs = kmalloc(sizeof(ext2_filesystem_t));
// Clean the memory.
memset(fs, 0, sizeof(ext2_filesystem_t));
// Initialize the filesystem spinlock.
spinlock_init(&fs->spinlock);
// Initialize the list of opened files.
list_head_init(&fs->opened_files);
// Set the pointer to the block device.
fs->block_device = block_device;
// Read the superblock.
if (ext2_read_superblock(fs) == -1) {
pr_err("Failed to read the superblock table at 1024.\n");
// Free just the filesystem.
goto free_filesystem;
}
// Check the superblock magic number.
if (fs->superblock.magic != EXT2_SUPERBLOCK_MAGIC) {
pr_err("Wrong magic number, it is not an EXT2 filesystem.\n");
ext2_dump_superblock(&fs->superblock);
// Free just the filesystem.
goto free_filesystem;
}
// Compute the volume size.
fs->block_size = 1024U << fs->superblock.log_block_size;
// Initialize the buffer cache.
fs->ext2_buffer_cache = kmem_cache_create(
"ext2_buffer_cache",
fs->block_size,
fs->block_size,
GFP_KERNEL,
NULL,
NULL);
// Compute the maximum number of inodes per block.
fs->inodes_per_block_count = fs->block_size / sizeof(ext2_inode_t);
// Compute the number of blocks per block. This value is mostly used for
// inodes.
// If you check inside the inode structure you will find the `blocks_count`
// field. A 32-bit value representing the total number of 512-bytes blocks
// reserved to contain the data of this inode, regardless if these blocks
// are used or not. The block numbers of these reserved blocks are contained
// in the `block` array.
// Since this value represents 512-byte blocks and not file system blocks,
// this value should not be directly used as an index to the `block` array.
// Rather, the maximum index of the `block` array should be computed from
// inode->blocks_count / ((1024 << superblock->log_block_size) / 512)
// or once simplified:
// inode->blocks_count / (2 << superblock->log_block_size)
// Now we just need to precompute the right part.
fs->blocks_per_block_count = fs->block_size / 512U;
// Compute the number of block pointers per block.
fs->pointers_per_block = fs->block_size / 4U;
// Compute the index of indirect blocks.
fs->indirect_blocks_index = EXT2_INDIRECT_BLOCKS + fs->pointers_per_block;
fs->doubly_indirect_blocks_index = EXT2_INDIRECT_BLOCKS + fs->pointers_per_block * (fs->pointers_per_block + 1);
fs->trebly_indirect_blocks_index = fs->doubly_indirect_blocks_index +
(fs->pointers_per_block * fs->pointers_per_block) * (fs->pointers_per_block + 1);
// Compute the number of block groups.
fs->block_groups_count = fs->superblock.blocks_count / fs->superblock.blocks_per_group;
if (fs->superblock.blocks_per_group * fs->block_groups_count < fs->superblock.blocks_count) {
fs->block_groups_count += 1;
}
// The block group descriptor table starts on the first block following the
// superblock. This would be the second block for 2KiB and larger block file systems.
if (fs->block_size > K) {
fs->bgdt_start_block = 1;
} else {
// However, it would be the third block on a 1KiB block file system.
fs->bgdt_start_block = 2;
}
// The block group descriptor table ends a certain amount of blocks.
fs->bgdt_end_block = fs->bgdt_start_block + ((sizeof(ext2_group_descriptor_t) * fs->block_groups_count) / fs->block_size) + 1;
// Compute the length in blocks of the BGDT.
fs->bgdt_length = fs->bgdt_end_block - fs->bgdt_start_block;
// Now, we have the size of a block, calculate the location of the Block
// Group Descriptor Table (BGDT). The BGDT is located directly after the
// superblock, so obtain the block of the superblock first.
fs->block_groups = kmalloc(fs->block_size * fs->bgdt_length);
if (fs->block_groups == NULL) {
pr_err("Failed to allocate memory for the block buffer.\n");
// Free just the filesystem.
goto free_filesystem;
}
// Try to read the BGDT.
if (ext2_read_bgdt(fs) == -1) {
pr_err("Failed to read the BGDT.\n");
// Free the block_groups and the filesystem.
goto free_block_groups;
}
// We need the root inode in order to set the root file.
ext2_inode_t root_inode;
if (ext2_read_inode(fs, &root_inode, 2U) == -1) {
pr_err("Failed to set the root inode.\n");
// Free the block_buffer, the block_groups and the filesystem.
goto free_block_buffer;
}
if ((root_inode.mode & EXT2_S_IFDIR) != EXT2_S_IFDIR) {
pr_err("The root is not a directory.\n");
// Free the block_buffer, the block_groups and the filesystem.
goto free_block_buffer;
}
// Allocate the memory for the root.
fs->root = kmem_cache_alloc(vfs_file_cache, GFP_KERNEL);
if (!fs->root) {
pr_err("Failed to allocate memory for the EXT2 root file!\n");
// Free the block_buffer, the block_groups and the filesystem.
goto free_block_buffer;
}
if (ext2_init_vfs_file(fs, fs->root, &root_inode, 2, path, strlen(path)) == -1) {
pr_err("Failed to set the EXT2 root.\n");
// Free the block_buffer, the block_groups and the filesystem.
goto free_all;
}
// Add the root to the list of opened files.
list_head_add_tail(&fs->root->siblings, &fs->opened_files);
// Dump the filesystem details for debugging.
ext2_dump_filesystem(fs);
// Dump the superblock details for debugging.
ext2_dump_superblock(&fs->superblock);
// Dump the block group descriptor table.
ext2_dump_bgdt(fs);
return fs->root;
free_all:
// Free the memory occupied by the root.
kmem_cache_free(fs->root);
free_block_buffer:
// Free the memory occupied by the block buffer.
kmem_cache_destroy(fs->ext2_buffer_cache);
free_block_groups:
// Free the memory occupied by the block groups.
kfree(fs->block_groups);
free_filesystem:
// Free the memory occupied by the filesystem.
kfree(fs);
return NULL;
}
// ============================================================================
// Initialization Functions
// ============================================================================
static vfs_file_t *ext2_mount_callback(const char *path, const char *device)
{
// Allocate a variable for the path.
char absolute_path[PATH_MAX];
// If the first character is not the '/' then get the absolute path.
if (!realpath(device, absolute_path)) {
pr_err("ext2_mount_callback(%s, %s): Cannot get the absolute path.", path, device);
return NULL;
}
super_block_t *sb = vfs_get_superblock(absolute_path);
if (sb == NULL) {
pr_err("ext2_mount_callback(%s, %s): Cannot find the superblock at absolute path `%s`!\n", path, device, absolute_path);
return NULL;
}
vfs_file_t *block_device = sb->root;
if (block_device == NULL) {
pr_err("ext2_mount_callback(%s, %s): Cannot find the superblock root.", path, device);
return NULL;
}
if (block_device->flags != DT_BLK) {
pr_err("ext2_mount_callback(%s, %s): The device is not a block device.\n", path, device);
return NULL;
}
return ext2_mount(block_device, path);
}
/// Filesystem information.
static file_system_type ext2_file_system_type = {
.name = "ext2",
.fs_flags = 0,
.mount = ext2_mount_callback
};
int ext2_initialize(void)
{
// Register the filesystem.
vfs_register_filesystem(&ext2_file_system_type);
return 0;
}
int ext2_finalize(void)
{
vfs_unregister_filesystem(&ext2_file_system_type);
return 0;
}