From 87bbf1d37458b134e994e4cf0e355184a9a1af4b Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Tue, 6 Jun 2023 12:20:47 -0500 Subject: [PATCH] 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]