Made LFS_O_EXCL error if file is open but uncreated
One of the unexpected side-effects of lazy file creation is that
suddenly LFS_O_EXCL doesn't make sense.
The standard definition: "Fail if the file exists", is easy enough to
implement, but doesn't really match what the user expects.
The user expects one of these calls to fail:
lfsr_file_open(&lfs, &file_a, "file.txt",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfsr_file_open(&lfs, &file_b, "file.txt",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
But because we create files lazily (to prevent zero-length files after
powerloss), these both succeed.
---
I considered deferring the "file exists" check until we actually would
create the file, but while this _technically_ satisfies the
exclusitivity requirement, I decided against it as I think it just makes
the API way too confusing:
lfsr_file_open(&lfs, &file_a, "file.txt",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfsr_file_open(&lfs, &file_b, "file.txt",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfsr_file_close(&lfs, &file_a) => 0;
lfsr_file_close(&lfs, &file_b) => LFS_ERR_EXIST;
---
Instead, a simpler, more pragmatic approach: Fail if the file exists
_or_ if the file is open in a mode that will create the file:
lfsr_file_open(&lfs, &file_a, "file.txt",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfsr_file_open(&lfs, &file_b, "file.txt",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
This explicitly does _not_ error on zombie/desync files:
lfsr_file_open(&lfs, &file_a, "file.txt",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfsr_file_desync(&lfs, &file_a) => 0;
lfsr_file_open(&lfs, &file_b, "file.txt",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
And it does mean we aren't necessarily guaranteeing the file will be
created, but I think this does more-or-less what the user expects:
- open(a) -> desync(a) -> open(b) -> resync(a) is roughly equivalent to
opening a after creating b, which is perfectly fine with LFS_O_EXCL.
- open(a) -> open(b) (errors) -> desync(a) is one way to not actually
create the file, but is somewhat similar to removing the file after
creation.
If you're using desync files you should probably have a good
understanding of littlefs's sync model anyways.
And of course the user can always sync immediately after open to
guarantee file creation, while opting into the possibility of
zero-length files after powerloss.
Code changes:
code stack ctx
before: 38084 2624 752
after: 38128 (+0.1%) 2624 (+0.0%) 752 (+0.0%)
This commit is contained in:
@@ -7329,6 +7329,22 @@ static bool lfsr_omdir_ismidopen(lfs_t *lfs, lfsr_smid_t mid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// like lfsr_omdir_ismidopen but ignores zombies/desynced files
|
||||
static bool lfsr_omdir_ismidalive(lfs_t *lfs, lfsr_smid_t mid) {
|
||||
for (lfsr_omdir_t *o = lfs->omdirs; o; o = o->next) {
|
||||
// we really only care about regular open files here, all
|
||||
// others are either transient (dirs) or fake (orphans)
|
||||
if (lfsr_o_type(o->flags) == LFS_TYPE_REG
|
||||
&& o->mdir.mid == mid
|
||||
&& !lfsr_o_iszombie(o->flags)
|
||||
&& !lfsr_o_isdesync(o->flags)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// traversal invalidation things
|
||||
|
||||
// needed in lfsr_omdir_clobber
|
||||
@@ -10744,7 +10760,6 @@ int lfsr_rename(lfs_t *lfs, const char *old_path, const char *new_path) {
|
||||
if (err && !(err == LFS_ERR_NOENT && lfsr_path_islast(new_path))) {
|
||||
return err;
|
||||
}
|
||||
// already exists?
|
||||
bool exists = (err != LFS_ERR_NOENT);
|
||||
|
||||
// there are a few cases we need to watch out for
|
||||
@@ -11569,9 +11584,10 @@ int lfsr_file_opencfg(lfs_t *lfs, lfsr_file_t *file,
|
||||
if (err && !(err == LFS_ERR_NOENT && lfsr_path_islast(path))) {
|
||||
return err;
|
||||
}
|
||||
bool exists = err != LFS_ERR_NOENT;
|
||||
|
||||
// creating a new entry?
|
||||
if (err == LFS_ERR_NOENT || tag == LFSR_TAG_STICKYNOTE) {
|
||||
if (!exists || tag == LFSR_TAG_STICKYNOTE) {
|
||||
if (!lfsr_o_iscreat(flags)) {
|
||||
return LFS_ERR_NOENT;
|
||||
}
|
||||
@@ -11582,15 +11598,27 @@ int lfsr_file_opencfg(lfs_t *lfs, lfsr_file_t *file,
|
||||
return LFS_ERR_NOTDIR;
|
||||
}
|
||||
|
||||
// check that name fits
|
||||
lfs_size_t name_len = lfsr_path_namelen(path);
|
||||
if (name_len > lfs->name_limit) {
|
||||
return LFS_ERR_NAMETOOLONG;
|
||||
// if we're EXCL and we found a stickynote, check if the file
|
||||
// is open and not zombied/desynced
|
||||
//
|
||||
// we error here even though the file isn't created yet so
|
||||
// EXCL only lets one create through (ignoring desync+sync
|
||||
// shenanigans)
|
||||
if (exists
|
||||
&& lfsr_o_isexcl(flags)
|
||||
&& lfsr_omdir_ismidalive(lfs, file->o.o.mdir.mid)) {
|
||||
return LFS_ERR_EXIST;
|
||||
}
|
||||
|
||||
// create a stickynote entry if we don't have one, this reserves the
|
||||
// mid until first sync
|
||||
if (err == LFS_ERR_NOENT) {
|
||||
if (!exists) {
|
||||
// check that name fits
|
||||
lfs_size_t name_len = lfsr_path_namelen(path);
|
||||
if (name_len > lfs->name_limit) {
|
||||
return LFS_ERR_NAMETOOLONG;
|
||||
}
|
||||
|
||||
lfs_alloc_ckpoint(lfs);
|
||||
err = lfsr_mdir_commit(lfs, &file->o.o.mdir, LFSR_RATS(
|
||||
LFSR_RAT_NAME(
|
||||
|
||||
@@ -468,7 +468,7 @@ code = '''
|
||||
// but we should still recieve sync broadcasts on sync/close
|
||||
lfsr_file_t file__;
|
||||
lfsr_file_open(&lfs, &file__, "batman",
|
||||
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
||||
LFS_O_RDWR | LFS_O_CREAT) => 0;
|
||||
|
||||
// mkconsistent should have no effect
|
||||
if (MKCONSISTENT) {
|
||||
@@ -609,7 +609,7 @@ code = '''
|
||||
// open a second reference
|
||||
lfsr_file_t file__;
|
||||
lfsr_file_open(&lfs, &file__, "batman",
|
||||
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
||||
LFS_O_WRONLY | LFS_O_CREAT) => 0;
|
||||
|
||||
// mkconsistent should have no effect
|
||||
if (MKCONSISTENT) {
|
||||
@@ -814,7 +814,7 @@ code = '''
|
||||
// and a third for checking sync broadcasts
|
||||
lfsr_file_t file___;
|
||||
lfsr_file_open(&lfs, &file___, "batman",
|
||||
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
||||
LFS_O_RDWR | LFS_O_CREAT) => 0;
|
||||
|
||||
// mkconsistent should have no effect
|
||||
if (MKCONSISTENT) {
|
||||
@@ -1275,7 +1275,7 @@ code = '''
|
||||
// and a third for checking sync broadcasts
|
||||
lfsr_file_t file___;
|
||||
lfsr_file_open(&lfs, &file___, "batman",
|
||||
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
||||
LFS_O_RDWR | LFS_O_CREAT) => 0;
|
||||
|
||||
// mkconsistent should have no effect
|
||||
if (MKCONSISTENT) {
|
||||
|
||||
Reference in New Issue
Block a user