Quellcode durchsuchen

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.
Christopher Haster vor 2 Jahren
Ursprung
Commit
87bbf1d374
4 geänderte Dateien mit 237 neuen und 20 gelöschten Zeilen
  1. 58 2
      lfs.c
  2. 27 0
      lfs.h
  3. 104 18
      tests/test_compat.toml
  4. 48 0
      tests/test_superblocks.toml

+ 58 - 2
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) {

+ 27 - 0
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

+ 104 - 18
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;
 '''

+ 48 - 0
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]