diff options
author | Christopher Haster <geky@geky.net> | 2023-04-21 00:42:19 +0300 |
---|---|---|
committer | Christopher Haster <geky@geky.net> | 2023-04-21 08:56:55 +0300 |
commit | 4c9360020ed8b6db5d943fd61155a09553e8f30f (patch) | |
tree | a552ffa0f3dfbb4abddbaa340c6ffaea3b030d61 | |
parent | ca0da3d4907df931636b8fa46aeb17d8ee799aa4 (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.c | 83 | ||||
-rw-r--r-- | tests/test_compat.toml | 82 |
2 files changed, 160 insertions, 5 deletions
@@ -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; +''' |