Add elegant solution using iterators for iterating directory entries inside an inode.

This commit is contained in:
Enrico Fraccaroli
2021-12-23 15:01:53 +01:00
parent 642284d3fb
commit 3db113f2d7
+226 -167
View File
@@ -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)