mirror of
https://github.com/Mathewnd/Astral.git
synced 2026-10-27 12:48:22 +00:00
fatfs: initial implementation
This commit is contained in:
@@ -0,0 +1,961 @@
|
||||
#include <kernel/fatfs.h>
|
||||
#include <kernel/vfs.h>
|
||||
#include <util.h>
|
||||
#include <logging.h>
|
||||
#include <kernel/alloc.h>
|
||||
#include <kernel/init.h>
|
||||
#include <kernel/slab.h>
|
||||
#include <kernel/auth.h>
|
||||
#include <kernel/pmm.h>
|
||||
#include <kernel/vmmcache.h>
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,618 @@
|
||||
#include <kernel/fatfs.h>
|
||||
#include <logging.h>
|
||||
#include <util.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
@@ -0,0 +1,346 @@
|
||||
#include <kernel/fatfs.h>
|
||||
#include <logging.h>
|
||||
#include <kernel/alloc.h>
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
#include <kernel/fatfs.h>
|
||||
#include <logging.h>
|
||||
|
||||
// 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);
|
||||
}
|
||||
Reference in New Issue
Block a user