[fs][fat] Implement first implementation of file create

Limitations:
Only supports simple 8.3 file names
Cannot create with size > 0
Timestamp is bogus
This commit is contained in:
Travis Geiselbrecht
2022-05-07 16:51:04 -07:00
parent 1c58670d46
commit afa659732e
8 changed files with 350 additions and 40 deletions

View File

@@ -11,6 +11,7 @@
#include <lk/cpp.h>
#include <lk/err.h>
#include <lk/trace.h>
#include <ctype.h>
#include <endian.h>
#include <stdint.h>
#include <stdlib.h>
@@ -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;
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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,