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 diff --git a/lfs.c b/lfs.c index f0daf540..9736c18e 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); @@ -4324,11 +4325,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 +4420,42 @@ 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->disk_version = LFS_DISK_VERSION; + + // 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 on-disk version + fsinfo->disk_version = superblock.version; + } + + // 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) { @@ -4631,12 +4666,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) { @@ -4934,6 +4967,7 @@ static lfs_ssize_t lfs_fs_rawsize(lfs_t *lfs) { return size; } + #ifdef LFS_MIGRATE ////// Migration from littelfs v1 below this ////// @@ -6053,6 +6087,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..596ba5ed 100644 --- a/lfs.h +++ b/lfs.h @@ -280,6 +280,21 @@ struct lfs_info { char name[LFS_NAME_MAX+1]; }; +// Filesystem info structure +struct lfs_fsinfo { + // On-disk version. + uint32_t disk_version; + + // 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 +674,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..d84e68ea 100644 --- a/tests/test_compat.toml +++ b/tests/test_compat.toml @@ -22,14 +22,16 @@ 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 #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 @@ -58,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; @@ -74,13 +76,19 @@ 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.disk_version == LFSP_DISK_VERSION); + lfs_unmount(&lfs) => 0; ''' # 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; @@ -102,6 +110,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.disk_version == LFSP_DISK_VERSION); + // can we list the directories? lfs_dir_t dir; lfs_dir_open(&lfs, &dir, "/") => 0; @@ -132,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; @@ -166,6 +179,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.disk_version == LFSP_DISK_VERSION); + // can we list the files? lfs_dir_t dir; lfs_dir_open(&lfs, &dir, "/") => 0; @@ -214,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; @@ -251,6 +269,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.disk_version == LFSP_DISK_VERSION); + // can we list the directories? lfs_dir_t dir; lfs_dir_open(&lfs, &dir, "/") => 0; @@ -321,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; @@ -343,6 +366,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.disk_version == LFSP_DISK_VERSION); + // write another COUNT/2 dirs for (lfs_size_t i = COUNT/2; i < COUNT; i++) { char name[8]; @@ -380,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; @@ -420,6 +448,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.disk_version == LFSP_DISK_VERSION); + // write half COUNT files prng = 42; for (lfs_size_t i = 0; i < COUNT; i++) { @@ -494,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; @@ -537,6 +570,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.disk_version == LFSP_DISK_VERSION); + // write half COUNT files prng = 42; for (lfs_size_t i = 0; i < COUNT; i++) { @@ -636,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; @@ -651,13 +689,14 @@ code = ''' memcpy(&cfgp, cfg, sizeof(cfgp)); lfsp_t lfsp; lfsp_mount(&lfsp, &cfgp) => 0; + lfsp_unmount(&lfsp) => 0; ''' # 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; @@ -709,7 +748,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; @@ -791,7 +830,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; @@ -898,7 +937,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; @@ -957,7 +996,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; @@ -1071,7 +1110,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; @@ -1316,45 +1355,54 @@ code = ''' // mount should still work lfs_mount(&lfs, cfg) => 0; + + struct lfs_fsinfo fsinfo; + lfs_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS_DISK_VERSION-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.disk_version == LFS_DISK_VERSION-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.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; 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.disk_version == LFS_DISK_VERSION); + 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.disk_version == LFS_DISK_VERSION); + 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.disk_version == LFS_DISK_VERSION); + lfs_unmount(&lfs) => 0; ''' diff --git a/tests/test_superblocks.toml b/tests/test_superblocks.toml index 689bbcd2..f2fe452a 100644 --- a/tests/test_superblocks.toml +++ b/tests/test_superblocks.toml @@ -34,6 +34,52 @@ 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.disk_version == LFS_DISK_VERSION); + 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.disk_version == LFS_DISK_VERSION); + 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]