From: Lawrence S. <ljs...@us...> - 2019-08-13 00:37:30
|
This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "A pseudo Operating System for the Dreamcast.". The branch, master has been updated via 1b79175ac39ec0d7e66c6416ce6c6842fc380b3c (commit) from 74382b5cdcaebd7bfba63d4d8823f077760c037d (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- commit 1b79175ac39ec0d7e66c6416ce6c6842fc380b3c Author: Lawrence Sebald <ljs...@us...> Date: Mon Aug 12 20:36:19 2019 -0400 libkosfat: Implement unlink and rmdir. Note: These aren't usable at the moment, as it is not possible to mount the filesystem read/write yet. ----------------------------------------------------------------------- Summary of changes: addons/libkosfat/directory.c | 188 +++++++++++++++++++++++++++++++++++++++++++ addons/libkosfat/directory.h | 3 + addons/libkosfat/fs_fat.c | 132 +++++++++++++++++++++++++++++- 3 files changed, 321 insertions(+), 2 deletions(-) diff --git a/addons/libkosfat/directory.c b/addons/libkosfat/directory.c index 3d8df3d..6c10782 100644 --- a/addons/libkosfat/directory.c +++ b/addons/libkosfat/directory.c @@ -488,6 +488,8 @@ int fat_find_dentry(fat_fs_t *fs, const char *fn, fat_dentry_t *rv, *rcl = 0; *roff = 0; + *rlcl = 0; + *rloff = 0; err = 0; goto out; } @@ -559,6 +561,192 @@ out: return err; } +int fat_erase_dentry(fat_fs_t *fs, uint32_t cl, uint32_t off, uint32_t lcl, + uint32_t loff) { + fat_dentry_t *ent; + uint8_t *buf; + uint32_t max, max2, i; + int done = 0, err; + + /* Read the cluster/block where the short name lives. */ + if(!(buf = fat_cluster_read(fs, cl, &err))) { + dbglog(DBG_ERROR, "Error reading directory entry at cluster %" PRIu32 + ", offset %" PRIu32 ": %s\n", cl, off, strerror(err)); + return -EIO; + } + + /* Mark the short entry as free. */ + ent = (fat_dentry_t *)(buf + off); + ent->name[0] = FAT_ENTRY_FREE; + + fat_cluster_mark_dirty(fs, cl); + + /* If there is a long name chain, mark it all as free too... */ + if(lcl) { + /* Figure out how many directory entries there are in each + cluster/block. */ + if(fs->sb.fs_type == FAT_FS_FAT32 || !(lcl & 0x80000000)) { + /* Either we're working with a regular directory or we're working + with the root directory on FAT32 (which is the same as a normal + directory). We care about the number of entries per cluster. */ + max = (fs->sb.bytes_per_sector * fs->sb.sectors_per_cluster) >> 5; + max2 = 0xFFFFFFFF; + } + else { + /* We're working with the root directory on FAT12/FAT16, so we need + to look at the number of entries per sector. */ + max = fs->sb.bytes_per_sector >> 5; + max2 = (int32_t)fs->sb.root_dir; + } + + while(!done) { + if(!(buf = fat_cluster_read(fs, lcl, &err))) { + dbglog(DBG_ERROR, "Error reading directory entry at cluster %" + PRIu32 ", offst %" PRIu32 ": %s\n", lcl, loff, + strerror(err)); + return -EIO; + } + + /* Just go ahead and do this now to save us the trouble later... */ + fat_cluster_mark_dirty(fs, lcl); + + for(i = loff >> 5; i < max; ++i) { + ent = (fat_dentry_t *)(buf + (i << 5)); + + /* If name[0] is zero, then we've hit the end of the + directory. */ + if(ent->name[0] == FAT_ENTRY_EOD) { + dbglog(DBG_ERROR, "End of directory hit while reading long " + "name entry for deletion at cluster %" PRIu32 + ", offset %" PRIu32 "\n", lcl, i << 5); + return -EIO; + } + /* If name[0] == 0xE5, then this entry is empty. We previously + marked the short name entry (which should be right after the + long name chain) as empty, so this means we have finished + deleting the long name. */ + else if(ent->name[0] == FAT_ENTRY_FREE) { + done = 1; + break; + } + /* We shouldn't have any entries that aren't long names in the + middle of a long name chain... */ + else if(!FAT_IS_LONG_NAME(ent)) { + dbglog(DBG_ERROR, "Invalid dentry hit while reading long " + "name entry for deletion at cluster %" PRIu32 + ", offset %" PRIu32 "\n", lcl, i << 5); + return -EIO; + } + + /* Mark the entry as empty and move on... */ + ent->name[0] = FAT_ENTRY_FREE; + } + + /* Move onto the next cluster. */ + if(!(lcl & 0x80000000)) { + lcl = fat_read_fat(fs, lcl, &err); + if(lcl == 0xFFFFFFFF) { + dbglog(DBG_ERROR, "Invalid FAT value hit while reading long" + " name entry for deletion at cluster %" PRIu32 + ", offset %" PRIu32 "\n", lcl, i << 5); + return -err; + } + + /* This shouldn't happen either... */ + if(fat_is_eof(fs, lcl)) { + dbglog(DBG_ERROR, "End of directory hit while reading long " + "name entry for deletion at cluster %" PRIu32 + ", offset %" PRIu32 "\n", lcl, i << 5); + return -EIO; + } + } + else { + ++lcl; + max2 -= max; + + if(max2 <= 0) { + dbglog(DBG_ERROR, "End of directory hit while reading long " + "name entry for deletion at cluster %" PRIu32 + ", offset %" PRIu32 "\n", lcl, i << 5); + return -EIO; + } + } + } + } + + return 0; +} + +int fat_is_dir_empty(fat_fs_t *fs, uint32_t cluster) { + uint8_t *cl; + int err, done = 0; + uint32_t i, j = 0, max; + fat_dentry_t *ent; + + /* Figure out how many directory entries there are in each cluster/block. */ + if(fs->sb.fs_type == FAT_FS_FAT32 || !(cluster & 0x80000000)) { + /* Either we're working with a regular directory or we're working with + the root directory on FAT32 (which is the same as a normal + directory). We care about the number of entries per cluster. */ + max = (fs->sb.bytes_per_sector * fs->sb.sectors_per_cluster) >> 5; + } + else { + /* We're working with the root directory on FAT12/FAT16, so we need to + look at the number of entries per sector. */ + max = fs->sb.bytes_per_sector >> 5; + } + + while(!done) { + if(!(cl = fat_cluster_read(fs, cluster, &err))) { + dbglog(DBG_ERROR, "Error reading directory at cluster %" PRIu32 + ": %s\n", cluster, strerror(err)); + return -EIO; + } + + for(i = 0; i < max && !done; ++i, ++j) { + ent = (fat_dentry_t *)(cl + (i << 5)); + + /* If name[0] is zero, then we've hit the end of the directory. */ + if(ent->name[0] == FAT_ENTRY_EOD) { + return 1; + } + /* If name[0] == 0xE5, then this entry is empty (but there might + still be additional entries after it). */ + else if(ent->name[0] == FAT_ENTRY_FREE) { + continue; + } + /* Ignore long name entries. */ + else if(FAT_IS_LONG_NAME(ent)) { + continue; + } + + /* If we find a valid short entry, the directory is not empty. Bail + out now. */ + return 0; + } + + if(!(cluster & 0x80000000)) { + cluster = fat_read_fat(fs, cluster, &err); + if(cluster == 0xFFFFFFFF) + return -err; + + if(fat_is_eof(fs, cluster)) + done = 1; + } + else { + ++cluster; + + if(j >= fs->sb.root_dir) + done = 1; + } + } + + /* If we get here, we hit the end of chain marker in the FAT (or the max + entries in the root dir of FAT12/FAT16). Thus, there's nothing in the + directory or we would have bailed out before now. */ + return 1; +} + #ifdef FAT_DEBUG void fat_dentry_print(const fat_dentry_t *ent) { uint32_t cl = ent->cluster_low | (ent->cluster_high << 16); diff --git a/addons/libkosfat/directory.h b/addons/libkosfat/directory.h index c69df88..1ce9d71 100644 --- a/addons/libkosfat/directory.h +++ b/addons/libkosfat/directory.h @@ -64,6 +64,9 @@ int fat_find_dentry(fat_fs_t *fs, const char *fn, fat_dentry_t *rv, int fat_find_child(fat_fs_t *fs, const char *fn, fat_dentry_t *parent, fat_dentry_t *rv, uint32_t *rcl, uint32_t *roff, uint32_t *rlcl, uint32_t *rloff); +int fat_erase_dentry(fat_fs_t *fs, uint32_t cl, uint32_t off, uint32_t lcl, + uint32_t loff); +int fat_is_dir_empty(fat_fs_t *fs, uint32_t cluster); #ifdef FAT_DEBUG void fat_dentry_print(const fat_dentry_t *ent); diff --git a/addons/libkosfat/fs_fat.c b/addons/libkosfat/fs_fat.c index 9e22ca7..3abed6e 100644 --- a/addons/libkosfat/fs_fat.c +++ b/addons/libkosfat/fs_fat.c @@ -614,6 +614,62 @@ static int fs_fat_fcntl(void *h, int cmd, va_list ap) { return rv; } +static int fs_fat_unlink(struct vfs_handler *vfs, const char *fn) { + fs_fat_fs_t *fs = (fs_fat_fs_t *)vfs->privdata; + fat_dentry_t ent; + int irv = 0, err; + uint32_t cl, off, lcl, loff, cluster; + + mutex_lock(&fat_mutex); + + /* Make sure the filesystem isn't mounted read-only. */ + if(!(fs->mount_flags & FS_FAT_MOUNT_READWRITE)) { + mutex_unlock(&fat_mutex); + errno = EROFS; + return -1; + } + + /* Find the object in question */ + if((irv = fat_find_dentry(fs->fs, fn, &ent, &cl, &off, &lcl, &loff)) < 0) { + mutex_unlock(&fat_mutex); + errno = -irv; + return -1; + } + + /* Make sure that the user isn't trying to delete a directory. */ + if((ent.attr & FAT_ATTR_DIRECTORY)) { + mutex_unlock(&fat_mutex); + errno = EISDIR; + return -1; + } + + if((ent.attr & FAT_ATTR_VOLUME_ID)) { + mutex_unlock(&fat_mutex); + errno = ENOENT; + return -1; + } + + /* First clean up the clusters of the file... */ + cluster = ent.cluster_low | (ent.cluster_high << 16); + if((err = fat_erase_chain(fs->fs, cluster))) { + /* Uh oh... This is really bad... */ + dbglog(DBG_ERROR, "fs_fat: Error erasing FAT chain for file %s\n", fn); + irv = -1; + errno = -err; + } + + /* Next, erase the directory entry (and long name, if applicable). */ + if((irv = fat_erase_dentry(fs->fs, cl, off, lcl, loff)) < 0) { + dbglog(DBG_ERROR, "fs_fat: Error erasing directory entry for file %s\n", + fn); + irv = -1; + errno = -err; + } + + mutex_unlock(&fat_mutex); + return irv; +} + static int fs_fat_stat(vfs_handler_t *vfs, const char *path, struct stat *buf, int flag) { fs_fat_fs_t *fs = (fs_fat_fs_t *)vfs->privdata; @@ -681,6 +737,78 @@ static int fs_fat_stat(vfs_handler_t *vfs, const char *path, struct stat *buf, return irv; } +static int fs_fat_rmdir(struct vfs_handler *vfs, const char *fn) { + fs_fat_fs_t *fs = (fs_fat_fs_t *)vfs->privdata; + fat_dentry_t ent; + int irv = 0, err; + uint32_t cl, off, lcl, loff, cluster; + + mutex_lock(&fat_mutex); + + /* Make sure the filesystem isn't mounted read-only. */ + if(!(fs->mount_flags & FS_FAT_MOUNT_READWRITE)) { + mutex_unlock(&fat_mutex); + errno = EROFS; + return -1; + } + + /* Find the object in question */ + if((irv = fat_find_dentry(fs->fs, fn, &ent, &cl, &off, &lcl, &loff)) < 0) { + mutex_unlock(&fat_mutex); + errno = -irv; + return -1; + } + + /* Make sure that the user isn't trying to rmdir a file. */ + if(!(ent.attr & FAT_ATTR_DIRECTORY)) { + mutex_unlock(&fat_mutex); + errno = ENOTDIR; + return -1; + } + + /* Make sure they're not trying to delete the root directory... */ + if(!cl) { + mutex_unlock(&fat_mutex); + errno = EPERM; + return -1; + } + + /* Make sure the directory is empty... */ + cluster = ent.cluster_low | (ent.cluster_high << 16); + irv = fat_is_dir_empty(fs->fs, cluster); + + if(irv < 0) { + mutex_unlock(&fat_mutex); + errno = -irv; + return -1; + } + else if(irv == 0) { + mutex_unlock(&fat_mutex); + errno = ENOTEMPTY; + return -1; + } + + /* First clean up the clusters of the directory... */ + if((err = fat_erase_chain(fs->fs, cluster))) { + /* Uh oh... This is really bad... */ + dbglog(DBG_ERROR, "fs_fat: Error erasing FAT chain for directory %s\n", + fn); + irv = -1; + errno = -err; + } + + /* Next, erase the directory entry (and long name, if applicable). */ + if((irv = fat_erase_dentry(fs->fs, cl, off, lcl, loff)) < 0) { + dbglog(DBG_ERROR, "fs_fat: Error erasing directory entry for directory " + "%s\n", fn); + irv = -1; + errno = -err; + } + + mutex_unlock(&fat_mutex); + return irv; +} + static int fs_fat_rewinddir(void *h) { file_t fd = ((file_t)h) - 1; @@ -791,12 +919,12 @@ static vfs_handler_t vh = { fs_fat_readdir, /* readdir */ NULL, /* ioctl */ NULL, /* rename */ - NULL, /* unlink */ + fs_fat_unlink, /* unlink */ NULL, /* mmap */ NULL, /* complete */ fs_fat_stat, /* stat */ NULL, /* mkdir */ - NULL, /* rmdir */ + fs_fat_rmdir, /* rmdir */ fs_fat_fcntl, /* fcntl */ NULL, /* poll */ NULL, /* link */ hooks/post-receive -- A pseudo Operating System for the Dreamcast. |