From afa659732e4062c361f8dfc9a09cff087cf6bf62 Mon Sep 17 00:00:00 2001 From: Travis Geiselbrecht Date: Sat, 7 May 2022 16:51:04 -0700 Subject: [PATCH] [fs][fat] Implement first implementation of file create Limitations: Only supports simple 8.3 file names Cannot create with size > 0 Timestamp is bogus --- lib/fs/fat/dir.cpp | 256 +++++++++++++++++++++++++++++++++-- lib/fs/fat/fat_fs.h | 17 +++ lib/fs/fat/fat_priv.h | 7 +- lib/fs/fat/file.cpp | 40 +++++- lib/fs/fat/file.h | 3 +- lib/fs/fat/file_iterator.cpp | 8 ++ lib/fs/fat/file_iterator.h | 11 +- lib/fs/fat/fs.cpp | 48 +++---- 8 files changed, 350 insertions(+), 40 deletions(-) diff --git a/lib/fs/fat/dir.cpp b/lib/fs/fat/dir.cpp index 7b286c43..caffe2af 100644 --- a/lib/fs/fat/dir.cpp +++ b/lib/fs/fat/dir.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -37,13 +38,11 @@ struct fat_dir_cookie { static const uint32_t index_eod = 0xffffffff; }; -namespace { - // walk one entry into the dir, starting at byte offset into the directory block iterator. // both dbi and offset will be modified during the call. // filles out the entry and returns a pointer into the passed in buffer in out_filename. // NOTE: *must* pass at least a MAX_FILE_NAME_LEN byte char pointer in the filename_buffer slot. -status_t fat_find_next_entry(fat_fs *fat, file_block_iterator &dbi, uint32_t &offset, dir_entry *entry, +static status_t fat_find_next_entry(fat_fs *fat, file_block_iterator &dbi, uint32_t &offset, dir_entry *entry, char filename_buffer[MAX_FILE_NAME_LEN], char **out_filename) { DEBUG_ASSERT(entry && filename_buffer && out_filename); @@ -69,7 +68,7 @@ status_t fat_find_next_entry(fat_fs *fat, file_block_iterator &dbi, uint32_t &of // walk within a sector while (offset < fat->info().bytes_per_sector) { - LTRACEF_LEVEL(2, "looking at offset %u\n", offset); + LTRACEF_LEVEL(2, "looking at offset %#x\n", offset); const uint8_t *ent = dbi.get_bcache_ptr(offset); if (ent[0] == 0) { // no more entries // we're completely done @@ -206,10 +205,11 @@ status_t fat_find_next_entry(fat_fs *fat, file_block_iterator &dbi, uint32_t &of return ERR_NOT_FOUND; } -status_t fat_find_file_in_dir(fat_fs *fat, uint32_t starting_cluster, const char *name, dir_entry *entry, uint32_t *found_offset) { +static status_t fat_find_file_in_dir(fat_fs *fat, uint32_t starting_cluster, const char *name, dir_entry *entry, uint32_t *found_offset) { LTRACEF("start_cluster %u, name '%s', out entry %p\n", starting_cluster, name, entry); DEBUG_ASSERT(fat->lock.is_held()); + DEBUG_ASSERT(entry); // cache the length of the string we're matching against const size_t namelen = strlen(name); @@ -237,15 +237,15 @@ status_t fat_find_file_in_dir(fat_fs *fat, uint32_t starting_cluster, const char // see if we've matched an entry if (filenamelen == namelen && !strnicmp(name, filename, filenamelen)) { // we have, return with a good status - *found_offset = offset; + if (found_offset) { + *found_offset = offset; + } return NO_ERROR; } } } -} // namespace - -status_t fat_walk(fat_fs *fat, const char *path, dir_entry *out_entry, dir_entry_location *loc) { +status_t fat_dir_walk(fat_fs *fat, const char *path, dir_entry *out_entry, dir_entry_location *loc) { LTRACEF("path %s\n", path); DEBUG_ASSERT(fat->lock.is_held()); @@ -321,14 +321,244 @@ status_t fat_walk(fat_fs *fat, const char *path, dir_entry *out_entry, dir_entry } } else { // we got a hit at the terminal entry of the path, pass it out to the caller as a success - *out_entry = entry; - loc->starting_dir_cluster = dir_start_cluster; - loc->dir_offset = found_offset; + if (out_entry) { + *out_entry = entry; + } + if (loc) { + loc->starting_dir_cluster = dir_start_cluster; + loc->dir_offset = found_offset; + } return NO_ERROR; } } } +// splits a path into the part of it leading up to the last element and the last element +// if the leading part is zero length, return a single "/" element +// will modify string passed in +// TODO: write unit test +static void split_path(char *path, const char **leading_path, const char **last_element) { + char *last_slash = strrchr(path, '/'); + if (last_slash) { + *last_slash = 0; + if (path[0] != 0) { + *leading_path = path; + } else { + *leading_path = "/"; + } + *last_element = last_slash + 1; + } else { + *leading_path = "/"; + *last_element = path; + } +} + +// construct a short file name from the incoming name +// the sfn is padded out with spaces the same way a real FAT entry is +// TODO: write unit test +static status_t name_to_short_file_name(char sfn[8 + 3 + 1], const char *name) { + // zero length inputs don't fly + if (name[0] == 0) { + return ERR_INVALID_ARGS; + } + + // start off with a spaced out sfn + memset(sfn, ' ', 8 + 3); + sfn[8 + 3] = 0; + + size_t input_pos = 0; + size_t output_pos = 0; + + // pick out the 8 entry part + for (auto i = 0; i < 8; i++) { + char c = name[input_pos]; + if (c == 0) { + break; + } else if (c == '.') { + output_pos = 8; + break; + } else { + sfn[output_pos++] = toupper(c); + input_pos++; + } + } + + // at this point input pos had better be looking at a . or a null + if (name[input_pos] == 0) { + return NO_ERROR; + } + if (name[input_pos] != '.') { + return ERR_INVALID_ARGS; + } + input_pos++; + + for (auto i = 0; i < 3; i++) { + char c = name[input_pos]; + if (c == 0) { + break; + } else if (c == '.') { + // can only see '.' once + return ERR_INVALID_ARGS; + } else { + sfn[output_pos++] = toupper(c); + input_pos++; + } + } + + // at this point we should be looking at the end of the input string + if (name[input_pos] != 0) { + return ERR_INVALID_ARGS; + } + + return NO_ERROR; +} + +status_t fat_dir_allocate(fat_fs *fat, const char *path, const fat_attribute attr, const uint32_t starting_cluster, const uint32_t size, dir_entry_location *loc) { + LTRACEF("path %s\n", path); + + DEBUG_ASSERT(fat->lock.is_held()); + + // trim the last segment off the path, splitting into stuff leading up to the last segment and the last segment + char local_path[FS_MAX_FILE_LEN + 1]; + strlcpy(local_path, path, FS_MAX_FILE_LEN); + + const char *leading_path; + const char *last_element; + split_path(local_path, &leading_path, &last_element); + + DEBUG_ASSERT(leading_path && last_element); + + LTRACEF("path is now split into %s and %s\n", leading_path, last_element); + + // find the starting directory cluster of the container directory + // 0 may mean root dir on fat12/16 + uint32_t starting_dir_cluster; + if (strcmp(leading_path, "/") == 0) { + // root dir is a special case since we know where to start + if (fat->info().root_cluster) { + starting_dir_cluster = fat->info().root_cluster; + } else { + // fat 12/16 has a linear root dir, cluster 0 is a special case to fat_find_file_in_dir below + starting_dir_cluster = 0; + } + } else { + // walk to find the containing directory + dir_entry entry; + dir_entry_location dir_loc; + status_t err = fat_dir_walk(fat, local_path, &entry, &dir_loc); + if (err < 0) { + return err; + } + + // verify it's a directory + if (entry.attributes != fat_attribute::directory) { + return ERR_BAD_PATH; + } + + LTRACEF("found containing dir at %u:%u: starting cluster %u\n", dir_loc.starting_dir_cluster, dir_loc.dir_offset, entry.start_cluster); + + starting_dir_cluster = entry.start_cluster; + if (starting_dir_cluster < 2 || starting_dir_cluster >= fat->info().total_clusters) { + TRACEF("directory entry contains out of bounds cluster %u\n", starting_dir_cluster); + return ERR_BAD_STATE; + } + } + + LTRACEF("starting dir cluster of parent dir %u\n", starting_dir_cluster); + + // verify the file doesn't already exist + dir_entry entry; + status_t err = fat_find_file_in_dir(fat, starting_dir_cluster, last_element, &entry, nullptr); + if (err >= 0) { + // we found it, cant create a new file in its place + return ERR_ALREADY_EXISTS; + } + + // TODO: handle long file names + char sfn[8 + 3 + 1]; + err = name_to_short_file_name(sfn, last_element); + if (err < 0) { + // if we couldn't convert to a SFN trivially, abort + return err; + } + + LTRACEF("short file name '%s'\n", sfn); + + // now we have a starting cluster for the containing directory and proof that it doesn't already exist. + // start walking to find a free slot + file_block_iterator dbi(fat, starting_dir_cluster); + err = dbi.next_sectors(0); + if (err < 0) { + return err; + } + + uint32_t dir_offset = 0; + uint32_t sector_offset = 0; + for (;;) { + if (LOCAL_TRACE >= 2) { + LTRACEF("dir sector:\n"); + hexdump8_ex(dbi.get_bcache_ptr(0), fat->info().bytes_per_sector, 0); + } + + // walk within a sector + while (sector_offset < fat->info().bytes_per_sector) { + LTRACEF_LEVEL(2, "looking at offset %#x\n", sector_offset); + uint8_t *ent = dbi.get_bcache_ptr(sector_offset); + if (ent[0] == 0xe5 || ent[0] == 0) { + // deleted or last entry in the list + LTRACEF("found usable at offset %#x\n", sector_offset); + if (LOCAL_TRACE > 1) hexdump8_ex(ent, DIR_ENTRY_LENGTH, 0); + + // fill in an entry here + memcpy(&ent[0], sfn, 11); // name + ent[11] = (uint8_t)attr; // attribute + ent[12] = 0; // reserved + ent[13] = 0; // creation time tenth of second + fat_write16(ent, 14, 0); // creation time seconds / 2 + fat_write16(ent, 16, 0); // creation date + fat_write16(ent, 18, 0); // last accessed date + fat_write16(ent, 20, starting_cluster >> 16); // fat cluster high + fat_write16(ent, 22, 0); // modification time + fat_write16(ent, 24, 0); // modification date + fat_write16(ent, 26, starting_cluster); // fat cluster low + fat_write32(ent, 28, size); // file size + + LTRACEF_LEVEL(2, "filled in entry\n"); + if (LOCAL_TRACE > 1) hexdump8_ex(ent, DIR_ENTRY_LENGTH, 0); + + // flush the data and exit + dbi.mark_bcache_dirty(); + + // flush it + bcache_flush(fat->bcache()); + + // fill in our location data and exit + if (loc) { + loc->starting_dir_cluster = starting_dir_cluster; + loc->dir_offset = dir_offset; + } + + return NO_ERROR; + } + + dir_offset += DIR_ENTRY_LENGTH; + sector_offset += DIR_ENTRY_LENGTH; + } + + // move to the next sector + err = dbi.next_sector(); + if (err < 0) { + return err; + } + // starting over at offset 0 in the new sector + sector_offset = 0; + } + + // TODO: we probably ran out of space, add another cluster to the dir and start over + + return ERR_NOT_IMPLEMENTED; +} + status_t fat_dir::opendir_priv(const dir_entry &entry, const dir_entry_location &loc, fat_dir_cookie **out_cookie) { // fill in our file info based on the entry start_cluster_ = entry.start_cluster; @@ -376,7 +606,7 @@ status_t fat_dir::opendir(fscookie *cookie, const char *name, dircookie **dcooki loc.starting_dir_cluster = 1; loc.dir_offset = 0; } else { - status_t err = fat_walk(fat, name, &entry, &loc); + status_t err = fat_dir_walk(fat, name, &entry, &loc); if (err != NO_ERROR) { return err; } diff --git a/lib/fs/fat/fat_fs.h b/lib/fs/fat/fat_fs.h index fde694ff..e2988ef0 100644 --- a/lib/fs/fat/fat_fs.h +++ b/lib/fs/fat/fat_fs.h @@ -69,6 +69,7 @@ private: }; enum class fat_attribute : uint8_t { + file = 0x0, // lack of attribute is a file read_only = 0x01, hidden = 0x02, system = 0x04, @@ -87,6 +88,15 @@ inline uint32_t fat_read32(const void *_buffer, size_t offset) { (buffer[offset + 3] << 24); } +inline void fat_write32(void *_buffer, size_t offset, uint32_t val) { + auto *buffer = (uint8_t *)_buffer; + + buffer[offset] = val; + buffer[offset + 1] = val >> 8; + buffer[offset + 2] = val >> 16; + buffer[offset + 3] = val >> 24; +} + inline uint16_t fat_read16(const void *_buffer, size_t offset) { auto *buffer = (const uint8_t *)_buffer; @@ -94,6 +104,13 @@ inline uint16_t fat_read16(const void *_buffer, size_t offset) { (buffer[offset + 1] << 8); } +inline void fat_write16(void *_buffer, size_t offset, uint16_t val) { + auto *buffer = (uint8_t *)_buffer; + + buffer[offset] = val; + buffer[offset + 1] = val >> 8; +} + // In fat32, clusters between 0x0fff.fff8 and 0x0fff.ffff are interpreted as // end of file. const uint32_t EOF_CLUSTER_BASE = 0x0ffffff8; diff --git a/lib/fs/fat/fat_priv.h b/lib/fs/fat/fat_priv.h index 6438f01c..4ded7e1b 100644 --- a/lib/fs/fat/fat_priv.h +++ b/lib/fs/fat/fat_priv.h @@ -47,4 +47,9 @@ inline bool operator==(const dir_entry_location &a, const dir_entry_location &b) return (a.starting_dir_cluster == b.starting_dir_cluster && a.dir_offset == b.dir_offset); } -status_t fat_walk(fat_fs *fat, const char *path, dir_entry *out_entry, dir_entry_location *loc); +// walk a path, returning the entry and the location where it was found +status_t fat_dir_walk(fat_fs *fat, const char *path, dir_entry *out_entry, dir_entry_location *loc); + +// walk a path, allocating a new entry with the path name. +// returns the dir entry location +status_t fat_dir_allocate(fat_fs *fat, const char *path, fat_attribute attr, uint32_t starting_cluster, uint32_t size, dir_entry_location *loc); diff --git a/lib/fs/fat/file.cpp b/lib/fs/fat/file.cpp index 6ccc688f..87398eab 100644 --- a/lib/fs/fat/file.cpp +++ b/lib/fs/fat/file.cpp @@ -78,6 +78,7 @@ status_t fat_file::open_file_priv(const dir_entry &entry, const dir_entry_locati } } +// static status_t fat_file::open_file(fscookie *cookie, const char *path, filecookie **fcookie) { fat_fs *fs = (fat_fs *)cookie; @@ -88,7 +89,7 @@ status_t fat_file::open_file(fscookie *cookie, const char *path, filecookie **fc // look for the file in the fs dir_entry entry; dir_entry_location loc; - status_t err = fat_walk(fs, path, &entry, &loc); + status_t err = fat_dir_walk(fs, path, &entry, &loc); if (err != NO_ERROR) { return err; } @@ -204,6 +205,7 @@ ssize_t fat_file::read_file_priv(void *_buf, const off_t offset, size_t len) { return amount_read; } +// static ssize_t fat_file::read_file(filecookie *fcookie, void *_buf, const off_t offset, size_t len) { fat_file *file = (fat_file *)fcookie; @@ -213,11 +215,14 @@ ssize_t fat_file::read_file(filecookie *fcookie, void *_buf, const off_t offset, status_t fat_file::stat_file_priv(struct file_stat *stat) { AutoLock guard(fs_->lock); + LTRACEF("file %p state %p\n", this, stat); + stat->size = length_; stat->is_dir = is_dir(); return NO_ERROR; } +// static status_t fat_file::stat_file(filecookie *fcookie, struct file_stat *stat) { fat_file *file = (fat_file *)fcookie; @@ -234,6 +239,7 @@ status_t fat_file::close_file_priv(bool *last_ref) { return NO_ERROR; } +// static status_t fat_file::close_file(filecookie *fcookie) { fat_file *file = (fat_file *)fcookie; @@ -251,3 +257,35 @@ status_t fat_file::close_file(filecookie *fcookie) { return NO_ERROR; } + +// static +status_t fat_file::create_file(fscookie *cookie, const char *path, filecookie **fcookie, uint64_t len) { + fat_fs *fs = (fat_fs *)cookie; + + LTRACEF("fs %p path '%s' len %" PRIu64 "\n", fs, path, len); + + // currently only support zero length files + if (len != 0) { + return ERR_NOT_IMPLEMENTED; + } + + { + AutoLock guard(fs->lock); + + // tell the dir code to find us a spot + dir_entry_location loc; + status_t err = fat_dir_allocate(fs, path, fat_attribute::file, 0, 0, &loc); + if (err < 0) { + return err; + } + + // we have found and allocated a spot + fat_file *file = new fat_file(fs); + file->dir_loc_ = loc; + file->inc_ref(); + *fcookie = (filecookie *)file; + } + + return NO_ERROR; +} + diff --git a/lib/fs/fat/file.h b/lib/fs/fat/file.h index 3e9b3f19..0b804021 100644 --- a/lib/fs/fat/file.h +++ b/lib/fs/fat/file.h @@ -26,6 +26,7 @@ public: static ssize_t read_file(filecookie *fcookie, void *_buf, const off_t offset, size_t len); static status_t stat_file(filecookie *fcookie, struct file_stat *stat); static status_t close_file(filecookie *fcookie); + static status_t create_file(fscookie *cookie, const char *path, filecookie **fcookie, uint64_t len); // used by fs node list maintenance // node in the fs's list of open files and dirs @@ -48,7 +49,7 @@ protected: fat_fs *fs_ = nullptr; // pointer back to the fs instance we're in - // pointer to our dir entry, acts as our unique key + // pointer to our dir entry, acts as our unique key in the fs list dir_entry_location dir_loc_ {}; // our start cluster and length diff --git a/lib/fs/fat/file_iterator.cpp b/lib/fs/fat/file_iterator.cpp index 53dc75f6..765948b4 100644 --- a/lib/fs/fat/file_iterator.cpp +++ b/lib/fs/fat/file_iterator.cpp @@ -107,3 +107,11 @@ status_t file_block_iterator::load_bcache_block(bnum_t bnum) { return err; } +status_t file_block_iterator::mark_bcache_dirty() { + if (bcache_buf) { + return bcache_mark_block_dirty(fat->bcache(), bcache_bnum); + } else { + return ERR_NO_RESOURCES; + } +} + diff --git a/lib/fs/fat/file_iterator.h b/lib/fs/fat/file_iterator.h index df4c44f1..97b670c4 100644 --- a/lib/fs/fat/file_iterator.h +++ b/lib/fs/fat/file_iterator.h @@ -29,12 +29,21 @@ public: DISALLOW_COPY_ASSIGN_AND_MOVE(file_block_iterator); - const uint8_t *get_bcache_ptr(size_t offset) { + const uint8_t *get_bcache_ptr(size_t offset) const { DEBUG_ASSERT(offset < fat->info().bytes_per_sector); DEBUG_ASSERT(bcache_buf); return (const uint8_t *)bcache_buf + offset; } + uint8_t *get_bcache_ptr(size_t offset) { + DEBUG_ASSERT(offset < fat->info().bytes_per_sector); + DEBUG_ASSERT(bcache_buf); + return (uint8_t *)bcache_buf + offset; + } + + // write mark the current block as modified + status_t mark_bcache_dirty(); + // move N sectors ahead in the file, walking the FAT cluster chain as necessary. // sectors == 0 will ensure the current block is loaded. status_t next_sectors(uint32_t sectors); diff --git a/lib/fs/fat/fs.cpp b/lib/fs/fat/fs.cpp index 253bedf0..c48beba6 100644 --- a/lib/fs/fat/fs.cpp +++ b/lib/fs/fat/fs.cpp @@ -46,6 +46,29 @@ __NO_INLINE static void fat_dump(fat_fs *fat) { fat_fs::fat_fs() = default; fat_fs::~fat_fs() = default; +void fat_fs::add_to_file_list(fat_file *file) { + DEBUG_ASSERT(lock.is_held()); + DEBUG_ASSERT(!list_in_list(&file->node_)); + + LTRACEF("file %p, location %u:%u\n", file, file->dir_loc().starting_dir_cluster, file->dir_loc().dir_offset); + + list_add_head(&file_list_, &file->node_); +} + +fat_file *fat_fs::lookup_file(const dir_entry_location &loc) { + DEBUG_ASSERT(lock.is_held()); + + fat_file *f; + list_for_every_entry(&file_list_, f, fat_file, node_) { + if (loc == f->dir_loc()) { + return f; + } + } + + return nullptr; +} + +// static fs hooks status_t fat_fs::mount(bdev_t *dev, fscookie **cookie) { status_t result = NO_ERROR; @@ -210,6 +233,7 @@ status_t fat_fs::mount(bdev_t *dev, fscookie **cookie) { return result; } +// static status_t fat_fs::unmount(fscookie *cookie) { auto *fat = (fat_fs *)cookie; @@ -227,28 +251,6 @@ status_t fat_fs::unmount(fscookie *cookie) { return NO_ERROR; } -void fat_fs::add_to_file_list(fat_file *file) { - DEBUG_ASSERT(lock.is_held()); - DEBUG_ASSERT(!list_in_list(&file->node_)); - - LTRACEF("file %p, location %u:%u\n", file, file->dir_loc().starting_dir_cluster, file->dir_loc().dir_offset); - - list_add_head(&file_list_, &file->node_); -} - -fat_file *fat_fs::lookup_file(const dir_entry_location &loc) { - DEBUG_ASSERT(lock.is_held()); - - fat_file *f; - list_for_every_entry(&file_list_, f, fat_file, node_) { - if (loc == f->dir_loc()) { - return f; - } - } - - return nullptr; -} - static const struct fs_api fat_api = { .format = nullptr, .fs_stat = nullptr, @@ -256,7 +258,7 @@ static const struct fs_api fat_api = { .mount = fat_fs::mount, .unmount = fat_fs::unmount, .open = fat_file::open_file, - .create = nullptr, + .create = fat_file::create_file, .remove = nullptr, .truncate = nullptr, .stat = fat_file::stat_file,