From 3db113f2d710c5bc5dc118d0442d16db9298730d Mon Sep 17 00:00:00 2001 From: Enrico Fraccaroli Date: Thu, 23 Dec 2021 15:01:53 +0100 Subject: [PATCH] Add elegant solution using iterators for iterating directory entries inside an inode. --- mentos/src/fs/ext2.c | 393 +++++++++++++++++++++++++------------------ 1 file changed, 226 insertions(+), 167 deletions(-) diff --git a/mentos/src/fs/ext2.c b/mentos/src/fs/ext2.c index fba453c..18b644f 100644 --- a/mentos/src/fs/ext2.c +++ b/mentos/src/fs/ext2.c @@ -645,28 +645,28 @@ static uint32_t ext2_get_block_index_from_inode_offset(ext2_filesystem_t *fs, ui return (inode_offset * fs->superblock.inode_size) / fs->block_size; } -/// @brief Cheks if the bit at the given index is free. +/// @brief Cheks if the bit at the given linear index is free. /// @param buffer the buffer containing the bitmap -/// @param index the index we want to check. +/// @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 index) +static ext2_block_status_t ext2_check_bitmap_bit(uint8_t *buffer, uint32_t linear_index) { - return (ext2_block_status_t)(bit_check(buffer[index / 8], index % 8) != 0); + return (ext2_block_status_t)(bit_check(buffer[linear_index / 8], linear_index % 8) != 0); } -/// @brief Sets the bit at the given index accordingly to `status`. +/// @brief Sets the bit at the given linear index accordingly to `status`. /// @param buffer the buffer containing the bitmap -/// @param index the index we want to check. +/// @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 index, ext2_block_status_t status) +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[index / 8], index % 8); + bit_set_assign(buffer[linear_index / 8], linear_index % 8); else - bit_clear_assign(buffer[index / 8], index % 8); + bit_clear_assign(buffer[linear_index / 8], linear_index % 8); } /// @brief Searches for a free inode inside the group data loaded inside the cache. @@ -1322,7 +1322,7 @@ static int ext2_allocate_inode_block(ext2_filesystem_t *fs, ext2_inode_t *inode, 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!", + 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; } @@ -1389,7 +1389,7 @@ static ssize_t ext2_read_inode_data(ext2_filesystem_t *fs, ext2_inode_t *inode, if (start_block == end_block) { // Read the real block. - if (!ext2_read_inode_block(fs, inode, start_block, cache)) { + 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; } @@ -1400,7 +1400,7 @@ static ssize_t ext2_read_inode_data(ext2_filesystem_t *fs, ext2_inode_t *inode, 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)) { + 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; } @@ -1413,7 +1413,7 @@ static ssize_t ext2_read_inode_data(ext2_filesystem_t *fs, ext2_inode_t *inode, } if (end_size) { // Read the real block. - if (!ext2_read_inode_block(fs, inode, end_block, cache)) { + 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; } @@ -1456,7 +1456,7 @@ static ssize_t ext2_write_inode_data(ext2_filesystem_t *fs, ext2_inode_t *inode, if (start_block == end_block) { // Read the real block. - if (!ext2_read_inode_block(fs, inode, start_block, cache)) { + 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; } @@ -1471,7 +1471,7 @@ static ssize_t ext2_write_inode_data(ext2_filesystem_t *fs, ext2_inode_t *inode, 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)) { + 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; } @@ -1490,7 +1490,7 @@ static ssize_t ext2_write_inode_data(ext2_filesystem_t *fs, ext2_inode_t *inode, } if (end_size) { // Read the real block. - if (!ext2_read_inode_block(fs, inode, end_block, cache)) { + 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; } @@ -1510,6 +1510,100 @@ free_cache_return_error: 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"); @@ -1549,53 +1643,28 @@ static int ext2_find_entry(ext2_filesystem_t *fs, ino_t ino, const char *name, e pr_err("Failed to read the inode (%d).\n", ino); goto free_cache_return_error; } - uint32_t block_index = 0, block_offset = 0, total_offset = 0; - ext2_dirent_t *entry = NULL; - // Start by reading the first block of the inode. - if (!ext2_read_inode_block(fs, &inode, block_index, cache)) { - pr_err("Failed to read the inode block `%d`\n", block_index); - goto free_cache_return_error; - } - // Keep reading until we searched the whole inode. - while (total_offset < inode.size) { - // If we exceed the size of a block, move to the next block. - if (block_offset >= fs->block_size) { - // Increase the block index. - ++block_index; - // Remove the exceeding size, so that we start correctly in the new block. - block_offset -= fs->block_size; - // Read the new block. - if (!ext2_read_inode_block(fs, &inode, block_index, cache)) { - pr_err("Failed to read the inode block `%d`\n", block_index); - goto free_cache_return_error; - } - } - // Get the directory entry. - entry = (ext2_dirent_t *)((uintptr_t)cache + block_offset); - if (!strcmp(entry->name, ".") && !strcmp(name, "/")) { + 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 ((entry->inode != 0) && (strlen(name) == entry->name_len)) - if (!strncmp(entry->name, name, entry->name_len)) + if ((it.direntry->inode != 0) && (strlen(name) == it.direntry->name_len)) + if (!strncmp(it.direntry->name, name, it.direntry->name_len)) break; - // Advance the offsets. - block_offset += entry->rec_len; - total_offset += entry->rec_len; - // Reset the direntry pointer. - entry = NULL; } // 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 (entry == NULL) + if (it.direntry == NULL) goto free_cache_return_error; // Copy the direntry. - memcpy(search->direntry, entry, sizeof(ext2_dirent_t)); + memcpy(search->direntry, it.direntry, sizeof(ext2_dirent_t)); // Copy the index of the block containing the direntry. - search->block_index = block_index; + search->block_index = it.block_index; // Copy the offset of the direntry inside the block. - search->block_offset = block_offset; + search->block_offset = it.block_offset; // Free the cache. kmem_cache_free(cache); return 0; @@ -1774,20 +1843,25 @@ static int ext2_init_root(ext2_filesystem_t *fs, ext2_inode_t *inode, const char return 0; } -static int ext2_init_file(ext2_filesystem_t *fs, ext2_inode_t *inode, ext2_dirent_t *direntry, vfs_file_t *file) +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 = direntry->inode; - memcpy(&file->name, &direntry->name, direntry->name_len); - file->name[direntry->name_len] = '\0'; + file->ino = inode_index; + // Copy the name + memcpy(&file->name, name, name_len); // 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) { @@ -1837,75 +1911,100 @@ static vfs_file_t *ext2_find_vfs_file_with_inode(ext2_filesystem_t *fs, ino_t in // 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) { - pr_err("Not implemented yet.\n"); -#if 0 // 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; } - task_struct *task = scheduler_get_current_process(); - if (task == NULL) { - pr_err("Failed to get the current running process.\n"); - return NULL; - } - // Allocate an inode for it (TODO: Use the parent block hint). - ino_t inode_index = ext2_allocate_inode(fs, 0); - if (inode_index == 0) { - pr_err("Failed to allocate a new inode for `%s`.\n", name); - return NULL; - } - // Prepare the structure for the inode. + 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; - 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 for `%s`.\n", name); + 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; } - // Set the file mode. - inode.mode = EXT2_S_IFREG | (0xFFF & mode); - // 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. - ext2_write_inode(fs, &inode, inode_index); - // Initialize the file. ext2_allocate_direntry(fs, parent, name, inode_index); #endif @@ -1973,7 +2072,7 @@ static vfs_file_t *ext2_open(const char *path, int flags, mode_t mode) pr_err("Failed to allocate memory for the parent.\n"); return NULL; } - if (ext2_init_file(fs, &parent_inode, &direntry, parent) == -1) { + if (ext2_init_vfs_file(fs, &parent_inode, &direntry, parent) == -1) { pr_err("Failed to properly set the VFS file.\n"); kmem_cache_free(parent); return NULL; @@ -2013,7 +2112,7 @@ static vfs_file_t *ext2_open(const char *path, int flags, mode_t mode) pr_err("Failed to allocate memory for the EXT2 file.\n"); return NULL; } - if (ext2_init_file(fs, &inode, &direntry, file) == -1) { + 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; } @@ -2071,7 +2170,7 @@ static int ext2_unlink(const char *path) 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)) { + 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; } @@ -2297,73 +2396,33 @@ static int ext2_getdents(vfs_file_t *file, dirent_t *dirp, off_t doff, size_t co pr_err("Failed to read the inode (%d).\n", file->ino); return -ENOENT; } - - uint32_t block_index = 0, block_offset = 0, total_offset = 0, written = 0; - off_t current = 0; - ext2_dirent_t *direntry = NULL; + 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); - - // Start by reading the first block of the inode. - if (!ext2_read_inode_block(fs, &inode, block_index, cache)) { - pr_err("Failed to read the inode block `%d`\n", block_index); - goto free_cache_return_error; - } - - // Keep reading until we searched the whole inode. - while ((total_offset < inode.size) && (written < count)) { - // If we exceed the size of a block, move to the next block. - if (block_offset >= fs->block_size) { - // Increase the block index. - ++block_index; - // Remove the exceeding size, so that we start correctly in the new block. - block_offset -= fs->block_size; - // Read the new block. - if (!ext2_read_inode_block(fs, &inode, block_index, cache)) { - pr_err("Failed to read the inode block `%d`\n", block_index); - goto free_cache_return_error; - } - } - // Get the directory entry. - direntry = (ext2_dirent_t *)((uintptr_t)cache + block_offset); - if (direntry == NULL) { - pr_err("We found a NULL ext2_dirent_t\n"); - goto free_cache_return_error; - } - - // Advance the offsets. - block_offset += direntry->rec_len; - total_offset += direntry->rec_len; - + // 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) { + if (current <= doff) continue; - } - // Write on current directory entry data. - dirp->d_ino = direntry->inode; - dirp->d_type = ext2_file_type_to_vfs_file_type(direntry->file_type); + 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, direntry->name, direntry->name_len); - dirp->d_off = direntry->rec_len; - dirp->d_reclen = direntry->rec_len; - + 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; -free_cache_return_error: - // Free the cache. - kmem_cache_free(cache); - return -1; } static int ext2_mkdir(const char *path, mode_t mode)