kv: Adopted LFS3_o_WRSET for better key-value API integration
This adds LFS3_o_WRSET as an internal-only 3rd file open mode (I knew
that missing open mode would come in handy) that has some _very_
interesting behavior:
- Do _not_ clear the configured file cache. The file cache is prefilled
with the file's data.
- If the file does _not_ exist and is small, create it immediately in
lfs3_file_open using the provided file cache.
- If the file _does_ exist or is not small, do nothing and open the file
normally. lfs3_file_close/sync can do the rest of the work in one
commit.
This makes it possible to implement one-commit lfs3_set on top of the
file APIs with minimal code impact:
- All of the metadata commit logic can be handled by lfs3_file_sync_, we
just call lfs3_file_sync_ with the found did+name in lfs3_file_opencfg
when WRSET.
- The invariant that lfs3_file_opencfg always reserves an mid remains
intact, since we go ahead and write the full file if necessary,
minimizing the impact on lfs3_file_opencfg's internals.
This claws back most of the code cost of the one-commit key-value API:
code stack ctx
before: 38232 2400 636
after: 37856 (-1.0%) 2416 (+0.7%) 636 (+0.0%)
before kv: 37352 2280 636
after kv: 37856 (+1.3%) 2416 (+6.0%) 636 (+0.0%)
---
I'm quite happy how this turned out. I was worried there for a bit the
key-value API was going to end up an ugly wart for the internals, but
with LFS3_o_WRSET this integrates quite nicely.
It also raises a really interesting question, should LFS3_o_WRSET be
exposed to users?
For now I'm going to play it safe and say no. While potentially useful,
it's still a pretty unintuitive API.
Another thing worth mentioning is that this does have a negative impact
on compile-time gc. Duplication adds code cost when viewing the system
as a whole, but tighter integration can backfire if the user never calls
half the APIs.
Oh well, compile-time opt-out is always an option in the future, and
users seem to care more about pre-linked measurements, probably because
it's an easier thing to find. Still, it's funny how measuring code can
have a negative impact on code. Something something Goodhart's law.
This commit is contained in:
@@ -6839,6 +6839,15 @@ static inline bool lfs3_o_iswronly(uint32_t flags) {
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline bool lfs3_o_iswrset(uint32_t flags) {
|
||||
(void)flags;
|
||||
#ifndef LFS3_RDONLY
|
||||
return (flags & LFS3_O_MODE) == LFS3_o_WRSET;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline bool lfs3_o_iscreat(uint32_t flags) {
|
||||
(void)flags;
|
||||
#ifndef LFS3_RDONLY
|
||||
@@ -11444,9 +11453,185 @@ static int lfs3_file_fetch(lfs3_t *lfs3, lfs3_file_t *file, uint32_t flags) {
|
||||
|
||||
// needed in lfs3_file_opencfg
|
||||
static void lfs3_file_close_(lfs3_t *lfs3, const lfs3_file_t *file);
|
||||
static int lfs3_file_sync_(lfs3_t *lfs3, lfs3_file_t *file,
|
||||
const lfs3_name_t *name);
|
||||
static int lfs3_file_ck(lfs3_t *lfs3, const lfs3_file_t *file,
|
||||
uint32_t flags);
|
||||
|
||||
int lfs3_file_opencfg_(lfs3_t *lfs3, lfs3_file_t *file,
|
||||
const char *path, uint32_t flags,
|
||||
const struct lfs3_file_config *cfg) {
|
||||
#ifndef LFS3_RDONLY
|
||||
if (!lfs3_o_isrdonly(flags)) {
|
||||
// prepare our filesystem for writing
|
||||
int err = lfs3_fs_mkconsistent(lfs3);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// setup file state
|
||||
lfs3_file_init(file,
|
||||
// mounted with LFS3_M_FLUSH/SYNC? implies LFS3_O_FLUSH/SYNC
|
||||
flags | (lfs3->flags & (LFS3_M_FLUSH | LFS3_M_SYNC)),
|
||||
cfg);
|
||||
|
||||
// allocate cache if necessary
|
||||
//
|
||||
// wrset is a special lfs3_set specific mode that passes data via
|
||||
// the file cache, so make sure not to clobber it
|
||||
if (lfs3_o_iswrset(file->b.o.flags)) {
|
||||
file->b.o.flags |= LFS3_o_UNFLUSH;
|
||||
file->cache.buffer = file->cfg->cache_buffer;
|
||||
file->cache.pos = 0;
|
||||
file->cache.size = file->cfg->cache_size;
|
||||
} else if (file->cfg->cache_buffer) {
|
||||
file->cache.buffer = file->cfg->cache_buffer;
|
||||
} else {
|
||||
file->cache.buffer = lfs3_malloc(lfs3_file_cachesize(lfs3, file));
|
||||
if (!file->cache.buffer) {
|
||||
return LFS3_ERR_NOMEM;
|
||||
}
|
||||
}
|
||||
|
||||
// lookup our parent
|
||||
lfs3_tag_t tag;
|
||||
lfs3_did_t did;
|
||||
int err = lfs3_mtree_pathlookup(lfs3, &path,
|
||||
&file->b.o.mdir, &tag, &did);
|
||||
if (err && !(err == LFS3_ERR_NOENT && lfs3_path_islast(path))) {
|
||||
goto failed;
|
||||
}
|
||||
bool exists = (err != LFS3_ERR_NOENT);
|
||||
|
||||
// creating a new entry?
|
||||
if (!exists || tag == LFS3_TAG_ORPHAN) {
|
||||
if (!lfs3_o_iscreat(file->b.o.flags)) {
|
||||
err = LFS3_ERR_NOENT;
|
||||
goto failed;
|
||||
}
|
||||
LFS3_ASSERT(!lfs3_o_isrdonly(file->b.o.flags));
|
||||
|
||||
#ifndef LFS3_RDONLY
|
||||
// we're a file, don't allow trailing slashes
|
||||
if (lfs3_path_isdir(path)) {
|
||||
err = LFS3_ERR_NOTDIR;
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// check that name fits
|
||||
lfs3_size_t name_len = lfs3_path_namelen(path);
|
||||
if (name_len > lfs3->name_limit) {
|
||||
err = LFS3_ERR_NAMETOOLONG;
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// if stickynote, mark as uncreated + unsync
|
||||
if (exists) {
|
||||
file->b.o.flags |= LFS3_o_UNCREAT | LFS3_o_UNSYNC;
|
||||
|
||||
// otherwise we need to create an entry
|
||||
} else {
|
||||
// small file? can we atomically commit everything? currently
|
||||
// this is only possible via lfs3_set
|
||||
if (lfs3_o_iswrset(file->b.o.flags)
|
||||
&& file->cache.size <= lfs3->cfg->inline_size
|
||||
&& file->cache.size <= lfs3->cfg->fragment_size
|
||||
&& file->cache.size < lfs3->cfg->crystal_thresh) {
|
||||
// we need to mark as unsync for sync to do anything
|
||||
file->b.o.flags |= LFS3_o_UNSYNC;
|
||||
|
||||
err = lfs3_file_sync_(lfs3, file, &(lfs3_name_t){
|
||||
.did=did,
|
||||
.name=path,
|
||||
.name_len=name_len});
|
||||
if (err) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
} else {
|
||||
// create a stickynote entry if we don't have one, this
|
||||
// reserves the mid until first sync
|
||||
lfs3_alloc_ckpoint(lfs3);
|
||||
err = lfs3_mdir_commit(lfs3, &file->b.o.mdir, LFS3_RATTRS(
|
||||
LFS3_RATTR_NAME(
|
||||
LFS3_TAG_STICKYNOTE, +1,
|
||||
did, path, name_len)));
|
||||
if (err) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// mark as uncreated + unsync
|
||||
file->b.o.flags |= LFS3_o_UNCREAT | LFS3_o_UNSYNC;
|
||||
}
|
||||
|
||||
// TODO move this to lfs3_mdir_commit
|
||||
// update dir positions
|
||||
for (lfs3_omdir_t *o = lfs3->omdirs; o; o = o->next) {
|
||||
if (lfs3_o_type(o->flags) == LFS3_TYPE_DIR
|
||||
&& ((lfs3_dir_t*)o)->did == did
|
||||
&& o->mdir.mid >= file->b.o.mdir.mid) {
|
||||
((lfs3_dir_t*)o)->pos += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
// wanted to create a new entry?
|
||||
if (lfs3_o_isexcl(file->b.o.flags)) {
|
||||
err = LFS3_ERR_EXIST;
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// wrong type?
|
||||
if (tag == LFS3_TAG_DIR) {
|
||||
err = LFS3_ERR_ISDIR;
|
||||
goto failed;
|
||||
}
|
||||
if (tag == LFS3_TAG_UNKNOWN) {
|
||||
err = LFS3_ERR_NOTSUP;
|
||||
goto failed;
|
||||
}
|
||||
|
||||
#ifndef LFS3_RDONLY
|
||||
// if stickynote, mark as uncreated + unsync
|
||||
if (tag == LFS3_TAG_STICKYNOTE) {
|
||||
file->b.o.flags |= LFS3_o_UNCREAT | LFS3_o_UNSYNC;
|
||||
}
|
||||
|
||||
// if truncating, mark as unsync
|
||||
if (lfs3_o_istrunc(file->b.o.flags)) {
|
||||
file->b.o.flags |= LFS3_o_UNSYNC;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// fetch the file struct and custom attrs
|
||||
err = lfs3_file_fetch(lfs3, file, file->b.o.flags);
|
||||
if (err) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// check metadata/data for errors?
|
||||
if (lfs3_t_isckmeta(file->b.o.flags)
|
||||
|| lfs3_t_isckdata(file->b.o.flags)) {
|
||||
err = lfs3_file_ck(lfs3, file, file->b.o.flags);
|
||||
if (err) {
|
||||
goto failed;
|
||||
}
|
||||
}
|
||||
|
||||
// add to tracked mdirs
|
||||
lfs3_omdir_open(lfs3, &file->b.o);
|
||||
return 0;
|
||||
|
||||
failed:;
|
||||
// clean up resources
|
||||
lfs3_file_close_(lfs3, file);
|
||||
return err;
|
||||
}
|
||||
|
||||
int lfs3_file_opencfg(lfs3_t *lfs3, lfs3_file_t *file,
|
||||
const char *path, uint32_t flags,
|
||||
const struct lfs3_file_config *cfg) {
|
||||
@@ -11482,136 +11667,8 @@ int lfs3_file_opencfg(lfs3_t *lfs3, lfs3_file_t *file,
|
||||
|| !lfs3_o_isexcl(cfg->attrs[i].flags));
|
||||
}
|
||||
|
||||
if (!lfs3_o_isrdonly(flags)) {
|
||||
// prepare our filesystem for writing
|
||||
#ifndef LFS3_RDONLY
|
||||
int err = lfs3_fs_mkconsistent(lfs3);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// setup file state
|
||||
lfs3_file_init(file,
|
||||
// mounted with LFS3_M_FLUSH/SYNC? implies LFS3_O_FLUSH/SYNC
|
||||
flags | (lfs3->flags & (LFS3_M_FLUSH | LFS3_M_SYNC)),
|
||||
return lfs3_file_opencfg_(lfs3, file, path, flags,
|
||||
cfg);
|
||||
|
||||
// lookup our parent
|
||||
lfs3_tag_t tag;
|
||||
lfs3_did_t did;
|
||||
int err = lfs3_mtree_pathlookup(lfs3, &path,
|
||||
&file->b.o.mdir, &tag, &did);
|
||||
if (err && !(err == LFS3_ERR_NOENT && lfs3_path_islast(path))) {
|
||||
return err;
|
||||
}
|
||||
bool exists = (err != LFS3_ERR_NOENT);
|
||||
|
||||
// creating a new entry?
|
||||
if (!exists || tag == LFS3_TAG_ORPHAN) {
|
||||
if (!lfs3_o_iscreat(file->b.o.flags)) {
|
||||
return LFS3_ERR_NOENT;
|
||||
}
|
||||
LFS3_ASSERT(!lfs3_o_isrdonly(file->b.o.flags));
|
||||
|
||||
#ifndef LFS3_RDONLY
|
||||
// we're a file, don't allow trailing slashes
|
||||
if (lfs3_path_isdir(path)) {
|
||||
return LFS3_ERR_NOTDIR;
|
||||
}
|
||||
|
||||
// check that name fits
|
||||
lfs3_size_t name_len = lfs3_path_namelen(path);
|
||||
if (name_len > lfs3->name_limit) {
|
||||
return LFS3_ERR_NAMETOOLONG;
|
||||
}
|
||||
|
||||
if (!exists) {
|
||||
// create a stickynote entry if we don't have one, this
|
||||
// reserves the mid until first sync
|
||||
lfs3_alloc_ckpoint(lfs3);
|
||||
err = lfs3_mdir_commit(lfs3, &file->b.o.mdir, LFS3_RATTRS(
|
||||
LFS3_RATTR_NAME(
|
||||
LFS3_TAG_STICKYNOTE, +1,
|
||||
did, path, name_len)));
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// update dir positions
|
||||
for (lfs3_omdir_t *o = lfs3->omdirs; o; o = o->next) {
|
||||
if (lfs3_o_type(o->flags) == LFS3_TYPE_DIR
|
||||
&& ((lfs3_dir_t*)o)->did == did
|
||||
&& o->mdir.mid >= file->b.o.mdir.mid) {
|
||||
((lfs3_dir_t*)o)->pos += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// mark as uncreated + unsync
|
||||
file->b.o.flags |= LFS3_o_UNCREAT | LFS3_o_UNSYNC;
|
||||
#endif
|
||||
} else {
|
||||
// wanted to create a new entry?
|
||||
if (lfs3_o_isexcl(file->b.o.flags)) {
|
||||
return LFS3_ERR_EXIST;
|
||||
}
|
||||
|
||||
// wrong type?
|
||||
if (tag == LFS3_TAG_DIR) {
|
||||
return LFS3_ERR_ISDIR;
|
||||
}
|
||||
if (tag == LFS3_TAG_UNKNOWN) {
|
||||
return LFS3_ERR_NOTSUP;
|
||||
}
|
||||
|
||||
#ifndef LFS3_RDONLY
|
||||
// if stickynote, mark as uncreated + unsync
|
||||
if (tag == LFS3_TAG_STICKYNOTE) {
|
||||
file->b.o.flags |= LFS3_o_UNCREAT | LFS3_o_UNSYNC;
|
||||
}
|
||||
|
||||
// if truncating, mark as unsync
|
||||
if (lfs3_o_istrunc(file->b.o.flags)) {
|
||||
file->b.o.flags |= LFS3_o_UNSYNC;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// allocate cache if necessary
|
||||
if (file->cfg->cache_buffer) {
|
||||
file->cache.buffer = file->cfg->cache_buffer;
|
||||
} else {
|
||||
file->cache.buffer = lfs3_malloc(lfs3_file_cachesize(lfs3, file));
|
||||
if (!file->cache.buffer) {
|
||||
return LFS3_ERR_NOMEM;
|
||||
}
|
||||
}
|
||||
|
||||
// fetch the file struct and custom attrs
|
||||
err = lfs3_file_fetch(lfs3, file, file->b.o.flags);
|
||||
if (err) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// check metadata/data for errors?
|
||||
if (lfs3_t_isckmeta(file->b.o.flags)
|
||||
|| lfs3_t_isckdata(file->b.o.flags)) {
|
||||
err = lfs3_file_ck(lfs3, file, file->b.o.flags);
|
||||
if (err) {
|
||||
goto failed;
|
||||
}
|
||||
}
|
||||
|
||||
// add to tracked mdirs
|
||||
lfs3_omdir_open(lfs3, &file->b.o);
|
||||
return 0;
|
||||
|
||||
failed:;
|
||||
// clean up resources
|
||||
lfs3_file_close_(lfs3, file);
|
||||
return err;
|
||||
}
|
||||
|
||||
// default file config
|
||||
@@ -13145,7 +13202,8 @@ failed:;
|
||||
// this LFS3_NOINLINE is to force lfs3_file_sync_ off the stack hot-path
|
||||
#ifndef LFS3_RDONLY
|
||||
LFS3_NOINLINE
|
||||
static int lfs3_file_sync_(lfs3_t *lfs3, lfs3_file_t *file) {
|
||||
static int lfs3_file_sync_(lfs3_t *lfs3, lfs3_file_t *file,
|
||||
const lfs3_name_t *name) {
|
||||
// build a commit of any pending file metadata
|
||||
lfs3_rattr_t rattrs[4];
|
||||
lfs3_size_t rattr_count = 0;
|
||||
@@ -13154,61 +13212,76 @@ static int lfs3_file_sync_(lfs3_t *lfs3, lfs3_file_t *file) {
|
||||
lfs3_size_t shrub_rattr_count = 0;
|
||||
lfs3_shrubcommit_t shrub_commit;
|
||||
|
||||
// not created yet? need to convert to normal file
|
||||
if (lfs3_o_isuncreat(file->b.o.flags)) {
|
||||
// uncreated files must be unsynced
|
||||
LFS3_ASSERT(lfs3_o_isunsync(file->b.o.flags));
|
||||
// uncreated files must be unsync
|
||||
LFS3_ASSERT(!lfs3_o_isuncreat(file->b.o.flags)
|
||||
|| lfs3_o_isunsync(file->b.o.flags));
|
||||
// small unflushed files must be unsync
|
||||
LFS3_ASSERT(!lfs3_o_isunflush(file->b.o.flags)
|
||||
|| lfs3_o_isunsync(file->b.o.flags));
|
||||
|
||||
int err = lfs3_rbyd_lookup(lfs3, &file->b.o.mdir.rbyd,
|
||||
lfs3_mrid(lfs3, file->b.o.mdir.mid), LFS3_TAG_STICKYNOTE,
|
||||
NULL, &name_data);
|
||||
if (err) {
|
||||
// orphan flag but no stickynote tag?
|
||||
LFS3_ASSERT(err != LFS3_ERR_NOENT);
|
||||
return err;
|
||||
}
|
||||
|
||||
rattrs[rattr_count++] = LFS3_RATTR_DATA(
|
||||
LFS3_TAG_MASK8 | LFS3_TAG_REG, 0,
|
||||
&name_data);
|
||||
}
|
||||
|
||||
// pending small file flush?
|
||||
if (lfs3_o_isunflush(file->b.o.flags)) {
|
||||
// this only works if the file is entirely in our cache
|
||||
LFS3_ASSERT(file->cache.pos == 0);
|
||||
LFS3_ASSERT(file->cache.size == lfs3_file_size_(file));
|
||||
// bshrub should be discarded here
|
||||
LFS3_ASSERT(lfs3_file_weight_(file) == 0);
|
||||
|
||||
// build a small shrub commit
|
||||
if (file->cache.size > 0) {
|
||||
shrub_rattrs[shrub_rattr_count++] = LFS3_RATTR_DATA(
|
||||
LFS3_TAG_DATA, +file->cache.size,
|
||||
(const lfs3_data_t*)&file->cache);
|
||||
|
||||
LFS3_ASSERT(shrub_rattr_count
|
||||
<= sizeof(shrub_rattrs)/sizeof(lfs3_rattr_t));
|
||||
shrub_commit.bshrub = &file->b;
|
||||
shrub_commit.rid = 0;
|
||||
shrub_commit.rattrs = shrub_rattrs;
|
||||
shrub_commit.rattr_count = shrub_rattr_count;
|
||||
rattrs[rattr_count++] = LFS3_RATTR_SHRUBCOMMIT(&shrub_commit);
|
||||
}
|
||||
}
|
||||
|
||||
// pending file changes?
|
||||
// pending metadata changes?
|
||||
if (lfs3_o_isunsync(file->b.o.flags)) {
|
||||
// make sure data is on-disk before committing metadata
|
||||
int err = lfs3_bd_sync(lfs3);
|
||||
if (err) {
|
||||
return err;
|
||||
// explicit name?
|
||||
if (name) {
|
||||
rattrs[rattr_count++] = LFS3_RATTR_NAME_(
|
||||
LFS3_TAG_REG, +1,
|
||||
name);
|
||||
|
||||
// not created yet? need to convert to normal file
|
||||
} else if (lfs3_o_isuncreat(file->b.o.flags)) {
|
||||
// convert stickynote -> reg file
|
||||
int err = lfs3_rbyd_lookup(lfs3, &file->b.o.mdir.rbyd,
|
||||
lfs3_mrid(lfs3, file->b.o.mdir.mid), LFS3_TAG_STICKYNOTE,
|
||||
NULL, &name_data);
|
||||
if (err) {
|
||||
// orphan flag but no stickynote tag?
|
||||
LFS3_ASSERT(err != LFS3_ERR_NOENT);
|
||||
return err;
|
||||
}
|
||||
|
||||
rattrs[rattr_count++] = LFS3_RATTR_DATA(
|
||||
LFS3_TAG_MASK8 | LFS3_TAG_REG, 0,
|
||||
&name_data);
|
||||
}
|
||||
|
||||
// pending small file flush?
|
||||
if (lfs3_o_isunflush(file->b.o.flags)) {
|
||||
// this only works if the file is entirely in our cache
|
||||
LFS3_ASSERT(file->cache.pos == 0);
|
||||
LFS3_ASSERT(file->cache.size == lfs3_file_size_(file));
|
||||
// bshrub should be discarded here
|
||||
LFS3_ASSERT(lfs3_file_weight_(file) == 0);
|
||||
|
||||
// build a small shrub commit
|
||||
if (file->cache.size > 0) {
|
||||
shrub_rattrs[shrub_rattr_count++] = LFS3_RATTR_DATA(
|
||||
LFS3_TAG_DATA, +file->cache.size,
|
||||
(const lfs3_data_t*)&file->cache);
|
||||
|
||||
LFS3_ASSERT(shrub_rattr_count
|
||||
<= sizeof(shrub_rattrs)/sizeof(lfs3_rattr_t));
|
||||
shrub_commit.bshrub = &file->b;
|
||||
shrub_commit.rid = 0;
|
||||
shrub_commit.rattrs = shrub_rattrs;
|
||||
shrub_commit.rattr_count = shrub_rattr_count;
|
||||
rattrs[rattr_count++] = LFS3_RATTR_SHRUBCOMMIT(&shrub_commit);
|
||||
}
|
||||
}
|
||||
|
||||
// zero size files should have no bshrub/btree
|
||||
LFS3_ASSERT(lfs3_file_size_(file) > 0
|
||||
|| lfs3_bshrub_isbnull(&file->b));
|
||||
|
||||
// TODO is all this logic optimal for zero-length files?
|
||||
// make sure data is on-disk before committing metadata
|
||||
if (!lfs3_bshrub_isbnull(&file->b)
|
||||
&& !lfs3_o_isunflush(file->b.o.flags)) {
|
||||
int err = lfs3_bd_sync(lfs3);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
// no bshrub/btree?
|
||||
if (lfs3_bshrub_isbnull(&file->b)
|
||||
&& !(lfs3_o_isunflush(file->b.o.flags)
|
||||
@@ -13426,7 +13499,7 @@ int lfs3_file_sync(lfs3_t *lfs3, lfs3_file_t *file) {
|
||||
// the use of a second function here is mainly to isolate the
|
||||
// stack costs of lfs3_file_flush and lfs3_file_sync_
|
||||
//
|
||||
err = lfs3_file_sync_(lfs3, file);
|
||||
err = lfs3_file_sync_(lfs3, file, NULL);
|
||||
if (err) {
|
||||
goto failed;
|
||||
}
|
||||
@@ -13916,153 +13989,32 @@ lfs3_ssize_t lfs3_size(lfs3_t *lfs3, const char *path) {
|
||||
return size_;
|
||||
}
|
||||
|
||||
#ifndef LFS3_RDONLY
|
||||
LFS3_NOINLINE
|
||||
static int lfs3_file_openset(lfs3_t *lfs3, lfs3_file_t *file,
|
||||
const char *path,
|
||||
const void *buffer, lfs3_size_t size) {
|
||||
// prepare our filesystem for writing
|
||||
int err = lfs3_fs_mkconsistent(lfs3);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// setup file state
|
||||
lfs3_file_init(file, LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_TRUNC,
|
||||
&lfs3_file_kvconfig);
|
||||
|
||||
// lookup our parent
|
||||
lfs3_tag_t tag;
|
||||
lfs3_did_t did;
|
||||
err = lfs3_mtree_pathlookup(lfs3, &path,
|
||||
&file->b.o.mdir, &tag, &did);
|
||||
if (err && !(err == LFS3_ERR_NOENT && lfs3_path_islast(path))) {
|
||||
return err;
|
||||
}
|
||||
bool exists = (err != LFS3_ERR_NOENT);
|
||||
|
||||
// creating a new entry?
|
||||
if (!exists || tag == LFS3_TAG_ORPHAN) {
|
||||
// we're a file, don't allow trailing slashes
|
||||
if (lfs3_path_isdir(path)) {
|
||||
return LFS3_ERR_NOTDIR;
|
||||
}
|
||||
|
||||
// check that name fits
|
||||
lfs3_size_t name_len = lfs3_path_namelen(path);
|
||||
if (name_len > lfs3->name_limit) {
|
||||
return LFS3_ERR_NAMETOOLONG;
|
||||
}
|
||||
|
||||
// mark as uncreated + unsync
|
||||
file->b.o.flags |= LFS3_o_UNCREAT | LFS3_o_UNSYNC;
|
||||
|
||||
if (!exists) {
|
||||
// small file? can we atomically commit everything? currently
|
||||
// this is only possible via lfs3_set
|
||||
if (size <= lfs3->cfg->inline_size
|
||||
&& size <= lfs3->cfg->fragment_size
|
||||
&& size < lfs3->cfg->crystal_thresh) {
|
||||
// TODO build commit?
|
||||
// TODO test size=0?
|
||||
// commit create + flush atomically
|
||||
lfs3_alloc_ckpoint(lfs3);
|
||||
err = lfs3_mdir_commit(lfs3, &file->b.o.mdir, LFS3_RATTRS(
|
||||
LFS3_RATTR_NAME(
|
||||
LFS3_TAG_REG, +1,
|
||||
did, path, name_len),
|
||||
(size > 0)
|
||||
? LFS3_RATTR_SHRUBCOMMIT(
|
||||
(&(lfs3_shrubcommit_t){
|
||||
.bshrub=&file->b,
|
||||
.rid=0,
|
||||
.rattrs=&LFS3_RATTR_DATA(
|
||||
LFS3_TAG_DATA, +size,
|
||||
&LFS3_DATA_BUF(buffer, size)),
|
||||
.rattr_count=1}))
|
||||
: LFS3_RATTR_NOOP(),
|
||||
(size > 0)
|
||||
? LFS3_RATTR_SHRUB(
|
||||
LFS3_TAG_BSHRUB, 0,
|
||||
&file->b.shrub_)
|
||||
: LFS3_RATTR_NOOP()));
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// TODO better way to structure this?
|
||||
// clear the uncreated + unsync flags
|
||||
file->b.o.flags &= ~LFS3_o_UNCREAT & ~LFS3_o_UNSYNC;
|
||||
|
||||
} else {
|
||||
// create a stickynote entry if we don't have one, this
|
||||
// reserves the mid until first sync
|
||||
lfs3_alloc_ckpoint(lfs3);
|
||||
err = lfs3_mdir_commit(lfs3, &file->b.o.mdir, LFS3_RATTRS(
|
||||
LFS3_RATTR_NAME(
|
||||
LFS3_TAG_STICKYNOTE, +1,
|
||||
did, path, name_len)));
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO move this to lfs3_mdir_commit
|
||||
// update dir positions
|
||||
for (lfs3_omdir_t *o = lfs3->omdirs; o; o = o->next) {
|
||||
if (lfs3_o_type(o->flags) == LFS3_TYPE_DIR
|
||||
&& ((lfs3_dir_t*)o)->did == did
|
||||
&& o->mdir.mid >= file->b.o.mdir.mid) {
|
||||
((lfs3_dir_t*)o)->pos += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// wrong type?
|
||||
if (tag == LFS3_TAG_DIR) {
|
||||
return LFS3_ERR_ISDIR;
|
||||
}
|
||||
if (tag == LFS3_TAG_UNKNOWN) {
|
||||
return LFS3_ERR_NOTSUP;
|
||||
}
|
||||
|
||||
// if stickynote, mark as uncreated + unsync
|
||||
if (tag == LFS3_TAG_STICKYNOTE) {
|
||||
file->b.o.flags |= LFS3_o_UNCREAT | LFS3_o_UNSYNC;
|
||||
}
|
||||
|
||||
// if truncating, mark as unsync
|
||||
file->b.o.flags |= LFS3_o_UNSYNC;
|
||||
}
|
||||
|
||||
// add to tracked mdirs
|
||||
lfs3_omdir_open(lfs3, &file->b.o);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef LFS3_RDONLY
|
||||
int lfs3_set(lfs3_t *lfs3, const char *path,
|
||||
const void *buffer, lfs3_size_t size) {
|
||||
// LFS3_o_WRSET is a special mode specifically to make lfs3_set work
|
||||
// atomically when possible
|
||||
//
|
||||
// - if we need to reserve the mid _and_ we're small, everything is
|
||||
// committed/broadcasted in lfs3_file_opencfg
|
||||
//
|
||||
// - otherwise (exists? stickynote?), we flush/sync/broadcast
|
||||
// normally in lfs3_file_close, lfs3_file_sync has its own logic
|
||||
// to try to commit small files atomically
|
||||
//
|
||||
struct lfs3_file_config cfg = {
|
||||
.cache_buffer = (buffer) ? (uint8_t*)buffer : (uint8_t*)true,
|
||||
.cache_size = size,
|
||||
};
|
||||
lfs3_file_t file;
|
||||
int err = lfs3_file_openset(lfs3, &file, path,
|
||||
buffer, size);
|
||||
int err = lfs3_file_opencfg_(lfs3, &file, path,
|
||||
LFS3_o_WRSET | LFS3_O_CREAT | LFS3_O_TRUNC,
|
||||
&cfg);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// if not small, pretend the buffer is our cache
|
||||
if (lfs3_o_isunsync(file.b.o.flags)) {
|
||||
file.b.o.flags |= LFS3_o_UNFLUSH;
|
||||
file.cache.buffer = (uint8_t*)buffer;
|
||||
file.cache.pos = 0;
|
||||
file.cache.size = size;
|
||||
}
|
||||
|
||||
// let close do all the remaining work
|
||||
//
|
||||
// this includes grafting our cache into the bshrub/btree, and
|
||||
// broadcasting the changes to any open files
|
||||
// let close do any remaining work
|
||||
return lfs3_file_close(lfs3, &file);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -139,6 +139,7 @@ enum lfs3_type {
|
||||
#define LFS3_O_CKDATA 0x00002000 // Check metadata + data checksums
|
||||
|
||||
// internally used flags, don't use these
|
||||
#define LFS3_o_WRSET 3 // Open a file as an atomic write
|
||||
#define LFS3_o_TYPE 0xf0000000 // The file's type
|
||||
#define LFS3_o_ZOMBIE 0x08000000 // File has been removed
|
||||
#define LFS3_o_UNCREAT 0x02000000 // File does not exist yet
|
||||
|
||||
+1
-1
@@ -3310,7 +3310,7 @@ class Lfs3:
|
||||
return 'btree %s' % self.bshrub.addr()
|
||||
# btree?
|
||||
else:
|
||||
return str(self.struct)
|
||||
return self.struct.repr()
|
||||
else:
|
||||
return ''
|
||||
|
||||
|
||||
@@ -3340,7 +3340,7 @@ class Lfs3:
|
||||
return 'btree %s' % self.bshrub.addr()
|
||||
# btree?
|
||||
else:
|
||||
return str(self.struct)
|
||||
return self.struct.repr()
|
||||
else:
|
||||
return ''
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ FLAGS = [
|
||||
('O', 'CKMETA', 0x00001000, "Check metadata checksums" ),
|
||||
('O', 'CKDATA', 0x00002000, "Check metadata + data checksums" ),
|
||||
|
||||
('o', 'WRSET', 3, "Open a file as an atomic write" ),
|
||||
('o', 'TYPE', 0xf0000000, "The file's type" ),
|
||||
('^', 'REG', 0x10000000, "Type = regular-file" ),
|
||||
('^', 'DIR', 0x20000000, "Type = directory" ),
|
||||
|
||||
+1
-1
@@ -3267,7 +3267,7 @@ class Lfs3:
|
||||
return 'btree %s' % self.bshrub.addr()
|
||||
# btree?
|
||||
else:
|
||||
return str(self.struct)
|
||||
return self.struct.repr()
|
||||
else:
|
||||
return ''
|
||||
|
||||
|
||||
Reference in New Issue
Block a user