From fbdb44b71dacaa57d68d2233ab7ac83fc990f24b Mon Sep 17 00:00:00 2001 From: Mark Date: Mon, 3 Feb 2020 12:56:53 +0200 Subject: [PATCH] Backport ext2 --- etc/make/conf.mk | 5 + include/sys/block/part_gpt.h | 7 + include/sys/fs/ext2/block.h | 17 ++ include/sys/fs/ext2/ext2.h | 146 ++++++++++++++++ include/sys/fs/ext2/node.h | 10 ++ sys/block/blk.c | 145 +++++++-------- sys/block/part_gpt.c | 129 ++++++++++++++ sys/fs/ext2/block.c | 105 +++++++++++ sys/fs/ext2/ext2.c | 119 +++++++++++++ sys/fs/ext2/node.c | 330 +++++++++++++++++++++++++++++++++++ 10 files changed, 941 insertions(+), 72 deletions(-) create mode 100644 include/sys/block/part_gpt.h create mode 100644 include/sys/fs/ext2/block.h create mode 100644 include/sys/fs/ext2/ext2.h create mode 100644 include/sys/fs/ext2/node.h create mode 100644 sys/block/part_gpt.c create mode 100644 sys/fs/ext2/block.c create mode 100644 sys/fs/ext2/ext2.c create mode 100644 sys/fs/ext2/node.c diff --git a/etc/make/conf.mk b/etc/make/conf.mk index 1c81cfb..0739685 100644 --- a/etc/make/conf.mk +++ b/etc/make/conf.mk @@ -43,6 +43,7 @@ OBJS+=$(O)/sys/debug.o \ $(O)/sys/char/tty.o \ $(O)/sys/char/chr.o \ $(O)/sys/block/pseudo.o \ + $(O)/sys/block/part_gpt.o \ $(O)/sys/block/ram.o \ $(O)/sys/block/blk.o \ $(O)/sys/dev.o \ @@ -53,6 +54,9 @@ OBJS+=$(O)/sys/debug.o \ $(O)/sys/fs/node.o \ $(O)/sys/fs/tar.o \ $(O)/sys/fs/sysfs.o \ + $(O)/sys/fs/ext2/block.o \ + $(O)/sys/fs/ext2/ext2.o \ + $(O)/sys/fs/ext2/node.o \ $(O)/sys/time.o \ $(O)/sys/sys_file.o \ $(O)/sys/sys_sys.o \ @@ -65,6 +69,7 @@ OBJS+=$(O)/sys/driver/pci/pci.o \ $(O)/sys/driver/ata/ahci.o DIRS+=$(O)/sys/fs \ + $(O)/sys/fs/ext2 \ $(O)/sys/char \ $(O)/sys/block \ $(O)/sys/driver/pci \ diff --git a/include/sys/block/part_gpt.h b/include/sys/block/part_gpt.h new file mode 100644 index 0000000..b6882c6 --- /dev/null +++ b/include/sys/block/part_gpt.h @@ -0,0 +1,7 @@ +#pragma once +#include "sys/types.h" + +struct blkdev; +struct vnode; + +int blk_enumerate_gpt(struct blkdev *dev, struct vnode *of, uint8_t *head); diff --git a/include/sys/fs/ext2/block.h b/include/sys/fs/ext2/block.h new file mode 100644 index 0000000..5ace354 --- /dev/null +++ b/include/sys/fs/ext2/block.h @@ -0,0 +1,17 @@ +#pragma once +#include "sys/types.h" + +struct ext2_inode; +struct fs; + +int ext2_read_superblock(struct fs *ext2); +int ext2_write_superblock(struct fs *ext2); + +int ext2_read_block(struct fs *ext2, void *blk, uint32_t no); + +int ext2_read_inode_block(struct fs *ext2, struct ext2_inode *inode, void *block, uint32_t index); + +int ext2_read_inode(struct fs *ext2, struct ext2_inode *dst, uint32_t ino); +// NOTE: this function automatically updates access and modification time +// on the inode written +int ext2_write_inode(struct fs *ext2, struct ext2_inode *node, uint32_t ino); diff --git a/include/sys/fs/ext2/ext2.h b/include/sys/fs/ext2/ext2.h new file mode 100644 index 0000000..8a5e18e --- /dev/null +++ b/include/sys/fs/ext2/ext2.h @@ -0,0 +1,146 @@ +#pragma once +#include "sys/types.h" + +#define EXT2_FLAG_RO (1 << 0) + +#define EXT2_STATE_CLEAN 1 +#define EXT2_STATE_DIRTY 2 + +#define EXT2_ERROR_IGNORE 1 +#define EXT2_ERROR_READONLY 2 +#define EXT2_ERROR_PANIC 3 + +#define EXT2_REQ_COMP (1 << 0) +#define EXT2_REQ_ENT_TYPE (1 << 1) +#define EXT2_REQ_REPLAY_JOURNAL (1 << 2) +#define EXT2_REQ_JOURNAL_DEV (1 << 3) + +#define EXT2_RO_SPARSE_SB (1 << 0) +#define EXT2_RO_64BIT_SIZE (1 << 1) +#define EXT2_RO_DIR_BTREE (1 << 2) + +#define EXT2_SIGNATURE 0xEF53 + +#define EXT2_IFIFO 0x1000 +#define EXT2_IFCHR 0x2000 +#define EXT2_IFDIR 0x4000 +#define EXT2_IFBLK 0x6000 +#define EXT2_IFREG 0x8000 +#define EXT2_IFLNK 0xA000 +#define EXT2_IFSOCK 0xC000 + +struct vnode; + +struct ext2_superblock { + // Base superblock + uint32_t inode_count; + uint32_t block_count; + uint32_t reserved_blocks; + uint32_t free_blocks; + uint32_t free_inodes; + uint32_t superblock_no; + uint32_t log2_block_size; + uint32_t log2_frag_size; + uint32_t block_group_blocks; + uint32_t block_group_frags; + uint32_t block_group_inodes; + uint32_t mount_time; + uint32_t write_time; + uint16_t mount_count; + uint16_t fsck_mount_count; + uint16_t ext2_signature; + uint16_t state; + uint16_t error_behavior; + uint16_t version_minor; + uint32_t fsck_time; + uint32_t fsck_interval; + uint32_t os_id; + uint32_t version_major; + uint16_t reserved_uid; + uint16_t reserved_gid; + // Extended superblock + uint32_t first_inode; // == 11 in < 1.0 + uint16_t inode_size; // == 128 in < 1.0 + uint16_t superblock_block_group; // For backup + uint32_t optional_features; // Don't matter + uint32_t required_features; // Cannot read without these + uint32_t write_required_features; // Can read without these + char filesystem_id[16]; + char volume_name[16]; + char last_mount_path[64]; + uint32_t compression; + uint8_t prealloc_file_blocks; + uint8_t prealloc_dir_blocks; + uint16_t __res0; + char journal_id[16]; + uint32_t journal_inode; + uint32_t journal_device; + uint32_t orphan_inode_list_head; +} __attribute__((packed)); + +struct ext2_blkgrp_desc { + uint32_t block_bitmap_no; + uint32_t inode_bitmap_no; + uint32_t inode_table_no; + uint16_t free_blocks; + uint16_t free_inodes; + uint16_t directory_count; + char __pad[14]; +} __attribute__((packed)); + +struct ext2_inode { + uint16_t mode; + uint16_t uid; + uint32_t size_lower; + uint32_t atime; + uint32_t ctime; + uint32_t mtime; + uint32_t dtime; + uint16_t gid; + uint16_t hard_links; + uint32_t sector_count; + uint32_t flags; + uint32_t os_val1; + uint32_t direct_blocks[12]; + uint32_t indirect_block_l1; + uint32_t indirect_block_l2; + uint32_t indirect_block_l3; + uint32_t generation; + uint32_t facl; + union { + uint32_t size_upper; + uint32_t dir_acl; + }; + uint32_t frag_block_no; + uint32_t os_val2; +} __attribute__((packed)); + +struct ext2_dirent { + uint32_t ino; + uint16_t ent_size; + uint8_t name_length_low; + union { + uint8_t type_indicator; + uint8_t name_length_high; + }; + char name[0]; +} __attribute__((packed)); + +struct ext2_data { + union { + struct ext2_superblock sb; + char __data[1024]; + }; + struct ext2_blkgrp_desc *bgdt; + + struct vnode *root; + struct ext2_inode *root_inode; + + uint32_t flags; + uint32_t block_size; + uint32_t inode_size; + uint32_t inodes_per_block; + uint32_t blkgrp_inode_blocks; + uint32_t bgdt_entry_count; + uint32_t bgdt_size_blocks; +}; diff --git a/include/sys/fs/ext2/node.h b/include/sys/fs/ext2/node.h new file mode 100644 index 0000000..7e3f60c --- /dev/null +++ b/include/sys/fs/ext2/node.h @@ -0,0 +1,10 @@ +#pragma once +#include "sys/types.h" + +struct vnode_operations; +struct ext2_inode; +struct vnode; + +extern struct vnode_operations g_ext2_vnode_ops; + +void ext2_inode_to_vnode(struct vnode *vnode, struct ext2_inode *inode, uint32_t ino); diff --git a/sys/block/blk.c b/sys/block/blk.c index 2895cf0..765647c 100644 --- a/sys/block/blk.c +++ b/sys/block/blk.c @@ -1,12 +1,14 @@ -//#include "sys/blk/part_gpt.h" +#include "sys/block/part_gpt.h" #include "sys/user/errno.h" #include "sys/block/blk.h" +#include "sys/fs/node.h" #include "sys/assert.h" #include "sys/fs/vfs.h" #include "sys/string.h" #include "sys/fs/fs.h" #include "sys/debug.h" #include "sys/heap.h" +#include "sys/dev.h" struct blk_part { struct blkdev *device; @@ -65,77 +67,76 @@ int blk_mount_auto(struct vnode *at, struct blkdev *blk, const char *opt) { return -EINVAL; } -//static ssize_t blk_part_write(struct blkdev *dev, const void *buf, size_t off, size_t count) { -// struct blk_part *part = (struct blk_part *) dev->dev_data; -// size_t part_size_bytes = part->size * 512; -// size_t part_off_bytes = part->lba_start * 512; -// -// if (off >= part_size_bytes) { -// return -1; -// } -// -// size_t l = MIN(part_size_bytes - off, count); -// -// return blk_write(part->device, buf, off + part_off_bytes, l); -//} -// -//static ssize_t blk_part_read(struct blkdev *dev, void *buf, size_t off, size_t count) { -// struct blk_part *part = (struct blk_part *) dev->dev_data; -// size_t part_size_bytes = part->size * 512; -// size_t part_off_bytes = part->lba_start * 512; -// -// if (off >= part_size_bytes) { -// return -1; -// } -// -// size_t l = MIN(part_size_bytes - off, count); -// -// return blk_read(part->device, buf, off + part_off_bytes, l); -//} -// -//int blk_add_part(struct vnode *of, int n, uint64_t lba, uint64_t size) { -// // Allocate both a device and partition details -// char *buf = kmalloc(sizeof(struct blk_part) + sizeof(struct blkdev)); -// _assert(buf); -// struct blk_part *part = (struct blk_part *) (buf + sizeof(struct blkdev)); -// struct blkdev *dev = (struct blkdev *) buf; -// -// dev->dev_data = part; -// dev->read = blk_part_read; -// dev->write = blk_part_write; -// dev->flags = 0; -// -// part->device = of->dev; -// part->lba_start = lba; -// part->size = size; -// -// char name[16]; -// size_t len = strlen(of->name); -// _assert(len < sizeof(name) - 2); -// strncpy(name, of->name, len); -// name[len] = '1' + n; -// name[len + 1] = 0; -// -// dev_add(DEV_CLASS_BLOCK, DEV_BLOCK_PART, dev, name); -// -// return 0; -//} +static ssize_t blk_part_write(struct blkdev *dev, const void *buf, size_t off, size_t count) { + struct blk_part *part = (struct blk_part *) dev->dev_data; + size_t part_size_bytes = part->size * 512; + size_t part_off_bytes = part->lba_start * 512; + + if (off >= part_size_bytes) { + return -1; + } + + size_t l = MIN(part_size_bytes - off, count); + + return blk_write(part->device, buf, off + part_off_bytes, l); +} + +static ssize_t blk_part_read(struct blkdev *dev, void *buf, size_t off, size_t count) { + struct blk_part *part = (struct blk_part *) dev->dev_data; + size_t part_size_bytes = part->size * 512; + size_t part_off_bytes = part->lba_start * 512; + + if (off >= part_size_bytes) { + return -1; + } + + size_t l = MIN(part_size_bytes - off, count); + + return blk_read(part->device, buf, off + part_off_bytes, l); +} + +int blk_add_part(struct vnode *of, int n, uint64_t lba, uint64_t size) { + // Allocate both a device and partition details + char *buf = kmalloc(sizeof(struct blk_part) + sizeof(struct blkdev)); + _assert(buf); + struct blk_part *part = (struct blk_part *) (buf + sizeof(struct blkdev)); + struct blkdev *dev = (struct blkdev *) buf; + + dev->dev_data = part; + dev->read = blk_part_read; + dev->write = blk_part_write; + dev->flags = 0; + + part->device = of->dev; + part->lba_start = lba; + part->size = size; + + char name[16]; + size_t len = strlen(of->name); + _assert(len < sizeof(name) - 2); + strncpy(name, of->name, len); + name[len] = '1' + n; + name[len + 1] = 0; + + dev_add(DEV_CLASS_BLOCK, DEV_BLOCK_PART, dev, name); -int blk_enumerate_partitions(struct blkdev *blk, struct vnode *node) { -// // TODO: better differentiation for CD/DVD-drives than just assuming every -// // drive with block size of 2048 is one -// ssize_t res; -// uint8_t buf[1024]; -// -// if (blk->block_size == 512) { -// if ((res = blk_read(blk, buf, 0, 1024)) < 0) { -// return res; -// } -// -// if (!strncmp((const char *) (buf + 512), "EFI PART", 8)) { -// return blk_enumerate_gpt(blk, node, buf); -// } -// } -// + return 0; +} + +int blk_enumerate_partitions(struct blkdev *blk, struct vnode *node) { + // TODO: better differentiation for CD/DVD-drives than just assuming every + // drive with block size of 2048 is one + ssize_t res; + uint8_t buf[1024]; + + if (blk->block_size == 512) { + if ((res = blk_read(blk, buf, 0, 1024)) < 0) { + return res; + } + + if (!strncmp((const char *) (buf + 512), "EFI PART", 8)) { + return blk_enumerate_gpt(blk, node, buf); + } + } return 0; } diff --git a/sys/block/part_gpt.c b/sys/block/part_gpt.c new file mode 100644 index 0000000..6a2aac5 --- /dev/null +++ b/sys/block/part_gpt.c @@ -0,0 +1,129 @@ +#include "sys/user/errno.h" +#include "sys/block/blk.h" +#include "sys/fs/node.h" +#include "sys/string.h" +#include "sys/assert.h" +#include "sys/debug.h" + +// Align to some nice boundary (it's 36) +#define GUID_STRLEN 40 + +const char GUID_EMPTY[16] = {0}; +const struct { + const char *name; + const char *guid; +} guiddb[] = { + // A bunch of known GUID so I can pretty-print stuff + { "EFI System Partition", "C12A7328-F81F-11D2-BA4B-00A0C93EC93B" }, + { "Linux Filesystem", "0FC63DAF-8483-4772-8E79-3D69D8477DE4" }, + { "Microsoft Basic Data", "EBD0A0A2-B9E5-4433-87C0-68B6B72699C7" }, +}; + +struct gpt_header { + char signature[8]; + uint32_t revision; + uint32_t header_size; + uint32_t header_crc32; + uint32_t __res0; + uint64_t my_lba; + uint64_t alt_lba; + uint64_t first_lba; + uint64_t last_lba; + char disk_guid[16]; + uint64_t partition_entry_lba; + uint32_t partition_count; + uint32_t partition_entry_size; + uint32_t partition_array_crc32; +} __attribute__((packed)); + +struct gpt_part_entry { + char type_guid[16]; + char part_guid[16]; + uint64_t start_lba; + uint64_t end_lba; + uint64_t attr; + char part_name[72]; +}; + +static void strguid(char *str, const char *guid) { + static const char *const hexc = "0123456789ABCDEF"; + // TODO: snprintf + int off = 0; + for (int i = 3; i >= 0; --i) { + str[off++] = hexc[(guid[i] >> 4) & 0xF]; + str[off++] = hexc[guid[i] & 0xF]; + } + str[off++] = '-'; + for (int i = 1; i >= 0; --i) { + str[off++] = hexc[(guid[i + 4] >> 4) & 0xF]; + str[off++] = hexc[guid[i + 4] & 0xF]; + } + str[off++] = '-'; + for (int i = 1; i >= 0; --i) { + str[off++] = hexc[(guid[i + 6] >> 4) & 0xF]; + str[off++] = hexc[guid[i + 6] & 0xF]; + } + str[off++] = '-'; + for (int i = 0; i < 2; ++i) { + str[off++] = hexc[(guid[i + 8] >> 4) & 0xF]; + str[off++] = hexc[guid[i + 8] & 0xF]; + } + str[off++] = '-'; + for (int i = 0; i < 6; ++i) { + str[off++] = hexc[(guid[i + 10] >> 4) & 0xF]; + str[off++] = hexc[guid[i + 10] & 0xF]; + } + str[off] = 0; +} + +static void gpt_describe_entry(struct vnode *of, size_t i, struct gpt_part_entry *ent) { + char type_guid_str[GUID_STRLEN]; + char part_guid_str[GUID_STRLEN]; + kdebug("%s partition %d:\n", of->name, i); + strguid(type_guid_str, ent->type_guid); + strguid(part_guid_str, ent->part_guid); + kdebug("Type: %s\n", type_guid_str); + for (size_t j = 0; j < sizeof(guiddb) / sizeof(guiddb[0]); ++j) { + if (!strncmp(guiddb[j].guid, type_guid_str, GUID_STRLEN)) { + kdebug(" %s\n", guiddb[j].name); + break; + } + } + kdebug("Part: %s\n", part_guid_str); +} + +int blk_enumerate_gpt(struct blkdev *dev, struct vnode *of, uint8_t *head) { + // TODO: support other devices + _assert(dev->block_size == 512); + // LBA1 - GPT header + struct gpt_header *hdr = (struct gpt_header *) &head[512]; + // Read a single block for partition table entries + char buf[512]; + + _assert(blk_read(dev, buf, hdr->partition_entry_lba * 512, 512) >= 0); + + size_t offset = 0; + size_t part_n = 0; + int res; + + for (size_t i = 0; i < hdr->partition_count; ++i) { + struct gpt_part_entry *ent = (struct gpt_part_entry *) &buf[offset]; + + if (memcmp(ent->type_guid, GUID_EMPTY, 16)) { + // Maybe some known filesystem here, ignore partition type guid + if ((res = blk_add_part(of, i, ent->start_lba, ent->end_lba - ent->start_lba)) != 0) { + kerror("Failed to add partition: %s\n", kstrerror(res)); + } else { + gpt_describe_entry(of, i, ent); + } + } + + offset += hdr->partition_entry_size; + if (offset >= 512) { + break; + } + } + + return 0; +} + diff --git a/sys/fs/ext2/block.c b/sys/fs/ext2/block.c new file mode 100644 index 0000000..71b6362 --- /dev/null +++ b/sys/fs/ext2/block.c @@ -0,0 +1,105 @@ +#include "sys/fs/ext2/block.h" +#include "sys/fs/ext2/ext2.h" +#include "sys/user/errno.h" +#include "sys/block/blk.h" +#include "sys/assert.h" +#include "sys/string.h" +#include "sys/fs/fs.h" +#include "sys/debug.h" + +int ext2_read_superblock(struct fs *fs) { + struct ext2_data *data = fs->fs_private; + _assert(data); + int res = blk_read(fs->blk, data->__data, 1024, 1024); + if (res == 1024) { + return 0; + } + kerror("Superblock read failed\n"); + return res; +} + +int ext2_read_block(struct fs *fs, void *block, uint32_t no) { + struct ext2_data *data = fs->fs_private; + _assert(data); + kdebug("Read block %u: %p\n", no, no * data->block_size); + int res = blk_read(fs->blk, block, no * data->block_size, data->block_size); + if (res == 1024) { + return 0; + } + kerror("Failed to read block %u: %s\n", no, kstrerror(res)); + return res; +} + +int ext2_write_block(struct fs *fs, const void *block, uint32_t no) { + struct ext2_data *data = fs->fs_private; + _assert(data); + kdebug("Write block %u: %p\n", no, no * data->block_size); + int res = blk_write(fs->blk, block, no * data->block_size, data->block_size); + if (res == 1024) { + return 0; + } + kerror("Failed to write block %u: %s\n", no, kstrerror(res)); + return res; +} + +int ext2_read_inode(struct fs *fs, struct ext2_inode *inode, uint32_t ino) { + struct ext2_data *data = fs->fs_private; + _assert(data); + char block[data->block_size]; + if (ino < 1 || ino >= data->sb.inode_count) { + panic("Invalid inode number: %u\n", ino); + } + int res; + + --ino; + uint32_t ino_group = ino / data->sb.block_group_inodes; + uint32_t ino_in_group = ino % data->sb.block_group_inodes; + uint32_t ino_block = data->bgdt[ino_group].inode_table_no + ino_in_group / data->inodes_per_block; + uint32_t offset_in_block = (ino_in_group % data->inodes_per_block) * data->inode_size; + _assert(offset_in_block < data->block_size); + + if ((res = ext2_read_block(fs, block, ino_block)) != 0) { + return res; + } + + memcpy(inode, offset_in_block + block, data->inode_size); + + return 0; +} + +int ext2_write_inode(struct fs *fs, struct ext2_inode *inode, uint32_t ino) { + // Automatically update inode times + inode->mtime = time(); + inode->atime = inode->mtime; + + struct ext2_data *data = fs->fs_private; + _assert(data); + char block[data->block_size]; + if (ino < 1 || ino >= data->sb.inode_count) { + panic("Invalid inode number: %u\n", ino); + } + int res; + + --ino; + uint32_t ino_group = ino / data->sb.block_group_inodes; + uint32_t ino_in_group = ino % data->sb.block_group_inodes; + uint32_t ino_block = data->bgdt[ino_group].inode_table_no + ino_in_group / data->inodes_per_block; + uint32_t offset_in_block = (ino_in_group % data->inodes_per_block) * data->inode_size; + _assert(offset_in_block < data->block_size); + + if ((res = ext2_read_block(fs, block, ino_block)) != 0) { + return res; + } + + memcpy(offset_in_block + block, inode, data->inode_size); + + return ext2_write_block(fs, block, ino_block); +} + +int ext2_read_inode_block(struct fs *fs, struct ext2_inode *inode, void *buf, uint32_t index) { + if (index < 12) { + return ext2_read_block(fs, buf, inode->direct_blocks[index]); + } else { + panic("Not implemented\n"); + } +} diff --git a/sys/fs/ext2/ext2.c b/sys/fs/ext2/ext2.c new file mode 100644 index 0000000..12d4830 --- /dev/null +++ b/sys/fs/ext2/ext2.c @@ -0,0 +1,119 @@ +#include "sys/fs/ext2/block.h" +#include "sys/fs/ext2/node.h" +#include "sys/fs/ext2/ext2.h" +#include "sys/user/errno.h" +#include "sys/fs/node.h" +#include "sys/string.h" +#include "sys/assert.h" +#include "sys/debug.h" +#include "sys/fs/fs.h" +#include "sys/heap.h" +#include "sys/attr.h" + +static int ext2_init(struct fs *ext2, const char *opt); +static struct vnode *ext2_get_root(struct fs *ext2); + +//// + +static struct fs_class g_ext2 = { + .name = "ext2", + .opt = 0, + .init = ext2_init, + .get_root = ext2_get_root +}; + +//// + +static int ext2_init(struct fs *ext2, const char *opt) { + _assert(ext2->blk); + + struct ext2_data *data = kmalloc(sizeof(struct ext2_data)); + _assert(data); + ext2->fs_private = data; + + int res; + + // Read superblock + if ((res = ext2_read_superblock(ext2)) != 0) { + kfree(data); + ext2->fs_private = NULL; + return res; + } + + // Check the signature + if (data->sb.ext2_signature != EXT2_SIGNATURE) { + kerror("Invalid ext2 signature\n"); + kfree(data); + ext2->fs_private = NULL; + return -EINVAL; + } + + // TODO: perform some kind of fsck? + kinfo("Volume ID: \"%s\"\n", data->sb.volume_name); + kinfo("Filesystem version: %d.%d\n", data->sb.version_major, data->sb.version_minor); + + // Calculate filesystem parameters + data->block_size = 1024 << data->sb.log2_block_size; + + if (data->sb.version_major != 0) { + data->inode_size = data->sb.inode_size; + } else { + data->inode_size = 128; + } + + data->inodes_per_block = data->block_size / data->inode_size; + data->blkgrp_inode_blocks = data->sb.block_group_inodes / data->inodes_per_block; + + kinfo("Block size: %u, inode size: %u\n", data->block_size, data->inode_size); + + data->bgdt_entry_count = (data->sb.block_count + data->sb.block_group_blocks - 1) / data->sb.block_group_blocks; + uint32_t bgdt_size = data->bgdt_entry_count * sizeof(struct ext2_blkgrp_desc); + data->bgdt_size_blocks = (bgdt_size + data->block_size - 1) / data->block_size; + + kinfo("Block groups: %u\n", data->bgdt_entry_count); + kinfo("BGDT occupies %u block(s)\n", data->bgdt_size_blocks); + + data->bgdt = kmalloc(data->bgdt_size_blocks * data->block_size); + _assert(data->bgdt); + + // BGDT starts with block 2 + for (uint32_t i = 0; i < data->bgdt_size_blocks; ++i) { + if ((res = ext2_read_block(ext2, ((void *) data->bgdt) + i * data->block_size, i + 2)) != 0) { + kfree(data->bgdt); + kfree(data); + ext2->fs_private = NULL; + return res; + } + } + + data->root_inode = kmalloc(data->inode_size); + _assert(data->root_inode); + data->root = vnode_create(VN_DIR, NULL); + _assert(data->root); + + // Read root inode + if ((res = ext2_read_inode(ext2, data->root_inode, 2)) != 0) { + kfree(data->root_inode); + kfree(data->root); + kfree(data->bgdt); + kfree(data); + return res; + } + + data->root->fs = ext2; + ext2_inode_to_vnode(data->root, data->root_inode, 2); + + return 0; +} + +static struct vnode *ext2_get_root(struct fs *ext2) { + struct ext2_data *data = ext2->fs_private; + _assert(data); + _assert(data->root); + + return data->root; +} + +static void __init ext2_class_init(void) { + fs_class_register(&g_ext2); +} diff --git a/sys/fs/ext2/node.c b/sys/fs/ext2/node.c new file mode 100644 index 0000000..344ab18 --- /dev/null +++ b/sys/fs/ext2/node.c @@ -0,0 +1,330 @@ +#include "sys/fs/ext2/block.h" +#include "sys/fs/ext2/node.h" +#include "sys/fs/ext2/ext2.h" +#include "sys/user/fcntl.h" +#include "sys/user/errno.h" +#include "sys/fs/ofile.h" +#include "sys/fs/node.h" +#include "sys/string.h" +#include "sys/assert.h" +#include "sys/fs/fs.h" +#include "sys/panic.h" +#include "sys/debug.h" +#include "sys/heap.h" + +static int ext2_vnode_find(struct vnode *at, const char *name, struct vnode **res); +static ssize_t ext2_vnode_read(struct ofile *fd, void *buf, size_t count); +static int ext2_vnode_open(struct ofile *fd, int opt); +static int ext2_vnode_opendir(struct ofile *fd); +static int ext2_vnode_chmod(struct vnode *vn, mode_t new_mode); +static int ext2_vnode_chown(struct vnode *node, uid_t new_uid, gid_t new_gid); +static ssize_t ext2_vnode_readdir(struct ofile *fd, struct dirent *ent); +static int ext2_vnode_stat(struct vnode *at, struct stat *st); + +//// + +struct vnode_operations g_ext2_vnode_ops = { + .find = ext2_vnode_find, + + .opendir = ext2_vnode_opendir, + .readdir = ext2_vnode_readdir, + + .stat = ext2_vnode_stat, + .chmod = ext2_vnode_chmod, + .chown = ext2_vnode_chown, + + .open = ext2_vnode_open, + .read = ext2_vnode_read, +}; + +//// + +static int ext2_vnode_find(struct vnode *at, const char *name, struct vnode **result) { + _assert(at && at->type == VN_DIR); + _assert(name); + _assert(result); + struct ext2_inode *at_inode = at->fs_data; + _assert(at_inode); + struct fs *ext2 = at->fs; + _assert(ext2); + struct ext2_data *data = ext2->fs_private; + _assert(data); + + char block_buffer[data->block_size]; + uint32_t block_offset; + uint32_t dir_blocks = at_inode->size_lower / data->block_size; + size_t name_length = strlen(name); + int res; + + // Because + _assert(name_length < 255); + + for (uint32_t block_index = 0; block_index < dir_blocks; ++block_index) { + block_offset = 0; + + if ((res = ext2_read_inode_block(ext2, at_inode, block_buffer, block_index)) != 0) { + return res; + } + + while (block_offset < data->block_size) { + struct ext2_dirent *dirent = (struct ext2_dirent *) (block_buffer + block_offset); + if (dirent->ino && (dirent->name_length_low == name_length)) { + if (!strncmp(dirent->name, name, name_length)) { + // Found the dirent + struct ext2_inode *res_inode = kmalloc(data->inode_size); + _assert(res_inode); + struct vnode *node = vnode_create(VN_DIR, name); + + if ((res = ext2_read_inode(ext2, res_inode, dirent->ino)) != 0) { + kfree(res_inode); + return res; + } + + node->fs = ext2; + ext2_inode_to_vnode(node, res_inode, dirent->ino); + + *result = node; + return 0; + } + } + + block_offset += dirent->ent_size; + } + } + + return -ENOENT; +} + +static int ext2_vnode_open(struct ofile *fd, int opt) { + if ((opt & O_APPEND) && (opt & O_ACCMODE) == O_RDONLY) { + // Impossible, I guess + return -EINVAL; + } + if ((opt & O_ACCMODE) != O_RDONLY) { + // Not implemented + return -EROFS; + } + _assert(!(opt & O_DIRECTORY)); + + fd->pos = 0; + + return 0; +} + +static ssize_t ext2_vnode_read(struct ofile *fd, void *buf, size_t count) { + _assert(fd); + _assert(buf); + struct vnode *node = fd->vnode; + _assert(node); + struct ext2_inode *inode = node->fs_data; + _assert(inode); + struct fs *ext2 = node->fs; + _assert(ext2); + struct ext2_data *data = ext2->fs_private; + _assert(data); + + // TODO + _assert(!inode->size_upper); + + if (fd->pos >= inode->size_lower) { + return 0; + } + + char block_buffer[data->block_size]; + size_t rem = MIN(count, inode->size_lower - fd->pos); + size_t bread = 0; + int res; + + while (rem) { + size_t block_offset = fd->pos % data->block_size; + size_t can_read = MIN(rem, data->block_size - block_offset); + uint32_t block_index = fd->pos / data->block_size; + + if ((res = ext2_read_inode_block(ext2, inode, block_buffer, block_index)) != 0) { + return res; + } + + memcpy(buf, block_buffer + block_offset, can_read); + + buf += can_read; + fd->pos += can_read; + bread += can_read; + rem -= can_read; + } + + return bread; +} + +static int ext2_vnode_opendir(struct ofile *fd) { + _assert(fd && fd->vnode); + _assert(fd->vnode->type == VN_DIR); + fd->pos = 0; + return 0; +} + +static ssize_t ext2_vnode_readdir(struct ofile *fd, struct dirent *ent) { + _assert(fd); + _assert(ent); + struct vnode *node = fd->vnode; + _assert(node); + struct ext2_inode *inode = node->fs_data; + _assert(inode); + struct fs *ext2 = node->fs; + _assert(ext2); + struct ext2_data *data = ext2->fs_private; + _assert(data); + + if (fd->pos >= inode->size_lower) { + return 0; + } + + char block_buffer[data->block_size]; + uint32_t block_index = fd->pos / data->block_size; + struct ext2_dirent *dirent = (struct ext2_dirent *) (block_buffer + fd->pos % data->block_size); + int res; + + if ((res = ext2_read_inode_block(ext2, inode, block_buffer, block_index)) != 0) { + return res; + } + + if (!dirent->ino) { + fd->pos += dirent->ent_size; + + if (fd->pos / data->block_size != block_index) { + // Dirent reclen cannot point beyond block size + _assert(!(fd->pos % data->block_size)); + // Requires reading a next block for dirent + return ext2_vnode_readdir(fd, ent); + } + + // Don't think the scenario of two empty dirents following each other is likely + dirent = (struct ext2_dirent *) (block_buffer + fd->pos % data->block_size); + _assert(dirent->ino); + } + + uint32_t name_length = dirent->name_length_low; + ent->d_type = DT_UNKNOWN; + if (data->sb.required_features & EXT2_REQ_ENT_TYPE) { + switch (dirent->type_indicator) { + case 1: + ent->d_type = DT_REG; + break; + case 2: + ent->d_type = DT_DIR; + break; + case 7: + ent->d_type = DT_LNK; + break; + default: + panic("Unsupported dirent type: %u\n", dirent->type_indicator); + } + } else { + name_length += (uint16_t) dirent->name_length_high << 8; + } + strncpy(ent->d_name, dirent->name, name_length); + ent->d_name[name_length] = 0; + ent->d_off = fd->pos; + ent->d_reclen = dirent->ent_size; + + // Calculate next dirent position + fd->pos += dirent->ent_size; + + return ent->d_reclen; +} + +static int ext2_vnode_chmod(struct vnode *node, mode_t new_mode) { + _assert(node); + struct ext2_inode *inode = node->fs_data; + _assert(inode); + struct fs *ext2 = node->fs; + _assert(ext2); + + // Only rewrite inode if something really changed + if ((inode->mode & 0xFFF) != (new_mode & 0xFFF)) { + // chmod() only updates file mode, don't touch file type + inode->mode &= ~0xFFF; + inode->mode |= new_mode & 0xFFF; + node->mode = new_mode & 0xFFF; + + return ext2_write_inode(ext2, inode, node->ino); + } + + return 0; +} + +static int ext2_vnode_chown(struct vnode *node, uid_t new_uid, gid_t new_gid) { + _assert(node); + struct ext2_inode *inode = node->fs_data; + _assert(inode); + struct fs *ext2 = node->fs; + _assert(ext2); + + if (new_uid != inode->uid || new_gid != inode->gid) { + inode->uid = new_uid; + inode->gid = new_gid; + node->uid = new_uid; + node->gid = new_gid; + + return ext2_write_inode(ext2, inode, node->ino); + } + + return 0; +} + +static int ext2_vnode_stat(struct vnode *node, struct stat *st) { + _assert(node && st); + struct ext2_inode *inode = node->fs_data; + _assert(inode); + struct fs *ext2 = node->fs; + _assert(ext2); + struct ext2_data *data = ext2->fs_private; + _assert(data); + + _assert(inode->uid == node->uid); + _assert(inode->gid == node->gid); + _assert((inode->mode & 0xFFF) == node->mode); + + st->st_mode = inode->mode; + st->st_uid = inode->uid; + st->st_gid = inode->gid; + + st->st_size = inode->size_lower; + _assert(!inode->size_upper); + st->st_blksize = data->block_size; + st->st_blocks = (st->st_size + data->block_size - 1) / data->block_size; + + st->st_dev = 0; + st->st_rdev = 0; + + st->st_mtime = inode->mtime; + st->st_atime = inode->atime; + st->st_ctime = inode->ctime; + + st->st_nlink = inode->hard_links; + + return 0; +} + +//// + +// Map inode parameters to vnode fields +void ext2_inode_to_vnode(struct vnode *vnode, struct ext2_inode *inode, uint32_t ino) { + vnode->ino = ino; + vnode->uid = inode->uid; + vnode->gid = inode->gid; + vnode->mode = inode->mode & 0xFFF; + vnode->fs_data = inode; + vnode->op = &g_ext2_vnode_ops; + + switch (inode->mode & 0xF000) { + case EXT2_IFDIR: + vnode->type = VN_DIR; + break; + case EXT2_IFREG: + vnode->type = VN_REG; + break; + default: + panic("Unsupported inode type: %04x\n", inode->mode & 0xF000); + } +} +