Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/littlefs-project/littlefs.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristopher Haster <geky@geky.net>2023-04-21 00:42:19 +0300
committerChristopher Haster <geky@geky.net>2023-04-21 08:56:55 +0300
commit4c9360020ed8b6db5d943fd61155a09553e8f30f (patch)
treea552ffa0f3dfbb4abddbaa340c6ffaea3b030d61
parentca0da3d4907df931636b8fa46aeb17d8ee799aa4 (diff)
Added ability to bump on-disk minor version
This just means a rewrite of the superblock entry with the new minor version. Though it's interesting to note, we don't need to rewrite the superblock entry until the first write operation in the filesystem, an optimization that is already in use for the fixing of orphans and in-flight moves. To keep track of any outdated minor version found during lfs_mount, we can carve out a bit from the reserved bits in our gstate. These are currently used for a counter tracking the number of orphans in the filesystem, but this is usually a very small number so this hopefully won't be an issue. In-device gstate tag: [-- 32 --] [1|- 11 -| 10 |1| 9 ] ^----^-----^--^--^-- 1-bit has orphans '-----|--|--|-- 11-bit move type '--|--|-- 10-bit move id '--|-- 1-bit needs superblock '-- 9-bit orphan count
-rw-r--r--lfs.c83
-rw-r--r--tests/test_compat.toml82
2 files changed, 160 insertions, 5 deletions
diff --git a/lfs.c b/lfs.c
index 7a4b41a..5ad8a1f 100644
--- a/lfs.c
+++ b/lfs.c
@@ -411,12 +411,16 @@ static inline bool lfs_gstate_hasorphans(const lfs_gstate_t *a) {
}
static inline uint8_t lfs_gstate_getorphans(const lfs_gstate_t *a) {
- return lfs_tag_size(a->tag);
+ return lfs_tag_size(a->tag) & 0x1ff;
}
static inline bool lfs_gstate_hasmove(const lfs_gstate_t *a) {
return lfs_tag_type1(a->tag);
}
+
+static inline bool lfs_gstate_needssuperblock(const lfs_gstate_t *a) {
+ return lfs_tag_size(a->tag) >> 9;
+}
#endif
static inline bool lfs_gstate_hasmovehere(const lfs_gstate_t *a,
@@ -533,6 +537,7 @@ static int lfs_file_outline(lfs_t *lfs, lfs_file_t *file);
static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file);
static int lfs_fs_deorphan(lfs_t *lfs, bool powerloss);
+static void lfs_fs_prepsuperblock(lfs_t *lfs, bool needssuperblock);
static int lfs_fs_preporphans(lfs_t *lfs, int8_t orphans);
static void lfs_fs_prepmove(lfs_t *lfs,
uint16_t id, const lfs_block_t pair[2]);
@@ -4258,12 +4263,29 @@ static int lfs_rawmount(lfs_t *lfs, const struct lfs_config *cfg) {
uint16_t minor_version = (0xffff & (superblock.version >> 0));
if ((major_version != LFS_DISK_VERSION_MAJOR ||
minor_version > LFS_DISK_VERSION_MINOR)) {
- LFS_ERROR("Invalid version v%"PRIu16".%"PRIu16,
- major_version, minor_version);
+ LFS_ERROR("Invalid version "
+ "v%"PRIu16".%"PRIu16" != v%"PRIu16".%"PRIu16,
+ major_version, minor_version,
+ LFS_DISK_VERSION_MAJOR, LFS_DISK_VERSION_MINOR);
err = LFS_ERR_INVAL;
goto cleanup;
}
+ // found older minor version? set an in-device only bit in the
+ // gstate so we know we need to rewrite the superblock before
+ // the first write
+ if (minor_version < LFS_DISK_VERSION_MINOR) {
+ LFS_DEBUG("Found older minor version "
+ "v%"PRIu16".%"PRIu16" < v%"PRIu16".%"PRIu16,
+ major_version, minor_version,
+ LFS_DISK_VERSION_MAJOR, LFS_DISK_VERSION_MINOR);
+ #ifndef LFS_READONLY
+ // note this bit is reserved on disk, so fetching more gstate
+ // will not interfere here
+ lfs_fs_prepsuperblock(lfs, true);
+ #endif
+ }
+
// check superblock configuration
if (superblock.name_max) {
if (superblock.name_max > lfs->name_max) {
@@ -4538,9 +4560,16 @@ static lfs_stag_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t pair[2],
#endif
#ifndef LFS_READONLY
+static void lfs_fs_prepsuperblock(lfs_t *lfs, bool needssuperblock) {
+ lfs->gstate.tag = (lfs->gstate.tag & ~LFS_MKTAG(0, 0, 0x200))
+ | (uint32_t)needssuperblock << 9;
+}
+#endif
+
+#ifndef LFS_READONLY
static int lfs_fs_preporphans(lfs_t *lfs, int8_t orphans) {
LFS_ASSERT(lfs_tag_size(lfs->gstate.tag) > 0x000 || orphans >= 0);
- LFS_ASSERT(lfs_tag_size(lfs->gstate.tag) < 0x3ff || orphans <= 0);
+ LFS_ASSERT(lfs_tag_size(lfs->gstate.tag) < 0x1ff || orphans <= 0);
lfs->gstate.tag += orphans;
lfs->gstate.tag = ((lfs->gstate.tag & ~LFS_MKTAG(0x800, 0, 0)) |
((uint32_t)lfs_gstate_hasorphans(&lfs->gstate) << 31));
@@ -4560,6 +4589,45 @@ static void lfs_fs_prepmove(lfs_t *lfs,
#endif
#ifndef LFS_READONLY
+static int lfs_fs_desuperblock(lfs_t *lfs) {
+ if (!lfs_gstate_needssuperblock(&lfs->gstate)) {
+ return 0;
+ }
+
+ LFS_DEBUG("Rewriting superblock {0x%"PRIx32", 0x%"PRIx32"}",
+ lfs->root[0],
+ lfs->root[1]);
+
+ lfs_mdir_t root;
+ int err = lfs_dir_fetch(lfs, &root, lfs->root);
+ if (err) {
+ return err;
+ }
+
+ // write a new superblock
+ lfs_superblock_t superblock = {
+ .version = LFS_DISK_VERSION,
+ .block_size = lfs->cfg->block_size,
+ .block_count = lfs->cfg->block_count,
+ .name_max = lfs->name_max,
+ .file_max = lfs->file_max,
+ .attr_max = lfs->attr_max,
+ };
+
+ lfs_superblock_tole32(&superblock);
+ err = lfs_dir_commit(lfs, &root, LFS_MKATTRS(
+ {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)),
+ &superblock}));
+ if (err) {
+ return err;
+ }
+
+ lfs_fs_prepsuperblock(lfs, false);
+ return 0;
+}
+#endif
+
+#ifndef LFS_READONLY
static int lfs_fs_demove(lfs_t *lfs) {
if (!lfs_gstate_hasmove(&lfs->gdisk)) {
return 0;
@@ -4736,7 +4804,12 @@ static int lfs_fs_deorphan(lfs_t *lfs, bool powerloss) {
#ifndef LFS_READONLY
static int lfs_fs_forceconsistency(lfs_t *lfs) {
- int err = lfs_fs_demove(lfs);
+ int err = lfs_fs_desuperblock(lfs);
+ if (err) {
+ return err;
+ }
+
+ err = lfs_fs_demove(lfs);
if (err) {
return err;
}
diff --git a/tests/test_compat.toml b/tests/test_compat.toml
index 7af487f..a36c38a 100644
--- a/tests/test_compat.toml
+++ b/tests/test_compat.toml
@@ -1276,3 +1276,85 @@ code = '''
// mount should now fail
lfs_mount(&lfs, cfg) => LFS_ERR_INVAL;
'''
+
+# test that we correctly bump the minor version
+[cases.test_compat_minor_bump]
+in = 'lfs.c'
+if = 'LFS_DISK_VERSION_MINOR > 0'
+code = '''
+ // create a superblock
+ lfs_t lfs;
+ lfs_format(&lfs, cfg) => 0;
+ lfs_mount(&lfs, cfg) => 0;
+ lfs_file_t file;
+ lfs_file_open(&lfs, &file, "test",
+ LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+ lfs_file_write(&lfs, &file, "testtest", 8) => 8;
+ lfs_file_close(&lfs, &file) => 0;
+ lfs_unmount(&lfs) => 0;
+
+ // write an old minor version
+ //
+ // note we're messing around with internals to do this! this
+ // is not a user API
+ lfs_mount(&lfs, cfg) => 0;
+ lfs_mdir_t mdir;
+ lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
+ lfs_superblock_t superblock = {
+ .version = LFS_DISK_VERSION - 0x00000001,
+ .block_size = lfs.cfg->block_size,
+ .block_count = lfs.cfg->block_count,
+ .name_max = lfs.name_max,
+ .file_max = lfs.file_max,
+ .attr_max = lfs.attr_max,
+ };
+ lfs_superblock_tole32(&superblock);
+ lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
+ {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)),
+ &superblock})) => 0;
+ lfs_unmount(&lfs) => 0;
+
+ // mount should still work
+ lfs_mount(&lfs, cfg) => 0;
+ lfs_file_open(&lfs, &file, "test", LFS_O_RDONLY) => 0;
+ uint8_t buffer[8];
+ lfs_file_read(&lfs, &file, buffer, 8) => 8;
+ assert(memcmp(buffer, "testtest", 8) == 0);
+ lfs_file_close(&lfs, &file) => 0;
+ lfs_unmount(&lfs) => 0;
+
+ // if we write, we need to bump the minor version
+ lfs_mount(&lfs, cfg) => 0;
+ lfs_file_open(&lfs, &file, "test", LFS_O_WRONLY | LFS_O_TRUNC) => 0;
+ lfs_file_write(&lfs, &file, "teeeeest", 8) => 8;
+ lfs_file_close(&lfs, &file) => 0;
+
+ // minor version should have changed
+ lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
+ lfs_dir_get(&lfs, &mdir, LFS_MKTAG(0x7ff, 0x3ff, 0),
+ LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)),
+ &superblock)
+ => LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock));
+ lfs_superblock_fromle32(&superblock);
+ assert((superblock.version >> 16) & 0xffff == LFS_DISK_VERSION_MAJOR);
+ assert((superblock.version >> 0) & 0xffff == LFS_DISK_VERSION_MINOR);
+ lfs_unmount(&lfs) => 0;
+
+ // and of course mount should still work
+ lfs_mount(&lfs, cfg) => 0;
+ lfs_file_open(&lfs, &file, "test", LFS_O_RDONLY) => 0;
+ lfs_file_read(&lfs, &file, buffer, 8) => 8;
+ assert(memcmp(buffer, "teeeeest", 8) == 0);
+ lfs_file_close(&lfs, &file) => 0;
+
+ // minor version should have changed
+ lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
+ lfs_dir_get(&lfs, &mdir, LFS_MKTAG(0x7ff, 0x3ff, 0),
+ LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)),
+ &superblock)
+ => LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock));
+ lfs_superblock_fromle32(&superblock);
+ assert((superblock.version >> 16) & 0xffff == LFS_DISK_VERSION_MAJOR);
+ assert((superblock.version >> 0) & 0xffff == LFS_DISK_VERSION_MINOR);
+ lfs_unmount(&lfs) => 0;
+'''