From 87bbf1d37458b134e994e4cf0e355184a9a1af4b Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Tue, 6 Jun 2023 12:20:47 -0500 Subject: [PATCH 1/7] Added lfs_fs_stat for access to filesystem status/configuration Currently this includes: - minor_version - on-disk minor version - block_usage - estimated number of in-use blocks - name_max - configurable name limit - file_max - configurable file limit - attr_max - configurable attr limit These are currently the only configuration operations that need to be written to disk. Other configuration is either needed to mount, such as block_size, or does not change the on-disk representation, such as read/prog_size. This also includes the current block usage, which is common in other filesystems, though a more expensive to find in littlefs. I figure it's not unreasonable to make lfs_fs_stat no worse than block allocation, hopefully this isn't a mistake. It may be worth caching the current usage after the most recent lookahead scan. More configuration may be added to this struct in the future. --- lfs.c | 60 +++++++++++++++++- lfs.h | 27 ++++++++ tests/test_compat.toml | 122 ++++++++++++++++++++++++++++++------ tests/test_superblocks.toml | 48 ++++++++++++++ 4 files changed, 237 insertions(+), 20 deletions(-) diff --git a/lfs.c b/lfs.c index f0daf540..a215ba8f 100644 --- a/lfs.c +++ b/lfs.c @@ -4324,11 +4324,9 @@ static int lfs_rawmount(lfs_t *lfs, const struct lfs_config *cfg) { "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 @@ -4421,6 +4419,49 @@ static int lfs_rawunmount(lfs_t *lfs) { /// Filesystem filesystem operations /// +static int lfs_fs_rawstat(lfs_t *lfs, struct lfs_fsinfo *fsinfo) { + // if the superblock is up-to-date, we must be on the most recent + // minor version of littlefs + if (!lfs_gstate_needssuperblock(&lfs->gstate)) { + fsinfo->minor_version = LFS_DISK_VERSION_MINOR; + + // otherwise we need to read the minor version on disk + } else { + // fetch the superblock + lfs_mdir_t dir; + int err = lfs_dir_fetch(lfs, &dir, lfs->root); + if (err) { + return err; + } + + lfs_superblock_t superblock; + lfs_stag_t tag = lfs_dir_get(lfs, &dir, LFS_MKTAG(0x7ff, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)), + &superblock); + if (tag < 0) { + return tag; + } + lfs_superblock_fromle32(&superblock); + + // read the minor version + fsinfo->minor_version = (0xffff & (superblock.version >> 0)); + } + + // find the current block usage + lfs_ssize_t usage = lfs_fs_rawsize(lfs); + if (usage < 0) { + return usage; + } + fsinfo->block_usage = usage; + + // other on-disk configuration, we cache all of these for internal use + fsinfo->name_max = lfs->name_max; + fsinfo->file_max = lfs->file_max; + fsinfo->attr_max = lfs->attr_max; + + return 0; +} + int lfs_fs_rawtraverse(lfs_t *lfs, int (*cb)(void *data, lfs_block_t block), void *data, bool includeorphans) { @@ -4934,6 +4975,7 @@ static lfs_ssize_t lfs_fs_rawsize(lfs_t *lfs) { return size; } + #ifdef LFS_MIGRATE ////// Migration from littelfs v1 below this ////// @@ -6053,6 +6095,20 @@ int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir) { return err; } +int lfs_fs_stat(lfs_t *lfs, struct lfs_fsinfo *fsinfo) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_fs_stat(%p, %p)", (void*)lfs, (void*)fsinfo); + + err = lfs_fs_rawstat(lfs, fsinfo); + + LFS_TRACE("lfs_fs_stat -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} + lfs_ssize_t lfs_fs_size(lfs_t *lfs) { int err = LFS_LOCK(lfs->cfg); if (err) { diff --git a/lfs.h b/lfs.h index eb5c355d..25bd03c8 100644 --- a/lfs.h +++ b/lfs.h @@ -280,6 +280,27 @@ struct lfs_info { char name[LFS_NAME_MAX+1]; }; +// Filesystem info structure +struct lfs_fsinfo { + // On-disk minor version. + uint16_t minor_version; + + // Number of blocks in use, this is the same as lfs_fs_size. + // + // Note: block_usage is best effort. If files share COW structures, the + // calculated block_usage may be larger than the actual contents on-disk. + lfs_size_t block_usage; + + // Upper limit on the length of file names in bytes. + lfs_size_t name_max; + + // Upper limit on the size of files in bytes. + lfs_size_t file_max; + + // Upper limit on the size of custom attributes in bytes. + lfs_size_t attr_max; +}; + // Custom attribute structure, used to describe custom attributes // committed atomically during file writes. struct lfs_attr { @@ -659,6 +680,12 @@ int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir); /// Filesystem-level filesystem operations +// Find on-disk info about the filesystem +// +// Fills out the fsinfo structure based on the filesystem found on-disk. +// Returns a negative error code on failure. +int lfs_fs_stat(lfs_t *lfs, struct lfs_fsinfo *fsinfo); + // Finds the current size of the filesystem // // Note: Result is best effort. If files share COW structures, the returned diff --git a/tests/test_compat.toml b/tests/test_compat.toml index a36c38a4..e458ab01 100644 --- a/tests/test_compat.toml +++ b/tests/test_compat.toml @@ -25,11 +25,16 @@ code = ''' #define LFSP_VERSION LFS_VERSION #define LFSP_VERSION_MAJOR LFS_VERSION_MAJOR #define LFSP_VERSION_MINOR LFS_VERSION_MINOR +#define LFSP_DISK_VERSION LFS_DISK_VERSION +#define LFSP_DISK_VERSION_MAJOR LFS_DISK_VERSION_MAJOR +#define LFSP_DISK_VERSION_MINOR LFS_DISK_VERSION_MINOR #define lfsp_t lfs_t #define lfsp_config lfs_config #define lfsp_format lfs_format #define lfsp_mount lfs_mount #define lfsp_unmount lfs_unmount +#define lfsp_fsinfo lfs_fsinfo +#define lfsp_fs_stat lfs_fs_stat #define lfsp_dir_t lfs_dir_t #define lfsp_info lfs_info #define LFSP_TYPE_REG LFS_TYPE_REG @@ -74,6 +79,12 @@ code = ''' // now test the new mount lfs_t lfs; lfs_mount(&lfs, cfg) => 0; + + // we should be able to read the version using lfs_fs_stat + struct lfs_fsinfo fsinfo; + lfs_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.minor_version == LFSP_DISK_VERSION_MINOR); + lfs_unmount(&lfs) => 0; ''' @@ -102,6 +113,11 @@ code = ''' lfs_t lfs; lfs_mount(&lfs, cfg) => 0; + // we should be able to read the version using lfs_fs_stat + struct lfs_fsinfo fsinfo; + lfs_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.minor_version == LFSP_DISK_VERSION_MINOR); + // can we list the directories? lfs_dir_t dir; lfs_dir_open(&lfs, &dir, "/") => 0; @@ -166,6 +182,11 @@ code = ''' lfs_t lfs; lfs_mount(&lfs, cfg) => 0; + // we should be able to read the version using lfs_fs_stat + struct lfs_fsinfo fsinfo; + lfs_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.minor_version == LFSP_DISK_VERSION_MINOR); + // can we list the files? lfs_dir_t dir; lfs_dir_open(&lfs, &dir, "/") => 0; @@ -251,6 +272,11 @@ code = ''' lfs_t lfs; lfs_mount(&lfs, cfg) => 0; + // we should be able to read the version using lfs_fs_stat + struct lfs_fsinfo fsinfo; + lfs_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.minor_version == LFSP_DISK_VERSION_MINOR); + // can we list the directories? lfs_dir_t dir; lfs_dir_open(&lfs, &dir, "/") => 0; @@ -343,6 +369,11 @@ code = ''' lfs_t lfs; lfs_mount(&lfs, cfg) => 0; + // we should be able to read the version using lfs_fs_stat + struct lfs_fsinfo fsinfo; + lfs_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.minor_version == LFSP_DISK_VERSION_MINOR); + // write another COUNT/2 dirs for (lfs_size_t i = COUNT/2; i < COUNT; i++) { char name[8]; @@ -420,6 +451,11 @@ code = ''' lfs_t lfs; lfs_mount(&lfs, cfg) => 0; + // we should be able to read the version using lfs_fs_stat + struct lfs_fsinfo fsinfo; + lfs_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.minor_version == LFSP_DISK_VERSION_MINOR); + // write half COUNT files prng = 42; for (lfs_size_t i = 0; i < COUNT; i++) { @@ -537,6 +573,11 @@ code = ''' lfs_t lfs; lfs_mount(&lfs, cfg) => 0; + // we should be able to read the version using lfs_fs_stat + struct lfs_fsinfo fsinfo; + lfs_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.minor_version == LFSP_DISK_VERSION_MINOR); + // write half COUNT files prng = 42; for (lfs_size_t i = 0; i < COUNT; i++) { @@ -651,6 +692,12 @@ code = ''' memcpy(&cfgp, cfg, sizeof(cfgp)); lfsp_t lfsp; lfsp_mount(&lfsp, &cfgp) => 0; + + // we should be able to read the version using lfs_fs_stat + struct lfsp_fsinfo fsinfo; + lfsp_fs_stat(&lfsp, &fsinfo) => 0; + assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR); + lfsp_unmount(&lfsp) => 0; ''' @@ -679,6 +726,11 @@ code = ''' lfsp_t lfsp; lfsp_mount(&lfsp, &cfgp) => 0; + // we should be able to read the version using lfs_fs_stat + struct lfs_fsinfo fsinfo; + lfs_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR); + // can we list the directories? lfsp_dir_t dir; lfsp_dir_open(&lfsp, &dir, "/") => 0; @@ -743,6 +795,11 @@ code = ''' lfsp_t lfsp; lfsp_mount(&lfsp, &cfgp) => 0; + // we should be able to read the version using lfs_fs_stat + struct lfsp_fsinfo fsinfo; + lfsp_fs_stat(&lfsp, &fsinfo) => 0; + assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR); + // can we list the files? lfsp_dir_t dir; lfsp_dir_open(&lfsp, &dir, "/") => 0; @@ -828,6 +885,11 @@ code = ''' lfsp_t lfsp; lfsp_mount(&lfsp, &cfgp) => 0; + // we should be able to read the version using lfs_fs_stat + struct lfsp_fsinfo fsinfo; + lfsp_fs_stat(&lfsp, &fsinfo) => 0; + assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR); + // can we list the directories? lfsp_dir_t dir; lfsp_dir_open(&lfsp, &dir, "/") => 0; @@ -920,6 +982,11 @@ code = ''' lfsp_t lfsp; lfsp_mount(&lfsp, &cfgp) => 0; + // we should be able to read the version using lfs_fs_stat + struct lfsp_fsinfo fsinfo; + lfsp_fs_stat(&lfsp, &fsinfo) => 0; + assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR); + // write another COUNT/2 dirs for (lfs_size_t i = COUNT/2; i < COUNT; i++) { char name[8]; @@ -997,6 +1064,11 @@ code = ''' lfsp_t lfsp; lfsp_mount(&lfsp, &cfgp) => 0; + // we should be able to read the version using lfs_fs_stat + struct lfsp_fsinfo fsinfo; + lfsp_fs_stat(&lfsp, &fsinfo) => 0; + assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR); + // write half COUNT files prng = 42; for (lfs_size_t i = 0; i < COUNT; i++) { @@ -1114,6 +1186,11 @@ code = ''' lfsp_t lfsp; lfsp_mount(&lfsp, &cfgp) => 0; + // we should be able to read the version using lfs_fs_stat + struct lfsp_fsinfo fsinfo; + lfsp_fs_stat(&lfsp, &fsinfo) => 0; + assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR); + // write half COUNT files prng = 42; for (lfs_size_t i = 0; i < COUNT; i++) { @@ -1316,45 +1393,54 @@ code = ''' // mount should still work lfs_mount(&lfs, cfg) => 0; + + struct lfs_fsinfo fsinfo; + lfs_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR-1); + 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; + + // minor version should be unchanged + lfs_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR-1); + lfs_unmount(&lfs) => 0; // if we write, we need to bump the minor version lfs_mount(&lfs, cfg) => 0; + + lfs_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR-1); + 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); + // minor version should be changed + lfs_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR); + lfs_unmount(&lfs) => 0; // and of course mount should still work lfs_mount(&lfs, cfg) => 0; + + // minor version should have changed + lfs_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR); + 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); + // yep, still changed + lfs_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR); + lfs_unmount(&lfs) => 0; ''' diff --git a/tests/test_superblocks.toml b/tests/test_superblocks.toml index 689bbcd2..3001fb41 100644 --- a/tests/test_superblocks.toml +++ b/tests/test_superblocks.toml @@ -34,6 +34,54 @@ code = ''' lfs_mount(&lfs, cfg) => LFS_ERR_CORRUPT; ''' +# test we can read superblock info through lfs_fs_stat +[cases.test_superblocks_stat] +code = ''' + lfs_t lfs; + lfs_format(&lfs, cfg) => 0; + + // test we can mount and read fsinfo + lfs_mount(&lfs, cfg) => 0; + + struct lfs_fsinfo fsinfo; + lfs_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR); + assert(fsinfo.block_usage > 0 && fsinfo.block_usage < BLOCK_COUNT); + assert(fsinfo.name_max == LFS_NAME_MAX); + assert(fsinfo.file_max == LFS_FILE_MAX); + assert(fsinfo.attr_max == LFS_ATTR_MAX); + + lfs_unmount(&lfs) => 0; +''' + +[cases.test_superblocks_stat_tweaked] +defines.TWEAKED_NAME_MAX = 63 +defines.TWEAKED_FILE_MAX = '(1 << 16)-1' +defines.TWEAKED_ATTR_MAX = 512 +code = ''' + // create filesystem with tweaked params + struct lfs_config tweaked_cfg = *cfg; + tweaked_cfg.name_max = TWEAKED_NAME_MAX; + tweaked_cfg.file_max = TWEAKED_FILE_MAX; + tweaked_cfg.attr_max = TWEAKED_ATTR_MAX; + + lfs_t lfs; + lfs_format(&lfs, &tweaked_cfg) => 0; + + // test we can mount and read these params with the original config + lfs_mount(&lfs, cfg) => 0; + + struct lfs_fsinfo fsinfo; + lfs_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR); + assert(fsinfo.block_usage > 0 && fsinfo.block_usage < BLOCK_COUNT); + assert(fsinfo.name_max == TWEAKED_NAME_MAX); + assert(fsinfo.file_max == TWEAKED_FILE_MAX); + assert(fsinfo.attr_max == TWEAKED_ATTR_MAX); + + lfs_unmount(&lfs) => 0; +''' + # expanding superblock [cases.test_superblocks_expand] defines.BLOCK_CYCLES = [32, 33, 1] From fdee127f742caa34618c68c1328bb68f451da82f Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Tue, 6 Jun 2023 14:55:22 -0500 Subject: [PATCH 2/7] Removed use of LFS_VERSION in test_compat LFS_VERSION -> LFS_DISK_VERSION These tests shouldn't depend on LFS_VERSION. It's a bit subtle, but LFS_VERSION versions the API, and LFS_DISK_VERSION versions the on-disk format, which is what test_compat should be testing. --- tests/test_compat.toml | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/tests/test_compat.toml b/tests/test_compat.toml index e458ab01..61bf2aa9 100644 --- a/tests/test_compat.toml +++ b/tests/test_compat.toml @@ -22,9 +22,6 @@ code = ''' #define STRINGIZE_(x) #x #include STRINGIZE(LFSP) #else -#define LFSP_VERSION LFS_VERSION -#define LFSP_VERSION_MAJOR LFS_VERSION_MAJOR -#define LFSP_VERSION_MINOR LFS_VERSION_MINOR #define LFSP_DISK_VERSION LFS_DISK_VERSION #define LFSP_DISK_VERSION_MAJOR LFS_DISK_VERSION_MAJOR #define LFSP_DISK_VERSION_MINOR LFS_DISK_VERSION_MINOR @@ -63,7 +60,7 @@ code = ''' # test we can mount in a new version [cases.test_compat_forward_mount] -if = 'LFS_VERSION_MAJOR == LFSP_VERSION_MAJOR' +if = 'LFS_DISK_VERSION_MAJOR == LFSP_DISK_VERSION_MAJOR' code = ''' // create the previous version struct lfsp_config cfgp; @@ -91,7 +88,7 @@ code = ''' # test we can read dirs in a new version [cases.test_compat_forward_read_dirs] defines.COUNT = 5 -if = 'LFS_VERSION_MAJOR == LFSP_VERSION_MAJOR' +if = 'LFS_DISK_VERSION_MAJOR == LFSP_DISK_VERSION_MAJOR' code = ''' // create the previous version struct lfsp_config cfgp; @@ -148,7 +145,7 @@ code = ''' defines.COUNT = 5 defines.SIZE = [4, 32, 512, 8192] defines.CHUNK = 4 -if = 'LFS_VERSION_MAJOR == LFSP_VERSION_MAJOR' +if = 'LFS_DISK_VERSION_MAJOR == LFSP_DISK_VERSION_MAJOR' code = ''' // create the previous version struct lfsp_config cfgp; @@ -235,7 +232,7 @@ code = ''' defines.COUNT = 5 defines.SIZE = [4, 32, 512, 8192] defines.CHUNK = 4 -if = 'LFS_VERSION_MAJOR == LFSP_VERSION_MAJOR' +if = 'LFS_DISK_VERSION_MAJOR == LFSP_DISK_VERSION_MAJOR' code = ''' // create the previous version struct lfsp_config cfgp; @@ -347,7 +344,7 @@ code = ''' # test we can write dirs in a new version [cases.test_compat_forward_write_dirs] defines.COUNT = 10 -if = 'LFS_VERSION_MAJOR == LFSP_VERSION_MAJOR' +if = 'LFS_DISK_VERSION_MAJOR == LFSP_DISK_VERSION_MAJOR' code = ''' // create the previous version struct lfsp_config cfgp; @@ -411,7 +408,7 @@ code = ''' defines.COUNT = 5 defines.SIZE = [4, 32, 512, 8192] defines.CHUNK = 2 -if = 'LFS_VERSION_MAJOR == LFSP_VERSION_MAJOR' +if = 'LFS_DISK_VERSION_MAJOR == LFSP_DISK_VERSION_MAJOR' code = ''' // create the previous version struct lfsp_config cfgp; @@ -530,7 +527,7 @@ code = ''' defines.COUNT = 5 defines.SIZE = [4, 32, 512, 8192] defines.CHUNK = 2 -if = 'LFS_VERSION_MAJOR == LFSP_VERSION_MAJOR' +if = 'LFS_DISK_VERSION_MAJOR == LFSP_DISK_VERSION_MAJOR' code = ''' // create the previous version struct lfsp_config cfgp; @@ -677,7 +674,7 @@ code = ''' # test we can mount in an old version [cases.test_compat_backward_mount] -if = 'LFS_VERSION == LFSP_VERSION' +if = 'LFS_DISK_VERSION == LFSP_DISK_VERSION' code = ''' // create the new version lfs_t lfs; @@ -704,7 +701,7 @@ code = ''' # test we can read dirs in an old version [cases.test_compat_backward_read_dirs] defines.COUNT = 5 -if = 'LFS_VERSION == LFSP_VERSION' +if = 'LFS_DISK_VERSION == LFSP_DISK_VERSION' code = ''' // create the new version lfs_t lfs; @@ -761,7 +758,7 @@ code = ''' defines.COUNT = 5 defines.SIZE = [4, 32, 512, 8192] defines.CHUNK = 4 -if = 'LFS_VERSION == LFSP_VERSION' +if = 'LFS_DISK_VERSION == LFSP_DISK_VERSION' code = ''' // create the new version lfs_t lfs; @@ -848,7 +845,7 @@ code = ''' defines.COUNT = 5 defines.SIZE = [4, 32, 512, 8192] defines.CHUNK = 4 -if = 'LFS_VERSION == LFSP_VERSION' +if = 'LFS_DISK_VERSION == LFSP_DISK_VERSION' code = ''' // create the new version lfs_t lfs; @@ -960,7 +957,7 @@ code = ''' # test we can write dirs in an old version [cases.test_compat_backward_write_dirs] defines.COUNT = 10 -if = 'LFS_VERSION == LFSP_VERSION' +if = 'LFS_DISK_VERSION == LFSP_DISK_VERSION' code = ''' // create the new version lfs_t lfs; @@ -1024,7 +1021,7 @@ code = ''' defines.COUNT = 5 defines.SIZE = [4, 32, 512, 8192] defines.CHUNK = 2 -if = 'LFS_VERSION == LFSP_VERSION' +if = 'LFS_DISK_VERSION == LFSP_DISK_VERSION' code = ''' // create the previous version lfs_t lfs; @@ -1143,7 +1140,7 @@ code = ''' defines.COUNT = 5 defines.SIZE = [4, 32, 512, 8192] defines.CHUNK = 2 -if = 'LFS_VERSION == LFSP_VERSION' +if = 'LFS_DISK_VERSION == LFSP_DISK_VERSION' code = ''' // create the previous version lfs_t lfs; From a7ccc1df596134bc240163d4bfa7e652a3c42817 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Tue, 6 Jun 2023 15:59:45 -0500 Subject: [PATCH 3/7] Promoted lfs_gstate_needssuperblock to be available in readonly builds Needed for minor version reporting in lfs_fs_stat to work correctly. --- lfs.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lfs.c b/lfs.c index a215ba8f..09fd71d9 100644 --- a/lfs.c +++ b/lfs.c @@ -415,11 +415,11 @@ static inline uint8_t lfs_gstate_getorphans(const lfs_gstate_t *a) { static inline bool lfs_gstate_hasmove(const lfs_gstate_t *a) { return lfs_tag_type1(a->tag); } +#endif 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, const lfs_block_t *pair) { @@ -535,7 +535,6 @@ 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]); @@ -546,6 +545,8 @@ static lfs_stag_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t dir[2], static int lfs_fs_forceconsistency(lfs_t *lfs); #endif +static void lfs_fs_prepsuperblock(lfs_t *lfs, bool needssuperblock); + #ifdef LFS_MIGRATE static int lfs1_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data); @@ -4672,12 +4673,10 @@ 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) { From a51be18765ca466f360179492d2bd5066358f87f Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Tue, 6 Jun 2023 16:09:31 -0500 Subject: [PATCH 4/7] Removed previous-version lfsp_fs_stat checks in test_compat This function naturally doesn't exist in the previous version. We should eventually add these calls when we can expect the previous version to support this function, though it's a bit unclear when that should happen. Or maybe not! Maybe this is testing more of the previous version than we really care about. --- tests/test_compat.toml | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/tests/test_compat.toml b/tests/test_compat.toml index 61bf2aa9..4bb45d09 100644 --- a/tests/test_compat.toml +++ b/tests/test_compat.toml @@ -690,11 +690,6 @@ code = ''' lfsp_t lfsp; lfsp_mount(&lfsp, &cfgp) => 0; - // we should be able to read the version using lfs_fs_stat - struct lfsp_fsinfo fsinfo; - lfsp_fs_stat(&lfsp, &fsinfo) => 0; - assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR); - lfsp_unmount(&lfsp) => 0; ''' @@ -723,11 +718,6 @@ code = ''' lfsp_t lfsp; lfsp_mount(&lfsp, &cfgp) => 0; - // we should be able to read the version using lfs_fs_stat - struct lfs_fsinfo fsinfo; - lfs_fs_stat(&lfs, &fsinfo) => 0; - assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR); - // can we list the directories? lfsp_dir_t dir; lfsp_dir_open(&lfsp, &dir, "/") => 0; @@ -792,11 +782,6 @@ code = ''' lfsp_t lfsp; lfsp_mount(&lfsp, &cfgp) => 0; - // we should be able to read the version using lfs_fs_stat - struct lfsp_fsinfo fsinfo; - lfsp_fs_stat(&lfsp, &fsinfo) => 0; - assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR); - // can we list the files? lfsp_dir_t dir; lfsp_dir_open(&lfsp, &dir, "/") => 0; @@ -882,11 +867,6 @@ code = ''' lfsp_t lfsp; lfsp_mount(&lfsp, &cfgp) => 0; - // we should be able to read the version using lfs_fs_stat - struct lfsp_fsinfo fsinfo; - lfsp_fs_stat(&lfsp, &fsinfo) => 0; - assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR); - // can we list the directories? lfsp_dir_t dir; lfsp_dir_open(&lfsp, &dir, "/") => 0; @@ -979,11 +959,6 @@ code = ''' lfsp_t lfsp; lfsp_mount(&lfsp, &cfgp) => 0; - // we should be able to read the version using lfs_fs_stat - struct lfsp_fsinfo fsinfo; - lfsp_fs_stat(&lfsp, &fsinfo) => 0; - assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR); - // write another COUNT/2 dirs for (lfs_size_t i = COUNT/2; i < COUNT; i++) { char name[8]; @@ -1061,11 +1036,6 @@ code = ''' lfsp_t lfsp; lfsp_mount(&lfsp, &cfgp) => 0; - // we should be able to read the version using lfs_fs_stat - struct lfsp_fsinfo fsinfo; - lfsp_fs_stat(&lfsp, &fsinfo) => 0; - assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR); - // write half COUNT files prng = 42; for (lfs_size_t i = 0; i < COUNT; i++) { @@ -1183,11 +1153,6 @@ code = ''' lfsp_t lfsp; lfsp_mount(&lfsp, &cfgp) => 0; - // we should be able to read the version using lfs_fs_stat - struct lfsp_fsinfo fsinfo; - lfsp_fs_stat(&lfsp, &fsinfo) => 0; - assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR); - // write half COUNT files prng = 42; for (lfs_size_t i = 0; i < COUNT; i++) { From 8610f7c36b6554d65b7502222c76c286e4c8d0e2 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Tue, 6 Jun 2023 21:31:50 -0500 Subject: [PATCH 5/7] Increased context on failures for Valgrind in CI Valgrind output is very verbose but useful, with a default limit of 5 lines the output usually doesn't contain much useful info. --- .github/workflows/test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2cee3528..0d54b20a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -371,7 +371,8 @@ jobs: # on one geometry - name: test-valgrind run: | - TESTFLAGS="$TESTFLAGS --valgrind -Gdefault -Pnone" make test + TESTFLAGS="$TESTFLAGS --valgrind --context=1024 -Gdefault -Pnone" \ + make test # test that compilation is warning free under clang # run with Clang, mostly to check for Clang-specific warnings From c5fb3f181beff64de9cb2a7079594dfae29c982a Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Tue, 6 Jun 2023 21:52:04 -0500 Subject: [PATCH 6/7] Changed fsinfo.minor_version -> fsinfo.disk_version Version are now returned with major/minor packed into 32-bits, so 0x00020001 is the current disk version, for example. 1. This needed to change to use a disk_* prefix for consistency with the defines that already exist for LFS_VERSION/LFS_DISK_VERSION. 2. Encoding the version this way has the nice side effect of making 0 an invalid value. This is useful for adding a similar config option that needs to have reasonable default behavior for backwards compatibility. In theory this uses more space, but in practice most other config/status is 32-bits in littlefs. We would be wasting this space for alignment anyways. --- lfs.c | 6 +++--- lfs.h | 4 ++-- tests/test_compat.toml | 26 +++++++++++++------------- tests/test_superblocks.toml | 4 ++-- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/lfs.c b/lfs.c index 09fd71d9..c930adc5 100644 --- a/lfs.c +++ b/lfs.c @@ -4424,7 +4424,7 @@ static int lfs_fs_rawstat(lfs_t *lfs, struct lfs_fsinfo *fsinfo) { // if the superblock is up-to-date, we must be on the most recent // minor version of littlefs if (!lfs_gstate_needssuperblock(&lfs->gstate)) { - fsinfo->minor_version = LFS_DISK_VERSION_MINOR; + fsinfo->disk_version = LFS_DISK_VERSION; // otherwise we need to read the minor version on disk } else { @@ -4444,8 +4444,8 @@ static int lfs_fs_rawstat(lfs_t *lfs, struct lfs_fsinfo *fsinfo) { } lfs_superblock_fromle32(&superblock); - // read the minor version - fsinfo->minor_version = (0xffff & (superblock.version >> 0)); + // read the on-disk version + fsinfo->disk_version = superblock.version; } // find the current block usage diff --git a/lfs.h b/lfs.h index 25bd03c8..d477cdd4 100644 --- a/lfs.h +++ b/lfs.h @@ -282,8 +282,8 @@ struct lfs_info { // Filesystem info structure struct lfs_fsinfo { - // On-disk minor version. - uint16_t minor_version; + // On-disk version. + uint32_t disk_version; // Number of blocks in use, this is the same as lfs_fs_size. // diff --git a/tests/test_compat.toml b/tests/test_compat.toml index 4bb45d09..d84e68ea 100644 --- a/tests/test_compat.toml +++ b/tests/test_compat.toml @@ -80,7 +80,7 @@ code = ''' // we should be able to read the version using lfs_fs_stat struct lfs_fsinfo fsinfo; lfs_fs_stat(&lfs, &fsinfo) => 0; - assert(fsinfo.minor_version == LFSP_DISK_VERSION_MINOR); + assert(fsinfo.disk_version == LFSP_DISK_VERSION); lfs_unmount(&lfs) => 0; ''' @@ -113,7 +113,7 @@ code = ''' // we should be able to read the version using lfs_fs_stat struct lfs_fsinfo fsinfo; lfs_fs_stat(&lfs, &fsinfo) => 0; - assert(fsinfo.minor_version == LFSP_DISK_VERSION_MINOR); + assert(fsinfo.disk_version == LFSP_DISK_VERSION); // can we list the directories? lfs_dir_t dir; @@ -182,7 +182,7 @@ code = ''' // we should be able to read the version using lfs_fs_stat struct lfs_fsinfo fsinfo; lfs_fs_stat(&lfs, &fsinfo) => 0; - assert(fsinfo.minor_version == LFSP_DISK_VERSION_MINOR); + assert(fsinfo.disk_version == LFSP_DISK_VERSION); // can we list the files? lfs_dir_t dir; @@ -272,7 +272,7 @@ code = ''' // we should be able to read the version using lfs_fs_stat struct lfs_fsinfo fsinfo; lfs_fs_stat(&lfs, &fsinfo) => 0; - assert(fsinfo.minor_version == LFSP_DISK_VERSION_MINOR); + assert(fsinfo.disk_version == LFSP_DISK_VERSION); // can we list the directories? lfs_dir_t dir; @@ -369,7 +369,7 @@ code = ''' // we should be able to read the version using lfs_fs_stat struct lfs_fsinfo fsinfo; lfs_fs_stat(&lfs, &fsinfo) => 0; - assert(fsinfo.minor_version == LFSP_DISK_VERSION_MINOR); + assert(fsinfo.disk_version == LFSP_DISK_VERSION); // write another COUNT/2 dirs for (lfs_size_t i = COUNT/2; i < COUNT; i++) { @@ -451,7 +451,7 @@ code = ''' // we should be able to read the version using lfs_fs_stat struct lfs_fsinfo fsinfo; lfs_fs_stat(&lfs, &fsinfo) => 0; - assert(fsinfo.minor_version == LFSP_DISK_VERSION_MINOR); + assert(fsinfo.disk_version == LFSP_DISK_VERSION); // write half COUNT files prng = 42; @@ -573,7 +573,7 @@ code = ''' // we should be able to read the version using lfs_fs_stat struct lfs_fsinfo fsinfo; lfs_fs_stat(&lfs, &fsinfo) => 0; - assert(fsinfo.minor_version == LFSP_DISK_VERSION_MINOR); + assert(fsinfo.disk_version == LFSP_DISK_VERSION); // write half COUNT files prng = 42; @@ -1358,7 +1358,7 @@ code = ''' struct lfs_fsinfo fsinfo; lfs_fs_stat(&lfs, &fsinfo) => 0; - assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR-1); + assert(fsinfo.disk_version == LFS_DISK_VERSION-1); lfs_file_open(&lfs, &file, "test", LFS_O_RDONLY) => 0; uint8_t buffer[8]; @@ -1368,7 +1368,7 @@ code = ''' // minor version should be unchanged lfs_fs_stat(&lfs, &fsinfo) => 0; - assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR-1); + assert(fsinfo.disk_version == LFS_DISK_VERSION-1); lfs_unmount(&lfs) => 0; @@ -1376,7 +1376,7 @@ code = ''' lfs_mount(&lfs, cfg) => 0; lfs_fs_stat(&lfs, &fsinfo) => 0; - assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR-1); + assert(fsinfo.disk_version == LFS_DISK_VERSION-1); lfs_file_open(&lfs, &file, "test", LFS_O_WRONLY | LFS_O_TRUNC) => 0; lfs_file_write(&lfs, &file, "teeeeest", 8) => 8; @@ -1384,7 +1384,7 @@ code = ''' // minor version should be changed lfs_fs_stat(&lfs, &fsinfo) => 0; - assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR); + assert(fsinfo.disk_version == LFS_DISK_VERSION); lfs_unmount(&lfs) => 0; @@ -1393,7 +1393,7 @@ code = ''' // minor version should have changed lfs_fs_stat(&lfs, &fsinfo) => 0; - assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR); + assert(fsinfo.disk_version == LFS_DISK_VERSION); lfs_file_open(&lfs, &file, "test", LFS_O_RDONLY) => 0; lfs_file_read(&lfs, &file, buffer, 8) => 8; @@ -1402,7 +1402,7 @@ code = ''' // yep, still changed lfs_fs_stat(&lfs, &fsinfo) => 0; - assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR); + assert(fsinfo.disk_version == LFS_DISK_VERSION); lfs_unmount(&lfs) => 0; ''' diff --git a/tests/test_superblocks.toml b/tests/test_superblocks.toml index 3001fb41..6ce148ba 100644 --- a/tests/test_superblocks.toml +++ b/tests/test_superblocks.toml @@ -45,7 +45,7 @@ code = ''' struct lfs_fsinfo fsinfo; lfs_fs_stat(&lfs, &fsinfo) => 0; - assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR); + assert(fsinfo.disk_version == LFS_DISK_VERSION); assert(fsinfo.block_usage > 0 && fsinfo.block_usage < BLOCK_COUNT); assert(fsinfo.name_max == LFS_NAME_MAX); assert(fsinfo.file_max == LFS_FILE_MAX); @@ -73,7 +73,7 @@ code = ''' struct lfs_fsinfo fsinfo; lfs_fs_stat(&lfs, &fsinfo) => 0; - assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR); + assert(fsinfo.disk_version == LFS_DISK_VERSION); assert(fsinfo.block_usage > 0 && fsinfo.block_usage < BLOCK_COUNT); assert(fsinfo.name_max == TWEAKED_NAME_MAX); assert(fsinfo.file_max == TWEAKED_FILE_MAX); From 265692e709a1e379019d596fddaff8bfb4efb5e9 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Thu, 29 Jun 2023 12:23:33 -0500 Subject: [PATCH 7/7] Removed fsinfo.block_usage for now In terms of ease-of-use, a user familiar with other filesystems expects block_usage in fsinfo. But in terms of practicality, block_usage can be expensive to find in littlefs, so if it's not needed in the resulting fsinfo, that operation is wasteful. It's not clear to me what the best course of action is, but since block_usage can always be added to fsinfo later, but not removed without breaking backwards compatibility, I'm leaving this out for now. Block usage can still be found by explicitly calling lfs_fs_size. --- lfs.c | 7 ------- lfs.h | 6 ------ tests/test_superblocks.toml | 2 -- 3 files changed, 15 deletions(-) diff --git a/lfs.c b/lfs.c index c930adc5..9736c18e 100644 --- a/lfs.c +++ b/lfs.c @@ -4448,13 +4448,6 @@ static int lfs_fs_rawstat(lfs_t *lfs, struct lfs_fsinfo *fsinfo) { fsinfo->disk_version = superblock.version; } - // find the current block usage - lfs_ssize_t usage = lfs_fs_rawsize(lfs); - if (usage < 0) { - return usage; - } - fsinfo->block_usage = usage; - // other on-disk configuration, we cache all of these for internal use fsinfo->name_max = lfs->name_max; fsinfo->file_max = lfs->file_max; diff --git a/lfs.h b/lfs.h index d477cdd4..596ba5ed 100644 --- a/lfs.h +++ b/lfs.h @@ -285,12 +285,6 @@ struct lfs_fsinfo { // On-disk version. uint32_t disk_version; - // Number of blocks in use, this is the same as lfs_fs_size. - // - // Note: block_usage is best effort. If files share COW structures, the - // calculated block_usage may be larger than the actual contents on-disk. - lfs_size_t block_usage; - // Upper limit on the length of file names in bytes. lfs_size_t name_max; diff --git a/tests/test_superblocks.toml b/tests/test_superblocks.toml index 6ce148ba..f2fe452a 100644 --- a/tests/test_superblocks.toml +++ b/tests/test_superblocks.toml @@ -46,7 +46,6 @@ code = ''' struct lfs_fsinfo fsinfo; lfs_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.disk_version == LFS_DISK_VERSION); - assert(fsinfo.block_usage > 0 && fsinfo.block_usage < BLOCK_COUNT); assert(fsinfo.name_max == LFS_NAME_MAX); assert(fsinfo.file_max == LFS_FILE_MAX); assert(fsinfo.attr_max == LFS_ATTR_MAX); @@ -74,7 +73,6 @@ code = ''' struct lfs_fsinfo fsinfo; lfs_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.disk_version == LFS_DISK_VERSION); - assert(fsinfo.block_usage > 0 && fsinfo.block_usage < BLOCK_COUNT); assert(fsinfo.name_max == TWEAKED_NAME_MAX); assert(fsinfo.file_max == TWEAKED_FILE_MAX); assert(fsinfo.attr_max == TWEAKED_ATTR_MAX);