From fcdb7fba62e0d3a9f0016ae84362c555c518fea5 Mon Sep 17 00:00:00 2001 From: lassedtu Date: Fri, 7 Aug 2026 13:34:12 +0200 Subject: [PATCH] Add file and directory management functions to EXT2 filesystem - Extend EXT2_VOLUME structure to include superblock and additional metadata - Implement functions for creating and removing files and directories in the EXT2 filesystem - Add support for renaming files and directories - Introduce fs_stat function to retrieve file metadata, including inode information and timestamps - Updated VFS layer to provide high-level interfaces for the new file and directory operations: create, remove, rename, and stat --- src/drivers/disk/ata.c | 80 +- src/drivers/disk/ata.h | 2 +- src/drivers/disk/block_device.c | 10 + src/drivers/disk/block_device.h | 18 +- src/drivers/disk/partition.c | 21 + src/fs/ext2/ext2.c | 1523 ++++++++++++++++++++++++++++++- src/fs/ext2/ext2.h | 47 +- src/kernel/fs/fs.c | 97 ++ src/kernel/fs/fs.h | 66 ++ src/kernel/vfs/vfs.c | 60 ++ src/kernel/vfs/vfs.h | 46 + 11 files changed, 1931 insertions(+), 39 deletions(-) diff --git a/src/drivers/disk/ata.c b/src/drivers/disk/ata.c index cf2300c..cf8ffcf 100644 --- a/src/drivers/disk/ata.c +++ b/src/drivers/disk/ata.c @@ -16,7 +16,9 @@ #define ATA_STATUS_DRQ 0x08u // data request (ready to transfer data) #define ATA_STATUS_BSY 0x80u // busy (device is processing a command and cannot accept new commands) -#define ATA_COMMAND_READ_SECTORS 0x20u // command to read sectors from the disk +#define ATA_COMMAND_READ_SECTORS 0x20u // command to read sectors from the disk +#define ATA_COMMAND_WRITE_SECTORS 0x30u // command to write sectors to the disk +#define ATA_COMMAND_CACHE_FLUSH 0xE7u // command to flush the drive cache /** * write a byte to an I/O port. @@ -44,6 +46,14 @@ static inline void io_insw(uint16_t port, void *buffer, uint32_t word_count) __asm__ __volatile__("cld; rep insw" : "+D"(buffer), "+c"(word_count) : "d"(port) : "memory"); } +/** + * write words (2 bytes each) to an I/O port from a buffer. + */ +static inline void io_outsw(uint16_t port, const void *buffer, uint32_t word_count) +{ + __asm__ __volatile__("cld; rep outsw" : "+S"(buffer), "+c"(word_count) : "d"(port) : "memory"); +} + /** * perform a 400ns delay by reading the alternate status register four times. */ @@ -152,6 +162,64 @@ static bool ata_read_lba28(uint32_t lba, uint8_t sector_count, void *dest) return true; } +/** + * write sectors to an ATA device using 28-bit LBA addressing. + */ +static bool ata_write_lba28(uint32_t lba, uint8_t sector_count, const void *src) +{ + const uint8_t *in; + uint32_t sector_index; + + if (!src || sector_count == 0) + { + return false; + } + + if (lba > 0x0FFFFFFFu) + { + return false; + } + + if (!ata_wait_not_busy()) + { + return false; + } + + io_outb(ATA_IO_DRIVE_HEAD, (uint8_t)(0xE0u | ((lba >> 24u) & 0x0Fu))); + ata_400ns_delay(); + + io_outb(ATA_IO_SECTOR_COUNT, sector_count); + io_outb(ATA_IO_LBA_LOW, (uint8_t)(lba & 0xFFu)); + io_outb(ATA_IO_LBA_MID, (uint8_t)((lba >> 8u) & 0xFFu)); + io_outb(ATA_IO_LBA_HIGH, (uint8_t)((lba >> 16u) & 0xFFu)); + io_outb(ATA_IO_STATUS_COMMAND, ATA_COMMAND_WRITE_SECTORS); + + in = (const uint8_t *)src; + for (sector_index = 0; sector_index < sector_count; sector_index++) + { + if (!ata_wait_data_request()) + { + return false; + } + + io_outsw(ATA_IO_DATA, in, 256u); + in += 512u; + } + + if (!ata_wait_not_busy()) + { + return false; + } + + io_outb(ATA_IO_STATUS_COMMAND, ATA_COMMAND_CACHE_FLUSH); + if (!ata_wait_not_busy()) + { + return false; + } + + return true; +} + /** * read blocks from an ATA device. */ @@ -161,6 +229,15 @@ static bool ata_block_read(BLOCK_DEVICE *device, uint32_t startBlock, uint8_t bl return ata_read_lba28(startBlock, blockCount, dest); } +/** + * write blocks to an ATA device. + */ +static bool ata_block_write(BLOCK_DEVICE *device, uint32_t startBlock, uint8_t blockCount, const void *src) +{ + (void)device; + return ata_write_lba28(startBlock, blockCount, src); +} + bool ATA_Initialize(ATA_DEVICE *device) { if (!device) @@ -171,5 +248,6 @@ bool ATA_Initialize(ATA_DEVICE *device) device->block.bytes_per_block = 512; device->block.context = device; device->block.read_blocks = ata_block_read; + device->block.write_blocks = ata_block_write; return true; } diff --git a/src/drivers/disk/ata.h b/src/drivers/disk/ata.h index dd8bb44..fe55b74 100644 --- a/src/drivers/disk/ata.h +++ b/src/drivers/disk/ata.h @@ -11,7 +11,7 @@ typedef struct } ATA_DEVICE; /** - * initialize an ATA device for reading blocks. + * initialize an ATA device for reading and writing blocks. * @param device ATA device object to initialize. * @return true on success, false on failure. */ diff --git a/src/drivers/disk/block_device.c b/src/drivers/disk/block_device.c index 9299247..43aaa56 100644 --- a/src/drivers/disk/block_device.c +++ b/src/drivers/disk/block_device.c @@ -9,3 +9,13 @@ bool block_device_read(BLOCK_DEVICE *device, uint32_t startBlock, uint8_t blockC return device->read_blocks(device, startBlock, blockCount, dest); } + +bool block_device_write(BLOCK_DEVICE *device, uint32_t startBlock, uint8_t blockCount, const void *src) +{ + if (!device || !device->write_blocks || !src || blockCount == 0) + { + return false; + } + + return device->write_blocks(device, startBlock, blockCount, src); +} diff --git a/src/drivers/disk/block_device.h b/src/drivers/disk/block_device.h index 4e43153..51b3e94 100644 --- a/src/drivers/disk/block_device.h +++ b/src/drivers/disk/block_device.h @@ -5,15 +5,17 @@ typedef struct BLOCK_DEVICE BLOCK_DEVICE; typedef bool (*BlockReadFn)(BLOCK_DEVICE *device, uint32_t startBlock, uint8_t blockCount, void *dest); +typedef bool (*BlockWriteFn)(BLOCK_DEVICE *device, uint32_t startBlock, uint8_t blockCount, const void *src); /** * block device abstraction for reading blocks from a storage device. */ struct BLOCK_DEVICE { - uint16_t bytes_per_block; // number of bytes in each block (sector) of the device. - void *context; // pointer to device-specific context data (e.g., ATA device structure). - BlockReadFn read_blocks; // function pointer to the block read function for the device. + uint16_t bytes_per_block; // number of bytes in each block (sector) of the device. + void *context; // pointer to device-specific context data (e.g., ATA device structure). + BlockReadFn read_blocks; // function pointer to the block read function for the device. + BlockWriteFn write_blocks; // function pointer to the block write function for the device. }; /** @@ -25,3 +27,13 @@ struct BLOCK_DEVICE * @return true on success. false on failure. */ bool block_device_read(BLOCK_DEVICE *device, uint32_t startBlock, uint8_t blockCount, void *dest); + +/** + * write blocks to a block device. + * @param device initialized block device backend. + * @param startBlock starting block number to write to. + * @param blockCount number of blocks to write. + * @param src source buffer containing the data to write. + * @return true on success. false on failure. + */ +bool block_device_write(BLOCK_DEVICE *device, uint32_t startBlock, uint8_t blockCount, const void *src); diff --git a/src/drivers/disk/partition.c b/src/drivers/disk/partition.c index abcf198..2c4533b 100644 --- a/src/drivers/disk/partition.c +++ b/src/drivers/disk/partition.c @@ -22,6 +22,26 @@ static bool partition_block_read(BLOCK_DEVICE *device, uint32_t startBlock, uint return block_device_read(part->parent, part->start_lba + startBlock, blockCount, dest); } +/** + * write blocks to a partition. + */ +static bool partition_block_write(BLOCK_DEVICE *device, uint32_t startBlock, uint8_t blockCount, const void *src) +{ + PARTITION_DEVICE *part; + if (!device || !device->context) + { + return false; + } + + part = (PARTITION_DEVICE *)device->context; + if (!part->parent) + { + return false; + } + + return block_device_write(part->parent, part->start_lba + startBlock, blockCount, src); +} + bool PARTITION_Initialize(PARTITION_DEVICE *part, BLOCK_DEVICE *parent, uint32_t start_lba) { if (!part || !parent) @@ -34,6 +54,7 @@ bool PARTITION_Initialize(PARTITION_DEVICE *part, BLOCK_DEVICE *parent, uint32_t part->block.bytes_per_block = parent->bytes_per_block; part->block.context = part; part->block.read_blocks = partition_block_read; + part->block.write_blocks = partition_block_write; return true; } diff --git a/src/fs/ext2/ext2.c b/src/fs/ext2/ext2.c index bf7ba95..02d5b85 100644 --- a/src/fs/ext2/ext2.c +++ b/src/fs/ext2/ext2.c @@ -11,57 +11,1180 @@ static uint8_t g_sector_buffer[EXT2_SECTOR_SIZE]; static uint8_t g_block_buffer[EXT2_MAX_BLOCK_SIZE]; static uint8_t g_block_buffer2[EXT2_MAX_BLOCK_SIZE]; static uint8_t g_inode_buffer[EXT2_MAX_INODE_SIZE]; +static uint8_t g_bitmap_buffer[EXT2_MAX_BLOCK_SIZE]; + +static bool inode_is_dir(const EXT2_INODE *inode); +static bool inode_is_regular(const EXT2_INODE *inode); +static bool inode_is_directory(const EXT2_INODE *inode); static bool read_abs_bytes(BLOCK_DEVICE *disk, uint32_t byte_offset, uint32_t size, void *out) { uint8_t *dst = (uint8_t *)out; - while (size > 0) + while (size > 0) + { + uint32_t lba = byte_offset / EXT2_SECTOR_SIZE; + uint32_t offset = byte_offset % EXT2_SECTOR_SIZE; + uint32_t chunk = EXT2_MIN(EXT2_SECTOR_SIZE - offset, size); + + if (!block_device_read(disk, lba, 1, g_sector_buffer)) + { + return false; + } + + memcpy(dst, g_sector_buffer + offset, chunk); + + dst += chunk; + byte_offset += chunk; + size -= chunk; + } + + return true; +} + +static bool read_block(EXT2_VOLUME *volume, uint32_t block, void *out) +{ + uint32_t lba; + if (!volume || volume->sectors_per_block == 0) + { + return false; + } + + lba = block * volume->sectors_per_block; + return block_device_read(volume->disk, lba, (uint8_t)volume->sectors_per_block, out); +} + +static bool read_group_desc(EXT2_VOLUME *volume, uint32_t group, EXT2_BLOCK_GROUP_DESC *out_desc) +{ + uint32_t bgdt_byte; + uint32_t desc_offset; + + if (!volume || !out_desc) + { + return false; + } + + bgdt_byte = volume->bgdt_start_block * volume->block_size; + desc_offset = group * (uint32_t)sizeof(EXT2_BLOCK_GROUP_DESC); + return read_abs_bytes(volume->disk, bgdt_byte + desc_offset, (uint32_t)sizeof(EXT2_BLOCK_GROUP_DESC), out_desc); +} + +static uint32_t ext2_align4(uint32_t value) +{ + return (value + 3u) & ~3u; +} + +static uint32_t ext2_dir_entry_size(uint32_t name_len) +{ + return ext2_align4(OFFSETOF(EXT2_DIR_ENTRY, file_type) + 1u + name_len); +} + +static bool write_abs_bytes(BLOCK_DEVICE *disk, uint32_t byte_offset, uint32_t size, const void *in) +{ + const uint8_t *src = (const uint8_t *)in; + + if (!disk || !src) + { + return false; + } + + while (size > 0) + { + uint32_t lba = byte_offset / EXT2_SECTOR_SIZE; + uint32_t offset = byte_offset % EXT2_SECTOR_SIZE; + uint32_t chunk = EXT2_MIN(EXT2_SECTOR_SIZE - offset, size); + + if (offset == 0 && chunk == EXT2_SECTOR_SIZE) + { + if (!block_device_write(disk, lba, 1, src)) + { + return false; + } + } + else + { + if (!block_device_read(disk, lba, 1, g_sector_buffer)) + { + return false; + } + + memcpy(g_sector_buffer + offset, src, chunk); + + if (!block_device_write(disk, lba, 1, g_sector_buffer)) + { + return false; + } + } + + src += chunk; + byte_offset += chunk; + size -= chunk; + } + + return true; +} + +static bool write_block(EXT2_VOLUME *volume, uint32_t block, const void *in) +{ + if (!volume || !volume->disk || !in || volume->sectors_per_block == 0) + { + return false; + } + + return block_device_write(volume->disk, block * volume->sectors_per_block, (uint8_t)volume->sectors_per_block, in); +} + +static bool write_group_desc(EXT2_VOLUME *volume, uint32_t group, const EXT2_BLOCK_GROUP_DESC *desc) +{ + uint32_t bgdt_byte; + uint32_t desc_offset; + + if (!volume || !desc || group >= volume->block_group_count) + { + return false; + } + + bgdt_byte = volume->bgdt_start_block * volume->block_size; + desc_offset = group * (uint32_t)sizeof(EXT2_BLOCK_GROUP_DESC); + return write_abs_bytes(volume->disk, bgdt_byte + desc_offset, (uint32_t)sizeof(EXT2_BLOCK_GROUP_DESC), desc); +} + +static bool write_superblock(EXT2_VOLUME *volume) +{ + if (!volume) + { + return false; + } + + return write_abs_bytes(volume->disk, EXT2_SUPERBLOCK_OFFSET, (uint32_t)sizeof(EXT2_SUPERBLOCK), &volume->superblock); +} + +static bool read_inode_bitmap(EXT2_VOLUME *volume, uint32_t group, uint8_t *bitmap) +{ + EXT2_BLOCK_GROUP_DESC desc; + + if (!volume || !bitmap || group >= volume->block_group_count) + { + return false; + } + + if (!read_group_desc(volume, group, &desc)) + { + return false; + } + + return read_block(volume, desc.bg_inode_bitmap, bitmap); +} + +static bool write_inode_bitmap(EXT2_VOLUME *volume, uint32_t group, const uint8_t *bitmap) +{ + EXT2_BLOCK_GROUP_DESC desc; + + if (!volume || !bitmap || group >= volume->block_group_count) + { + return false; + } + + if (!read_group_desc(volume, group, &desc)) + { + return false; + } + + return write_block(volume, desc.bg_inode_bitmap, bitmap); +} + +static bool read_block_bitmap(EXT2_VOLUME *volume, uint32_t group, uint8_t *bitmap) +{ + EXT2_BLOCK_GROUP_DESC desc; + + if (!volume || !bitmap || group >= volume->block_group_count) + { + return false; + } + + if (!read_group_desc(volume, group, &desc)) + { + return false; + } + + return read_block(volume, desc.bg_block_bitmap, bitmap); +} + +static bool write_block_bitmap(EXT2_VOLUME *volume, uint32_t group, const uint8_t *bitmap) +{ + EXT2_BLOCK_GROUP_DESC desc; + + if (!volume || !bitmap || group >= volume->block_group_count) + { + return false; + } + + if (!read_group_desc(volume, group, &desc)) + { + return false; + } + + return write_block(volume, desc.bg_block_bitmap, bitmap); +} + +static bool bitmap_test(const uint8_t *bitmap, uint32_t index) +{ + return (bitmap[index / 8u] & (uint8_t)(1u << (index % 8u))) != 0; +} + +static void bitmap_set(uint8_t *bitmap, uint32_t index, bool value) +{ + uint8_t mask = (uint8_t)(1u << (index % 8u)); + + if (value) + { + bitmap[index / 8u] |= mask; + } + else + { + bitmap[index / 8u] &= (uint8_t)~mask; + } +} + +static bool bitmap_find_free(const uint8_t *bitmap, uint32_t bit_count, uint32_t start_bit, uint32_t *bit_out) +{ + uint32_t i; + + if (!bitmap || !bit_out) + { + return false; + } + + for (i = start_bit; i < bit_count; i++) + { + if (!bitmap_test(bitmap, i)) + { + *bit_out = i; + return true; + } + } + + return false; +} + +static bool inode_location(EXT2_VOLUME *volume, uint32_t inode_number, EXT2_BLOCK_GROUP_DESC *desc_out, uint32_t *inode_byte_offset_out, uint32_t *group_out) +{ + uint32_t zero_based; + uint32_t group; + uint32_t index; + EXT2_BLOCK_GROUP_DESC desc; + + if (!volume || !inode_number || !desc_out || !inode_byte_offset_out) + { + return false; + } + + zero_based = inode_number - 1u; + group = zero_based / volume->inodes_per_group; + index = zero_based % volume->inodes_per_group; + + if (group >= volume->block_group_count) + { + return false; + } + + if (!read_group_desc(volume, group, &desc)) + { + return false; + } + + *desc_out = desc; + *inode_byte_offset_out = (desc.bg_inode_table * volume->block_size) + (index * volume->inode_size); + + if (group_out) + { + *group_out = group; + } + + return true; +} + +static bool write_inode(EXT2_VOLUME *volume, uint32_t inode_number, const EXT2_INODE *inode) +{ + EXT2_BLOCK_GROUP_DESC desc; + uint32_t inode_byte_offset; + uint8_t inode_buffer[EXT2_MAX_INODE_SIZE]; + + if (!volume || !inode || volume->inode_size > EXT2_MAX_INODE_SIZE) + { + return false; + } + + if (!inode_location(volume, inode_number, &desc, &inode_byte_offset, 0)) + { + return false; + } + + memset(inode_buffer, 0, sizeof(inode_buffer)); + memcpy(inode_buffer, inode, sizeof(EXT2_INODE)); + return write_abs_bytes(volume->disk, inode_byte_offset, volume->inode_size, inode_buffer); +} + +static bool update_group_and_super_counts(EXT2_VOLUME *volume, uint32_t group, int32_t free_blocks_delta, int32_t free_inodes_delta, int32_t used_dirs_delta) +{ + EXT2_BLOCK_GROUP_DESC desc; + int32_t new_free_blocks; + int32_t new_free_inodes; + int32_t new_used_dirs; + + if (!volume || group >= volume->block_group_count) + { + return false; + } + + if (!read_group_desc(volume, group, &desc)) + { + return false; + } + + if (free_blocks_delta < 0 && desc.bg_free_blocks_count < (uint16_t)(-free_blocks_delta)) + { + return false; + } + + if (free_inodes_delta < 0 && desc.bg_free_inodes_count < (uint16_t)(-free_inodes_delta)) + { + return false; + } + + if (used_dirs_delta < 0 && desc.bg_used_dirs_count < (uint16_t)(-used_dirs_delta)) + { + return false; + } + + new_free_blocks = (int32_t)desc.bg_free_blocks_count + free_blocks_delta; + new_free_inodes = (int32_t)desc.bg_free_inodes_count + free_inodes_delta; + new_used_dirs = (int32_t)desc.bg_used_dirs_count + used_dirs_delta; + + if (new_free_blocks < 0 || new_free_inodes < 0 || new_used_dirs < 0) + { + return false; + } + + if ((int32_t)volume->superblock.s_free_blocks_count + free_blocks_delta < 0 || (int32_t)volume->superblock.s_free_inodes_count + free_inodes_delta < 0) + { + return false; + } + + desc.bg_free_blocks_count = (uint16_t)new_free_blocks; + desc.bg_free_inodes_count = (uint16_t)new_free_inodes; + desc.bg_used_dirs_count = (uint16_t)new_used_dirs; + + volume->superblock.s_free_blocks_count = (uint32_t)((int32_t)volume->superblock.s_free_blocks_count + free_blocks_delta); + volume->superblock.s_free_inodes_count = (uint32_t)((int32_t)volume->superblock.s_free_inodes_count + free_inodes_delta); + + if (!write_group_desc(volume, group, &desc)) + { + return false; + } + + return write_superblock(volume); +} + +static bool alloc_inode(EXT2_VOLUME *volume, uint32_t *inode_number_out) +{ + uint32_t group; + + if (!volume || !inode_number_out) + { + return false; + } + + for (group = 0; group < volume->block_group_count; group++) + { + EXT2_BLOCK_GROUP_DESC desc; + uint32_t bit_limit; + uint32_t start_bit = 0; + uint32_t free_bit; + + if (!read_group_desc(volume, group, &desc)) + { + return false; + } + + if (desc.bg_free_inodes_count == 0) + { + continue; + } + + if (group == 0 && volume->first_non_reserved_inode > 0) + { + start_bit = volume->first_non_reserved_inode - 1u; + } + + bit_limit = volume->inodes_per_group; + if (group == volume->block_group_count - 1u) + { + uint32_t remaining = volume->inode_count - (group * volume->inodes_per_group); + if (remaining < bit_limit) + { + bit_limit = remaining; + } + } + + if (!read_inode_bitmap(volume, group, g_bitmap_buffer)) + { + return false; + } + + if (!bitmap_find_free(g_bitmap_buffer, bit_limit, start_bit, &free_bit)) + { + continue; + } + + bitmap_set(g_bitmap_buffer, free_bit, true); + if (!write_inode_bitmap(volume, group, g_bitmap_buffer)) + { + return false; + } + + if (!update_group_and_super_counts(volume, group, 0, -1, 0)) + { + return false; + } + + *inode_number_out = (group * volume->inodes_per_group) + free_bit + 1u; + return true; + } + + return false; +} + +static bool free_inode(EXT2_VOLUME *volume, uint32_t inode_number) +{ + EXT2_BLOCK_GROUP_DESC desc; + uint32_t zero_based; + uint32_t group; + uint32_t index; + + if (!volume || inode_number == 0) + { + return false; + } + + zero_based = inode_number - 1u; + group = zero_based / volume->inodes_per_group; + index = zero_based % volume->inodes_per_group; + + if (group >= volume->block_group_count) + { + return false; + } + + if (!read_group_desc(volume, group, &desc)) + { + return false; + } + + if (!read_inode_bitmap(volume, group, g_bitmap_buffer)) + { + return false; + } + + bitmap_set(g_bitmap_buffer, index, false); + if (!write_inode_bitmap(volume, group, g_bitmap_buffer)) + { + return false; + } + + return update_group_and_super_counts(volume, group, 0, 1, 0); +} + +static bool alloc_block(EXT2_VOLUME *volume, uint32_t *block_number_out) +{ + uint32_t group; + + if (!volume || !block_number_out) + { + return false; + } + + for (group = 0; group < volume->block_group_count; group++) + { + EXT2_BLOCK_GROUP_DESC desc; + uint32_t bit_limit; + uint32_t start_block; + uint32_t free_bit; + + if (!read_group_desc(volume, group, &desc)) + { + return false; + } + + if (desc.bg_free_blocks_count == 0) + { + continue; + } + + if (!read_block_bitmap(volume, group, g_bitmap_buffer)) + { + return false; + } + + start_block = volume->first_data_block + (group * volume->blocks_per_group); + if (start_block >= volume->block_count) + { + continue; + } + + bit_limit = volume->blocks_per_group; + if (start_block + bit_limit > volume->block_count) + { + bit_limit = volume->block_count - start_block; + } + + if (!bitmap_find_free(g_bitmap_buffer, bit_limit, 0, &free_bit)) + { + continue; + } + + bitmap_set(g_bitmap_buffer, free_bit, true); + if (!write_block_bitmap(volume, group, g_bitmap_buffer)) + { + return false; + } + + if (!update_group_and_super_counts(volume, group, -1, 0, 0)) + { + return false; + } + + *block_number_out = start_block + free_bit; + return true; + } + + return false; +} + +static bool free_block(EXT2_VOLUME *volume, uint32_t block_number) +{ + EXT2_BLOCK_GROUP_DESC desc; + uint32_t relative; + uint32_t group; + + if (!volume) + { + return false; + } + + if (block_number < volume->first_data_block) + { + return false; + } + + relative = block_number - volume->first_data_block; + group = relative / volume->blocks_per_group; + if (group >= volume->block_group_count) + { + return false; + } + + if (!read_group_desc(volume, group, &desc)) + { + return false; + } + + if (!read_block_bitmap(volume, group, g_bitmap_buffer)) + { + return false; + } + + bitmap_set(g_bitmap_buffer, relative % volume->blocks_per_group, false); + if (!write_block_bitmap(volume, group, g_bitmap_buffer)) + { + return false; + } + + return update_group_and_super_counts(volume, group, 1, 0, 0); +} + +static bool free_inode_block_chain(EXT2_VOLUME *volume, EXT2_INODE *inode) +{ + uint32_t i; + + if (!volume || !inode) + { + return false; + } + + for (i = 0; i < EXT2_NDIR_BLOCKS; i++) + { + if (inode->i_block[i] != 0) + { + if (!free_block(volume, inode->i_block[i])) + { + return false; + } + + inode->i_block[i] = 0; + } + } + + if (inode->i_block[12] != 0) + { + uint32_t *entries; + uint32_t entry_count; + + if (!read_block(volume, inode->i_block[12], g_block_buffer)) + { + return false; + } + + entries = (uint32_t *)g_block_buffer; + entry_count = volume->block_size / 4u; + for (i = 0; i < entry_count; i++) + { + if (entries[i] != 0) + { + if (!free_block(volume, entries[i])) + { + return false; + } + } + } + + if (!free_block(volume, inode->i_block[12])) + { + return false; + } + + inode->i_block[12] = 0; + } + + return true; +} + +static bool split_path(const char *path, char *parent, uint32_t parent_size, char *name, uint32_t name_size) +{ + uint32_t len; + uint32_t end; + uint32_t start; + uint32_t parent_len; + uint32_t i; + + if (!path || !parent || !name || parent_size == 0 || name_size == 0 || path[0] != '/') + { + return false; + } + + len = (uint32_t)strlen(path); + while (len > 1u && path[len - 1u] == '/') + { + len--; + } + + if (len <= 1u) + { + return false; + } + + end = len; + start = end; + while (start > 0u && path[start - 1u] != '/') + { + start--; + } + + if (start == 0u) + { + return false; + } + + if ((end - start) + 1u > name_size) + { + return false; + } + + for (i = 0; i < end - start; i++) + { + name[i] = path[start + i]; + } + name[end - start] = '\0'; + + parent_len = start - 1u; + if (parent_len == 0u) + { + if (parent_size < 2u) + { + return false; + } + + parent[0] = '/'; + parent[1] = '\0'; + return true; + } + + if (parent_len + 1u > parent_size) + { + return false; + } + + for (i = 0; i < parent_len; i++) + { + parent[i] = path[i]; + } + parent[parent_len] = '\0'; + return true; +} + +static bool find_directory_entry(EXT2_VOLUME *volume, uint32_t dir_inode_number, const char *name, uint32_t name_len, uint32_t *block_index_out, uint32_t *offset_out, EXT2_DIR_ENTRY *entry_out) +{ + EXT2_INODE dir_inode; + uint32_t block_index; + uint32_t header_size = OFFSETOF(EXT2_DIR_ENTRY, file_type) + 1; + + if (!volume || !name || !block_index_out || !offset_out || !entry_out) + { + return false; + } + + if (!EXT2_ReadInode(volume, dir_inode_number, &dir_inode) || !inode_is_dir(&dir_inode)) + { + return false; + } + + for (block_index = 0; block_index < EXT2_NDIR_BLOCKS; block_index++) + { + uint32_t data_block = dir_inode.i_block[block_index]; + uint32_t offset = 0; + + if (data_block == 0) + { + continue; + } + + if (!read_block(volume, data_block, g_block_buffer)) + { + return false; + } + + while (offset + header_size <= volume->block_size) + { + EXT2_DIR_ENTRY *entry = (EXT2_DIR_ENTRY *)(g_block_buffer + offset); + uint32_t min_size = header_size + entry->name_len; + const char *entry_name; + + if (entry->rec_len < min_size || entry->rec_len == 0 || offset + entry->rec_len > volume->block_size) + { + return false; + } + + entry_name = (const char *)(g_block_buffer + offset + header_size); + if (entry->inode != 0 && name_len == entry->name_len && memcmp(name, entry_name, name_len) == 0) + { + *block_index_out = block_index; + *offset_out = offset; + *entry_out = *entry; + return true; + } + + offset += entry->rec_len; + } + } + + return false; +} + +static bool append_or_replace_directory_entry(EXT2_VOLUME *volume, uint32_t parent_inode_number, EXT2_INODE *parent_inode, const char *name, uint32_t name_len, uint32_t child_inode_number, uint8_t file_type) +{ + uint32_t header_size = OFFSETOF(EXT2_DIR_ENTRY, file_type) + 1; + uint32_t needed = ext2_dir_entry_size(name_len); + uint32_t block_index; + if (!volume || !parent_inode || !name || name_len == 0 || name_len >= 255) + { + return false; + } + + for (block_index = 0; block_index < EXT2_NDIR_BLOCKS; block_index++) + { + uint32_t data_block = parent_inode->i_block[block_index]; + uint32_t cursor = 0; + + if (data_block == 0) + { + continue; + } + + if (!read_block(volume, data_block, g_block_buffer)) + { + return false; + } + + while (cursor + header_size <= volume->block_size) + { + EXT2_DIR_ENTRY *entry = (EXT2_DIR_ENTRY *)(g_block_buffer + cursor); + uint32_t used = ext2_dir_entry_size(entry->name_len); + uint32_t available; + + if (entry->rec_len == 0 || cursor + entry->rec_len > volume->block_size) + { + return false; + } + + available = entry->rec_len; + if (entry->inode == 0) + { + if (available >= needed) + { + EXT2_DIR_ENTRY *new_entry = entry; + + memset(new_entry, 0, available); + new_entry->inode = child_inode_number; + new_entry->rec_len = (uint16_t)available; + new_entry->name_len = (uint8_t)name_len; + new_entry->file_type = file_type; + memcpy((uint8_t *)new_entry + header_size, name, name_len); + return write_block(volume, data_block, g_block_buffer); + } + } + else if (available >= used + needed) + { + EXT2_DIR_ENTRY *new_entry = (EXT2_DIR_ENTRY *)(g_block_buffer + cursor + used); + uint32_t remaining = available - used; + + entry->rec_len = (uint16_t)used; + memset(new_entry, 0, remaining); + new_entry->inode = child_inode_number; + new_entry->rec_len = (uint16_t)remaining; + new_entry->name_len = (uint8_t)name_len; + new_entry->file_type = file_type; + memcpy((uint8_t *)new_entry + header_size, name, name_len); + return write_block(volume, data_block, g_block_buffer); + } + + cursor += entry->rec_len; + } + } + + for (block_index = 0; block_index < EXT2_NDIR_BLOCKS; block_index++) + { + if (parent_inode->i_block[block_index] == 0) + { + uint32_t new_block; + EXT2_DIR_ENTRY *entry; + + if (!alloc_block(volume, &new_block)) + { + return false; + } + + memset(g_block_buffer, 0, volume->block_size); + entry = (EXT2_DIR_ENTRY *)g_block_buffer; + entry->inode = child_inode_number; + entry->rec_len = (uint16_t)volume->block_size; + entry->name_len = (uint8_t)name_len; + entry->file_type = file_type; + memcpy((uint8_t *)entry + header_size, name, name_len); + + parent_inode->i_block[block_index] = new_block; + parent_inode->i_size += volume->block_size; + parent_inode->i_blocks += volume->sectors_per_block; + return write_block(volume, new_block, g_block_buffer) && write_inode(volume, parent_inode_number, parent_inode); + } + } + + return false; +} + +static bool update_directory_entry_name(EXT2_VOLUME *volume, uint32_t dir_inode_number, const char *old_name, uint32_t old_name_len, const char *new_name, uint32_t new_name_len) +{ + EXT2_INODE dir_inode; + uint32_t block_index; + uint32_t entry_offset; + EXT2_DIR_ENTRY entry; + uint32_t header_size = OFFSETOF(EXT2_DIR_ENTRY, file_type) + 1; + uint32_t needed = ext2_dir_entry_size(new_name_len); + + if (!volume || !old_name || !new_name || new_name_len == 0 || new_name_len >= 255) + { + return false; + } + + if (!EXT2_ReadInode(volume, dir_inode_number, &dir_inode) || !inode_is_dir(&dir_inode)) + { + return false; + } + + if (!find_directory_entry(volume, dir_inode_number, old_name, old_name_len, &block_index, &entry_offset, &entry)) + { + return false; + } + + if (!read_block(volume, dir_inode.i_block[block_index], g_block_buffer)) + { + return false; + } + + if (needed > entry.rec_len) + { + return false; + } + + ((EXT2_DIR_ENTRY *)(g_block_buffer + entry_offset))->name_len = (uint8_t)new_name_len; + ((EXT2_DIR_ENTRY *)(g_block_buffer + entry_offset))->file_type = entry.file_type; + memcpy(g_block_buffer + entry_offset + header_size, new_name, new_name_len); + return write_block(volume, dir_inode.i_block[block_index], g_block_buffer); +} + +static bool directory_is_empty(EXT2_VOLUME *volume, uint32_t inode_number) +{ + EXT2_INODE dir_inode; + uint32_t block_index; + uint32_t header_size = OFFSETOF(EXT2_DIR_ENTRY, file_type) + 1; + + if (!EXT2_ReadInode(volume, inode_number, &dir_inode) || !inode_is_dir(&dir_inode)) + { + return false; + } + + for (block_index = 0; block_index < EXT2_NDIR_BLOCKS; block_index++) + { + uint32_t data_block = dir_inode.i_block[block_index]; + uint32_t offset = 0; + + if (data_block == 0) + { + continue; + } + + if (!read_block(volume, data_block, g_block_buffer)) + { + return false; + } + + while (offset + header_size <= volume->block_size) + { + EXT2_DIR_ENTRY *entry = (EXT2_DIR_ENTRY *)(g_block_buffer + offset); + uint32_t min_size = header_size + entry->name_len; + + if (entry->rec_len < min_size || entry->rec_len == 0 || offset + entry->rec_len > volume->block_size) + { + return false; + } + + if (entry->inode != 0) + { + const char *entry_name = (const char *)(g_block_buffer + offset + header_size); + + if (!(entry->name_len == 1u && entry_name[0] == '.') && !(entry->name_len == 2u && entry_name[0] == '.' && entry_name[1] == '.')) + { + return false; + } + } + + offset += entry->rec_len; + } + } + + return true; +} + +static bool update_directory_parent_link(EXT2_VOLUME *volume, uint32_t dir_inode_number, uint32_t parent_inode_number) +{ + EXT2_INODE dir_inode; + EXT2_DIR_ENTRY *entry; + uint32_t header_size = OFFSETOF(EXT2_DIR_ENTRY, file_type) + 1; + + if (!EXT2_ReadInode(volume, dir_inode_number, &dir_inode) || !inode_is_dir(&dir_inode)) + { + return false; + } + + if (dir_inode.i_block[0] == 0) + { + return false; + } + + if (!read_block(volume, dir_inode.i_block[0], g_block_buffer)) + { + return false; + } + + entry = (EXT2_DIR_ENTRY *)(g_block_buffer + ext2_dir_entry_size(1u)); + if (entry->name_len != 2u) + { + return false; + } + + if (memcmp((uint8_t *)entry + header_size, "..", 2u) != 0) + { + return false; + } + + entry->inode = parent_inode_number; + return write_block(volume, dir_inode.i_block[0], g_block_buffer); +} + +static bool remove_directory_entry(EXT2_VOLUME *volume, uint32_t dir_inode_number, const char *name, uint32_t name_len) +{ + EXT2_INODE dir_inode; + uint32_t block_index; + uint32_t entry_offset; + EXT2_DIR_ENTRY entry; + + if (!EXT2_ReadInode(volume, dir_inode_number, &dir_inode) || !inode_is_dir(&dir_inode)) + { + return false; + } + + if (!find_directory_entry(volume, dir_inode_number, name, name_len, &block_index, &entry_offset, &entry)) + { + return false; + } + + if (!read_block(volume, dir_inode.i_block[block_index], g_block_buffer)) + { + return false; + } + + entry = *(EXT2_DIR_ENTRY *)(g_block_buffer + entry_offset); + entry.inode = 0; + entry.name_len = 0; + entry.file_type = 0; + memcpy(g_block_buffer + entry_offset, &entry, (uint32_t)sizeof(EXT2_DIR_ENTRY)); + return write_block(volume, dir_inode.i_block[block_index], g_block_buffer); +} + +static bool setup_new_file_inode(EXT2_INODE *inode) +{ + if (!inode) + { + return false; + } + + memset(inode, 0, (uint32_t)sizeof(EXT2_INODE)); + inode->i_mode = EXT2_S_IFREG | 0644u; + inode->i_links_count = 1u; + return true; +} + +static bool setup_new_dir_inode(EXT2_INODE *inode, EXT2_VOLUME *volume) +{ + if (!inode || !volume) + { + return false; + } + + memset(inode, 0, (uint32_t)sizeof(EXT2_INODE)); + inode->i_mode = EXT2_S_IFDIR | 0755u; + inode->i_links_count = 2u; + inode->i_size = volume->block_size; + inode->i_blocks = volume->sectors_per_block; + return true; +} + +static bool initialize_directory_block(EXT2_VOLUME *volume, uint32_t inode_number, uint32_t parent_inode_number, uint32_t block_number) +{ + EXT2_DIR_ENTRY *dot; + EXT2_DIR_ENTRY *dotdot; + uint32_t header_size = OFFSETOF(EXT2_DIR_ENTRY, file_type) + 1; + uint32_t dot_size = ext2_dir_entry_size(1u); + + if (!volume || volume->block_size < (dot_size + header_size + 1u)) + { + return false; + } + + memset(g_block_buffer, 0, volume->block_size); + dot = (EXT2_DIR_ENTRY *)g_block_buffer; + dot->inode = inode_number; + dot->rec_len = (uint16_t)dot_size; + dot->name_len = 1u; + dot->file_type = EXT2_FT_DIR; + memcpy((uint8_t *)dot + header_size, ".", 1u); + + dotdot = (EXT2_DIR_ENTRY *)(g_block_buffer + dot_size); + dotdot->inode = parent_inode_number; + dotdot->rec_len = (uint16_t)(volume->block_size - dot_size); + dotdot->name_len = 2u; + dotdot->file_type = EXT2_FT_DIR; + memcpy((uint8_t *)dotdot + header_size, "..", 2u); + + return write_block(volume, block_number, g_block_buffer); +} + +static bool lookup_parent_and_name(EXT2_VOLUME *volume, const char *path, uint32_t *parent_inode_out, char *name_out, uint32_t name_out_size, char *parent_path, uint32_t parent_path_size) +{ + uint32_t parent_inode; + EXT2_INODE parent_inode_cache; + + if (!volume || !path || !parent_inode_out || !name_out || !parent_path) + { + return false; + } + + if (!split_path(path, parent_path, parent_path_size, name_out, name_out_size)) { - uint32_t lba = byte_offset / EXT2_SECTOR_SIZE; - uint32_t offset = byte_offset % EXT2_SECTOR_SIZE; - uint32_t chunk = EXT2_MIN(EXT2_SECTOR_SIZE - offset, size); - - if (!block_device_read(disk, lba, 1, g_sector_buffer)) - { - return false; - } + return false; + } - memcpy(dst, g_sector_buffer + offset, chunk); + if (!EXT2_LookupPath(volume, parent_path, &parent_inode)) + { + return false; + } - dst += chunk; - byte_offset += chunk; - size -= chunk; + if (!EXT2_ReadInode(volume, parent_inode, &parent_inode_cache) || !inode_is_dir(&parent_inode_cache)) + { + return false; } + *parent_inode_out = parent_inode; return true; } -static bool read_block(EXT2_VOLUME *volume, uint32_t block, void *out) +static bool lookup_child_type(EXT2_VOLUME *volume, uint32_t parent_inode_number, const char *name, uint32_t name_len, uint32_t *child_inode_out, uint8_t *child_type_out) { - uint32_t lba; - if (!volume || volume->sectors_per_block == 0) + uint32_t block_index; + uint32_t entry_offset; + EXT2_DIR_ENTRY entry; + + if (!find_directory_entry(volume, parent_inode_number, name, name_len, &block_index, &entry_offset, &entry)) { return false; } - lba = block * volume->sectors_per_block; - return block_device_read(volume->disk, lba, (uint8_t)volume->sectors_per_block, out); + if (child_inode_out) + { + *child_inode_out = entry.inode; + } + + if (child_type_out) + { + *child_type_out = entry.file_type; + } + + return true; } -static bool read_group_desc(EXT2_VOLUME *volume, uint32_t group, EXT2_BLOCK_GROUP_DESC *out_desc) +static bool free_inode_and_blocks(EXT2_VOLUME *volume, uint32_t inode_number, EXT2_INODE *inode) { - uint32_t bgdt_byte; - uint32_t desc_offset; + EXT2_INODE cache; - if (!volume || !out_desc) + if (!volume) { return false; } - bgdt_byte = volume->bgdt_start_block * volume->block_size; - desc_offset = group * (uint32_t)sizeof(EXT2_BLOCK_GROUP_DESC); - return read_abs_bytes(volume->disk, bgdt_byte + desc_offset, (uint32_t)sizeof(EXT2_BLOCK_GROUP_DESC), out_desc); + if (inode) + { + cache = *inode; + } + else if (!EXT2_ReadInode(volume, inode_number, &cache)) + { + return false; + } + + if (!free_inode_block_chain(volume, &cache)) + { + return false; + } + + return free_inode(volume, inode_number); } static bool inode_is_dir(const EXT2_INODE *inode) @@ -204,9 +1327,13 @@ bool EXT2_Initialize(EXT2_VOLUME *volume, BLOCK_DEVICE *disk) } volume->disk = disk; + volume->superblock = sb; volume->block_size = 1024u << sb.s_log_block_size; volume->inode_size = (sb.s_inode_size == 0) ? 128u : sb.s_inode_size; volume->first_data_block = sb.s_first_data_block; + volume->block_count = sb.s_blocks_count; + volume->inode_count = sb.s_inodes_count; + volume->first_non_reserved_inode = (sb.s_first_ino == 0) ? 11u : sb.s_first_ino; volume->blocks_per_group = sb.s_blocks_per_group; volume->inodes_per_group = sb.s_inodes_per_group; volume->bgdt_start_block = sb.s_first_data_block + 1u; @@ -643,3 +1770,349 @@ bool EXT2_ReadFile(EXT2_VOLUME *volume, uint32_t inode_number, uint32_t offset, return true; } + +bool EXT2_CreateFile(EXT2_VOLUME *volume, const char *path) +{ + char parent_path[256]; + char name[256]; + uint32_t parent_inode_number; + EXT2_INODE parent_inode; + EXT2_INODE new_inode; + uint32_t child_inode_number; + uint32_t name_len; + uint8_t child_type; + + if (!volume || !path) + { + return false; + } + + if (!lookup_parent_and_name(volume, path, &parent_inode_number, name, sizeof(name), parent_path, sizeof(parent_path))) + { + return false; + } + + name_len = (uint32_t)strlen(name); + if (lookup_child_type(volume, parent_inode_number, name, name_len, 0, &child_type)) + { + return false; + } + + if (!EXT2_ReadInode(volume, parent_inode_number, &parent_inode) || !inode_is_directory(&parent_inode)) + { + return false; + } + + if (!alloc_inode(volume, &child_inode_number)) + { + return false; + } + + if (!setup_new_file_inode(&new_inode)) + { + return false; + } + + if (!write_inode(volume, child_inode_number, &new_inode)) + { + free_inode(volume, child_inode_number); + return false; + } + + if (!append_or_replace_directory_entry(volume, parent_inode_number, &parent_inode, name, name_len, child_inode_number, EXT2_FT_REG_FILE)) + { + free_inode(volume, child_inode_number); + return false; + } + + parent_inode.i_mtime = 0; + parent_inode.i_ctime = 0; + write_inode(volume, parent_inode_number, &parent_inode); + return true; +} + +bool EXT2_CreateDir(EXT2_VOLUME *volume, const char *path) +{ + char parent_path[256]; + char name[256]; + uint32_t parent_inode_number; + EXT2_INODE parent_inode; + EXT2_INODE new_inode; + uint32_t child_inode_number; + uint32_t child_block_number; + uint32_t name_len; + uint32_t child_group; + + if (!volume || !path) + { + return false; + } + + if (!lookup_parent_and_name(volume, path, &parent_inode_number, name, sizeof(name), parent_path, sizeof(parent_path))) + { + return false; + } + + name_len = (uint32_t)strlen(name); + if (lookup_child_type(volume, parent_inode_number, name, name_len, 0, 0)) + { + return false; + } + + if (!EXT2_ReadInode(volume, parent_inode_number, &parent_inode) || !inode_is_directory(&parent_inode)) + { + return false; + } + + if (!alloc_inode(volume, &child_inode_number)) + { + return false; + } + + child_group = (child_inode_number - 1u) / volume->inodes_per_group; + + if (!alloc_block(volume, &child_block_number)) + { + free_inode(volume, child_inode_number); + return false; + } + + if (!setup_new_dir_inode(&new_inode, volume)) + { + free_block(volume, child_block_number); + free_inode(volume, child_inode_number); + return false; + } + + new_inode.i_block[0] = child_block_number; + if (!initialize_directory_block(volume, child_inode_number, parent_inode_number, child_block_number)) + { + free_block(volume, child_block_number); + free_inode(volume, child_inode_number); + return false; + } + + if (!write_inode(volume, child_inode_number, &new_inode)) + { + free_block(volume, child_block_number); + free_inode(volume, child_inode_number); + return false; + } + + if (!append_or_replace_directory_entry(volume, parent_inode_number, &parent_inode, name, name_len, child_inode_number, EXT2_FT_DIR)) + { + free_block(volume, child_block_number); + free_inode(volume, child_inode_number); + return false; + } + + parent_inode.i_links_count++; + parent_inode.i_mtime = 0; + parent_inode.i_ctime = 0; + if (!write_inode(volume, parent_inode_number, &parent_inode)) + { + return false; + } + + update_group_and_super_counts(volume, child_group, 0, 0, 1); + return true; +} + +bool EXT2_RemoveFile(EXT2_VOLUME *volume, const char *path) +{ + char parent_path[256]; + char name[256]; + uint32_t parent_inode_number; + uint32_t child_inode_number; + uint8_t child_type; + uint32_t name_len; + + if (!volume || !path) + { + return false; + } + + if (!lookup_parent_and_name(volume, path, &parent_inode_number, name, sizeof(name), parent_path, sizeof(parent_path))) + { + return false; + } + + name_len = (uint32_t)strlen(name); + if (!lookup_child_type(volume, parent_inode_number, name, name_len, &child_inode_number, &child_type) || child_type != EXT2_FT_REG_FILE) + { + return false; + } + + if (!remove_directory_entry(volume, parent_inode_number, name, name_len)) + { + return false; + } + + if (!free_inode_and_blocks(volume, child_inode_number, 0)) + { + return false; + } + + return true; +} + +bool EXT2_RemoveDir(EXT2_VOLUME *volume, const char *path) +{ + char parent_path[256]; + char name[256]; + uint32_t parent_inode_number; + uint32_t child_inode_number; + uint8_t child_type; + EXT2_INODE child_inode; + EXT2_INODE parent_inode; + uint32_t name_len; + uint32_t child_group; + + if (!volume || !path) + { + return false; + } + + if (!lookup_parent_and_name(volume, path, &parent_inode_number, name, sizeof(name), parent_path, sizeof(parent_path))) + { + return false; + } + + name_len = (uint32_t)strlen(name); + if (!lookup_child_type(volume, parent_inode_number, name, name_len, &child_inode_number, &child_type) || child_type != EXT2_FT_DIR) + { + return false; + } + + if (!EXT2_ReadInode(volume, child_inode_number, &child_inode) || !inode_is_directory(&child_inode)) + { + return false; + } + + if (!directory_is_empty(volume, child_inode_number)) + { + return false; + } + + if (!remove_directory_entry(volume, parent_inode_number, name, name_len)) + { + return false; + } + + if (!EXT2_ReadInode(volume, parent_inode_number, &parent_inode)) + { + return false; + } + + if (parent_inode.i_links_count > 0) + { + parent_inode.i_links_count--; + parent_inode.i_mtime = 0; + parent_inode.i_ctime = 0; + write_inode(volume, parent_inode_number, &parent_inode); + } + + child_group = (child_inode_number - 1u) / volume->inodes_per_group; + if (!free_inode_and_blocks(volume, child_inode_number, &child_inode)) + { + return false; + } + + update_group_and_super_counts(volume, child_group, 0, 0, -1); + return true; +} + +bool EXT2_Rename(EXT2_VOLUME *volume, const char *old_path, const char *new_path) +{ + char old_parent_path[256]; + char old_name[256]; + char new_parent_path[256]; + char new_name[256]; + uint32_t old_parent_inode_number; + uint32_t new_parent_inode_number; + uint32_t child_inode_number; + uint8_t child_type; + EXT2_INODE old_parent_inode; + EXT2_INODE new_parent_inode; + uint32_t old_name_len; + uint32_t new_name_len; + uint32_t child_group; + + if (!volume || !old_path || !new_path) + { + return false; + } + + if (!lookup_parent_and_name(volume, old_path, &old_parent_inode_number, old_name, sizeof(old_name), old_parent_path, sizeof(old_parent_path))) + { + return false; + } + + if (!lookup_parent_and_name(volume, new_path, &new_parent_inode_number, new_name, sizeof(new_name), new_parent_path, sizeof(new_parent_path))) + { + return false; + } + + old_name_len = (uint32_t)strlen(old_name); + new_name_len = (uint32_t)strlen(new_name); + + if (!lookup_child_type(volume, old_parent_inode_number, old_name, old_name_len, &child_inode_number, &child_type)) + { + return false; + } + + if (!EXT2_ReadInode(volume, old_parent_inode_number, &old_parent_inode) || !inode_is_directory(&old_parent_inode)) + { + return false; + } + + if (!EXT2_ReadInode(volume, new_parent_inode_number, &new_parent_inode) || !inode_is_directory(&new_parent_inode)) + { + return false; + } + + if (lookup_child_type(volume, new_parent_inode_number, new_name, new_name_len, 0, 0)) + { + return false; + } + + if (old_parent_inode_number == new_parent_inode_number) + { + if (update_directory_entry_name(volume, old_parent_inode_number, old_name, old_name_len, new_name, new_name_len)) + { + return true; + } + } + + if (!append_or_replace_directory_entry(volume, new_parent_inode_number, &new_parent_inode, new_name, new_name_len, child_inode_number, child_type)) + { + return false; + } + + if (child_type == EXT2_FT_DIR && old_parent_inode_number != new_parent_inode_number) + { + if (!update_directory_parent_link(volume, child_inode_number, new_parent_inode_number)) + { + remove_directory_entry(volume, new_parent_inode_number, new_name, new_name_len); + return false; + } + + child_group = (child_inode_number - 1u) / volume->inodes_per_group; + if (old_parent_inode.i_links_count > 0) + { + old_parent_inode.i_links_count--; + } + + new_parent_inode.i_links_count++; + write_inode(volume, old_parent_inode_number, &old_parent_inode); + write_inode(volume, new_parent_inode_number, &new_parent_inode); + update_group_and_super_counts(volume, child_group, 0, 0, 0); + } + + if (!remove_directory_entry(volume, old_parent_inode_number, old_name, old_name_len)) + { + return false; + } + + return true; +} diff --git a/src/fs/ext2/ext2.h b/src/fs/ext2/ext2.h index 9ac2612..9e63c1f 100644 --- a/src/fs/ext2/ext2.h +++ b/src/fs/ext2/ext2.h @@ -108,15 +108,19 @@ typedef struct __attribute__((packed)) typedef struct { - BLOCK_DEVICE *disk; // pointer to the block device used by this filesystem - uint32_t block_size; // size of each block in bytes (calculated as 1024 << s_log_block_size) - uint32_t sectors_per_block; // number of sectors in each block (calculated as block_size / EXT2_SECTOR_SIZE) - uint32_t inode_size; // size of each inode structure in bytes (from s_inode_size, defaulting to 128 if zero) - uint32_t first_data_block; // block number of the first data block in the filesystem (from s_first_data_block) - uint32_t block_group_count; // total number of block groups in the filesystem (calculated from s_blocks_count and s_blocks_per_group) - uint32_t blocks_per_group; // number of blocks in each block group (from s_blocks_per_group) - uint32_t inodes_per_group; // number of inodes in each block group (from s_inodes_per_group) - uint32_t bgdt_start_block; // block number of the starting block of the block group descriptor table (calculated as first_data_block + 1) + BLOCK_DEVICE *disk; // pointer to the block device used by this filesystem + EXT2_SUPERBLOCK superblock; // cached superblock for mutation helpers + uint32_t block_size; // size of each block in bytes (calculated as 1024 << s_log_block_size) + uint32_t sectors_per_block; // number of sectors in each block (calculated as block_size / EXT2_SECTOR_SIZE) + uint32_t inode_size; // size of each inode structure in bytes (from s_inode_size, defaulting to 128 if zero) + uint32_t first_data_block; // block number of the first data block in the filesystem (from s_first_data_block) + uint32_t block_count; // total number of blocks in the filesystem + uint32_t inode_count; // total number of inodes in the filesystem + uint32_t first_non_reserved_inode; // first non-reserved inode number + uint32_t block_group_count; // total number of block groups in the filesystem (calculated from s_blocks_count and s_blocks_per_group) + uint32_t blocks_per_group; // number of blocks in each block group (from s_blocks_per_group) + uint32_t inodes_per_group; // number of inodes in each block group (from s_inodes_per_group) + uint32_t bgdt_start_block; // block number of the starting block of the block group descriptor table (calculated as first_data_block + 1) } EXT2_VOLUME; typedef struct @@ -174,6 +178,31 @@ bool EXT2_ListDirectory(EXT2_VOLUME *volume, uint32_t inode_number); */ bool EXT2_ReadFile(EXT2_VOLUME *volume, uint32_t inode_number, uint32_t offset, uint32_t length, void *buffer); +/** + * create a regular file at an absolute path. + */ +bool EXT2_CreateFile(EXT2_VOLUME *volume, const char *path); + +/** + * create a directory at an absolute path. + */ +bool EXT2_CreateDir(EXT2_VOLUME *volume, const char *path); + +/** + * remove a regular file at an absolute path. + */ +bool EXT2_RemoveFile(EXT2_VOLUME *volume, const char *path); + +/** + * remove a directory at an absolute path. + */ +bool EXT2_RemoveDir(EXT2_VOLUME *volume, const char *path); + +/** + * rename a file or directory. + */ +bool EXT2_Rename(EXT2_VOLUME *volume, const char *old_path, const char *new_path); + /** * resolve an absolute path to an inode. * @param volume initialized ext2 volume. diff --git a/src/kernel/fs/fs.c b/src/kernel/fs/fs.c index 82fa062..c4e2f26 100644 --- a/src/kernel/fs/fs.c +++ b/src/kernel/fs/fs.c @@ -21,6 +21,21 @@ static uint8_t map_ext2_file_type(uint8_t ext2_type) return FS_TYPE_UNKNOWN; } +static uint8_t map_inode_type(uint16_t mode) +{ + if ((mode & EXT2_S_IFMT) == EXT2_S_IFREG) + { + return FS_TYPE_FILE; + } + + if ((mode & EXT2_S_IFMT) == EXT2_S_IFDIR) + { + return FS_TYPE_DIR; + } + + return FS_TYPE_UNKNOWN; +} + bool fs_mount(FS_MOUNT *mount, BLOCK_DEVICE *device) { if (!mount || !device) @@ -54,6 +69,88 @@ bool fs_open(FS_MOUNT *mount, const char *path, FS_FILE *file) return true; } +bool fs_create(FS_MOUNT *mount, const char *path) +{ + if (!mount || !path || !mount->is_mounted) + { + return false; + } + + return EXT2_CreateFile(&mount->ext2, path); +} + +bool fs_mkdir(FS_MOUNT *mount, const char *path) +{ + if (!mount || !path || !mount->is_mounted) + { + return false; + } + + return EXT2_CreateDir(&mount->ext2, path); +} + +bool fs_remove(FS_MOUNT *mount, const char *path) +{ + if (!mount || !path || !mount->is_mounted) + { + return false; + } + + return EXT2_RemoveFile(&mount->ext2, path); +} + +bool fs_rmdir(FS_MOUNT *mount, const char *path) +{ + if (!mount || !path || !mount->is_mounted) + { + return false; + } + + return EXT2_RemoveDir(&mount->ext2, path); +} + +bool fs_rename(FS_MOUNT *mount, const char *old_path, const char *new_path) +{ + if (!mount || !old_path || !new_path || !mount->is_mounted) + { + return false; + } + + return EXT2_Rename(&mount->ext2, old_path, new_path); +} + +bool fs_stat(FS_MOUNT *mount, const char *path, FS_STAT *stat_out) +{ + uint32_t inode_number; + EXT2_INODE inode; + + if (!mount || !path || !stat_out || !mount->is_mounted) + { + return false; + } + + if (!EXT2_LookupPath(&mount->ext2, path, &inode_number)) + { + return false; + } + + if (!EXT2_ReadInode(&mount->ext2, inode_number, &inode)) + { + return false; + } + + stat_out->inode = inode_number; + stat_out->file_type = map_inode_type(inode.i_mode); + stat_out->mode = inode.i_mode; + stat_out->links_count = inode.i_links_count; + stat_out->size = inode.i_size; + stat_out->blocks = inode.i_blocks; + stat_out->atime = inode.i_atime; + stat_out->mtime = inode.i_mtime; + stat_out->ctime = inode.i_ctime; + return true; +} + uint32_t fs_read(FS_FILE *file, uint32_t byteCount, void *dataOut) { if (!file || !file->is_open) diff --git a/src/kernel/fs/fs.h b/src/kernel/fs/fs.h index ca95488..6b964ab 100644 --- a/src/kernel/fs/fs.h +++ b/src/kernel/fs/fs.h @@ -34,6 +34,22 @@ typedef struct uint8_t is_mounted; // flag indicating whether the filesystem is successfully mounted (1 for mounted, 0 for not mounted) } FS_MOUNT; +/** + * filesystem metadata structure returned by stat. + */ +typedef struct +{ + uint32_t inode; + uint8_t file_type; + uint16_t mode; + uint16_t links_count; + uint32_t size; + uint32_t blocks; + uint32_t atime; + uint32_t mtime; + uint32_t ctime; +} FS_STAT; + /** * file handle structure representing an open file or directory. */ @@ -61,6 +77,56 @@ bool fs_mount(FS_MOUNT *mount, BLOCK_DEVICE *device); */ bool fs_open(FS_MOUNT *mount, const char *path, FS_FILE *file); +/** + * create a regular file at an absolute path. + * @param mount initialized filesystem mount. + * @param path absolute path to the new file. + * @return true on success. false on failure. + */ +bool fs_create(FS_MOUNT *mount, const char *path); + +/** + * create a directory at an absolute path. + * @param mount initialized filesystem mount. + * @param path absolute path to the new directory. + * @return true on success. false on failure. + */ +bool fs_mkdir(FS_MOUNT *mount, const char *path); + +/** + * remove a file at an absolute path. + * @param mount initialized filesystem mount. + * @param path absolute path to the file to remove. + * @return true on success. false on failure. + */ +bool fs_remove(FS_MOUNT *mount, const char *path); + +/** + * remove a directory at an absolute path. + * @param mount initialized filesystem mount. + * @param path absolute path to the directory to remove. + * @return true on success. false on failure. + */ +bool fs_rmdir(FS_MOUNT *mount, const char *path); + +/** + * rename a file or directory. + * @param mount initialized filesystem mount. + * @param old_path absolute path to the existing file or directory. + * @param new_path absolute path to the new name for the file or directory. + * @return true on success. false on failure. + */ +bool fs_rename(FS_MOUNT *mount, const char *old_path, const char *new_path); + +/** + * stat a file or directory by absolute path. + * @param mount initialized filesystem mount. + * @param path absolute path to the file or directory. + * @param stat_out output stat structure. + * @return true on success. false on failure. + */ +bool fs_stat(FS_MOUNT *mount, const char *path, FS_STAT *stat_out); + /** * read bytes from an open file into a buffer. * @param file open file handle. diff --git a/src/kernel/vfs/vfs.c b/src/kernel/vfs/vfs.c index e6227fc..7a08325 100644 --- a/src/kernel/vfs/vfs.c +++ b/src/kernel/vfs/vfs.c @@ -148,6 +148,66 @@ bool vfs_open( return true; } +bool vfs_create(const char *path) +{ + if (!root_mount || !path) + { + return false; + } + + return fs_create(root_mount, path); +} + +bool vfs_mkdir(const char *path) +{ + if (!root_mount || !path) + { + return false; + } + + return fs_mkdir(root_mount, path); +} + +bool vfs_remove(const char *path) +{ + if (!root_mount || !path) + { + return false; + } + + return fs_remove(root_mount, path); +} + +bool vfs_rmdir(const char *path) +{ + if (!root_mount || !path) + { + return false; + } + + return fs_rmdir(root_mount, path); +} + +bool vfs_rename(const char *old_path, const char *new_path) +{ + if (!root_mount || !old_path || !new_path) + { + return false; + } + + return fs_rename(root_mount, old_path, new_path); +} + +bool vfs_stat(const char *path, VFS_STAT *stat_out) +{ + if (!root_mount || !path || !stat_out) + { + return false; + } + + return fs_stat(root_mount, path, stat_out); +} + bool vfs_resolve_path( const char *cwd, const char *input, diff --git a/src/kernel/vfs/vfs.h b/src/kernel/vfs/vfs.h index c1dc12a..adcbc70 100644 --- a/src/kernel/vfs/vfs.h +++ b/src/kernel/vfs/vfs.h @@ -2,6 +2,8 @@ #include "../fs/fs.h" +typedef FS_STAT VFS_STAT; + /** * virtual file system file handle structure representing an open file or directory. * this structure is used to abstract the underlying filesystem implementation. @@ -29,6 +31,50 @@ bool vfs_open( const char *path, VFS_FILE *file); +/** + * create a regular file by absolute path. + * @param path absolute path to the new file. + * @return true on success, false on failure. + */ +bool vfs_create(const char *path); + +/** + * create a directory by absolute path. + * @param path absolute path to the new directory. + * @return true on success, false on failure. + */ +bool vfs_mkdir(const char *path); + +/** + * remove a file by absolute path. + * @param path absolute path to the file to remove. + * @return true on success, false on failure. + */ +bool vfs_remove(const char *path); + +/** + * remove a directory by absolute path. + * @param path absolute path to the directory to remove. + * @return true on success, false on failure. + */ +bool vfs_rmdir(const char *path); + +/** + * rename a file or directory by absolute paths. + * @param old_path absolute path to the existing file or directory. + * @param new_path absolute path to the new name for the file or directory. + * @return true on success, false on failure. + */ +bool vfs_rename(const char *old_path, const char *new_path); + +/** + * stat a file or directory by absolute path. + * @param path absolute path to the file or directory. + * @param stat_out pointer to a VFS_STAT structure that will be filled with the file's metadata. + * @return true on success, false on failure. + */ +bool vfs_stat(const char *path, VFS_STAT *stat_out); + /** * resolve an input path against the current working directory. * absolute paths are returned as-is, relative paths are prefixed with cwd.