diff options
Diffstat (limited to 'src/loader/fs/ext2.c')
-rw-r--r-- | src/loader/fs/ext2.c | 201 |
1 files changed, 201 insertions, 0 deletions
diff --git a/src/loader/fs/ext2.c b/src/loader/fs/ext2.c new file mode 100644 index 0000000..fd46ca6 --- /dev/null +++ b/src/loader/fs/ext2.c @@ -0,0 +1,201 @@ +// MIT License, Copyright (c) 2021 Marvin Borner + +#include <dev.h> +#include <fs/ext2.h> +#include <lib.h> +#include <pnc.h> + +static void ext2_buffer_read(u32 block, void *buf, struct dev *dev) +{ + dev->read(buf, block * SECTOR_COUNT, SECTOR_COUNT, dev); +} + +static void ext2_get_superblock(struct ext2_superblock *buf, struct dev *dev) +{ + u8 data[BLOCK_SIZE] = { 0 }; + ext2_buffer_read(EXT2_SUPER, data, dev); + memcpy(buf, data, sizeof(*buf)); + + assert(buf->magic == EXT2_MAGIC); +} + +static struct ext2_inode *ext2_get_inode(u32 i, struct ext2_inode *in_buf, struct dev *dev) +{ + struct ext2_superblock sb = { 0 }; + ext2_get_superblock(&sb, dev); + + u8 data[BLOCK_SIZE] = { 0 }; + ext2_buffer_read(EXT2_SUPER + 1, data, dev); + struct ext2_bgd *bgd = (struct ext2_bgd *)data; + + u32 block_group = (i - 1) / sb.inodes_per_group; + u32 index = (i - 1) % sb.inodes_per_group; + u32 block = (index * EXT2_INODE_SIZE) / BLOCK_SIZE; + bgd += block_group; + + u8 buf[BLOCK_SIZE] = { 0 }; + ext2_buffer_read(bgd->inode_table + block, buf, dev); + struct ext2_inode *in = + (struct ext2_inode *)((u32)buf + + (index % (BLOCK_SIZE / EXT2_INODE_SIZE)) * EXT2_INODE_SIZE); + + memcpy(in_buf, in, sizeof(*in_buf)); + + return in_buf; +} + +static u32 ext2_read_indirect(u32 indirect, u32 block_num, struct dev *dev) +{ + u8 buf[BLOCK_SIZE] = { 0 }; + ext2_buffer_read(indirect, buf, dev); + u32 ind = *(u32 *)((u32)buf + block_num * sizeof(u32)); + return ind; +} + +static s32 ext2_read_inode(struct ext2_inode *in, void *buf, u32 offset, u32 count, struct dev *dev) +{ + if (!in || !buf) + return -1; + + if (in->size == 0) + return 0; + + u32 num_blocks = in->blocks / (BLOCK_SIZE / SECTOR_SIZE) + 1; + + if (!num_blocks) + return -1; + + u32 first_block = offset / BLOCK_SIZE; + u32 last_block = (offset + count) / BLOCK_SIZE; + if (last_block >= num_blocks) + last_block = num_blocks - 1; + u32 first_block_offset = offset % BLOCK_SIZE; + + u32 remaining = MIN(count, in->size - offset); + u32 copied = 0; + + u32 indirect, blocknum; + + // TODO: Support triply indirect pointers + for (u32 i = first_block; i <= last_block; i++) { + if (i < 12) { + blocknum = in->block[i]; + } else if (i < BLOCK_COUNT + 12) { + indirect = in->block[12]; + blocknum = ext2_read_indirect(indirect, i - 12, dev); + } else { + indirect = in->block[13]; + blocknum = ext2_read_indirect(indirect, + (i - (BLOCK_COUNT + 12)) / BLOCK_COUNT, dev); + blocknum = ext2_read_indirect(blocknum, + (i - (BLOCK_COUNT + 12)) % BLOCK_COUNT, dev); + } + + u8 data[BLOCK_SIZE] = { 0 }; + ext2_buffer_read(blocknum, data, dev); + u32 block_offset = (i == first_block) ? first_block_offset : 0; + u32 byte_count = MIN(BLOCK_SIZE - block_offset, remaining); + + memcpy((u8 *)buf + copied, data + block_offset, byte_count); + + copied += byte_count; + remaining -= byte_count; + } + + return copied; +} + +static u32 ext2_find_inode(const char *name, u32 dir_inode, struct dev *dev) +{ + if ((signed)dir_inode <= 0) + return (unsigned)-1; + + struct ext2_inode i = { 0 }; + ext2_get_inode(dir_inode, &i, dev); + + char buf[BLOCK_SIZE] = { 0 }; + assert(BLOCK_SIZE * i.blocks / 2 <= sizeof(buf)); // Shouldn't fail + + u8 data[BLOCK_SIZE] = { 0 }; + for (u32 q = 0; q < i.blocks / 2; q++) { + ext2_buffer_read(i.block[q], data, dev); + memcpy((u32 *)((u32)buf + q * BLOCK_SIZE), data, BLOCK_SIZE); + } + + struct ext2_dirent *d = (struct ext2_dirent *)buf; + + u32 sum = 0; + do { + // Calculate the 4byte aligned size of each entry + sum += d->total_len; + if (strlen(name) == d->name_len && + strncmp((void *)d->name, name, d->name_len) == 0) { + return d->inode_num; + } + d = (struct ext2_dirent *)((u32)d + d->total_len); + + } while (sum < (1024 * i.blocks / 2)); + + return (unsigned)-1; +} + +static struct ext2_inode *ext2_find_inode_by_path(const char *path, struct ext2_inode *in_buf, + struct dev *dev) +{ + char path_arr[1024] = { 0 }; + strlcpy(path_arr, path, sizeof(path_arr)); + char *path_cp = path_arr; + + if (path_cp[0] != '/') + return NULL; + + path_cp++; + u32 current_inode = EXT2_ROOT; + + while (1) { + u32 i; + for (i = 0; path_cp[i] != '/' && path_cp[i] != '\0'; i++) + ; + + if (path_cp[i] == '\0') + break; + + path_cp[i] = '\0'; + current_inode = ext2_find_inode(path_cp, current_inode, dev); + path_cp[i] = '/'; + + if ((signed)current_inode <= 0) { + return NULL; + } + + path_cp += i + 1; + } + + u32 inode = ext2_find_inode(path_cp, current_inode, dev); + if ((signed)inode <= 0) + return NULL; + + return ext2_get_inode(inode, in_buf, dev); +} + +static s32 ext2_read(const char *path, void *buf, u32 offset, u32 count, struct dev *dev) +{ + struct ext2_inode in = { 0 }; + if (ext2_find_inode_by_path(path, &in, dev) == &in) { + return ext2_read_inode(&in, buf, offset, count, dev); + } else { + return -1; + } +} + +u8 ext2_detect(struct dev *dev) +{ + struct ext2_superblock sb = { 0 }; + ext2_get_superblock(&sb, dev); + if (sb.magic != EXT2_MAGIC) + return 0; + + dev->fs.read = ext2_read; + + return 1; +} |