Jelajahi Sumber

Merge pull request #838 from littlefs-project/fs-stat

Add lfs_fs_stat for access to filesystem status/configuration
Christopher Haster 2 tahun lalu
induk
melakukan
f09c6a4eb7
5 mengubah file dengan 206 tambahan dan 42 penghapusan
  1. 2 1
      .github/workflows/test.yml
  2. 54 6
      lfs.c
  3. 21 0
      lfs.h
  4. 83 35
      tests/test_compat.toml
  5. 46 0
      tests/test_superblocks.toml

+ 2 - 1
.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

+ 54 - 6
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) {

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

+ 83 - 35
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;
 '''

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