Initial commit of basic file creation

Currently limited to inlined files and only simpler truncate-writes.

But still this lets us test file creation/deletion.

This is also enough logic to make it clear that, even though we have
some powerful high-level primitives, mapping file operations onto these
is still going to be non-trivial.
This commit is contained in:
Christopher Haster
2023-09-17 11:02:30 -05:00
parent e7bf5ad82f
commit c74ec1c133
7 changed files with 941 additions and 147 deletions
+410 -124
View File
@@ -1000,32 +1000,6 @@ static lfs_ssize_t lfsr_bd_progtag(lfs_t *lfs,
/// lfsr_data_t stuff ///
// either an on-disk or in-device data pointer
typedef struct lfsr_data {
union {
// The sign-bit of the size field indicates if the data is in-device
// or on-disk.
//
// After removing the sign bit, the size always encodes the resulting
// size on-disk.
//
lfs_ssize_t size;
struct {
lfs_ssize_t size;
// This leb128 field is a bit of a hack that allows a single leb128
// to be injected into lfsr_bd_progdata. Outside of
// lfsr_bd_progdata, this field is invalid!
int32_t leb128;
const uint8_t *buffer;
} b;
struct {
lfs_ssize_t size;
lfs_size_t off;
lfs_block_t block;
} d;
} u;
} lfsr_data_t;
// the most common use is to pass a buffer with explicit size
#define LFSR_DATA(_buffer, _size) \
((lfsr_data_t){ \
@@ -1764,10 +1738,6 @@ static int lfsr_data_readgrm(lfs_t *lfs, lfsr_data_t *data,
static int lfs_alloc(lfs_t *lfs, lfs_block_t *block);
static void lfs_alloc_ack(lfs_t *lfs);
// and our main "fix everything before writing" function
static int lfsr_fs_preparemutation(lfs_t *lfs);
static int lfsr_fs_fixgrm(lfs_t *lfs);
/// Red-black-yellow Dhara tree operations ///
@@ -6891,6 +6861,75 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) {
}
/// Prepare the filesystem for mutation ///
static int lfsr_fs_fixgrm(lfs_t *lfs) {
while (lfsr_grm_hasrm(&lfs->grm)) {
// find our mdir
lfsr_mdir_t mdir;
LFS_ASSERT(lfs->grm.rms[0] < lfs_smax32(
lfsr_mtree_weight(lfs),
lfsr_mleafweight(lfs)));
int err = lfsr_mtree_lookup(lfs, lfs->grm.rms[0], &mdir);
if (err) {
return err;
}
// mark grm as taken care of
lfsr_grm_t grm = lfs->grm;
lfsr_grm_poprm(&grm);
// make sure to adjust any remaining grms
if ((grm.rms[0] & lfsr_midbmask(lfs))
== (mdir.mid & lfsr_midbmask(lfs))
&& grm.rms[0] >= mdir.mid) {
LFS_ASSERT(grm.rms[0] != mdir.mid);
grm.rms[0] -= 1;
}
// remove the rid while also updating our grm
LFS_ASSERT((lfs->grm.rms[0] & lfsr_midrmask(lfs)) < mdir.u.m.weight);
err = lfsr_mdir_commit(lfs, &mdir, LFSR_ATTRS(
LFSR_ATTR(mdir.mid, RM, -1, NULL),
LFSR_ATTR(-1, GRM, 0, GRM(&grm))));
}
return 0;
}
static int lfsr_fs_preparemutation(lfs_t *lfs) {
// checkpoint the allocator
lfs_alloc_ack(lfs);
// fix pending grms
if (lfsr_grm_hasrm(&lfs->grm)) {
if (lfsr_grm_count(&lfs->grm) == 2) {
LFS_DEBUG("Fixing pending grm "
"%"PRId32".%"PRId32" %"PRId32".%"PRId32,
lfs->grm.rms[0] >> lfs->mleaf_bits,
lfs->grm.rms[0] & lfsr_midrmask(lfs),
lfs->grm.rms[1] >> lfs->mleaf_bits,
lfs->grm.rms[1] & lfsr_midrmask(lfs));
} else if (lfsr_grm_count(&lfs->grm) == 1) {
LFS_DEBUG("Fixing pending grm %"PRId32".%"PRId32,
lfs->grm.rms[0] >> lfs->mleaf_bits,
lfs->grm.rms[0] & lfsr_midrmask(lfs));
}
int err = lfsr_fs_fixgrm(lfs);
if (err) {
return err;
}
// checkpoint the allocator again since our fixgrm completed some
// work
lfs_alloc_ack(lfs);
}
return 0;
}
/// Directory operations ///
int lfsr_mkdir(lfs_t *lfs, const char *path) {
@@ -7238,9 +7277,66 @@ int lfsr_rename(lfs_t *lfs, const char *old_path, const char *new_path) {
return lfsr_fs_fixgrm(lfs);
}
int lfsr_stat(lfs_t *lfs, const char *path, struct lfs_info *info) {
memset(info, 0, sizeof(struct lfs_info));
// common stat function once we have an mdir
static int lfsr_mdir_stat(lfs_t *lfs, lfsr_mdir_t *mdir, lfsr_mid_t mid,
lfsr_sdid_t did, struct lfs_info *info) {
// lookup our name tag
lfsr_tag_t tag;
lfsr_data_t data;
int err = lfsr_mdir_lookup(lfs, mdir, mid, LFSR_TAG_WIDE(NAME),
&tag, &data);
if (err) {
return err;
}
// get our did
lfsr_did_t did_;
err = lfsr_data_readleb128(lfs, &data, (int32_t*)&did_);
if (err) {
return err;
}
// did mismatch? this terminates dir reads
if (did != -1 && did_ != (lfsr_did_t)did) {
return LFS_ERR_NOENT;
}
// get file type from the tag
info->type = lfsr_tag_filetype(tag);
// get file name from the name entry
LFS_ASSERT(lfsr_data_size(&data) <= LFS_NAME_MAX);
lfs_ssize_t name_size = lfsr_data_read(lfs, &data,
info->name, LFS_NAME_MAX);
if (name_size < 0) {
return name_size;
}
info->name[name_size] = '\0';
// get file size if we're a regular file, this gets a bit messy
// because of the different file representations
info->size = 0;
if (tag == LFSR_TAG_REG) {
err = lfsr_mdir_lookup(lfs, mdir, mid, LFSR_TAG_WIDE(STRUCT),
&tag, &data);
if (err && err != LFS_ERR_NOENT) {
return err;
}
if (err != LFS_ERR_NOENT) {
if (tag == LFSR_TAG_INLINED) {
info->size = lfsr_data_size(&data);
} else {
// TODO
LFS_ASSERT(false);
}
}
}
return 0;
}
int lfsr_stat(lfs_t *lfs, const char *path, struct lfs_info *info) {
// lookup our entry
lfsr_mdir_t mdir;
lfsr_tag_t tag;
@@ -7261,15 +7357,7 @@ int lfsr_stat(lfs_t *lfs, const char *path, struct lfs_info *info) {
}
// fill out our info struct
info->type = lfsr_tag_filetype(tag);
LFS_ASSERT(name_size <= LFS_NAME_MAX);
memcpy(info->name, name, name_size);
info->name[name_size] = '\0';
// TODO size once we have actual files
return 0;
return lfsr_mdir_stat(lfs, &mdir, mdir.mid, -1, info);
}
int lfsr_dir_open(lfs_t *lfs, lfsr_dir_t *dir, const char *path) {
@@ -7313,13 +7401,13 @@ int lfsr_dir_open(lfs_t *lfs, lfsr_dir_t *dir, const char *path) {
}
// add to tracked mdirs
lfsr_mdir_addopened(lfs, LFS_TYPE_DIR, (lfsr_openedmdir_t*)dir);
lfsr_mdir_addopened(lfs, LFS_TYPE_DIR, &dir->m);
return 0;
}
int lfsr_dir_close(lfs_t *lfs, lfsr_dir_t *dir) {
// remove from tracked mdirs
lfsr_mdir_removeopened(lfs, LFS_TYPE_DIR, (lfsr_openedmdir_t*)dir);
lfsr_mdir_removeopened(lfs, LFS_TYPE_DIR, &dir->m);
return 0;
}
@@ -7348,42 +7436,14 @@ int lfsr_dir_read(lfs_t *lfs, lfsr_dir_t *dir, struct lfs_info *info) {
return err;
}
// lookup our name tag
lfsr_tag_t tag;
lfsr_data_t data;
err = lfsr_mdir_lookup(lfs, &dir->m.mdir,
dir->m.mdir.mid, LFSR_TAG_WIDE(NAME),
&tag, &data);
// fill out our info struct
//
// this will return LFS_ERR_NOENT if our dids mismatch
err = lfsr_mdir_stat(lfs, &dir->m.mdir, dir->m.mdir.mid, dir->did, info);
if (err) {
return err;
}
// get our did
lfsr_did_t did;
err = lfsr_data_readleb128(lfs, &data, (int32_t*)&did);
if (err) {
return err;
}
// did mismatch? we must be done
if (did != dir->did) {
return LFS_ERR_NOENT;
}
// get file name from the name entry
LFS_ASSERT(lfsr_data_size(&data) <= LFS_NAME_MAX);
lfs_ssize_t name_size = lfsr_data_read(lfs, &data,
info->name, LFS_NAME_MAX);
if (name_size < 0) {
return name_size;
}
info->name[name_size] = '\0';
// get file type from the tag
info->type = lfsr_tag_filetype(tag);
// TODO get size once we actually have regular files
// eagerly look up the next entry
err = lfsr_mtree_seek(lfs, &dir->m.mdir, 1);
if (err && err != LFS_ERR_NOENT) {
@@ -7456,74 +7516,300 @@ int lfsr_dir_rewind(lfs_t *lfs, lfsr_dir_t *dir) {
}
/// Prepare the filesystem for mutation ///
/// File operations ///
static int lfsr_fs_fixgrm(lfs_t *lfs) {
while (lfsr_grm_hasrm(&lfs->grm)) {
// find our mdir
lfsr_mdir_t mdir;
LFS_ASSERT(lfs->grm.rms[0] < lfs_smax32(
lfsr_mtree_weight(lfs),
lfsr_mleafweight(lfs)));
int err = lfsr_mtree_lookup(lfs, lfs->grm.rms[0], &mdir);
static bool lfsr_file_isreadable(uint32_t flags) {
return (flags & LFS_O_RDONLY) == LFS_O_RDONLY;
}
static bool lfsr_file_iswriteable(uint32_t flags) {
return (flags & LFS_O_WRONLY) == LFS_O_WRONLY;
}
int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file);
int lfsr_file_opencfg(lfs_t *lfs, lfsr_file_t *file,
const char *path, uint32_t flags,
const struct lfs_file_config *cfg) {
if (lfsr_file_iswriteable(flags)) {
// prepare our filesystem for writing
int err = lfsr_fs_preparemutation(lfs);
if (err) {
return err;
}
// mark grm as taken care of
lfsr_grm_t grm = lfs->grm;
lfsr_grm_poprm(&grm);
// make sure to adjust any remaining grms
if ((grm.rms[0] & lfsr_midbmask(lfs))
== (mdir.mid & lfsr_midbmask(lfs))
&& grm.rms[0] >= mdir.mid) {
LFS_ASSERT(grm.rms[0] != mdir.mid);
grm.rms[0] -= 1;
}
// remove the rid while also updating our grm
LFS_ASSERT((lfs->grm.rms[0] & lfsr_midrmask(lfs)) < mdir.u.m.weight);
err = lfsr_mdir_commit(lfs, &mdir, LFSR_ATTRS(
LFSR_ATTR(mdir.mid, RM, -1, NULL),
LFSR_ATTR(-1, GRM, 0, GRM(&grm))));
}
return 0;
}
// default inlined state
file->flags = flags;
file->cfg = cfg;
file->pos = 0;
file->inlined_pos = 0;
file->inlined = LFSR_DATA_NULL;
static int lfsr_fs_preparemutation(lfs_t *lfs) {
// checkpoint the allocator
lfs_alloc_ack(lfs);
// lookup our parent
lfsr_tag_t tag;
lfsr_did_t did;
const char *name;
lfs_size_t name_size;
int err = lfsr_mtree_pathlookup(lfs, path,
&file->m.mdir, &tag,
&did, &name, &name_size);
if (err && err != LFS_ERR_NOENT) {
return err;
}
// fix pending grms
if (lfsr_grm_hasrm(&lfs->grm)) {
if (lfsr_grm_count(&lfs->grm) == 2) {
LFS_DEBUG("Fixing pending grm "
"%"PRId32".%"PRId32" %"PRId32".%"PRId32,
lfs->grm.rms[0] >> lfs->mleaf_bits,
lfs->grm.rms[0] & lfsr_midrmask(lfs),
lfs->grm.rms[1] >> lfs->mleaf_bits,
lfs->grm.rms[1] & lfsr_midrmask(lfs));
} else if (lfsr_grm_count(&lfs->grm) == 1) {
LFS_DEBUG("Fixing pending grm %"PRId32".%"PRId32,
lfs->grm.rms[0] >> lfs->mleaf_bits,
lfs->grm.rms[0] & lfsr_midrmask(lfs));
// creating a new entry?
if (err == LFS_ERR_NOENT) {
if (!(flags & LFS_O_CREAT)) {
return LFS_ERR_NOENT;
}
LFS_ASSERT(lfsr_file_iswriteable(flags));
// check that name fits
if (name_size > lfs->name_limit) {
return LFS_ERR_NAMETOOLONG;
}
int err = lfsr_fs_fixgrm(lfs);
// create our entry
//
// note this risks creating a zero-length file if we lose power here,
// but it's the only way for us to save the file name.
//
// TODO or is it? ;)
err = lfsr_mdir_commit(lfs, &file->m.mdir, LFSR_ATTRS(
LFSR_ATTR(file->m.mdir.mid,
REG, +1, NAME(did, name, name_size))));
if (err) {
return err;
}
} else {
if (flags & LFS_O_EXCL) {
// oh, we really wanted to create a new entry
return LFS_ERR_EXIST;
}
// checkpoint the allocator again since our fixgrm completed some
// work
lfs_alloc_ack(lfs);
// wrong type?
if (tag != LFSR_TAG_REG) {
return LFS_ERR_ISDIR;
}
// if we're truncating don't bother to read any state, we're
// just going to truncate after all
if (!(flags & LFS_O_TRUNC)) {
// read the inline state
err = lfsr_mdir_lookup(lfs, &file->m.mdir,
file->m.mdir.mid, LFSR_TAG_INLINED,
NULL, &file->inlined);
if (err && err != LFS_ERR_NOENT) {
return err;
}
}
}
// TODO common function for this?
// figure out the total size
file->size = lfsr_data_size(&file->inlined);
// allocate buffer if necessary
if (file->cfg->buffer) {
file->buffer = file->cfg->buffer;
} else {
file->buffer = lfs_malloc(lfs->cfg->cache_size);
if (!file->buffer) {
return LFS_ERR_NOMEM;
}
}
file->buffer_pos = 0;
file->buffer_size = 0;
// add to tracked mdirs
lfsr_mdir_addopened(lfs, LFS_TYPE_REG, &file->m);
return 0;
}
// default file config
static const struct lfs_file_config lfsr_file_defaults = {0};
int lfsr_file_open(lfs_t *lfs, lfsr_file_t *file,
const char *path, uint32_t flags) {
return lfsr_file_opencfg(lfs, file, path, flags, &lfsr_file_defaults);
}
int lfsr_file_close(lfs_t *lfs, lfsr_file_t *file) {
int err = lfsr_file_sync(lfs, file);
// remove from tracked mdirs
lfsr_mdir_removeopened(lfs, LFS_TYPE_REG, &file->m);
// clean up memory
if (!file->cfg->buffer) {
lfs_free(file->buffer);
}
return err;
}
lfs_ssize_t lfsr_file_read(lfs_t *lfs, lfsr_file_t *file,
void *buffer, lfs_size_t size) {
LFS_ASSERT(lfsr_file_isreadable(file->flags));
LFS_ASSERT(size <= 0x7fffffff);
lfs_off_t pos = file->pos;
uint8_t *buffer_ = buffer;
while (size > 0) {
lfs_ssize_t d = size;
// is the data in our write buffer?
if (pos < file->buffer_pos + file->buffer_size) {
if (pos >= file->buffer_pos) {
d = lfs_min32(
size,
file->buffer_size - (pos - file->buffer_pos));
memcpy(buffer_, &file->buffer[pos - file->buffer_pos], d);
pos += d;
buffer_ += d;
size -= d;
continue;
}
// buffered data takes priority
d = lfs_min32(d, file->buffer_pos - pos);
}
// is the data inlined?
if (pos < file->inlined_pos + lfsr_data_size(&file->inlined)) {
if (pos >= file->inlined_pos) {
lfsr_data_t inlined = file->inlined;
lfsr_data_add(&inlined, pos - file->inlined_pos);
d = lfsr_data_read(lfs, &inlined, buffer_, d);
if (d < 0) {
return d;
}
pos += d;
buffer_ += d;
size -= d;
continue;
}
// inlined data takes priority
d = lfs_min32(d, file->inlined_pos - pos);
}
// TODO
// no more data?
break;
}
lfs_size_t read = pos - file->pos;
file->pos = pos;
return read;
}
lfs_ssize_t lfsr_file_write(lfs_t *lfs, lfsr_file_t *file,
const void *buffer, lfs_size_t size) {
LFS_ASSERT(lfsr_file_iswriteable(file->flags));
LFS_ASSERT(size <= 0x7fffffff);
lfs_off_t pos = file->pos;
const uint8_t *buffer_ = buffer;
int err;
while (size > 0) {
// try to fill our write buffer
if (pos >= file->buffer_pos
&& pos <= file->buffer_pos + file->buffer_size
&& pos < file->buffer_pos + lfs->cfg->cache_size) {
lfs_size_t d = lfs_min32(
size,
lfs->cfg->cache_size - (pos - file->buffer_pos));
memcpy(&file->buffer[pos - file->buffer_pos], buffer_, d);
file->buffer_size = lfs_max32(
file->buffer_size,
pos+d - file->buffer_pos);
pos += d;
buffer_ += d;
size -= d;
file->flags |= LFS_F_UNSYNCED;
continue;
}
// TODO
LFS_ASSERT(false);
// // flush our write buffer to make it useable, first condition can
// // no longer fail
// err = lfsr_file_flush(lfs, file);
// if (err) {
// return err;
// }
}
lfs_size_t written = pos - file->pos;
file->pos = pos;
file->size = lfs_max32(file->size, pos);
return written;
failed:;
file->flags |= LFS_F_ERRORED;
return err;
}
int lfsr_file_flush(lfs_t *lfs, lfsr_file_t *file) {
// TODO
return 0;
}
int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) {
if (file->flags & LFS_F_ERRORED) {
// it's not safe to do anything if our file errored
return 0;
}
// do nothing if our file has been removed
if (file->m.mdir.mid == -1) {
return 0;
}
// write out any data we need to
int err = lfsr_file_flush(lfs, file);
if (err) {
goto failed;
}
if (file->flags & LFS_F_UNSYNCED) {
// TODO
// TODO what if buffer_size > inlined_size?
LFS_ASSERT(file->buffer_pos == 0);
// commit our file's metadata
err = lfsr_mdir_commit(lfs, &file->m.mdir, LFSR_ATTRS(
LFSR_ATTR(file->m.mdir.mid, WIDE(RM), 0, NULL),
(file->buffer_size > 0
? LFSR_ATTR(file->m.mdir.mid,
WIDE(INLINED), 0, BUF(
file->buffer, file->buffer_size))
: LFSR_ATTR_NOOP)));
if (err) {
goto failed;
}
file->flags &= ~LFS_F_UNSYNCED;
}
return 0;
failed:;
file->flags |= LFS_F_ERRORED;
return err;
}
lfs_soff_t lfsr_file_size(lfs_t *lfs, lfsr_file_t *file) {
(void)lfs;
return file->size;
}