/// @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; }