From 31380b9d29983f19bf2e535937d0000fd982e0c2 Mon Sep 17 00:00:00 2001 From: Matheus Date: Fri, 3 Oct 2025 19:29:58 -0300 Subject: [PATCH] fatfs: initial implementation --- kernel-src/fs/fat/fatfs.c | 961 ++++++++++++++++++++++++++++++ kernel-src/fs/fat/fatfs_dir.c | 618 +++++++++++++++++++ kernel-src/fs/fat/fatfs_fat.c | 346 +++++++++++ kernel-src/fs/fat/fatfs_rw.c | 165 +++++ kernel-src/include/kernel/fatfs.h | 115 ++++ 5 files changed, 2205 insertions(+) create mode 100644 kernel-src/fs/fat/fatfs.c create mode 100644 kernel-src/fs/fat/fatfs_dir.c create mode 100644 kernel-src/fs/fat/fatfs_fat.c create mode 100644 kernel-src/fs/fat/fatfs_rw.c create mode 100644 kernel-src/include/kernel/fatfs.h diff --git a/kernel-src/fs/fat/fatfs.c b/kernel-src/fs/fat/fatfs.c new file mode 100644 index 00000000..c97a0437 --- /dev/null +++ b/kernel-src/fs/fat/fatfs.c @@ -0,0 +1,961 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct { + uint8_t drive_num; + uint8_t reserved; + uint8_t signature; // if 0x29, these fields are valid + uint32_t serial_number; + uint8_t label[11]; + uint8_t type[8]; +} __attribute__((packed)) ebp_fat16_t; + +#define EXTENDED_FLAGS_ACTIVE_FAT_COUNT(x) (((x) & 0xf) + 1) +#define EXTENDED_FLAGS_MIRRORED_FAT 0x80 +typedef struct { + uint32_t fat_size_32; + uint16_t extended_flags; + uint16_t version; + uint32_t root_cluster; + uint16_t fsinfo_sector; + uint16_t bootsect; + uint8_t reserved[12]; + uint8_t drive_num; + uint8_t reserved2; + uint8_t signature; // if 0x29, these fields are valid + uint32_t serial_number; + uint8_t label[11]; + uint8_t type[8]; +} __attribute__((packed)) ebp_fat32_t; + +typedef struct { + uint8_t unused[3]; + uint8_t oem[8]; + uint16_t bytes_per_sector; + uint8_t sectors_per_cluster; + uint16_t reserved_sectors; + uint8_t fat_count; + uint16_t root_entry_count; // for fat12 and fat16. zero in fat32 + uint16_t sector_count_16; // can be zero, if so use the 32 entry + uint8_t media; + uint16_t fat_size_16; // for fat32, use the 32 entry in the fat32 bpb + uint16_t sectors_per_track; + uint16_t number_of_heads; + uint32_t hidden_sectors; + uint32_t sector_count_32; + union { + ebp_fat16_t ebp_fat12; + ebp_fat16_t ebp_fat16; + ebp_fat32_t ebp_fat32; + }; +} __attribute__((packed)) bpb_t; + +#define FATFS_FSINFO_LEAD_SIG 0x41615252 +#define FATFS_FSINFO_SIG 0x61417272 +#define FATFS_FSINFO_TRAIL_SIG 0xaA550000 + +typedef struct { + uint32_t lead_sig; + uint8_t reserved[480]; + uint32_t sig; + uint32_t free_clusters; + uint32_t next_free; + uint8_t reserved2[12]; + uint32_t trailing_sig; +} __attribute__((packed)) fatfs_fsinfo_t; + +static scache_t *fatnode_cache; + +static vfsops_t fatfs_ops; +static int fatfs_mount(vfs_t **vfs, vnode_t *mp, vnode_t *backing, void *data) { + if (backing == NULL) + return EINVAL; + + bpb_t bpb; + + size_t read_count; + int error = vfs_read(backing, &bpb, sizeof(bpb), 0, &read_count, 0); + if (error) + return error; + + if (read_count != sizeof(bpb)) + return EINVAL; + + // do some checks to verify if it is + if (bpb.fat_size_16 == 0 && bpb.root_entry_count) + return EINVAL; + + if (bpb.bytes_per_sector != 512 && bpb.bytes_per_sector != 1024 && bpb.bytes_per_sector != 2048 && bpb.bytes_per_sector != 4096) + return EINVAL; + + // only valid values are powers of 2 in the range (0, 128] + bool spc_ok = false; + for (int i = 1; i <= 128; i *= 2) { + if (bpb.sectors_per_cluster == i) + spc_ok = true; + } + if (!spc_ok) + return EINVAL; + + if (bpb.reserved_sectors == 0) + return EINVAL; + + if (bpb.fat_count == 0) + return EINVAL; + + // the root directory should be size aligned to a sector + // this check is still valid in fat32 (0 % n == 0) + if (bpb.root_entry_count * sizeof(fatfs_dent_t) % bpb.bytes_per_sector != 0) + return EINVAL; + + if (bpb.fat_size_16 == 0 && bpb.sector_count_16) + return EINVAL; + + if (bpb.media < 0xf0) + return EINVAL; + + if (bpb.sector_count_32 == 0 && bpb.sector_count_16 == 0) + return EINVAL; + + if (bpb.fat_size_16 == 0 && bpb.sector_count_32 == 0) + return EINVAL; + + vattr_t backing_vattr; + error = VOP_GETATTR(backing, &backing_vattr, NULL); + if (error) + return error; + + fatfs_t *fatfs = alloc(sizeof(fatfs_t)); + if (fatfs == NULL) + return ENOMEM; + + error = hashtable_init(&fatfs->vnode_map, 4096); + if (error) { + free(fatfs); + return error; + } + + MUTEX_INIT(&fatfs->root_mutex); + MUTEX_INIT(&fatfs->vnode_map_mutex); + MUTEX_INIT(&fatfs->fat_mutex); + + // is this fat32? + if (bpb.fat_size_16 == 0) { + fatfs->type = FATFS_FAT32; + size_t total_sectors = bpb.sector_count_16 ? bpb.sector_count_16 : bpb.sector_count_32; + size_t data_sectors = total_sectors - bpb.reserved_sectors - bpb.fat_count * bpb.ebp_fat32.fat_size_32; + fatfs->cluster_count = data_sectors / bpb.sectors_per_cluster; + } else { + // to figure out which fat filesystem we are dealing with, use the cluster count + size_t root_dir_sectors = ROUND_UP(bpb.root_entry_count * 32, bpb.bytes_per_sector) / bpb.bytes_per_sector; + size_t total_sectors = bpb.sector_count_16 ? bpb.sector_count_16 : bpb.sector_count_32; + size_t data_sectors = total_sectors - bpb.reserved_sectors - bpb.fat_count * bpb.fat_size_16 - root_dir_sectors; + fatfs->cluster_count = data_sectors / bpb.sectors_per_cluster; + fatfs->type = fatfs->cluster_count > 4096 ? FATFS_FAT16 : FATFS_FAT12; + } + + *vfs = (vfs_t *)fatfs; + fatfs->vfs.ops = &fatfs_ops; + fatfs->backing = backing; + VOP_HOLD(backing); + + fatfs->fat_offset = bpb.reserved_sectors * bpb.bytes_per_sector; + if (fatfs->type == FATFS_FAT32) { + fatfs->root_cluster = bpb.ebp_fat32.root_cluster; + fatfs->fat_size = bpb.ebp_fat32.fat_size_32 * bpb.bytes_per_sector; + fatfs->data_offset = fatfs->fat_offset + bpb.fat_count * fatfs->fat_size; + } else { + fatfs->fat_size = bpb.fat_size_16 * bpb.bytes_per_sector; + fatfs->root_offset = (bpb.reserved_sectors + bpb.fat_count * bpb.fat_size_16) * bpb.bytes_per_sector; + fatfs->root_entry_count = bpb.root_entry_count; + fatfs->data_offset = fatfs->root_offset + ROUND_UP(bpb.root_entry_count * 32, bpb.bytes_per_sector); + } + + fatfs->fat_count = bpb.fat_count; + fatfs->cluster_size = bpb.bytes_per_sector * bpb.sectors_per_cluster; + + return error; +} + +static int fatfs_unmount(vfs_t *vfs) { + __assert(!"unimplemented"); +} + +static int fatfs_sync(vfs_t *vfs) { + fatfs_t *fatfs = (fatfs_t *)vfs; + VOP_LOCK(fatfs->backing); + int error = vmmcache_syncvnode(fatfs->backing, 0, UINT64_MAX); + VOP_UNLOCK(fatfs->backing); + return error; +} + +static int fatfs_root(vfs_t *vfs, vnode_t **root) { + fatfs_t *fatfs = (fatfs_t *)vfs; + MUTEX_ACQUIRE(&fatfs->root_mutex); + int error = 0; + if (fatfs->root) { + *root = (vnode_t *)fatfs->root; + goto leave; + } + + fatnode_t *fatnode = fatfs_allocate_node(vfs, V_TYPE_DIR); + if (fatnode == NULL) { + error = ENOMEM; + goto leave; + } + + if (fatfs->type == FATFS_FAT32) { + fatnode->cluster = fatfs->root_cluster; + size_t chain_length; + error = fatfs_cluster_chain_length(fatfs, fatnode->cluster, &chain_length); + if (error) { + fatfs_free_node(fatnode); + goto leave; + } + + fatnode->size = chain_length * fatfs->cluster_size; + } + + *root = (vnode_t *)fatnode; + fatfs->root = fatnode; + VOP_HOLD(&fatnode->vnode); + + fatnode->vnode.flags |= V_FLAGS_ROOT; + + leave: + MUTEX_RELEASE(&fatfs->root_mutex); + return error; +} + +static vfsops_t fatfs_ops = { + .mount = fatfs_mount, + .unmount = fatfs_unmount, + .sync = fatfs_sync, + .root = fatfs_root +}; + +static void fatfs_init() { + __assert(vfs_register(&fatfs_ops, "fatfs") == 0); + fatnode_cache = slab_newcache(sizeof(fatnode_t), 0, NULL, NULL); + __assert(fatnode_cache); +} + +INIT_ROUTINE_DEFINE(fatfs, INIT_ROUTINE_FLAGS_NONE, fatfs_init, vfs); + +static int fatfs_syncvnode(vnode_t *vnode) { + fatfs_t *fs = (fatfs_t *)vnode->vfs; + fatnode_t *fatnode = (fatnode_t *)vnode; + + // sync file data + int e = vmmcache_syncvnode(vnode, 0, UINT64_MAX); + + VOP_LOCK(fs->backing); + // sync the dent + int e2 = 0; + if (fatnode->dent_disk_offset) + e2 = vmmcache_syncvnode(fs->backing, fatnode->dent_disk_offset, sizeof(fatfs_dent_t)); + + // and sync the fats + int e3 = vmmcache_syncvnode(fs->backing, fs->fat_offset, fs->fat_size * fs->fat_count); + + VOP_UNLOCK(fs->backing); + + // only the first errors are reported + if (e) + return e; + if (e2) + return e2; + + return e3; +} + +static int fatfs_open(vnode_t **node, int flags, cred_t *cred) { + return 0; +} + +static int fatfs_close(vnode_t *node, int flags, cred_t *cred) { + return 0; +} + +static int fatfs_symlink(vnode_t *parent, char *name, vattr_t *attr, char *path, cred_t *cred) { + return EPERM; +} + +static int fatfs_readlink(vnode_t *parent, char **link, cred_t *cred) { + return EINVAL; +} + +static int fatfs_mmap(vnode_t *node, void *addr, uintmax_t offset, int flags, cred_t *cred) { + __assert(!"unreachable"); +} + +static int fatfs_munmap(vnode_t *node, void *addr, uintmax_t offset, int flags, cred_t *cred) { + __assert(!"unreachable"); +} + +static int fatfs_access(vnode_t *vnode, mode_t mode, cred_t *cred) { + return auth_filesystem_check(cred, auth_filesystem_convertaccess(mode), vnode, NULL) ? EACCES : 0; +} + +// this *should*, by the rules of the vnode ops, accept a user buffer. however, this will never be the case. all I/O will +// go through the page cache +static int fatfs_write(vnode_t *vnode, iovec_iterator_t *iovec_iterator, size_t size, uintmax_t offset, int flags, size_t *write_count, cred_t *cred) { + if (vnode->type == V_TYPE_DIR) + return EISDIR; + + if (vnode->type != V_TYPE_REGULAR) + return EINVAL; + + fatnode_t *node = (fatnode_t *)vnode; + fatfs_t *fs = (fatfs_t *)vnode->vfs; + uintmax_t end_offset = offset + size; + int error = 0; + + // overflow check + if (offset > end_offset) + end_offset = -1ll; + + if (end_offset > node->size) { + if (offset > node->size) { + *write_count = 0; + goto cleanup; + } + + size = min(end_offset, node->size) - offset; + } + + error = fatfs_rw_bytes_iovec(fs, node, iovec_iterator, size, offset, true, false); + *write_count = error ? -1 : size; + if (error) + goto cleanup; + + cleanup: + return error; +} + +static int fatfs_read(vnode_t *vnode, iovec_iterator_t *iovec_iterator, size_t size, uintmax_t offset, int flags, size_t *read_count, cred_t *cred) { + if (vnode->type == V_TYPE_DIR) + return EISDIR; + + if (vnode->type != V_TYPE_REGULAR) + return EINVAL; + + fatnode_t *node = (fatnode_t *)vnode; + fatfs_t *fs = (fatfs_t *)vnode->vfs; + uintmax_t end_offset = offset + size; + int error = 0; + + if (offset >= node->size) { + *read_count = 0; + goto cleanup; + } + + // check for overflow + if (offset > end_offset) + end_offset = -1ll; + + if (end_offset > node->size) { + end_offset = node->size; + size = end_offset - offset; + } + + if (size == 0) + goto cleanup; + + error = fatfs_rw_bytes_iovec(fs, node, iovec_iterator, size, offset, false, false); + + *read_count = error ? -1 : size; + + cleanup: + return error; +} + +static int fatfs_putpage(vnode_t *node, uintmax_t offset, struct page_t *page) { + // only regular files get cached + __assert(node->type == V_TYPE_REGULAR); + size_t write_count; + iovec_t iovec = { + .addr = MAKE_HHDM(pmm_getpageaddress(page)), + .len = PAGE_SIZE + }; + + iovec_iterator_t iovec_iterator; + iovec_iterator_init(&iovec_iterator, &iovec, 1); + int error = VOP_WRITE(node, &iovec_iterator, PAGE_SIZE, offset, 0, &write_count, NULL); + + // its possible that write_count is zero here. the condition where this is possible is when + // the file is truncated after the check in the page sync function but before it actually is + // written back to disk. therefore, we check that the page IS infact truncated if this happens. + // if it isn't, something else happened and its not safe to continue. + // + // TODO: since this check will likely be copied between different filesystem drivers, it could be interesting to have + // fatfs_putpage take in a (size_t *) pointer and have this check be in whatever function calls VOP_PUTPAGE. + + if (write_count == 0) { + __assert(page->flags & PAGE_FLAGS_TRUNCATED); + } + + return error; +} + +static int fatfs_getpage(vnode_t *node, uintmax_t offset, struct page_t *page) { + // only regular files get cached + __assert(node->type == V_TYPE_REGULAR); + size_t read_count = PAGE_SIZE; + void *addr = MAKE_HHDM(pmm_getpageaddress(page)); + + iovec_t iovec = { + .addr = addr, + .len = PAGE_SIZE + }; + + iovec_iterator_t iovec_iterator; + iovec_iterator_init(&iovec_iterator, &iovec, 1); + int error = VOP_READ(node, &iovec_iterator, PAGE_SIZE, offset, 0, &read_count, NULL); + if (read_count == 0) + return ENXIO; + + else if (read_count != PAGE_SIZE) + memset((void *)((uintptr_t)addr + read_count), 0, PAGE_SIZE - read_count); + + return error; +} + +static int fatfs_lookup(vnode_t *vnode, char *name, vnode_t **result, cred_t *cred) { + fatnode_t *directory = (fatnode_t *)vnode; + fatnode_t *new_node = NULL; + fatfs_t *fatfs = (fatfs_t *)vnode->vfs; + + if (strcmp(name, ".") == 0 || (strcmp(name, "..") == 0 && fatfs->root == directory)) { + VOP_HOLD(vnode); + *result = vnode; + return 0; + } + + if (strcmp(name, "..") == 0) { + if (directory->parent_dir == NULL) + return ENOENT; + + *result = &directory->parent_dir->vnode; + VOP_HOLD(*result); + VOP_UNLOCK(vnode); + VOP_LOCK(*result); + return 0; + } + + size_t dent_disk_offset; + fatfs_dent_t dent; + int error = fatfs_directory_lookup(fatfs, directory, name, &dent, &dent_disk_offset); + if (error) + return error; + + MUTEX_ACQUIRE(&fatfs->vnode_map_mutex); + + // is this file already in memory? + void *v; + error = hashtable_get(&fatfs->vnode_map, &v, &dent_disk_offset, sizeof(dent_disk_offset)); + if (error == ENOENT) { + // it is not, allocate new object + new_node = fatfs_allocate_node(&fatfs->vfs, (dent.attributes & FATFS_DENT_ATTRIBUTE_DIRECTORY) ? V_TYPE_DIR : V_TYPE_REGULAR); + if (new_node == NULL) { + fatfs_free_node(new_node); + MUTEX_RELEASE(&fatfs->vnode_map_mutex); + return error; + } + + // set it in the table + error = hashtable_set(&fatfs->vnode_map, new_node, &dent_disk_offset, sizeof(dent_disk_offset), true); + if (error) { + fatfs_free_node(new_node); + MUTEX_RELEASE(&fatfs->vnode_map_mutex); + return error; + } + + new_node->parent_dir = directory; + VOP_HOLD(vnode); + + new_node->cluster = ((uint32_t)dent.cluster_high << 16) | dent.cluster_low; + + if (new_node->vnode.type == V_TYPE_DIR) { + size_t chain_length; + error = fatfs_cluster_chain_length(fatfs, new_node->cluster, &chain_length); + if (error) { + fatfs_free_node(new_node); + MUTEX_RELEASE(&fatfs->vnode_map_mutex); + return error; + } + + new_node->size = chain_length * fatfs->cluster_size; + } else { + new_node->size = dent.file_size_bytes; + } + + new_node->dent_disk_offset = dent_disk_offset; + + VOP_HOLD(&new_node->vnode); // table counts as a hold + } else if (error) { + MUTEX_RELEASE(&fatfs->vnode_map_mutex); + return error; + } else { + // it is, use the object in the map + new_node = v; + VOP_HOLD(&new_node->vnode); + } + + MUTEX_RELEASE(&fatfs->vnode_map_mutex); + + // VOP_LOOKUP returns the vnode locked + *result = &new_node->vnode; + VOP_LOCK(*result); + + return error; +} + +static int fatfs_getdents(vnode_t *vnode, dent_t *buffer, size_t count, uintmax_t dir_offset, size_t *read_count) { + fatfs_t *fatfs = (fatfs_t *)vnode->vfs; + fatnode_t *node = (fatnode_t *)vnode; + + if (vnode->type != V_TYPE_DIR) + return ENOTDIR; + + int i; + int error = 0; + for (i = 0; i < count; ++i) { + fatfs_dent_t dent; + size_t disk_offset; + error = fatfs_directory_get_dent(fatfs, node, dir_offset + i, buffer[i].d_name, &dent, &disk_offset); + if (error == ENOENT) { + error = 0; + break; + } else if (error) { + break; + } + + buffer[i].d_ino = disk_offset / sizeof(fatfs_dent_t); + buffer[i].d_off = dir_offset + i; + buffer[i].d_reclen = sizeof(dent_t); + buffer[i].d_type = vfs_getposixtype((dent.attributes & FATFS_DENT_ATTRIBUTE_DIRECTORY) ? V_TYPE_DIR : V_TYPE_REGULAR); + } + + + *read_count = i; + return error; +} + +static int fatfs_getattr(vnode_t *vnode, vattr_t *attr, cred_t *cred) { + fatnode_t * fatnode = (fatnode_t *)vnode; + fatfs_t *fatfs = (fatfs_t *)vnode->vfs; + attr->rdevmajor = 0; + attr->rdevminor = 0; + + vattr_t backing_attr; + + VOP_LOCK(fatfs->backing); + int error = VOP_GETATTR(fatfs->backing, &backing_attr, cred); + VOP_UNLOCK(fatfs->backing); + if (error) + return error; + + attr->devmajor = backing_attr.rdevmajor; + attr->devminor = backing_attr.rdevminor; + attr->uid = 0; + attr->gid = 0; + attr->inode = fatnode->dent_disk_offset / 32; + attr->type = fatnode->vnode.type; + attr->mode = 0755; + attr->nlinks = fatnode->parent_dir ? 1 : 0; + attr->size = fatnode->size; + attr->fsblocksize = fatfs->cluster_size; + // TODO times + attr->atime.s = 0; + attr->atime.ns = 0; + attr->ctime.s = 0; + attr->ctime.ns = 0; + attr->mtime.s = 0; + attr->mtime.ns = 0; + attr->blocksused = ROUND_UP(attr->size, attr->fsblocksize) / attr->fsblocksize; + return 0; +} + +static int fatfs_setattr(vnode_t *vnode, vattr_t *attr, int which, cred_t *cred) { + return 0; +} + +static int fatfs_create(vnode_t *parent, char *name, vattr_t *attr, int type, vnode_t **result, cred_t *cred) { + fatnode_t *node = (fatnode_t *)parent; + fatfs_t *fatfs = (fatfs_t *)parent->vfs; + if (type != V_TYPE_REGULAR && type != V_TYPE_DIR) + return EPERM; + + if (parent->type != V_TYPE_DIR) + return ENOTDIR; + + fatnode_t *new_node = fatfs_allocate_node(parent->vfs, type); + if (new_node == NULL) + return ENOMEM; + + fatfs_dent_t dent; + size_t dent_offset; + int error = fatfs_directory_lookup(fatfs, node, name, &dent, &dent_offset); + if (error == 0) + error = EEXIST; + + if (error != ENOENT) + goto cleanup; + + memset(&dent, 0, sizeof(dent)); + dent.attributes = (type == V_TYPE_DIR) ? FATFS_DENT_ATTRIBUTE_DIRECTORY : 0; + + size_t disk_offset; + error = fatfs_write_directory_entry(fatfs, node, &dent, name, &disk_offset); + if (error) + goto cleanup; + + MUTEX_ACQUIRE(&fatfs->vnode_map_mutex); + error = hashtable_set(&fatfs->vnode_map, new_node, &disk_offset, sizeof(disk_offset), true); + + if (!error) { + new_node->parent_dir = node; + VOP_HOLD(parent); + new_node->dent_disk_offset = disk_offset; + } + + MUTEX_RELEASE(&fatfs->vnode_map_mutex); + + if (error) + goto cleanup; + + // table hold + VOP_HOLD(&new_node->vnode); + *result = &new_node->vnode; + + // create . and .. entries if directory + if (type == V_TYPE_DIR) { + fatfs_dent_t dot_dents[2]; + memset(&dot_dents, 0, sizeof(dot_dents)); + + int e = fatfs_resize_file(fatfs, new_node, fatfs->cluster_size); + if (e == 0) { + memcpy(dot_dents[0].short_name, ". ", 11); + memcpy(dot_dents[1].short_name, ".. ", 11); + + dot_dents[0].cluster_low = new_node->cluster & 0xffff; + dot_dents[0].cluster_high = (new_node->cluster >> 16) & 0xffff; + + if (node != fatfs->root) { + dot_dents[1].cluster_low = node->cluster & 0xffff; + dot_dents[1].cluster_high = (node->cluster >> 16) & 0xffff; + } + + dot_dents[0].attributes = dot_dents[1].attributes = FATFS_DENT_ATTRIBUTE_DIRECTORY; + + fatfs_rw_bytes(fatfs, new_node, &dot_dents, sizeof(dot_dents), 0, true, true); + fatfs_update_dent(fatfs, new_node); + } + } + + // VOP_CREATE expects result to be locked + VOP_LOCK(*result); + + cleanup: + if (error) + fatfs_free_node(new_node); + + return error; +} + +static int fatfs_rename(vnode_t *vsource_dir, vnode_t *vsource, char *old_name, vnode_t *vtarget_dir, vnode_t *vtarget, char *new_name, int flags) { + fatfs_t *fatfs = (fatfs_t *)vsource_dir->vfs; + fatnode_t *source_dir = (fatnode_t *)vsource_dir; + fatnode_t *source = (fatnode_t *)vsource; + fatnode_t *target_dir = (fatnode_t *)vtarget_dir; + fatnode_t *target = (fatnode_t *)vtarget; + + if (vsource_dir->vfs != vtarget_dir->vfs) + return EXDEV; + + if (vsource_dir->type != V_TYPE_DIR || vtarget_dir->type != V_TYPE_DIR) + return ENOTDIR; + + fatfs_dent_t new_dent = { + .attributes = (vsource->type == V_TYPE_DIR) ? FATFS_DENT_ATTRIBUTE_DIRECTORY : 0, + .cluster_low = source->cluster & 0xffff, + .cluster_high = (source->cluster >> 16) & 0xffff, + .file_size_bytes = source->size + }; + + // switch out or create the target dent + fatfs_dent_t target_dent; + size_t dent_disk_offset; + int error = fatfs_directory_lookup(fatfs, target_dir, new_name, &target_dent, &dent_disk_offset); + + if (error == ENOENT) { + // create dirent + error = fatfs_write_directory_entry(fatfs, target_dir, &new_dent, new_name, &dent_disk_offset); + if (error) + return error; + } else if (error == 0) { + memcpy(new_dent.short_name, target_dent.short_name, 11); + + // already exists, switch it up + size_t written; + error = vfs_write(fatfs->backing, &new_dent, sizeof(new_dent), dent_disk_offset, &written, 0); + if (error) + return error; + + __assert(written == sizeof(new_dent)); + + // remove from the vnode map + MUTEX_ACQUIRE(&fatfs->vnode_map_mutex); + __assert(hashtable_remove(&fatfs->vnode_map, &target->dent_disk_offset, sizeof(target->dent_disk_offset)) == 0); + MUTEX_RELEASE(&fatfs->vnode_map_mutex); + VOP_RELEASE(vtarget); // table hold + + __assert(target->parent_dir == target_dir); + VOP_RELEASE(vtarget_dir) + target->parent_dir = NULL; + target->dent_disk_offset = 0; + } + + MUTEX_ACQUIRE(&fatfs->vnode_map_mutex); + __assert(hashtable_remove(&fatfs->vnode_map, &source->dent_disk_offset, sizeof(source->dent_disk_offset)) == 0); + + // TODO add a way to preallocate hashtable enties to prevent filesystem corruption + error = hashtable_set(&fatfs->vnode_map, source, &dent_disk_offset, sizeof(dent_disk_offset), true); + if (!error) { + __assert(source->parent_dir == source_dir); + VOP_RELEASE(vsource_dir); + source->parent_dir = target_dir; + VOP_HOLD(vtarget_dir); + + source->dent_disk_offset = dent_disk_offset; + } + + MUTEX_RELEASE(&fatfs->vnode_map_mutex); + + if (error) + return error; + + // table hold + VOP_HOLD(&source->vnode); + + // remove old dent + return fatfs_directory_lookup(fatfs, source_dir, old_name, &target_dent, NULL); +} + +static int fatfs_link(vnode_t *vnode, vnode_t *dir, char *name, cred_t *cred) { + fatnode_t *node = (fatnode_t *)dir; + fatfs_t *fatfs = (fatfs_t *)vnode->vfs; + fatnode_t *child_node = (fatnode_t *)vnode; + + if (vnode->vfs != dir->vfs) + return EXDEV; + + // fatfs only supports only one hard link per file + if (child_node->parent_dir) + return EMLINK; + + fatfs_dent_t dent; + size_t dent_offset; + int error = fatfs_directory_lookup(fatfs, node, name, &dent, &dent_offset); + if (error == 0) + return EEXIST; + else if (error != ENOENT) + return error; + + memset(&dent, 0, sizeof(dent)); + //dent.attributes = child_node->vnode.type == V_TYPE_DIR ? FATFS_DENT_ATTRIBUTE_DIRECTORY : 0; + //dent.cluster_high = (child_node->cluster & 0xffff) << 16; + //dent.cluster_low = (child_node->cluster >> 16) & 0xffff; + //dent.file_size_bytes = child_node->size; + size_t disk_offset; + error = fatfs_write_directory_entry(fatfs, node, &dent, name, &disk_offset); + if (error) + return error; + + MUTEX_ACQUIRE(&fatfs->vnode_map_mutex); + // TODO add a mechanism to preallocate hashtable entry data to prevent failure here + // when that is done, re-add the dent init code above + error = hashtable_set(&fatfs->vnode_map, child_node, &disk_offset, sizeof(disk_offset), true); + + if (!error) { + child_node->parent_dir = node; + VOP_HOLD(dir); + child_node->dent_disk_offset = disk_offset; + fatfs_update_dent(fatfs, child_node); + } + + MUTEX_RELEASE(&fatfs->vnode_map_mutex); + + if (error) + return error; + + // table hold + VOP_HOLD(vnode); + return error; +} + +static int fatfs_unlink(vnode_t *vnode, vnode_t *child, char *name, cred_t *cred) { + fatnode_t *node = (fatnode_t *)vnode; + fatfs_t *fs = (fatfs_t *)vnode->vfs; + fatnode_t *child_fatnode = (fatnode_t *)child; + + // check if its actually empty (readcount > 2, asserted 2 for . and ..) + if (child->type == V_TYPE_DIR) { + size_t readcount; + dent_t *dents = alloc(sizeof(dent_t) * 3); + if (dents == NULL) + return ENOMEM; + + int error = fatfs_getdents(child, dents, 3, 0, &readcount); + if (error) { + free(dents); + return error; + } + + if (readcount > 2) { + free(dents); + return ENOTEMPTY; + } + + // if any of the dot entries is missing, do not do anything as we cannot be sure of consistency + if (readcount != 2 || + strcmp(dents[0].d_name, ".") != 0 || strcmp(dents[1].d_name, "..") != 0) { + free(dents); + printf("fatfs: filesystem corruption detected: missng or bad dot entries\n"); + return ENOTEMPTY; + } + + free(dents); + } + + // remove the dent + fatfs_dent_t dent; + int error = fatfs_directory_lookup(fs, node, name, &dent, NULL); + if (error) + return error; + + // remove from the vnode map + MUTEX_ACQUIRE(&fs->vnode_map_mutex); + __assert(hashtable_remove(&fs->vnode_map, &child_fatnode->dent_disk_offset, sizeof(child_fatnode->dent_disk_offset)) == 0); + MUTEX_RELEASE(&fs->vnode_map_mutex); + VOP_RELEASE(child); // table hold + + __assert(child_fatnode->parent_dir == node); + VOP_RELEASE(vnode); + child_fatnode->parent_dir = NULL; + child_fatnode->dent_disk_offset = 0; + + return 0; +} + +static int fatfs_resize(vnode_t *vnode, size_t new_size, cred_t *cred) { + if (vnode->type == V_TYPE_DIR) + return EISDIR; + + if (vnode->type != V_TYPE_REGULAR) + return EINVAL; + + __assert(vnode->type == V_TYPE_REGULAR); + fatnode_t *node = (fatnode_t *)vnode; + fatfs_t *fs = (fatfs_t *)vnode->vfs; + + int error = 0; + if (node->size != new_size) { + error = fatfs_resize_file(fs, node, new_size); + if (node->size > new_size) + vmmcache_truncate(vnode, new_size); + } + + return error; +} + +static int fatfs_inactive(vnode_t *vnode) { + fatnode_t *fatnode = (fatnode_t *)vnode; + fatfs_t *fatfs = (fatfs_t *)vnode->vfs; + + if (fatnode->parent_dir) { + // TODO there is still no *normal* situation where this would happen, so it has + // not been implemented yet. the node still has links on the filesystem but is being + // released from memory due to only being referenced by the vnode map. + // this was done likely to free up memory (and the structures and data will still remain on disk) + // the vnode map mutex is nescessarily being held here by the function that called VOP_RELEASE + __assert(!"fatfs VOP_INACTIVE() with hardlink count >0 is currently unimplemented"); + } else { + // unlinked file having resources released when kernel refcount hits zero + vmmcache_truncate(vnode, 0); + fatfs_resize_file(fatfs, fatnode, 0); + } + + fatfs_free_node(fatnode); + return 0; +} + +static int fatfs_advlock(vnode_t *node, int op, advlock_t *advlock) { + return vfs_advlock(node, op, advlock); +} + +static int fatfs_lock(vnode_t *node) { + MUTEX_ACQUIRE(&node->lock); + return 0; +} + +static int fatfs_unlock(vnode_t *node) { + MUTEX_RELEASE(&node->lock); + return 0; +} + +static vops_t fatfs_vops = { + .write = fatfs_write, + .read = fatfs_read, + .getdents = fatfs_getdents, + .lookup = fatfs_lookup, + .getattr = fatfs_getattr, + .setattr = fatfs_setattr, + .open = fatfs_open, + .close = fatfs_close, + .poll = vfs_pollstub, + .access = fatfs_access, + .readlink = fatfs_readlink, + .resize = fatfs_resize, + .link = fatfs_link, + .create = fatfs_create, + .symlink = fatfs_symlink, + .mmap = fatfs_mmap, + .munmap = fatfs_munmap, + .unlink = fatfs_unlink, + .inactive = fatfs_inactive, + .rename = fatfs_rename, + .getpage = fatfs_getpage, + .putpage = fatfs_putpage, + .sync = fatfs_syncvnode, + .advlock = fatfs_advlock, + .lock = fatfs_lock, + .unlock = fatfs_unlock +}; + +void fatfs_free_node(fatnode_t *node) { + slab_free(fatnode_cache, node); +} + +fatnode_t *fatfs_allocate_node(vfs_t *vfs, int type) { + fatnode_t *fatnode = slab_allocate(fatnode_cache); + if (fatnode == NULL) + return fatnode; + + VOP_INIT(&fatnode->vnode, &fatfs_vops, 0, type, vfs); + + fatnode->cluster = 0; + fatnode->parent_dir = NULL; + fatnode->dent_disk_offset = 0; + + return fatnode; +} diff --git a/kernel-src/fs/fat/fatfs_dir.c b/kernel-src/fs/fat/fatfs_dir.c new file mode 100644 index 00000000..ae32cebd --- /dev/null +++ b/kernel-src/fs/fat/fatfs_dir.c @@ -0,0 +1,618 @@ +#include +#include +#include + +#define START_OF_LFN 0x40 + +#define FATFS_DENT_ATTRIBUTE_LONG_NAME (FATFS_DENT_ATTRIBUTE_READ_ONLY | \ + FATFS_DENT_ATTRIBUTE_HIDDEN | \ + FATFS_DENT_ATTRIBUTE_SYSTEM | \ + FATFS_DENT_ATTRIBUTE_VOLUME_ID) + +#define FATFS_DENT_ATTRIBUTE_LONG_NAME_MASK (FATFS_DENT_ATTRIBUTE_READ_ONLY | \ + FATFS_DENT_ATTRIBUTE_HIDDEN | \ + FATFS_DENT_ATTRIBUTE_SYSTEM | \ + FATFS_DENT_ATTRIBUTE_VOLUME_ID | \ + FATFS_DENT_ATTRIBUTE_DIRECTORY | \ + FATFS_DENT_ATTRIBUTE_ARCHIVE) + +#define IS_DENT_LFN(x) (((x).attributes & FATFS_DENT_ATTRIBUTE_LONG_NAME_MASK) == FATFS_DENT_ATTRIBUTE_LONG_NAME) + +#define FATFS_DENT_NAME_FREE (char)0xe5 +#define FATFS_DENT_NAME_FREE_INCLUDING_REST (char)0 +#define CHARACTERS_PER_LFN_ENTRY 13 + +static uint8_t sfn_checksum(const char sfn[11]) { + // sfn checksum + uint8_t checksum = 0; + for (int i = 0; i < 11; ++i) + checksum = ((checksum & 1) << 7) + (checksum >> 1) + sfn[i]; + + return checksum; +} + +static int directory_read_dent(fatfs_t *fatfs, fatnode_t *fatnode, fatfs_dent_t *buffer, size_t entry_offset, size_t *entries_read, size_t *disk_offset) { + if (fatfs->type == FATFS_FAT32 || fatfs->root != fatnode) { + // valid check since directory size is always block aligned + size_t byte_offset = entry_offset * sizeof(fatfs_dent_t); + if (byte_offset >= fatnode->size) { + *entries_read = 0; + return 0; + } + + int error = fatfs_get_file_disk_offset(fatfs, fatnode, byte_offset, disk_offset); + if (error) + return error; + + *entries_read = 1; + size_t bytes_read; + error = vfs_read(fatfs->backing, buffer, sizeof(fatfs_dent_t), *disk_offset, &bytes_read, 0); + if (error) + return error; + + __assert(bytes_read == sizeof(fatfs_dent_t)); + } else { + if (entry_offset >= fatfs->root_entry_count) { + *entries_read = 0; + return 0; + } + + *entries_read = 1; + size_t bytes_read; + + int error = vfs_read(fatfs->backing, buffer, sizeof(fatfs_dent_t), fatfs->root_offset + entry_offset * sizeof(fatfs_dent_t), &bytes_read, 0); + if (error) + return error; + + __assert(bytes_read == sizeof(fatfs_dent_t)); + + *disk_offset = fatfs->root_offset + entry_offset * sizeof(fatfs_dent_t); + } + + return 0; +} + +static int directory_write_dents(fatfs_t *fatfs, fatnode_t *fatnode, fatfs_dent_t *buffer, size_t entry_offset, size_t entry_count, size_t *disk_offset) { + if (fatfs->type == FATFS_FAT32 || fatfs->root != fatnode) { + size_t file_offset = entry_offset * sizeof(fatfs_dent_t); + int error = fatfs_rw_bytes(fatfs, fatnode, buffer, entry_count * sizeof(fatfs_dent_t), file_offset, true, true); + if (error) + return error; + + if (disk_offset) + error = fatfs_get_file_disk_offset(fatfs, fatnode, file_offset, disk_offset); + + return error; + } else { + size_t bytes_written; + size_t write_offset = fatfs->root_offset + entry_offset * sizeof(fatfs_dent_t); + int error = vfs_write(fatfs->backing, buffer, entry_count * sizeof(fatfs_dent_t), write_offset, &bytes_written, 0); + if (error) + return error; + + if (disk_offset) + *disk_offset = write_offset; + + __assert(bytes_written == entry_count * sizeof(fatfs_dent_t)); + return 0; + } +} + +// only uppercase and $ % ' - _ @ ~ ` ! ( ) { } ^ # & are valid short name characters +static inline bool valid_sfn_character(char c) { + if ('A' <= c && c <= 'Z') + return true; + + if ('0' <= c && c <= '9') + return true; + + switch (c) { + case '$': + case '%': + case '\'': + case '-': + case '_': + case '@': + case '~': + case '`': + case '!': + case '(': + case ')': + case '{': + case '}': + case '^': + case '#': + case '&': + return true; + } + + return false; +} + +static bool str_to_short_filename(const char *str, char short_filename[11]) { + // first, get the normal name + int i; + for (i = 0; i < 8 && str[i] && str[i] != '.'; ++i) { + if (!valid_sfn_character(str[i])) + return false; + + short_filename[i] = str[i]; + } + + // not a valid short name if there is no dot in the middle + if (i == 8 && str[i] != '.' && str[i]) + return false; + + int sfn_current; + // pad sfn + for (sfn_current = i; sfn_current < 8; ++sfn_current) + short_filename[sfn_current] = ' '; + + // if theres a dot, do extension + if (str[i] == '.') { + ++i; + while (sfn_current < 11 && str[i]) { + if (!valid_sfn_character(str[i])) + return false; + + short_filename[sfn_current] = str[i]; + + ++i; + ++sfn_current; + } + } + + // pad extension + while (sfn_current < 11) + short_filename[sfn_current++] = ' '; + + // valid only if we parsed the whole string + return str[i] == '\0'; +} + +static bool lfn_get_characters(fatfs_lfn_dent_t *lfn, char *characters) { + uint16_t buffer[CHARACTERS_PER_LFN_ENTRY]; + for (int i = 0; i < 5; ++i) + buffer[i] = lfn->name1[i]; + for (int i = 0; i < 6; ++i) + buffer[i + 5] = lfn->name2[i]; + + buffer[11] = lfn->name3[0]; + buffer[12] = lfn->name3[1]; + + for (int i = 0; i < 13; ++i) { + // if any of the upper 9 bits are set then this is not ascii and it is not supported + if (buffer[i] & 0xff80) + return false; + + characters[i] = (uint8_t)buffer[i]; + } + + characters[13] = 0; + return true; +} + +static void sfn_get_characters(fatfs_dent_t *dent, char *name_out) { + int i; + + // name + for (i = 0; i < 8 && dent->short_name[i] != ' '; ++i) + name_out[i] = dent->short_name[i]; + + // add dot if there is a file extrension + if (dent->short_name[8] != ' ') + name_out[i++] = '.'; + + // extension + for (int j = 8; j < 11 && dent->short_name[j] != ' '; ++j) + name_out[i++] = dent->short_name[j]; +} + +// if disk_offset_out is NULL then remove the found direntry +int fatfs_directory_lookup(fatfs_t *fatfs, fatnode_t *fatnode, const char *name, fatfs_dent_t *dent_out, size_t *disk_offset_out) { + fatfs_dent_t dent; + int error = 0; + size_t current_dent = 0; + size_t entries_read; + int remaining_lfn = -1; + bool lfn_found = false; + size_t name_len = strlen(name); + int lfn_checksum; + size_t lfn_dent_count = 0; + size_t disk_offset; + + for (;;) { + error = directory_read_dent(fatfs, fatnode, &dent, current_dent, &entries_read, &disk_offset); + if (error) + goto leave; + + // end of directory? + if (entries_read == 0 || dent.short_name[0] == FATFS_DENT_NAME_FREE_INCLUDING_REST) { + error = ENOENT; + goto leave; + } + + // this entry in specific is free? + if (dent.short_name[0] == FATFS_DENT_NAME_FREE) { + if (remaining_lfn > -1) { + // free entry while handling LFN, corrupted lfn + printf("fatfs: corrupted long filename, returning EIO (free while handling lfn)\n"); + error = EIO; + goto leave; + } + + ++current_dent; + continue; + } + + if (IS_DENT_LFN(dent)) { + // handle LFN + char lfn_entry[CHARACTERS_PER_LFN_ENTRY + 1]; + fatfs_lfn_dent_t *lfn = (fatfs_lfn_dent_t *)&dent; + int order = lfn->order; + size_t lfn_offset = ((lfn->order & ~START_OF_LFN) - 1) * CHARACTERS_PER_LFN_ENTRY; + + if (lfn_offset > 255) { + printf("fatfs: corrupted long filename, returning EIO (too big)\n"); + error = EIO; + goto leave; + } + + if (order & START_OF_LFN) { + order &= ~START_OF_LFN; + + if (remaining_lfn > -1) { + // start of lfn while already handling lfn, corrupted + printf("fatfs: corrupted long filename, returning EIO (bad start)\n"); + error = EIO; + goto leave; + } + + // name is larger than lfn? if so, skip this directory entry + if (name_len >= lfn_offset + CHARACTERS_PER_LFN_ENTRY) { + current_dent += order + 1; + continue; + } + + lfn_checksum = lfn->checksum; + remaining_lfn = order; + lfn_dent_count = order; + } else if (remaining_lfn == -1 || lfn->checksum != lfn_checksum) { + // not start of lfn while not already handling an lfn, corrupted + // OR + // checksum does not match + printf("fatfs: corrupted long filename, returing EIO (checksum/bad start)\n"); + error = EIO; + goto leave; + } + + lfn_get_characters(lfn, lfn_entry); + + if (strncmp(lfn_entry, name + lfn_offset, CHARACTERS_PER_LFN_ENTRY)) { + current_dent += remaining_lfn + 1; + remaining_lfn = -1; + continue; + } + + // lfn match + --remaining_lfn; + if (remaining_lfn == 0) + lfn_found = true; + + ++current_dent; + continue; + } else if (remaining_lfn > 0) { + // not lfn entry while there are remaining lfns, corrupted + printf("fatfs: corrupted long filename, returning EIO (non lfn while handling)\n"); + error = EIO; + goto leave; + } + + // not a lfn entry + char short_filename[11]; + bool search_short_filename = str_to_short_filename(name, short_filename); + + // if we parsed an lfn and found this to be the right dir entry, no need to check the short filename + + lfn_dent_count = lfn_found ? lfn_dent_count : 0; + + if (lfn_found == true) { + if (lfn_checksum != sfn_checksum(dent.short_name)) { + printf("fatfs: corrupted long filename, returning EIO (bad checksum)\n"); + return EIO; + } + lfn_found = false; + remaining_lfn = -1; + } else if (!search_short_filename || memcmp(dent.short_name, short_filename, 11)) { + // does not match + ++current_dent; + continue; + } + + // we have found the requested entry + *dent_out = dent; + + if (disk_offset_out) { + *disk_offset_out = disk_offset; + } else { + // remove the dent + size_t buffer_size = lfn_dent_count + 1; + fatfs_dent_t write_buffer[buffer_size]; + uint8_t to_set; + + // first check if this is the last dent + error = directory_read_dent(fatfs, fatnode, &dent, current_dent + 1, &entries_read, &disk_offset); + if (error) + return error; + + if (entries_read == 0 || dent.short_name[0] == FATFS_DENT_NAME_FREE_INCLUDING_REST) + to_set = FATFS_DENT_NAME_FREE_INCLUDING_REST; + else + to_set = FATFS_DENT_NAME_FREE; + + // set the directory entries to the right free value and write the result + + memset(write_buffer, 0, buffer_size * sizeof(fatfs_dent_t)); + for (int i = 0; i < buffer_size; ++i) + write_buffer[i].short_name[0] = to_set; + + error = directory_write_dents(fatfs, fatnode, write_buffer, current_dent - lfn_dent_count, buffer_size, &disk_offset); + } + + break; + } + +leave: + return error; +} + +int fatfs_directory_get_dent(fatfs_t *fatfs, fatnode_t *fatnode, size_t dent_offset, char *name_out, fatfs_dent_t *dent_out, size_t *disk_offset) { + size_t current_dent = 0; + size_t directory_offset = 0; + fatfs_dent_t dent; + size_t entries_read; + for (;;) { + int error = directory_read_dent(fatfs, fatnode, &dent, directory_offset, &entries_read, disk_offset); + if (error) + return error; + + if (entries_read == 0 || dent.short_name[0] == FATFS_DENT_NAME_FREE_INCLUDING_REST) + return ENOENT; + + if (dent.short_name[0] == FATFS_DENT_NAME_FREE) { + ++directory_offset; + continue; + } + + if (IS_DENT_LFN(dent) && (dent.short_name[0] & START_OF_LFN) == 0) { + printf("fatfs: corrupted long filename, returning EIO\n"); + return EIO; + } + + if (dent_offset != current_dent) { + // not the one we are searching for, keep looking + fatfs_lfn_dent_t *lfn = (fatfs_lfn_dent_t *)&dent; + + ++current_dent; + directory_offset += IS_DENT_LFN(dent) ? (lfn->order & ~START_OF_LFN) + 1: 1; + continue; + } + + break; + } + + // found the dent we are searching for, get its data + if (IS_DENT_LFN(dent)) { + fatfs_lfn_dent_t *lfn = (fatfs_lfn_dent_t *)&dent; + int limit = lfn->order & ~START_OF_LFN; + for (int i = 0; i < limit; ++i) { + if (IS_DENT_LFN(dent) == false) { + printf("fatfs: corrupted long filename, returning EIO\n"); + return EIO; + } + + int current_order = lfn->order & ~START_OF_LFN; + size_t lfn_offset = (current_order - 1) * CHARACTERS_PER_LFN_ENTRY; + char lfn_name[CHARACTERS_PER_LFN_ENTRY + 1]; + lfn_get_characters(lfn, lfn_name); + memcpy(name_out + lfn_offset, lfn_name, CHARACTERS_PER_LFN_ENTRY); + + int error = directory_read_dent(fatfs, fatnode, &dent, ++directory_offset, &entries_read, disk_offset); + if (error) + return error; + + if (IS_DENT_LFN(dent) && current_order - 1 != lfn->order) { + printf("fatfs: corrupt long filename, returning EIO\n"); + return EIO; + } + } + + // add zero terminator + name_out[limit * CHARACTERS_PER_LFN_ENTRY] = '\0'; + } else { + // just get the short filename + sfn_get_characters(&dent, name_out); + } + + *dent_out = dent; + return 0; +} + +int fatfs_update_dent(fatfs_t *fatfs, fatnode_t *fatnode) { + fatfs_dent_t dent; + size_t bytes_done; + + if (fatfs->type == FATFS_FAT32 && fatnode == fatfs->root) { + // fat32 root dir, update the start cluster + int error = vfs_write(fatfs->backing, &fatnode->cluster, sizeof(fatnode->cluster), 44, &bytes_done, 0); + if (error) + return error; + + __assert(bytes_done == 4); + return 0; + } + + // orphaned, if it is linked again the new dent will have updated information + if (fatnode->dent_disk_offset == 0) + return 0; + + int error = vfs_read(fatfs->backing, &dent, sizeof(fatfs_dent_t), fatnode->dent_disk_offset, &bytes_done, 0); + if (error) + return error; + + __assert(bytes_done == sizeof(fatfs_dent_t)); + + // directories have zero size + dent.file_size_bytes = fatnode->vnode.type == V_TYPE_DIR ? 0 : fatnode->size; + dent.cluster_low = fatnode->cluster & 0xffff; + dent.cluster_high = (fatnode->cluster >> 16) & 0xffff; + + error = vfs_write(fatfs->backing, &dent, sizeof(fatfs_dent_t), fatnode->dent_disk_offset, &bytes_done, 0); + if (error) + return error; + + __assert(bytes_done == sizeof(fatfs_dent_t)); + + return 0; +} + +static int get_free_dents(fatfs_t *fatfs, fatnode_t *fatnode, size_t dent_count, size_t *dent_offset) { + // TODO add a first free search + size_t offset = 0; + fatfs_dent_t dent; + size_t free_count = 0; + bool external_root_dir = fatfs->type != FATFS_FAT32 && fatfs->root == fatnode; + + for (;;) { + size_t entries_read; + size_t disk_offset; + int error = directory_read_dent(fatfs, fatnode, &dent, offset, &entries_read, &disk_offset); + if (error) + return error; + + if (entries_read == 0) { + if (external_root_dir) + return ENOSPC; + + // grow directory and return ok + // directories are always cluster size aligned + size_t grow_count = ROUND_UP((dent_count - free_count) * sizeof(fatfs_dent_t), fatfs->cluster_size); + error = fatfs_resize_file(fatfs, fatnode, fatnode->size + grow_count); + *dent_offset = offset - free_count; + return error; + } + + if (dent.short_name[0] == FATFS_DENT_NAME_FREE_INCLUDING_REST) { + // no more dents after this + size_t dir_dent_count = fatnode->size / sizeof(fatfs_dent_t); + size_t remaining_dents = dir_dent_count - offset; + if (remaining_dents >= dent_count) { + // the remaining dents in the dir are enough to hold this + *dent_offset = offset; + break; + } + + // we still have to grow the directory to have enough space + free_count = remaining_dents; + offset += remaining_dents; + continue; + } + + if (dent.short_name[0] == FATFS_DENT_NAME_FREE) { + if (free_count == 0) + *dent_offset = offset; + + ++free_count; + ++offset; + + if (free_count == dent_count) + break; + + continue; + } + + free_count = 0; + offset += IS_DENT_LFN(dent) ? (dent.short_name[0] & ~START_OF_LFN) + 1 : 1; + } + + return 0; +} + +static void set_up_lfns(fatfs_lfn_dent_t *dents, size_t lfn_count, const char *name, const char sfn[11]) { + size_t len = strlen(name); + uint8_t checksum = sfn_checksum(sfn); + + for (int i = lfn_count - 1; i >= 0; --i) { + dents[i].order = lfn_count - i; + dents[i].attr = FATFS_DENT_ATTRIBUTE_LONG_NAME; + dents[i].reserved = 0; + dents[i].checksum = checksum; + dents[i].zero = 0; + + for (int j = 0; j < 5 && *name; ++j) + dents[i].name1[j] = *name++; + + for (int j = 0; j < 6 && *name; ++j) + dents[i].name2[j] = *name++; + + for (int j = 0; j < 2 && *name; ++j) + dents[i].name3[j] = *name++; + } + + + size_t last_dent_null_offset = len % CHARACTERS_PER_LFN_ENTRY; + if (last_dent_null_offset) { + if (last_dent_null_offset < 5) { + dents[0].name1[last_dent_null_offset] = '\0'; + } else if (last_dent_null_offset >= 11) { + dents[0].name3[last_dent_null_offset - 11] = '\0'; + } else { + dents[0].name2[last_dent_null_offset - 5] = '\0'; + } + } + + dents[0].order |= START_OF_LFN; +} + +int fatfs_write_directory_entry(fatfs_t *fatfs, fatnode_t *fatnode, fatfs_dent_t *dent, const char *name, size_t *disk_offset) { + bool is_sfn = str_to_short_filename(name, dent->short_name); + size_t name_len = strlen(name); + size_t lfn_count = ROUND_UP(name_len, CHARACTERS_PER_LFN_ENTRY) / CHARACTERS_PER_LFN_ENTRY; + + if (name_len > 255) + return ENAMETOOLONG; + + size_t dent_offset; + int error = get_free_dents(fatfs, fatnode, is_sfn ? 1 : (lfn_count + 1), &dent_offset); + if (error) + return error; + + size_t file_offset = dent_offset * sizeof(fatfs_dent_t); + + if (is_sfn == false) { + // generate a short filename + char tmp_name[13]; + snprintf(tmp_name, 13, "%08d.%03d", dent_offset / 1000, dent_offset % 1000); + __assert(str_to_short_filename(tmp_name, dent->short_name)); + + fatfs_lfn_dent_t lfn_dent_buff[lfn_count]; + size_t lfn_byte_size = lfn_count * sizeof(fatfs_dent_t); + + set_up_lfns(lfn_dent_buff, lfn_count, name, dent->short_name); + + // write the lfn dents + error = directory_write_dents(fatfs, fatnode, (fatfs_dent_t *)lfn_dent_buff, dent_offset, lfn_count, NULL); + if (error) + return error; + + file_offset += lfn_byte_size; + dent_offset += lfn_count; + } + + // write the sfn dent + error = directory_write_dents(fatfs, fatnode, dent, dent_offset, 1, disk_offset); + if (error) + return error; + + return error; +} diff --git a/kernel-src/fs/fat/fatfs_fat.c b/kernel-src/fs/fat/fatfs_fat.c new file mode 100644 index 00000000..3c6985b0 --- /dev/null +++ b/kernel-src/fs/fat/fatfs_fat.c @@ -0,0 +1,346 @@ +#include +#include +#include + +static fatfs_cluster_t fatfs_eof_cluster(fatfs_t *fs) { + switch (fs->type) { + case FATFS_FAT12: + return FATFS_CLUSTER_EOF_FAT12; + case FATFS_FAT16: + return FATFS_CLUSTER_EOF_FAT16; + case FATFS_FAT32: + return FATFS_CLUSTER_EOF_FAT32; + } + + __assert(!"bad fatfs type"); +} + +bool fatfs_is_cluster_eof(fatfs_t *fs, fatfs_cluster_t cluster) { + switch (fs->type) { + case FATFS_FAT12: + return FATFS_CLUSTER_IS_EOF_FAT12(cluster); + case FATFS_FAT16: + return FATFS_CLUSTER_IS_EOF_FAT16(cluster); + case FATFS_FAT32: + return FATFS_CLUSTER_IS_EOF_FAT32(cluster); + } + + __assert(!"bad fatfs type"); +} + +static bool fatfs_is_cluster_in_range(fatfs_t *fs, fatfs_cluster_t cluster) { + return (cluster - 2) < fs->cluster_count; +} + +int fatfs_next_cluster(fatfs_t *fatfs, fatfs_cluster_t cluster, fatfs_cluster_t *ret) { + uint16_t cluster16; + int error = 0; + size_t bytes_read = 0; + + switch (fatfs->type) { + case FATFS_FAT32: + error = vfs_read(fatfs->backing, ret, 4, fatfs->fat_offset + 4 * cluster, &bytes_read, 0); + if (error) + return error; + + __assert(bytes_read == 4); + break; + case FATFS_FAT16: + error = vfs_read(fatfs->backing, &cluster16, 2, fatfs->fat_offset + 2 * cluster, &bytes_read, 0); + if (error) + return error; + + __assert(bytes_read == 2); + *ret = cluster16; + break; + case FATFS_FAT12: + error = vfs_read(fatfs->backing, &cluster16, 2, fatfs->fat_offset + cluster + cluster / 2, &bytes_read, 0); + if (error) + return error; + + __assert(bytes_read == 2); + *ret = ((cluster % 2) ? (cluster16 >> 4) : cluster16) & 0xfff; + break; + default: + __assert(!"bad fatfs type"); + } + + return error; +} + +static int set_next_cluster(fatfs_t *fatfs, int fat, fatfs_cluster_t cluster, fatfs_cluster_t next) { + uint16_t cluster16; + int error = 0; + size_t byte_count; + + size_t fat_offset = fatfs->fat_offset + fat * fatfs->fat_size; + + switch (fatfs->type) { + case FATFS_FAT32: + error = vfs_write(fatfs->backing, &next, 4, fat_offset + 4 * cluster, &byte_count, 0); + if (error) + goto leave; + + __assert(byte_count == 4); + break; + case FATFS_FAT16: + cluster16 = (uint16_t)next; + error = vfs_write(fatfs->backing, &cluster16, 2, fat_offset + 2 * cluster, &byte_count, 0); + if (error) + goto leave; + + __assert(byte_count == 2); + break; + case FATFS_FAT12: + next &= 0xfff; + error = vfs_read(fatfs->backing, &cluster16, 2, fat_offset + cluster + cluster / 2, &byte_count, 0); + if (error) + goto leave; + + __assert(byte_count == 2); + if (cluster % 2) + cluster16 = (cluster16 & 0x000f) | (next << 4); + else + cluster16 = (cluster16 & 0xf000) | (next & 0xfff); + + error = vfs_write(fatfs->backing, &cluster16, 2, fat_offset + cluster + cluster / 2, &byte_count, 0); + __assert(byte_count == 2); + + break; + default: + __assert(!"bad fatfs type"); + } + + leave: + return error; +} + +int fatfs_set_next_cluster(fatfs_t *fatfs, fatfs_cluster_t cluster, fatfs_cluster_t next, bool lock) { + if (lock) + MUTEX_ACQUIRE(&fatfs->fat_mutex); + + int error; + for (int i = 0; i < fatfs->fat_count; ++i) { + error = set_next_cluster(fatfs, i, cluster, next); + if (error) + break; + } + + if (lock) + MUTEX_RELEASE(&fatfs->fat_mutex); + + return error; +} + +int fatfs_cluster_chain_length(fatfs_t *fatfs, fatfs_cluster_t cluster, size_t *length) { + *length = 0; + if (cluster == 0) + return 0; + while (fatfs_is_cluster_eof(fatfs, cluster) == false) { + printf("chain length: %x\n", cluster); + int error = fatfs_next_cluster(fatfs, cluster, &cluster); + if (error) + return error; + + *length += 1; + } + + return 0; +} + +int fatfs_cut_chain(fatfs_t *fatfs, fatfs_cluster_t last_cluster) { + fatfs_cluster_t iterator; + int error = fatfs_next_cluster(fatfs, last_cluster, &iterator); + if (error) + return error; + + error = fatfs_set_next_cluster(fatfs, last_cluster, fatfs_eof_cluster(fatfs), true); + if (error) + return error; + + while (fatfs_is_cluster_eof(fatfs, iterator) == false) { + // if fsinfo is ever added, the free cluster count would be incremented here + fatfs_cluster_t to_free = iterator; + + error = fatfs_next_cluster(fatfs, iterator, &iterator); + if (error) + return error; + + error = fatfs_set_next_cluster(fatfs, to_free, FATFS_CLUSTER_FREE, true); + if (error) + return error; + } + + // if fsinfo is ever added, it would be written here + + return 0; +} + +int fatfs_grow_chain(fatfs_t *fatfs, fatfs_cluster_t chain, size_t grow_count) { + if (grow_count == 0) + return 0; + + fatfs_cluster_t last; + // find last cluster + do { + last = chain; + int error = fatfs_next_cluster(fatfs, chain, &chain); + if (error) + return error; + } while (chain != fatfs_eof_cluster(fatfs)); + + // grow + int error; + for (int i = 0; i < grow_count; ++i) { + fatfs_cluster_t new_cluster; + // ENOSPC is already handled by fatfs_allocate_cluster + error = fatfs_allocate_cluster(fatfs, last, &new_cluster); + if (error) + return error; + + error = fatfs_set_next_cluster(fatfs, last, new_cluster, true); + if (error) + return error; + + last = new_cluster; + } + + // [sync fsinfo] + + return error; +} + +int fatfs_allocate_cluster(fatfs_t *fatfs, fatfs_cluster_t reference, fatfs_cluster_t *new_cluster) { + int error = 0; + MUTEX_ACQUIRE(&fatfs->fat_mutex); + + if (reference && fatfs_is_cluster_in_range(fatfs, reference + 1)) { + fatfs_cluster_t result; + error = fatfs_next_cluster(fatfs, reference + 1, &result); + if (error) + goto leave; + + if (result == FATFS_CLUSTER_FREE) { + *new_cluster = reference + 1; + error = fatfs_set_next_cluster(fatfs, reference + 1, fatfs_eof_cluster(fatfs), false); + // [update fsinfo] + goto leave; + } + } + + fatfs_cluster_t iterator = 2; // [get info from fsinfo] + *new_cluster = 0; + + while (fatfs_is_cluster_in_range(fatfs, iterator)) { + fatfs_cluster_t result; + error = fatfs_next_cluster(fatfs, iterator, &result); + if (error) + goto leave; + + if (result == FATFS_CLUSTER_FREE) { + *new_cluster = iterator; + break; + } + + ++iterator; + } + + if (*new_cluster) { + error = fatfs_set_next_cluster(fatfs, *new_cluster, fatfs_eof_cluster(fatfs), false); + // [update fsinfo] + } + + leave: + MUTEX_RELEASE(&fatfs->fat_mutex); + + if (error == 0 && *new_cluster == 0) + error = ENOSPC; + + // zero the page if one was allocated + // TODO use page cache to directly push zero pages + if (error == 0 && *new_cluster) { + void *buffer = alloc(fatfs->cluster_size); + if (buffer == NULL) { + error = ENOMEM; + return error; + } + + size_t bytes_written; + error = vfs_write(fatfs->backing, buffer, fatfs->cluster_size, fatfs->data_offset + fatfs->cluster_size * (*new_cluster - 2), &bytes_written, V_FFLAGS_NOCACHE); + __assert(bytes_written == fatfs->cluster_size); + + free(buffer); + } + + return error; +} + +int fatfs_resize_file(fatfs_t *fatfs, fatnode_t *fatnode, size_t new_size) { + size_t current_cluster_count = ROUND_UP(fatnode->size, fatfs->cluster_size) / fatfs->cluster_size; + size_t new_cluster_count = ROUND_UP(new_size, fatfs->cluster_size) / fatfs->cluster_size; + int error; + + if (current_cluster_count == new_cluster_count) { + fatnode->size = new_size; + return fatfs_update_dent(fatfs, fatnode); + } + + if (new_cluster_count > current_cluster_count) { + // grow + fatfs_cluster_t cluster; + + // no cluster for this file? + if (fatnode->cluster == 0) { + error = fatfs_allocate_cluster(fatfs, 0, &cluster); + if (error) + return error; + + ++current_cluster_count; + fatnode->cluster = cluster; + + __assert(cluster); + } + + error = fatfs_get_cluster_from_index(fatfs, fatnode, current_cluster_count - 1, &cluster); + if (error) + return error; + + __assert(cluster != FATFS_CLUSTER_FREE && fatfs_is_cluster_eof(fatfs, cluster) == false); + error = fatfs_grow_chain(fatfs, fatnode->cluster, new_cluster_count - current_cluster_count); + if (error == ENOSPC) { + // try to undo the operation + fatfs_cut_chain(fatfs, cluster); + if (fatnode->size == 0) { + fatfs_set_next_cluster(fatfs, fatnode->cluster, FATFS_CLUSTER_FREE, true); + fatnode->cluster = 0; + } + } + } else if (new_cluster_count == 0) { + // truncate whole file + error = fatfs_cut_chain(fatfs, fatnode->cluster); + if (error) + return error; + + error = fatfs_set_next_cluster(fatfs, fatnode->cluster, FATFS_CLUSTER_FREE, true); + if (error) + return error; + + fatnode->cluster = 0; + } else { + // truncate a part of the file + fatfs_cluster_t cluster; + error = fatfs_get_cluster_from_index(fatfs, fatnode, new_cluster_count - 1, &cluster); + if (error) + return error; + + __assert(cluster != FATFS_CLUSTER_FREE && fatfs_is_cluster_eof(fatfs, cluster) == false); + error = fatfs_cut_chain(fatfs, cluster); + } + + if (error == 0) { + fatnode->size = new_size; + error = fatfs_update_dent(fatfs, fatnode); + } + + return error; +} diff --git a/kernel-src/fs/fat/fatfs_rw.c b/kernel-src/fs/fat/fatfs_rw.c new file mode 100644 index 00000000..77744853 --- /dev/null +++ b/kernel-src/fs/fat/fatfs_rw.c @@ -0,0 +1,165 @@ +#include +#include + +// TODO optimize this by keeping some metadata in the fatfs node +int fatfs_get_cluster_from_index(fatfs_t *fs, fatnode_t *node, uintmax_t index, fatfs_cluster_t *cluster) { + fatfs_cluster_t iterator = node->cluster; + + // empty file? + if (iterator == 0) { + // return EOF + switch (fs->type) { + case FATFS_FAT12: + *cluster = FATFS_CLUSTER_EOF_FAT12; + break; + case FATFS_FAT16: + *cluster = FATFS_CLUSTER_EOF_FAT16; + break; + case FATFS_FAT32: + *cluster = FATFS_CLUSTER_EOF_FAT32; + break; + } + + return 0; + } + + while (index--) { + int error = fatfs_next_cluster(fs, iterator, &iterator); + if (error) + return error; + + // we set iterator to the specific EOF cluster because many different values can + // signal an EOF (example: 0xff8 to 0xfff for fat12) + if (fs->type == FATFS_FAT12 && FATFS_CLUSTER_IS_EOF_FAT12(iterator)) { + iterator = FATFS_CLUSTER_EOF_FAT12; + break; + } else if (fs->type == FATFS_FAT16 && FATFS_CLUSTER_IS_EOF_FAT16(iterator)) { + iterator = FATFS_CLUSTER_EOF_FAT16; + break; + } else if (fs->type == FATFS_FAT32 && FATFS_CLUSTER_IS_EOF_FAT32(iterator)) { + iterator = FATFS_CLUSTER_EOF_FAT32; + break; + } + } + + *cluster = iterator; + return 0; +} + +static inline size_t cluster_disk_offset(fatfs_t *fs, fatfs_cluster_t cluster) { + return fs->data_offset + (cluster - 2) * fs->cluster_size; +} + +int fatfs_get_file_disk_offset(fatfs_t *fs, fatnode_t *node, size_t byte_offset, size_t *disk_offset) { + __assert(byte_offset < node->size); + + fatfs_cluster_t cluster; + int error = fatfs_get_cluster_from_index(fs, node, byte_offset / fs->cluster_size, &cluster); + if (error) + return error; + + *disk_offset = cluster_disk_offset(fs, cluster) + byte_offset % fs->cluster_size; + return 0; +} + +int fatfs_rw_clusters_iovec(fatfs_t *fs, fatnode_t *node, iovec_iterator_t *iovec_iterator, size_t count, uintmax_t index, bool write, bool cache) { + fatfs_cluster_t cluster; + int error = fatfs_get_cluster_from_index(fs, node, index, &cluster); + if (error) + return error; + + __assert(cluster && fatfs_is_cluster_eof(fs, cluster) == false); + + for (uintmax_t i = 0; i < count; ++i) { + __assert(index + i < ROUND_UP(node->size, fs->cluster_size) / fs->cluster_size); + + uintmax_t disk_offset = cluster_disk_offset(fs, cluster); + size_t done_count; + error = write ? + vfs_write_iovec(fs->backing, iovec_iterator, fs->cluster_size, disk_offset, &done_count, cache ? 0 : V_FFLAGS_NOCACHE) : + vfs_read_iovec(fs->backing, iovec_iterator, fs->cluster_size, disk_offset, &done_count, cache ? 0 : V_FFLAGS_NOCACHE); + + if (error) + return error; + + __assert(done_count == fs->cluster_size); + + if (i + 1 != count) { + error = fatfs_next_cluster(fs, cluster, &cluster); + if (error) + return error; + + __assert(cluster); + __assert(fatfs_is_cluster_eof(fs, cluster) == false); + } + } + + return 0; +} + +int fatfs_rw_cluster_iovec(fatfs_t *fs, fatnode_t *node, iovec_iterator_t *iovec_iterator, size_t count, uintmax_t offset, uintmax_t index, bool write, bool cache) { + __assert(offset + count <= fs->cluster_size); + __assert(index < ROUND_UP(node->size, fs->cluster_size) / fs->cluster_size); + + fatfs_cluster_t cluster; + int error = fatfs_get_cluster_from_index(fs, node, index, &cluster); + if (error) + return error; + + __assert(cluster && fatfs_is_cluster_eof(fs, cluster) == false); + + uintmax_t disk_offset = cluster_disk_offset(fs, cluster) + offset; + size_t done_count; + error = write ? + vfs_write_iovec(fs->backing, iovec_iterator, count, disk_offset, &done_count, cache ? 0 : V_FFLAGS_NOCACHE) : + vfs_read_iovec(fs->backing, iovec_iterator, count, disk_offset, &done_count, cache ? 0 : V_FFLAGS_NOCACHE); + + if (error) + return error; + + __assert(done_count == count); + return error; +} + +int fatfs_rw_bytes_iovec(fatfs_t *fs, fatnode_t *node, iovec_iterator_t *iovec_iterator, size_t count, uintmax_t offset, bool write, bool cache) { + uintmax_t index = offset / fs->cluster_size; + uintmax_t start_offset = offset % fs->cluster_size; + + // r/w the first cluster if there's an offset into it + if (start_offset) { + size_t block_remaining = fs->cluster_size - start_offset; + size_t do_count = min(block_remaining, count); + int error = fatfs_rw_cluster_iovec(fs, node, iovec_iterator, do_count, start_offset, index, write, cache); + if (error) + return error; + + count -= do_count; + index += 1; + } + + // r/w all middle clusters + size_t blocks = count / fs->cluster_size; + if (blocks) { + int error = fatfs_rw_clusters_iovec(fs, node, iovec_iterator, blocks, index, write, cache); + if (error) + return error; + + count -= blocks * fs->cluster_size; + index += blocks; + } + + // r/w remaining cluster if there's any data left and return + return count ? fatfs_rw_cluster_iovec(fs, node, iovec_iterator, count, 0, index, write, cache) : 0; +} + +int fatfs_rw_bytes(fatfs_t *fs, fatnode_t *node, void *buffer, size_t count, uintmax_t offset, bool write, bool cache) { + iovec_t iovec = { + .addr = buffer, + .len = count + }; + + iovec_iterator_t iovec_iterator; + iovec_iterator_init(&iovec_iterator, &iovec, 1); + + return fatfs_rw_bytes_iovec(fs, node, &iovec_iterator, count, offset, write, cache); +} diff --git a/kernel-src/include/kernel/fatfs.h b/kernel-src/include/kernel/fatfs.h new file mode 100644 index 00000000..f7effefb --- /dev/null +++ b/kernel-src/include/kernel/fatfs.h @@ -0,0 +1,115 @@ +#ifndef _FATFS_H +#define _FATFS_H + +#include +#include + +#define FATFS_FAT12 1 +#define FATFS_FAT16 2 +#define FATFS_FAT32 3 + +#define FATFS_CLUSTER_IS_EOF_FAT12(x) ((x) >= 0xff8) +#define FATFS_CLUSTER_IS_EOF_FAT16(x) ((x) >= 0xfff8) +#define FATFS_CLUSTER_IS_EOF_FAT32(x) (((x) & 0x0fffffff) >= 0x0ffffff8) + +#define FATFS_CLUSTER_FREE 0 + +#define FATFS_CLUSTER_EOF_FAT12 0xfff +#define FATFS_CLUSTER_EOF_FAT16 0xffff +#define FATFS_CLUSTER_EOF_FAT32 0xfffffff + +#define FATFS_DENT_ATTRIBUTE_READ_ONLY 1 +#define FATFS_DENT_ATTRIBUTE_HIDDEN 2 +#define FATFS_DENT_ATTRIBUTE_SYSTEM 4 +#define FATFS_DENT_ATTRIBUTE_VOLUME_ID 8 +#define FATFS_DENT_ATTRIBUTE_DIRECTORY 16 +#define FATFS_DENT_ATTRIBUTE_ARCHIVE 32 + +typedef struct { + char short_name[11]; + uint8_t attributes; + uint8_t reserved; + uint8_t creation_tenth; + uint16_t creation_time; // 2 second unit + uint16_t creation_date; + uint16_t access_date; + uint16_t cluster_high; + uint16_t write_time; // 2 second unit + uint16_t write_date; + uint16_t cluster_low; + uint32_t file_size_bytes; +} __attribute__((packed)) fatfs_dent_t; + +typedef struct { + uint8_t order; + uint16_t name1[5]; + uint8_t attr; + uint8_t reserved; + uint8_t checksum; + uint16_t name2[6]; + uint16_t zero; + uint16_t name3[2]; +} __attribute__((packed)) fatfs_lfn_dent_t; + +typedef uint32_t fatfs_cluster_t; + +typedef struct fatnode_t { + vnode_t vnode; + fatfs_cluster_t cluster; + + size_t size; + + struct fatnode_t *parent_dir; + size_t dent_disk_offset; +} fatnode_t; + +typedef struct { + vfs_t vfs; + vnode_t *backing; + int type; + + union { + fatfs_cluster_t root_cluster; + struct { + size_t root_offset; + size_t root_entry_count; + }; + }; + + size_t fat_offset; + size_t data_offset; + + size_t cluster_size; + size_t cluster_count; + + mutex_t fat_mutex; + size_t fat_size; + size_t fat_count; + + mutex_t root_mutex; + fatnode_t *root; + + mutex_t vnode_map_mutex; + hashtable_t vnode_map; // -> vnode +} fatfs_t; + +fatnode_t *fatfs_allocate_node(vfs_t *vfs, int type); +int fatfs_directory_lookup(fatfs_t *fatfs, fatnode_t *fatnode, const char *name, fatfs_dent_t *dent_out, size_t *disk_offset_out); // if disk_offset_out is NULL, remove the dent +int fatfs_directory_get_dent(fatfs_t *fatfs, fatnode_t *fatnode, size_t dent_offset, char *name_out, fatfs_dent_t *dent_out, size_t *disk_offset); +int fatfs_next_cluster(fatfs_t *fatfs, fatfs_cluster_t cluster, fatfs_cluster_t *ret); +bool fatfs_is_cluster_eof(fatfs_t *fs, fatfs_cluster_t cluster); +int fatfs_cluster_chain_length(fatfs_t *fatfs, fatfs_cluster_t cluster, size_t *length); +int fatfs_allocate_cluster(fatfs_t *fatfs, fatfs_cluster_t reference, fatfs_cluster_t *new_cluster); +int fatfs_grow_chain(fatfs_t *fatfs, fatfs_cluster_t chain, size_t grow_count); +void fatfs_free_node(fatnode_t *node); +int fatfs_get_cluster_from_index(fatfs_t *fs, fatnode_t *node, uintmax_t index, fatfs_cluster_t *cluster); +int fatfs_resize_file(fatfs_t *fatfs, fatnode_t *fatnode, size_t new_size); +int fatfs_update_dent(fatfs_t *fatfs, fatnode_t *fatnode); +int fatfs_rw_clusters_iovec(fatfs_t *fs, fatnode_t *node, iovec_iterator_t *iovec_iterator, size_t count, uintmax_t index, bool write, bool cache); +int fatfs_rw_cluster_iovec(fatfs_t *fs, fatnode_t *node, iovec_iterator_t *iovec_iterator, size_t count, uintmax_t offset, uintmax_t index, bool write, bool cache); +int fatfs_rw_bytes_iovec(fatfs_t *fs, fatnode_t *node, iovec_iterator_t *iovec_iterator, size_t count, uintmax_t offset, bool write, bool cache); +int fatfs_rw_bytes(fatfs_t *fs, fatnode_t *node, void *buffer, size_t count, uintmax_t offset, bool write, bool cache); +int fatfs_get_file_disk_offset(fatfs_t *fatfs, fatnode_t *node, size_t byte_offset, size_t *disk_offset); +int fatfs_write_directory_entry(fatfs_t *fatfs, fatnode_t *fatnode, fatfs_dent_t *dent, const char *name, size_t *disk_offset); + +#endif