Added useful handling of LFS_M_RDONLY

It was a bit tricky to figure out what this should look like.
Traditionally, filesystems tend to fallback to readonly if they detect
unsupported wcompat (ro_compat) flags or similar config mismatch.

We could do something similar in littlefs, but since we default to
asserting on writes to readonly objects for smaller code size, this
would be really weird and hard to use from a users perspective...

Instead, lfsr_mount returns LFS_ERR_NOTSUP on encountering wcompat-
mismatch in RDWR mode, but _not_ RDONLY mode. This allows the common
rdonly-fallback pattern to be implemented on the user's side of things,
similar to the common format-fallback pattern:

  int err = lfsr_mount(&lfs, LFS_M_RDWR, &cfg);
  if (err && err != LFS_ERR_NOTSUP) {
      return err;
  }
  if (err == LFS_ERR_NOTSUP) {
      err = lfsr_mount(&lfs, LFS_M_RDONLY, &cfg);
      if (err) {
          return err;
      }
  }

Note that lfsr_mount may still return LFS_ERR_NOTSUP if it encounters
rcompat-flags, even with RDONLY. Detecting this state will likely need
two lfsr_mount calls with the current API, but I don't think that will
be a big deal.

The main benefit of this scheme is that it is quite cheap thanks to
pushing the fallback logic on the user:

           code          stack
  before: 36356           2664
  after:  36396 (+0.1%)   2664 (+0.0%)

One missing puzzle piece here is how do you upgrade the filesystem? But I
think the lesson from the on-disk v2.0 -> v2.1 version bump is that this
should really be an explicit function (lfsr_fs_upgrade?). If explicit
and stand-alone, like lfsr_format, we shouldn't need a weird pseudo-
rdonly mode at all.
This commit is contained in:
Christopher Haster
2024-07-27 00:11:36 -05:00
parent 36eabb1c68
commit 0ab0406d53
2 changed files with 114 additions and 54 deletions
+87 -54
View File
@@ -9926,11 +9926,12 @@ int lfsr_file_opencfg(lfs_t *lfs, lfsr_file_t *file,
| LFS_O_FLUSH
| LFS_O_SYNC
| LFS_O_DESYNC)) == 0);
// writeable files require a writeable filesystem
LFS_ASSERT(!lfsr_m_isrdonly(lfs->flags) || lfsr_o_isrdonly(flags));
// these flags require a writable file
LFS_ASSERT(!lfsr_o_isrdonly(flags) || !lfsr_o_iscreat(flags));
LFS_ASSERT(!lfsr_o_isrdonly(flags) || !lfsr_o_isexcl(flags));
LFS_ASSERT(!lfsr_o_isrdonly(flags) || !lfsr_o_istrunc(flags));
LFS_ASSERT(!lfsr_o_isrdonly(flags) || !lfsr_o_isappend(flags));
if (!lfsr_o_isrdonly(flags)) {
// prepare our filesystem for writing
@@ -11534,6 +11535,8 @@ lfs_soff_t lfsr_file_size(lfs_t *lfs, lfsr_file_t *file) {
int lfsr_file_truncate(lfs_t *lfs, lfsr_file_t *file, lfs_off_t size_) {
LFS_ASSERT(lfsr_omdir_isopen(lfs, &file->o.o));
// can't write to readonly files
LFS_ASSERT(!lfsr_o_isrdonly(file->o.o.flags));
// exceeds our file limit?
if (size_ > lfs->file_limit) {
@@ -11640,6 +11643,8 @@ failed:;
int lfsr_file_fruncate(lfs_t *lfs, lfsr_file_t *file, lfs_off_t size_) {
LFS_ASSERT(lfsr_omdir_isopen(lfs, &file->o.o));
// can't write to readonly files
LFS_ASSERT(!lfsr_o_isrdonly(file->o.o.flags));
// exceeds our file limit?
if (size_ > lfs->file_limit) {
@@ -12275,16 +12280,14 @@ static int lfsr_mountmroot(lfs_t *lfs, const lfsr_mdir_t *mroot) {
lfsr_data_t data;
int err = lfsr_mdir_lookup(lfs, mroot, LFSR_TAG_VERSION,
&data);
if (err) {
if (err == LFS_ERR_NOENT) {
LFS_ERROR("No littlefs version found");
return LFS_ERR_CORRUPT;
}
if (err && err != LFS_ERR_NOENT) {
return err;
}
lfs_ssize_t d = lfsr_data_read(lfs, &data, version, 2);
if (d < 0) {
return err;
if (err != LFS_ERR_NOENT) {
lfs_ssize_t d = lfsr_data_read(lfs, &data, version, 2);
if (d < 0) {
return err;
}
}
if (version[0] != LFS_DISK_VERSION_MAJOR
@@ -12336,13 +12339,15 @@ static int lfsr_mountmroot(lfs_t *lfs, const lfsr_mdir_t *mroot) {
}
}
// TODO switch to readonly?
if (lfsr_wcompat_isincompat(wcompat)) {
LFS_ERROR("Incompatible wcompat flags 0x%0"PRIx16
LFS_WARN("Incompatible wcompat flags 0x%0"PRIx16
" (!= 0x%0"PRIx16")",
wcompat,
LFSR_WCOMPAT_COMPAT);
return LFS_ERR_NOTSUP;
// we can continue if rdonly
if (!lfsr_m_isrdonly(lfs->flags)) {
return LFS_ERR_NOTSUP;
}
}
// we don't bother to check for any ocompatflags, we would just
@@ -12542,37 +12547,40 @@ static int lfsr_mountinited(lfs_t *lfs) {
return err;
}
// check for any orphaned files
for (lfs_size_t rid = 0;
rid < mdir->rbyd.weight;
rid++) {
lfsr_tag_t tag;
err = lfsr_rbyd_sublookup(lfs, &mdir->rbyd, rid, LFSR_TAG_NAME,
&tag, NULL);
if (err) {
LFS_ASSERT(err != LFS_ERR_NOENT);
return err;
}
// name 0 should be reserved
LFS_ASSERT(tag != (LFSR_TAG_NAME + 0));
// check for any orphaned files, note we only need this if
// filesystem will be writable
if (!lfsr_m_isrdonly(lfs->flags)) {
for (lfs_size_t rid = 0;
rid < mdir->rbyd.weight;
rid++) {
lfsr_tag_t tag;
err = lfsr_rbyd_sublookup(lfs, &mdir->rbyd,
rid, LFSR_TAG_NAME,
&tag, NULL);
if (err) {
LFS_ASSERT(err != LFS_ERR_NOENT);
return err;
}
// name 0 should be reserved
LFS_ASSERT(tag != (LFSR_TAG_NAME + 0));
// found an orphaned file?
if (tag == LFSR_TAG_ORPHAN) {
LFS_DEBUG("Found orphan "
"%"PRId32".%"PRId32,
lfsr_mid_bid(lfs, mdir->mid) >> lfs->mdir_bits,
rid);
lfs->flags |= LFS_F_ORPHANS;
// found an orphaned file?
if (tag == LFSR_TAG_ORPHAN) {
LFS_DEBUG("Found orphan "
"%"PRId32".%"PRId32,
lfsr_mid_bid(lfs, mdir->mid) >> lfs->mdir_bits,
rid);
lfs->flags |= LFS_F_ORPHANS;
// found an unknown file type?
} else if (lfsr_tag_isunknown(tag)) {
// TODO switch to readonly?
LFS_ERROR("Found unknown file type "
"%"PRId32".%"PRId32" 0x%"PRIx16,
lfsr_mid_bid(lfs, mdir->mid) >> lfs->mdir_bits,
rid,
lfsr_tag_subtype(tag));
return LFS_ERR_NOTSUP;
// found an unknown file type?
} else if (lfsr_tag_isunknown(tag)) {
LFS_WARN("Found unknown file type "
"%"PRId32".%"PRId32" 0x%"PRIx16,
lfsr_mid_bid(lfs, mdir->mid) >> lfs->mdir_bits,
rid,
lfsr_tag_subtype(tag));
return LFS_ERR_NOTSUP;
}
}
}
@@ -12628,9 +12636,6 @@ static int lfsr_mountinited(lfs_t *lfs) {
int lfsr_mount(lfs_t *lfs, uint32_t flags,
const struct lfs_config *cfg) {
// some flags don't make sense when only traversing the mtree
LFS_ASSERT(!lfsr_t_ismtreeonly(flags) || !lfsr_t_islookahead(flags));
LFS_ASSERT(!lfsr_t_ismtreeonly(flags) || !lfsr_t_isckdata(flags));
// unknown flags?
LFS_ASSERT((flags & ~(
LFS_M_RDWR
@@ -12644,6 +12649,13 @@ int lfsr_mount(lfs_t *lfs, uint32_t flags,
| LFS_M_COMPACT
| LFS_M_CKMETA
| LFS_M_CKDATA)) == 0);
// these flags require a writable filesystem
LFS_ASSERT(!lfsr_m_isrdonly(flags) || !lfsr_t_ismkconsistent(flags));
LFS_ASSERT(!lfsr_m_isrdonly(flags) || !lfsr_t_islookahead(flags));
LFS_ASSERT(!lfsr_m_isrdonly(flags) || !lfsr_t_iscompact(flags));
// some flags don't make sense when only traversing the mtree
LFS_ASSERT(!lfsr_t_ismtreeonly(flags) || !lfsr_t_islookahead(flags));
LFS_ASSERT(!lfsr_t_ismtreeonly(flags) || !lfsr_t_isckdata(flags));
int err = lfs_init(lfs, flags, cfg);
if (err) {
@@ -12671,16 +12683,24 @@ int lfsr_mount(lfs_t *lfs, uint32_t flags,
1 << lfs->mdir_bits);
// run gc if requested
err = lfsr_fs_gc(lfs, -1,
flags & (
LFS_M_MTREEONLY
| LFS_M_MKCONSISTENT
| LFS_M_LOOKAHEAD
| LFS_M_COMPACT
| LFS_M_CKMETA
| LFS_M_CKDATA));
if (err) {
goto failed;
if (flags & (
LFS_M_MTREEONLY
| LFS_M_MKCONSISTENT
| LFS_M_LOOKAHEAD
| LFS_M_COMPACT
| LFS_M_CKMETA
| LFS_M_CKDATA)) {
err = lfsr_fs_gc(lfs, -1,
flags & (
LFS_M_MTREEONLY
| LFS_M_MKCONSISTENT
| LFS_M_LOOKAHEAD
| LFS_M_COMPACT
| LFS_M_CKMETA
| LFS_M_CKDATA));
if (err) {
goto failed;
}
}
return 0;
@@ -12962,6 +12982,9 @@ static int lfsr_fs_fixorphans(lfs_t *lfs) {
// prepare the filesystem for mutation
int lfsr_fs_mkconsistent(lfs_t *lfs) {
// filesystem must be writeable
LFS_ASSERT(!lfsr_m_isrdonly(lfs->flags));
// fix pending grms
if (lfsr_grm_count(lfs) > 0) {
if (lfsr_grm_count(lfs) == 2) {
@@ -13035,6 +13058,10 @@ int lfsr_fs_gc(lfs_t *lfs, lfs_soff_t steps, uint32_t flags) {
| LFS_GC_COMPACT
| LFS_GC_CKMETA
| LFS_GC_CKDATA)) == 0);
// these flags require a writable filesystem
LFS_ASSERT(!lfsr_m_isrdonly(lfs->flags) || !lfsr_t_ismkconsistent(flags));
LFS_ASSERT(!lfsr_m_isrdonly(lfs->flags) || !lfsr_t_islookahead(flags));
LFS_ASSERT(!lfsr_m_isrdonly(lfs->flags) || !lfsr_t_iscompact(flags));
// some flags don't make sense when only traversing the mtree
LFS_ASSERT(!lfsr_t_ismtreeonly(flags) || !lfsr_t_islookahead(flags));
LFS_ASSERT(!lfsr_t_ismtreeonly(flags) || !lfsr_t_isckdata(flags));
@@ -13150,6 +13177,8 @@ int lfsr_fs_gc(lfs_t *lfs, lfs_soff_t steps, uint32_t flags) {
int lfsr_fs_grow(lfs_t *lfs, lfs_size_t block_count_) {
// filesystem must be writeable
LFS_ASSERT(!lfsr_m_isrdonly(lfs->flags));
// shrinking the filesystem is not supported
LFS_ASSERT(block_count_ >= lfs->block_count);
@@ -13221,6 +13250,10 @@ int lfsr_traversal_open(lfs_t *lfs, lfsr_traversal_t *t, uint32_t flags) {
| LFS_T_COMPACT
| LFS_T_CKMETA
| LFS_T_CKDATA)) == 0);
// these flags require a writable filesystem
LFS_ASSERT(!lfsr_m_isrdonly(lfs->flags) || !lfsr_t_ismkconsistent(flags));
LFS_ASSERT(!lfsr_m_isrdonly(lfs->flags) || !lfsr_t_islookahead(flags));
LFS_ASSERT(!lfsr_m_isrdonly(lfs->flags) || !lfsr_t_iscompact(flags));
// some flags don't make sense when only traversing the mtree
LFS_ASSERT(!lfsr_t_ismtreeonly(flags) || !lfsr_t_islookahead(flags));
LFS_ASSERT(!lfsr_t_ismtreeonly(flags) || !lfsr_t_isckdata(flags));
+27
View File
@@ -452,6 +452,7 @@ code = '''
// mount should now fail
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_CORRUPT;
lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => LFS_ERR_CORRUPT;
'''
# test that we fail if we find bad magic
@@ -475,6 +476,7 @@ code = '''
// mount should now fail
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_CORRUPT;
lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => LFS_ERR_CORRUPT;
'''
# test that we fail to mount after a major version bump
@@ -500,6 +502,7 @@ code = '''
// mount should now fail
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_NOTSUP;
lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => LFS_ERR_NOTSUP;
'''
# test that we fail to mount after a minor version bump
@@ -525,6 +528,7 @@ code = '''
// mount should now fail
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_NOTSUP;
lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => LFS_ERR_NOTSUP;
'''
# test that we fail to mount incompatible rcompat flags
@@ -551,6 +555,7 @@ code = '''
// mount should now fail
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_NOTSUP;
lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => LFS_ERR_NOTSUP;
'''
# test that we fail to mount incompatible wcompat flags
@@ -577,6 +582,10 @@ code = '''
// mount should now fail
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_NOTSUP;
// but we _can_ mount readonly
lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => 0;
lfsr_unmount(&lfs) => 0;
'''
# test that an incompatible ocompat flag is a noop
@@ -604,6 +613,8 @@ code = '''
// mount should _not_ fail, ocompat should always be ignored
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => 0;
lfsr_unmount(&lfs) => 0;
'''
# these are just a bit harder to detect
@@ -628,6 +639,7 @@ code = '''
// mount should now fail
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_NOTSUP;
lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => LFS_ERR_NOTSUP;
'''
[cases.test_mount_incompat_wcompat_overflow]
@@ -651,6 +663,10 @@ code = '''
// mount should now fail
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_NOTSUP;
// but we _can_ mount readonly
lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => 0;
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mount_incompat_ocompat_overflow]
@@ -675,6 +691,8 @@ code = '''
// mount should _not_ fail, ocompat should always be ignored
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => 0;
lfsr_unmount(&lfs) => 0;
'''
# test that we fail to mount incompatible block sizes
@@ -701,6 +719,7 @@ code = '''
// mount should now fail
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_NOTSUP;
lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => LFS_ERR_NOTSUP;
'''
# test that we fail to mount after incompatible block counts
@@ -727,6 +746,7 @@ code = '''
// mount should now fail
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_NOTSUP;
lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => LFS_ERR_NOTSUP;
'''
# test that we fail to mount after incompatible name limit
@@ -751,6 +771,7 @@ code = '''
// mount should now fail
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_NOTSUP;
lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => LFS_ERR_NOTSUP;
'''
# test that we fail to mount after incompatible file limit
@@ -779,6 +800,7 @@ code = '''
// mount should now fail
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_NOTSUP;
lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => LFS_ERR_NOTSUP;
'''
# test what happens if we find an unknown config
@@ -799,6 +821,7 @@ code = '''
// mount should now fail
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_NOTSUP;
lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => LFS_ERR_NOTSUP;
'''
# test what happens if we find an unknown file type
@@ -846,4 +869,8 @@ code = '''
// mount should now fail
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_NOTSUP;
// but we _can_ mount readonly
lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => 0;
lfsr_unmount(&lfs) => 0;
'''