瀏覽代碼

Merge pull request #848 from littlefs-project/devel

Minor release: v2.7
Christopher Haster 2 年之前
父節點
當前提交
611c9b20db
共有 10 個文件被更改,包括 451 次插入91 次删除
  1. 1 1
      .github/workflows/release.yml
  2. 60 2
      .github/workflows/test.yml
  3. 5 0
      README.md
  4. 154 48
      lfs.c
  5. 30 1
      lfs.h
  6. 15 0
      runners/test_runner.c
  7. 5 2
      runners/test_runner.h
  8. 129 36
      tests/test_compat.toml
  9. 4 1
      tests/test_powerloss.toml
  10. 48 0
      tests/test_superblocks.toml

+ 1 - 1
.github/workflows/release.yml

@@ -102,7 +102,7 @@ jobs:
           # sizes table
           i=0
           j=0
-          for c in "" readonly threadsafe migrate error-asserts
+          for c in "" readonly threadsafe multiversion migrate error-asserts
           do
             # per-config results
             c_or_default=${c:-default}

+ 60 - 2
.github/workflows/test.yml

@@ -170,6 +170,27 @@ jobs:
           cp lfs.data.csv sizes/${{matrix.arch}}-threadsafe.data.csv
           cp lfs.stack.csv sizes/${{matrix.arch}}-threadsafe.stack.csv
           cp lfs.structs.csv sizes/${{matrix.arch}}-threadsafe.structs.csv
+      - name: sizes-multiversion
+        run: |
+          make clean
+          CFLAGS="$CFLAGS \
+            -DLFS_NO_ASSERT \
+            -DLFS_NO_DEBUG \
+            -DLFS_NO_WARN \
+            -DLFS_NO_ERROR \
+            -DLFS_MULTIVERSION" \
+            make lfs.code.csv lfs.data.csv lfs.stack.csv lfs.structs.csv
+          ./scripts/structs.py -u lfs.structs.csv
+          ./scripts/summary.py lfs.code.csv lfs.data.csv lfs.stack.csv \
+            -bfunction \
+            -fcode=code_size \
+            -fdata=data_size \
+            -fstack=stack_limit --max=stack_limit
+          mkdir -p sizes
+          cp lfs.code.csv sizes/${{matrix.arch}}-multiversion.code.csv
+          cp lfs.data.csv sizes/${{matrix.arch}}-multiversion.data.csv
+          cp lfs.stack.csv sizes/${{matrix.arch}}-multiversion.stack.csv
+          cp lfs.structs.csv sizes/${{matrix.arch}}-multiversion.structs.csv
       - name: sizes-migrate
         run: |
           make clean
@@ -353,6 +374,42 @@ jobs:
         run: |
           CFLAGS="$CFLAGS -DLFS_NO_INTRINSICS" make test
 
+  # run LFS_MULTIVERSION tests
+  test-multiversion:
+    runs-on: ubuntu-22.04
+    steps:
+      - uses: actions/checkout@v2
+      - name: install
+        run: |
+          # need a few things
+          sudo apt-get update -qq
+          sudo apt-get install -qq gcc python3 python3-pip
+          pip3 install toml
+          gcc --version
+          python3 --version
+      - name: test-multiversion
+        run: |
+          CFLAGS="$CFLAGS -DLFS_MULTIVERSION" make test
+
+  # run tests on the older version lfs2.0
+  test-lfs2_0:
+    runs-on: ubuntu-22.04
+    steps:
+      - uses: actions/checkout@v2
+      - name: install
+        run: |
+          # need a few things
+          sudo apt-get update -qq
+          sudo apt-get install -qq gcc python3 python3-pip
+          pip3 install toml
+          gcc --version
+          python3 --version
+      - name: test-lfs2_0
+        run: |
+          CFLAGS="$CFLAGS -DLFS_MULTIVERSION" \
+          TESTFLAGS="$TESTFLAGS -DDISK_VERSION=0x00020000" \
+            make test
+
   # run under Valgrind to check for memory errors
   test-valgrind:
     runs-on: ubuntu-22.04
@@ -371,7 +428,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
@@ -684,7 +742,7 @@ jobs:
           # sizes table
           i=0
           j=0
-          for c in "" readonly threadsafe migrate error-asserts
+          for c in "" readonly threadsafe multiversion migrate error-asserts
           do
             # per-config results
             c_or_default=${c:-default}

+ 5 - 0
README.md

@@ -250,6 +250,10 @@ License Identifiers that are here available: http://spdx.org/licenses/
   MCUs. It offers static wear-leveling and power-resilience with only a fixed
   _O(|address|)_ pointer structure stored on each block and in RAM.
 
+- [ChaN's FatFs] - A lightweight reimplementation of the infamous FAT filesystem
+  for microcontroller-scale devices. Due to limitations of FAT it can't provide
+  power-loss resilience, but it does allow easy interop with PCs.
+
 - [chamelon] - A pure-OCaml implementation of (most of) littlefs, designed for
   use with the MirageOS library operating system project. It is interoperable
   with the reference implementation, with some caveats.
@@ -266,6 +270,7 @@ License Identifiers that are here available: http://spdx.org/licenses/
 [LittleFileSystem]: https://os.mbed.com/docs/mbed-os/latest/apis/littlefilesystem.html
 [SPIFFS]: https://github.com/pellepl/spiffs
 [Dhara]: https://github.com/dlbeer/dhara
+[ChaN's FatFs]: http://elm-chan.org/fsw/ff/00index_e.html
 [littlefs-python]: https://pypi.org/project/littlefs-python/
 [littlefs2-rust]: https://crates.io/crates/littlefs2
 [chamelon]: https://github.com/yomimono/chamelon

+ 154 - 48
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) {
@@ -518,6 +518,28 @@ static void lfs_mlist_append(lfs_t *lfs, struct lfs_mlist *mlist) {
     lfs->mlist = mlist;
 }
 
+// some other filesystem operations
+static uint32_t lfs_fs_disk_version(lfs_t *lfs) {
+    (void)lfs;
+#ifdef LFS_MULTIVERSION
+    if (lfs->cfg->disk_version) {
+        return lfs->cfg->disk_version;
+    } else
+#endif
+    {
+        return LFS_DISK_VERSION;
+    }
+}
+
+static uint16_t lfs_fs_disk_version_major(lfs_t *lfs) {
+    return 0xffff & (lfs_fs_disk_version(lfs) >> 16);
+
+}
+
+static uint16_t lfs_fs_disk_version_minor(lfs_t *lfs) {
+    return 0xffff & (lfs_fs_disk_version(lfs) >> 0);
+}
+
 
 /// Internal operations predeclared here ///
 #ifndef LFS_READONLY
@@ -535,7 +557,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 +567,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);
@@ -1110,7 +1133,8 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs,
 
             // next commit not yet programmed?
             if (!lfs_tag_isvalid(tag)) {
-                maybeerased = true;
+                // we only might be erased if the last tag was a crc
+                maybeerased = (lfs_tag_type2(ptag) == LFS_TYPE_CCRC);
                 break;
             // out of range?
             } else if (off + lfs_tag_dsize(tag) > lfs->cfg->block_size) {
@@ -1155,14 +1179,11 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs,
                 dir->tail[1] = temptail[1];
                 dir->split = tempsplit;
 
-                // reset crc
+                // reset crc, hasfcrc
                 crc = 0xffffffff;
                 continue;
             }
 
-            // fcrc is only valid when last tag was a crc
-            hasfcrc = false;
-
             // crc the entry first, hopefully leaving it in the cache
             err = lfs_bd_crc(lfs,
                     NULL, &lfs->rcache, lfs->cfg->block_size,
@@ -1256,20 +1277,33 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs,
 
         // did we end on a valid commit? we may have an erased block
         dir->erased = false;
-        if (maybeerased && hasfcrc && dir->off % lfs->cfg->prog_size == 0) {
-            // check for an fcrc matching the next prog's erased state, if
-            // this failed most likely a previous prog was interrupted, we
-            // need a new erase
-            uint32_t fcrc_ = 0xffffffff;
-            int err = lfs_bd_crc(lfs,
-                    NULL, &lfs->rcache, lfs->cfg->block_size,
-                    dir->pair[0], dir->off, fcrc.size, &fcrc_);
-            if (err && err != LFS_ERR_CORRUPT) {
-                return err;
-            }
+        if (maybeerased && dir->off % lfs->cfg->prog_size == 0) {
+        #ifdef LFS_MULTIVERSION
+            // note versions < lfs2.1 did not have fcrc tags, if
+            // we're < lfs2.1 treat missing fcrc as erased data
+            //
+            // we don't strictly need to do this, but otherwise writing
+            // to lfs2.0 disks becomes very inefficient
+            if (lfs_fs_disk_version(lfs) < 0x00020001) {
+                dir->erased = true;
+
+            } else
+        #endif
+            if (hasfcrc) {
+                // check for an fcrc matching the next prog's erased state, if
+                // this failed most likely a previous prog was interrupted, we
+                // need a new erase
+                uint32_t fcrc_ = 0xffffffff;
+                int err = lfs_bd_crc(lfs,
+                        NULL, &lfs->rcache, lfs->cfg->block_size,
+                        dir->pair[0], dir->off, fcrc.size, &fcrc_);
+                if (err && err != LFS_ERR_CORRUPT) {
+                    return err;
+                }
 
-            // found beginning of erased part?
-            dir->erased = (fcrc_ == fcrc.crc);
+                // found beginning of erased part?
+                dir->erased = (fcrc_ == fcrc.crc);
+            }
         }
 
         // synthetic move
@@ -1605,22 +1639,34 @@ static int lfs_dir_commitcrc(lfs_t *lfs, struct lfs_commit *commit) {
                 return err;
             }
 
-            // find the expected fcrc, don't bother avoiding a reread
-            // of the eperturb, it should still be in our cache
-            struct lfs_fcrc fcrc = {.size=lfs->cfg->prog_size, .crc=0xffffffff};
-            err = lfs_bd_crc(lfs,
-                    NULL, &lfs->rcache, lfs->cfg->prog_size,
-                    commit->block, noff, fcrc.size, &fcrc.crc);
-            if (err && err != LFS_ERR_CORRUPT) {
-                return err;
-            }
+        #ifdef LFS_MULTIVERSION
+            // unfortunately fcrcs break mdir fetching < lfs2.1, so only write
+            // these if we're a >= lfs2.1 filesystem
+            if (lfs_fs_disk_version(lfs) <= 0x00020000) {
+                // don't write fcrc
+            } else
+        #endif
+            {
+                // find the expected fcrc, don't bother avoiding a reread
+                // of the eperturb, it should still be in our cache
+                struct lfs_fcrc fcrc = {
+                    .size = lfs->cfg->prog_size,
+                    .crc = 0xffffffff
+                };
+                err = lfs_bd_crc(lfs,
+                        NULL, &lfs->rcache, lfs->cfg->prog_size,
+                        commit->block, noff, fcrc.size, &fcrc.crc);
+                if (err && err != LFS_ERR_CORRUPT) {
+                    return err;
+                }
 
-            lfs_fcrc_tole32(&fcrc);
-            err = lfs_dir_commitattr(lfs, commit,
-                    LFS_MKTAG(LFS_TYPE_FCRC, 0x3ff, sizeof(struct lfs_fcrc)),
-                    &fcrc);
-            if (err) {
-                return err;
+                lfs_fcrc_tole32(&fcrc);
+                err = lfs_dir_commitattr(lfs, commit,
+                        LFS_MKTAG(LFS_TYPE_FCRC, 0x3ff, sizeof(struct lfs_fcrc)),
+                        &fcrc);
+                if (err) {
+                    return err;
+                }
             }
         }
 
@@ -4051,6 +4097,15 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) {
     lfs->cfg = cfg;
     int err = 0;
 
+#ifdef LFS_MULTIVERSION
+    // this driver only supports minor version < current minor version
+    LFS_ASSERT(!lfs->cfg->disk_version || (
+            (0xffff & (lfs->cfg->disk_version >> 16))
+                    == LFS_DISK_VERSION_MAJOR
+                && (0xffff & (lfs->cfg->disk_version >> 0))
+                    <= LFS_DISK_VERSION_MINOR));
+#endif
+
     // check that bool is a truthy-preserving type
     //
     // note the most common reason for this failure is a before-c99 compiler,
@@ -4208,7 +4263,7 @@ static int lfs_rawformat(lfs_t *lfs, const struct lfs_config *cfg) {
 
         // write one superblock
         lfs_superblock_t superblock = {
-            .version     = LFS_DISK_VERSION,
+            .version     = lfs_fs_disk_version(lfs),
             .block_size  = lfs->cfg->block_size,
             .block_count = lfs->cfg->block_count,
             .name_max    = lfs->name_max,
@@ -4306,12 +4361,14 @@ static int lfs_rawmount(lfs_t *lfs, const struct lfs_config *cfg) {
             // check version
             uint16_t major_version = (0xffff & (superblock.version >> 16));
             uint16_t minor_version = (0xffff & (superblock.version >>  0));
-            if ((major_version != LFS_DISK_VERSION_MAJOR ||
-                 minor_version > LFS_DISK_VERSION_MINOR)) {
+            if (major_version != lfs_fs_disk_version_major(lfs)
+                    || minor_version > lfs_fs_disk_version_minor(lfs)) {
                 LFS_ERROR("Invalid version "
                         "v%"PRIu16".%"PRIu16" != v%"PRIu16".%"PRIu16,
-                        major_version, minor_version,
-                        LFS_DISK_VERSION_MAJOR, LFS_DISK_VERSION_MINOR);
+                        major_version,
+                        minor_version,
+                        lfs_fs_disk_version_major(lfs),
+                        lfs_fs_disk_version_minor(lfs));
                 err = LFS_ERR_INVAL;
                 goto cleanup;
             }
@@ -4319,16 +4376,16 @@ static int lfs_rawmount(lfs_t *lfs, const struct lfs_config *cfg) {
             // found older minor version? set an in-device only bit in the
             // gstate so we know we need to rewrite the superblock before
             // the first write
-            if (minor_version < LFS_DISK_VERSION_MINOR) {
+            if (minor_version < lfs_fs_disk_version_minor(lfs)) {
                 LFS_DEBUG("Found older minor version "
                         "v%"PRIu16".%"PRIu16" < v%"PRIu16".%"PRIu16,
-                        major_version, minor_version,
-                        LFS_DISK_VERSION_MAJOR, LFS_DISK_VERSION_MINOR);
-            #ifndef LFS_READONLY
+                        major_version,
+                        minor_version,
+                        lfs_fs_disk_version_major(lfs),
+                        lfs_fs_disk_version_minor(lfs));
                 // 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 +4478,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_fs_disk_version(lfs);
+
+    // 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 +4724,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) {
@@ -4678,7 +4769,7 @@ static int lfs_fs_desuperblock(lfs_t *lfs) {
 
     // write a new superblock
     lfs_superblock_t superblock = {
-        .version     = LFS_DISK_VERSION,
+        .version     = lfs_fs_disk_version(lfs),
         .block_size  = lfs->cfg->block_size,
         .block_count = lfs->cfg->block_count,
         .name_max    = lfs->name_max,
@@ -4934,6 +5025,7 @@ static lfs_ssize_t lfs_fs_rawsize(lfs_t *lfs) {
     return size;
 }
 
+
 #ifdef LFS_MIGRATE
 ////// Migration from littelfs v1 below this //////
 
@@ -6053,6 +6145,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) {

+ 30 - 1
lfs.h

@@ -21,7 +21,7 @@ extern "C"
 // Software library version
 // Major (top-nibble), incremented on backwards incompatible changes
 // Minor (bottom-nibble), incremented on feature additions
-#define LFS_VERSION 0x00020006
+#define LFS_VERSION 0x00020007
 #define LFS_VERSION_MAJOR (0xffff & (LFS_VERSION >> 16))
 #define LFS_VERSION_MINOR (0xffff & (LFS_VERSION >>  0))
 
@@ -263,6 +263,14 @@ struct lfs_config {
     // can help bound the metadata compaction time. Must be <= block_size.
     // Defaults to block_size when zero.
     lfs_size_t metadata_max;
+
+#ifdef LFS_MULTIVERSION
+    // On-disk version to use when writing in the form of 16-bit major version
+    // + 16-bit minor version. This limiting metadata to what is supported by
+    // older minor versions. Note that some features will be lost. Defaults to 
+    // to the most recent minor version when zero.
+    uint32_t disk_version;
+#endif
 };
 
 // File info structure
@@ -280,6 +288,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 +682,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

+ 15 - 0
runners/test_runner.c

@@ -1346,6 +1346,9 @@ static void run_powerloss_none(
         .block_cycles       = BLOCK_CYCLES,
         .cache_size         = CACHE_SIZE,
         .lookahead_size     = LOOKAHEAD_SIZE,
+    #ifdef LFS_MULTIVERSION
+        .disk_version       = DISK_VERSION,
+    #endif
     };
 
     struct lfs_emubd_config bdcfg = {
@@ -1415,6 +1418,9 @@ static void run_powerloss_linear(
         .block_cycles       = BLOCK_CYCLES,
         .cache_size         = CACHE_SIZE,
         .lookahead_size     = LOOKAHEAD_SIZE,
+    #ifdef LFS_MULTIVERSION
+        .disk_version       = DISK_VERSION,
+    #endif
     };
 
     struct lfs_emubd_config bdcfg = {
@@ -1501,6 +1507,9 @@ static void run_powerloss_log(
         .block_cycles       = BLOCK_CYCLES,
         .cache_size         = CACHE_SIZE,
         .lookahead_size     = LOOKAHEAD_SIZE,
+    #ifdef LFS_MULTIVERSION
+        .disk_version       = DISK_VERSION,
+    #endif
     };
 
     struct lfs_emubd_config bdcfg = {
@@ -1585,6 +1594,9 @@ static void run_powerloss_cycles(
         .block_cycles       = BLOCK_CYCLES,
         .cache_size         = CACHE_SIZE,
         .lookahead_size     = LOOKAHEAD_SIZE,
+    #ifdef LFS_MULTIVERSION
+        .disk_version       = DISK_VERSION,
+    #endif
     };
 
     struct lfs_emubd_config bdcfg = {
@@ -1767,6 +1779,9 @@ static void run_powerloss_exhaustive(
         .block_cycles       = BLOCK_CYCLES,
         .cache_size         = CACHE_SIZE,
         .lookahead_size     = LOOKAHEAD_SIZE,
+    #ifdef LFS_MULTIVERSION
+        .disk_version       = DISK_VERSION,
+    #endif
     };
 
     struct lfs_emubd_config bdcfg = {

+ 5 - 2
runners/test_runner.h

@@ -91,6 +91,7 @@ intmax_t test_define(size_t define);
 #define ERASE_CYCLES_i       8
 #define BADBLOCK_BEHAVIOR_i  9
 #define POWERLOSS_BEHAVIOR_i 10
+#define DISK_VERSION_i       11
 
 #define READ_SIZE           TEST_DEFINE(READ_SIZE_i)
 #define PROG_SIZE           TEST_DEFINE(PROG_SIZE_i)
@@ -103,6 +104,7 @@ intmax_t test_define(size_t define);
 #define ERASE_CYCLES        TEST_DEFINE(ERASE_CYCLES_i)
 #define BADBLOCK_BEHAVIOR   TEST_DEFINE(BADBLOCK_BEHAVIOR_i)
 #define POWERLOSS_BEHAVIOR  TEST_DEFINE(POWERLOSS_BEHAVIOR_i)
+#define DISK_VERSION        TEST_DEFINE(DISK_VERSION_i)
 
 #define TEST_IMPLICIT_DEFINES \
     TEST_DEF(READ_SIZE,          PROG_SIZE) \
@@ -115,9 +117,10 @@ intmax_t test_define(size_t define);
     TEST_DEF(ERASE_VALUE,        0xff) \
     TEST_DEF(ERASE_CYCLES,       0) \
     TEST_DEF(BADBLOCK_BEHAVIOR,  LFS_EMUBD_BADBLOCK_PROGERROR) \
-    TEST_DEF(POWERLOSS_BEHAVIOR, LFS_EMUBD_POWERLOSS_NOOP)
+    TEST_DEF(POWERLOSS_BEHAVIOR, LFS_EMUBD_POWERLOSS_NOOP) \
+    TEST_DEF(DISK_VERSION,       0)
 
-#define TEST_IMPLICIT_DEFINE_COUNT 11
+#define TEST_IMPLICIT_DEFINE_COUNT 12
 #define TEST_GEOMETRY_DEFINE_COUNT 4
 
 

+ 129 - 36
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,10 @@ 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
+        && DISK_VERSION == 0
+'''
 code = '''
     // create the previous version
     struct lfsp_config cfgp;
@@ -74,13 +79,22 @@ 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
+        && DISK_VERSION == 0
+'''
 code = '''
     // create the previous version
     struct lfsp_config cfgp;
@@ -102,6 +116,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 +151,10 @@ 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
+        && DISK_VERSION == 0
+'''
 code = '''
     // create the previous version
     struct lfsp_config cfgp;
@@ -166,6 +188,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 +241,10 @@ 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
+        && DISK_VERSION == 0
+'''
 code = '''
     // create the previous version
     struct lfsp_config cfgp;
@@ -251,6 +281,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 +356,10 @@ 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
+        && DISK_VERSION == 0
+'''
 code = '''
     // create the previous version
     struct lfsp_config cfgp;
@@ -343,6 +381,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 +423,10 @@ 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
+        && DISK_VERSION == 0
+'''
 code = '''
     // create the previous version
     struct lfsp_config cfgp;
@@ -420,6 +466,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 +545,10 @@ 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
+        && DISK_VERSION == 0
+'''
 code = '''
     // create the previous version
     struct lfsp_config cfgp;
@@ -537,6 +591,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 +695,10 @@ 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
+        && DISK_VERSION == 0
+'''
 code = '''
     // create the new version
     lfs_t lfs;
@@ -651,13 +713,17 @@ 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
+        && DISK_VERSION == 0
+'''
 code = '''
     // create the new version
     lfs_t lfs;
@@ -709,7 +775,10 @@ 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
+        && DISK_VERSION == 0
+'''
 code = '''
     // create the new version
     lfs_t lfs;
@@ -791,7 +860,10 @@ 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
+        && DISK_VERSION == 0
+'''
 code = '''
     // create the new version
     lfs_t lfs;
@@ -898,7 +970,10 @@ 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
+        && DISK_VERSION == 0
+'''
 code = '''
     // create the new version
     lfs_t lfs;
@@ -957,7 +1032,10 @@ 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
+        && DISK_VERSION == 0
+'''
 code = '''
     // create the previous version
     lfs_t lfs;
@@ -1071,7 +1149,10 @@ 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
+        && DISK_VERSION == 0
+'''
 code = '''
     // create the previous version
     lfs_t lfs;
@@ -1280,7 +1361,10 @@ code = '''
 # test that we correctly bump the minor version
 [cases.test_compat_minor_bump]
 in = 'lfs.c'
-if = 'LFS_DISK_VERSION_MINOR > 0'
+if = '''
+    LFS_DISK_VERSION_MINOR > 0
+        && DISK_VERSION == 0
+'''
 code = '''
     // create a superblock
     lfs_t lfs;
@@ -1316,45 +1400,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;
 '''

+ 4 - 1
tests/test_powerloss.toml

@@ -90,7 +90,10 @@ code = '''
 
 # partial prog, may not be byte in order!
 [cases.test_powerloss_partial_prog]
-if = "PROG_SIZE < BLOCK_SIZE"
+if = '''
+    PROG_SIZE < BLOCK_SIZE
+        && (DISK_VERSION == 0 || DISK_VERSION >= 0x00020001)
+'''
 defines.BYTE_OFF = ["0", "PROG_SIZE-1", "PROG_SIZE/2"]
 defines.BYTE_VALUE = [0x33, 0xcc]
 in = "lfs.c"

+ 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]
+if = 'DISK_VERSION == 0'
+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]
+if = 'DISK_VERSION == 0'
+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]