Browse Source

Merge pull request #848 from littlefs-project/devel

Minor release: v2.7
Christopher Haster 2 years ago
parent
commit
611c9b20db
10 changed files with 451 additions and 91 deletions
  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
           # sizes table
           i=0
           i=0
           j=0
           j=0
-          for c in "" readonly threadsafe migrate error-asserts
+          for c in "" readonly threadsafe multiversion migrate error-asserts
           do
           do
             # per-config results
             # per-config results
             c_or_default=${c:-default}
             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.data.csv sizes/${{matrix.arch}}-threadsafe.data.csv
           cp lfs.stack.csv sizes/${{matrix.arch}}-threadsafe.stack.csv
           cp lfs.stack.csv sizes/${{matrix.arch}}-threadsafe.stack.csv
           cp lfs.structs.csv sizes/${{matrix.arch}}-threadsafe.structs.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
       - name: sizes-migrate
         run: |
         run: |
           make clean
           make clean
@@ -353,6 +374,42 @@ jobs:
         run: |
         run: |
           CFLAGS="$CFLAGS -DLFS_NO_INTRINSICS" make test
           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
   # run under Valgrind to check for memory errors
   test-valgrind:
   test-valgrind:
     runs-on: ubuntu-22.04
     runs-on: ubuntu-22.04
@@ -371,7 +428,8 @@ jobs:
       # on one geometry
       # on one geometry
       - name: test-valgrind
       - name: test-valgrind
         run: |
         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
   # test that compilation is warning free under clang
   # run with Clang, mostly to check for Clang-specific warnings
   # run with Clang, mostly to check for Clang-specific warnings
@@ -684,7 +742,7 @@ jobs:
           # sizes table
           # sizes table
           i=0
           i=0
           j=0
           j=0
-          for c in "" readonly threadsafe migrate error-asserts
+          for c in "" readonly threadsafe multiversion migrate error-asserts
           do
           do
             # per-config results
             # per-config results
             c_or_default=${c:-default}
             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
   MCUs. It offers static wear-leveling and power-resilience with only a fixed
   _O(|address|)_ pointer structure stored on each block and in RAM.
   _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
 - [chamelon] - A pure-OCaml implementation of (most of) littlefs, designed for
   use with the MirageOS library operating system project. It is interoperable
   use with the MirageOS library operating system project. It is interoperable
   with the reference implementation, with some caveats.
   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
 [LittleFileSystem]: https://os.mbed.com/docs/mbed-os/latest/apis/littlefilesystem.html
 [SPIFFS]: https://github.com/pellepl/spiffs
 [SPIFFS]: https://github.com/pellepl/spiffs
 [Dhara]: https://github.com/dlbeer/dhara
 [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/
 [littlefs-python]: https://pypi.org/project/littlefs-python/
 [littlefs2-rust]: https://crates.io/crates/littlefs2
 [littlefs2-rust]: https://crates.io/crates/littlefs2
 [chamelon]: https://github.com/yomimono/chamelon
 [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) {
 static inline bool lfs_gstate_hasmove(const lfs_gstate_t *a) {
     return lfs_tag_type1(a->tag);
     return lfs_tag_type1(a->tag);
 }
 }
+#endif
 
 
 static inline bool lfs_gstate_needssuperblock(const lfs_gstate_t *a) {
 static inline bool lfs_gstate_needssuperblock(const lfs_gstate_t *a) {
     return lfs_tag_size(a->tag) >> 9;
     return lfs_tag_size(a->tag) >> 9;
 }
 }
-#endif
 
 
 static inline bool lfs_gstate_hasmovehere(const lfs_gstate_t *a,
 static inline bool lfs_gstate_hasmovehere(const lfs_gstate_t *a,
         const lfs_block_t *pair) {
         const lfs_block_t *pair) {
@@ -518,6 +518,28 @@ static void lfs_mlist_append(lfs_t *lfs, struct lfs_mlist *mlist) {
     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 ///
 /// Internal operations predeclared here ///
 #ifndef LFS_READONLY
 #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_file_flush(lfs_t *lfs, lfs_file_t *file);
 
 
 static int lfs_fs_deorphan(lfs_t *lfs, bool powerloss);
 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 int lfs_fs_preporphans(lfs_t *lfs, int8_t orphans);
 static void lfs_fs_prepmove(lfs_t *lfs,
 static void lfs_fs_prepmove(lfs_t *lfs,
         uint16_t id, const lfs_block_t pair[2]);
         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);
 static int lfs_fs_forceconsistency(lfs_t *lfs);
 #endif
 #endif
 
 
+static void lfs_fs_prepsuperblock(lfs_t *lfs, bool needssuperblock);
+
 #ifdef LFS_MIGRATE
 #ifdef LFS_MIGRATE
 static int lfs1_traverse(lfs_t *lfs,
 static int lfs1_traverse(lfs_t *lfs,
         int (*cb)(void*, lfs_block_t), void *data);
         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?
             // next commit not yet programmed?
             if (!lfs_tag_isvalid(tag)) {
             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;
                 break;
             // out of range?
             // out of range?
             } else if (off + lfs_tag_dsize(tag) > lfs->cfg->block_size) {
             } 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->tail[1] = temptail[1];
                 dir->split = tempsplit;
                 dir->split = tempsplit;
 
 
-                // reset crc
+                // reset crc, hasfcrc
                 crc = 0xffffffff;
                 crc = 0xffffffff;
                 continue;
                 continue;
             }
             }
 
 
-            // fcrc is only valid when last tag was a crc
-            hasfcrc = false;
-
             // crc the entry first, hopefully leaving it in the cache
             // crc the entry first, hopefully leaving it in the cache
             err = lfs_bd_crc(lfs,
             err = lfs_bd_crc(lfs,
                     NULL, &lfs->rcache, lfs->cfg->block_size,
                     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
         // did we end on a valid commit? we may have an erased block
         dir->erased = false;
         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
         // synthetic move
@@ -1605,22 +1639,34 @@ static int lfs_dir_commitcrc(lfs_t *lfs, struct lfs_commit *commit) {
                 return err;
                 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;
     lfs->cfg = cfg;
     int err = 0;
     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
     // check that bool is a truthy-preserving type
     //
     //
     // note the most common reason for this failure is a before-c99 compiler,
     // 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
         // write one superblock
         lfs_superblock_t superblock = {
         lfs_superblock_t superblock = {
-            .version     = LFS_DISK_VERSION,
+            .version     = lfs_fs_disk_version(lfs),
             .block_size  = lfs->cfg->block_size,
             .block_size  = lfs->cfg->block_size,
             .block_count = lfs->cfg->block_count,
             .block_count = lfs->cfg->block_count,
             .name_max    = lfs->name_max,
             .name_max    = lfs->name_max,
@@ -4306,12 +4361,14 @@ static int lfs_rawmount(lfs_t *lfs, const struct lfs_config *cfg) {
             // check version
             // check version
             uint16_t major_version = (0xffff & (superblock.version >> 16));
             uint16_t major_version = (0xffff & (superblock.version >> 16));
             uint16_t minor_version = (0xffff & (superblock.version >>  0));
             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 "
                 LFS_ERROR("Invalid version "
                         "v%"PRIu16".%"PRIu16" != v%"PRIu16".%"PRIu16,
                         "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;
                 err = LFS_ERR_INVAL;
                 goto cleanup;
                 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
             // found older minor version? set an in-device only bit in the
             // gstate so we know we need to rewrite the superblock before
             // gstate so we know we need to rewrite the superblock before
             // the first write
             // 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 "
                 LFS_DEBUG("Found older minor version "
                         "v%"PRIu16".%"PRIu16" < v%"PRIu16".%"PRIu16,
                         "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
                 // note this bit is reserved on disk, so fetching more gstate
                 // will not interfere here
                 // will not interfere here
                 lfs_fs_prepsuperblock(lfs, true);
                 lfs_fs_prepsuperblock(lfs, true);
-            #endif
             }
             }
 
 
             // check superblock configuration
             // check superblock configuration
@@ -4421,6 +4478,42 @@ static int lfs_rawunmount(lfs_t *lfs) {
 
 
 
 
 /// Filesystem filesystem operations ///
 /// 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 lfs_fs_rawtraverse(lfs_t *lfs,
         int (*cb)(void *data, lfs_block_t block), void *data,
         int (*cb)(void *data, lfs_block_t block), void *data,
         bool includeorphans) {
         bool includeorphans) {
@@ -4631,12 +4724,10 @@ static lfs_stag_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t pair[2],
 }
 }
 #endif
 #endif
 
 
-#ifndef LFS_READONLY
 static void lfs_fs_prepsuperblock(lfs_t *lfs, bool needssuperblock) {
 static void lfs_fs_prepsuperblock(lfs_t *lfs, bool needssuperblock) {
     lfs->gstate.tag = (lfs->gstate.tag & ~LFS_MKTAG(0, 0, 0x200))
     lfs->gstate.tag = (lfs->gstate.tag & ~LFS_MKTAG(0, 0, 0x200))
             | (uint32_t)needssuperblock << 9;
             | (uint32_t)needssuperblock << 9;
 }
 }
-#endif
 
 
 #ifndef LFS_READONLY
 #ifndef LFS_READONLY
 static int lfs_fs_preporphans(lfs_t *lfs, int8_t orphans) {
 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
     // write a new superblock
     lfs_superblock_t superblock = {
     lfs_superblock_t superblock = {
-        .version     = LFS_DISK_VERSION,
+        .version     = lfs_fs_disk_version(lfs),
         .block_size  = lfs->cfg->block_size,
         .block_size  = lfs->cfg->block_size,
         .block_count = lfs->cfg->block_count,
         .block_count = lfs->cfg->block_count,
         .name_max    = lfs->name_max,
         .name_max    = lfs->name_max,
@@ -4934,6 +5025,7 @@ static lfs_ssize_t lfs_fs_rawsize(lfs_t *lfs) {
     return size;
     return size;
 }
 }
 
 
+
 #ifdef LFS_MIGRATE
 #ifdef LFS_MIGRATE
 ////// Migration from littelfs v1 below this //////
 ////// Migration from littelfs v1 below this //////
 
 
@@ -6053,6 +6145,20 @@ int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir) {
     return err;
     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) {
 lfs_ssize_t lfs_fs_size(lfs_t *lfs) {
     int err = LFS_LOCK(lfs->cfg);
     int err = LFS_LOCK(lfs->cfg);
     if (err) {
     if (err) {

+ 30 - 1
lfs.h

@@ -21,7 +21,7 @@ extern "C"
 // Software library version
 // Software library version
 // Major (top-nibble), incremented on backwards incompatible changes
 // Major (top-nibble), incremented on backwards incompatible changes
 // Minor (bottom-nibble), incremented on feature additions
 // 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_MAJOR (0xffff & (LFS_VERSION >> 16))
 #define LFS_VERSION_MINOR (0xffff & (LFS_VERSION >>  0))
 #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.
     // can help bound the metadata compaction time. Must be <= block_size.
     // Defaults to block_size when zero.
     // Defaults to block_size when zero.
     lfs_size_t metadata_max;
     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
 // File info structure
@@ -280,6 +288,21 @@ struct lfs_info {
     char name[LFS_NAME_MAX+1];
     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
 // Custom attribute structure, used to describe custom attributes
 // committed atomically during file writes.
 // committed atomically during file writes.
 struct lfs_attr {
 struct lfs_attr {
@@ -659,6 +682,12 @@ int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir);
 
 
 /// Filesystem-level filesystem operations
 /// 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
 // Finds the current size of the filesystem
 //
 //
 // Note: Result is best effort. If files share COW structures, the returned
 // 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,
         .block_cycles       = BLOCK_CYCLES,
         .cache_size         = CACHE_SIZE,
         .cache_size         = CACHE_SIZE,
         .lookahead_size     = LOOKAHEAD_SIZE,
         .lookahead_size     = LOOKAHEAD_SIZE,
+    #ifdef LFS_MULTIVERSION
+        .disk_version       = DISK_VERSION,
+    #endif
     };
     };
 
 
     struct lfs_emubd_config bdcfg = {
     struct lfs_emubd_config bdcfg = {
@@ -1415,6 +1418,9 @@ static void run_powerloss_linear(
         .block_cycles       = BLOCK_CYCLES,
         .block_cycles       = BLOCK_CYCLES,
         .cache_size         = CACHE_SIZE,
         .cache_size         = CACHE_SIZE,
         .lookahead_size     = LOOKAHEAD_SIZE,
         .lookahead_size     = LOOKAHEAD_SIZE,
+    #ifdef LFS_MULTIVERSION
+        .disk_version       = DISK_VERSION,
+    #endif
     };
     };
 
 
     struct lfs_emubd_config bdcfg = {
     struct lfs_emubd_config bdcfg = {
@@ -1501,6 +1507,9 @@ static void run_powerloss_log(
         .block_cycles       = BLOCK_CYCLES,
         .block_cycles       = BLOCK_CYCLES,
         .cache_size         = CACHE_SIZE,
         .cache_size         = CACHE_SIZE,
         .lookahead_size     = LOOKAHEAD_SIZE,
         .lookahead_size     = LOOKAHEAD_SIZE,
+    #ifdef LFS_MULTIVERSION
+        .disk_version       = DISK_VERSION,
+    #endif
     };
     };
 
 
     struct lfs_emubd_config bdcfg = {
     struct lfs_emubd_config bdcfg = {
@@ -1585,6 +1594,9 @@ static void run_powerloss_cycles(
         .block_cycles       = BLOCK_CYCLES,
         .block_cycles       = BLOCK_CYCLES,
         .cache_size         = CACHE_SIZE,
         .cache_size         = CACHE_SIZE,
         .lookahead_size     = LOOKAHEAD_SIZE,
         .lookahead_size     = LOOKAHEAD_SIZE,
+    #ifdef LFS_MULTIVERSION
+        .disk_version       = DISK_VERSION,
+    #endif
     };
     };
 
 
     struct lfs_emubd_config bdcfg = {
     struct lfs_emubd_config bdcfg = {
@@ -1767,6 +1779,9 @@ static void run_powerloss_exhaustive(
         .block_cycles       = BLOCK_CYCLES,
         .block_cycles       = BLOCK_CYCLES,
         .cache_size         = CACHE_SIZE,
         .cache_size         = CACHE_SIZE,
         .lookahead_size     = LOOKAHEAD_SIZE,
         .lookahead_size     = LOOKAHEAD_SIZE,
+    #ifdef LFS_MULTIVERSION
+        .disk_version       = DISK_VERSION,
+    #endif
     };
     };
 
 
     struct lfs_emubd_config bdcfg = {
     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 ERASE_CYCLES_i       8
 #define BADBLOCK_BEHAVIOR_i  9
 #define BADBLOCK_BEHAVIOR_i  9
 #define POWERLOSS_BEHAVIOR_i 10
 #define POWERLOSS_BEHAVIOR_i 10
+#define DISK_VERSION_i       11
 
 
 #define READ_SIZE           TEST_DEFINE(READ_SIZE_i)
 #define READ_SIZE           TEST_DEFINE(READ_SIZE_i)
 #define PROG_SIZE           TEST_DEFINE(PROG_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 ERASE_CYCLES        TEST_DEFINE(ERASE_CYCLES_i)
 #define BADBLOCK_BEHAVIOR   TEST_DEFINE(BADBLOCK_BEHAVIOR_i)
 #define BADBLOCK_BEHAVIOR   TEST_DEFINE(BADBLOCK_BEHAVIOR_i)
 #define POWERLOSS_BEHAVIOR  TEST_DEFINE(POWERLOSS_BEHAVIOR_i)
 #define POWERLOSS_BEHAVIOR  TEST_DEFINE(POWERLOSS_BEHAVIOR_i)
+#define DISK_VERSION        TEST_DEFINE(DISK_VERSION_i)
 
 
 #define TEST_IMPLICIT_DEFINES \
 #define TEST_IMPLICIT_DEFINES \
     TEST_DEF(READ_SIZE,          PROG_SIZE) \
     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_VALUE,        0xff) \
     TEST_DEF(ERASE_CYCLES,       0) \
     TEST_DEF(ERASE_CYCLES,       0) \
     TEST_DEF(BADBLOCK_BEHAVIOR,  LFS_EMUBD_BADBLOCK_PROGERROR) \
     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
 #define TEST_GEOMETRY_DEFINE_COUNT 4
 
 
 
 

+ 129 - 36
tests/test_compat.toml

@@ -22,14 +22,16 @@ code = '''
 #define STRINGIZE_(x) #x
 #define STRINGIZE_(x) #x
 #include STRINGIZE(LFSP)
 #include STRINGIZE(LFSP)
 #else
 #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_t lfs_t
 #define lfsp_config lfs_config
 #define lfsp_config lfs_config
 #define lfsp_format lfs_format
 #define lfsp_format lfs_format
 #define lfsp_mount lfs_mount
 #define lfsp_mount lfs_mount
 #define lfsp_unmount lfs_unmount
 #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_dir_t lfs_dir_t
 #define lfsp_info lfs_info
 #define lfsp_info lfs_info
 #define LFSP_TYPE_REG LFS_TYPE_REG
 #define LFSP_TYPE_REG LFS_TYPE_REG
@@ -58,7 +60,10 @@ code = '''
 
 
 # test we can mount in a new version
 # test we can mount in a new version
 [cases.test_compat_forward_mount]
 [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 = '''
 code = '''
     // create the previous version
     // create the previous version
     struct lfsp_config cfgp;
     struct lfsp_config cfgp;
@@ -74,13 +79,22 @@ code = '''
     // now test the new mount
     // now test the new mount
     lfs_t lfs;
     lfs_t lfs;
     lfs_mount(&lfs, cfg) => 0;
     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;
     lfs_unmount(&lfs) => 0;
 '''
 '''
 
 
 # test we can read dirs in a new version
 # test we can read dirs in a new version
 [cases.test_compat_forward_read_dirs]
 [cases.test_compat_forward_read_dirs]
 defines.COUNT = 5
 defines.COUNT = 5
-if = 'LFS_VERSION_MAJOR == LFSP_VERSION_MAJOR'
+if = '''
+    LFS_DISK_VERSION_MAJOR == LFSP_DISK_VERSION_MAJOR
+        && DISK_VERSION == 0
+'''
 code = '''
 code = '''
     // create the previous version
     // create the previous version
     struct lfsp_config cfgp;
     struct lfsp_config cfgp;
@@ -102,6 +116,11 @@ code = '''
     lfs_t lfs;
     lfs_t lfs;
     lfs_mount(&lfs, cfg) => 0;
     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?
     // can we list the directories?
     lfs_dir_t dir;
     lfs_dir_t dir;
     lfs_dir_open(&lfs, &dir, "/") => 0;
     lfs_dir_open(&lfs, &dir, "/") => 0;
@@ -132,7 +151,10 @@ code = '''
 defines.COUNT = 5
 defines.COUNT = 5
 defines.SIZE = [4, 32, 512, 8192]
 defines.SIZE = [4, 32, 512, 8192]
 defines.CHUNK = 4
 defines.CHUNK = 4
-if = 'LFS_VERSION_MAJOR == LFSP_VERSION_MAJOR'
+if = '''
+    LFS_DISK_VERSION_MAJOR == LFSP_DISK_VERSION_MAJOR
+        && DISK_VERSION == 0
+'''
 code = '''
 code = '''
     // create the previous version
     // create the previous version
     struct lfsp_config cfgp;
     struct lfsp_config cfgp;
@@ -166,6 +188,11 @@ code = '''
     lfs_t lfs;
     lfs_t lfs;
     lfs_mount(&lfs, cfg) => 0;
     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?
     // can we list the files?
     lfs_dir_t dir;
     lfs_dir_t dir;
     lfs_dir_open(&lfs, &dir, "/") => 0;
     lfs_dir_open(&lfs, &dir, "/") => 0;
@@ -214,7 +241,10 @@ code = '''
 defines.COUNT = 5
 defines.COUNT = 5
 defines.SIZE = [4, 32, 512, 8192]
 defines.SIZE = [4, 32, 512, 8192]
 defines.CHUNK = 4
 defines.CHUNK = 4
-if = 'LFS_VERSION_MAJOR == LFSP_VERSION_MAJOR'
+if = '''
+    LFS_DISK_VERSION_MAJOR == LFSP_DISK_VERSION_MAJOR
+        && DISK_VERSION == 0
+'''
 code = '''
 code = '''
     // create the previous version
     // create the previous version
     struct lfsp_config cfgp;
     struct lfsp_config cfgp;
@@ -251,6 +281,11 @@ code = '''
     lfs_t lfs;
     lfs_t lfs;
     lfs_mount(&lfs, cfg) => 0;
     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?
     // can we list the directories?
     lfs_dir_t dir;
     lfs_dir_t dir;
     lfs_dir_open(&lfs, &dir, "/") => 0;
     lfs_dir_open(&lfs, &dir, "/") => 0;
@@ -321,7 +356,10 @@ code = '''
 # test we can write dirs in a new version
 # test we can write dirs in a new version
 [cases.test_compat_forward_write_dirs]
 [cases.test_compat_forward_write_dirs]
 defines.COUNT = 10
 defines.COUNT = 10
-if = 'LFS_VERSION_MAJOR == LFSP_VERSION_MAJOR'
+if = '''
+    LFS_DISK_VERSION_MAJOR == LFSP_DISK_VERSION_MAJOR
+        && DISK_VERSION == 0
+'''
 code = '''
 code = '''
     // create the previous version
     // create the previous version
     struct lfsp_config cfgp;
     struct lfsp_config cfgp;
@@ -343,6 +381,11 @@ code = '''
     lfs_t lfs;
     lfs_t lfs;
     lfs_mount(&lfs, cfg) => 0;
     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
     // write another COUNT/2 dirs
     for (lfs_size_t i = COUNT/2; i < COUNT; i++) {
     for (lfs_size_t i = COUNT/2; i < COUNT; i++) {
         char name[8];
         char name[8];
@@ -380,7 +423,10 @@ code = '''
 defines.COUNT = 5
 defines.COUNT = 5
 defines.SIZE = [4, 32, 512, 8192]
 defines.SIZE = [4, 32, 512, 8192]
 defines.CHUNK = 2
 defines.CHUNK = 2
-if = 'LFS_VERSION_MAJOR == LFSP_VERSION_MAJOR'
+if = '''
+    LFS_DISK_VERSION_MAJOR == LFSP_DISK_VERSION_MAJOR
+        && DISK_VERSION == 0
+'''
 code = '''
 code = '''
     // create the previous version
     // create the previous version
     struct lfsp_config cfgp;
     struct lfsp_config cfgp;
@@ -420,6 +466,11 @@ code = '''
     lfs_t lfs;
     lfs_t lfs;
     lfs_mount(&lfs, cfg) => 0;
     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
     // write half COUNT files
     prng = 42;
     prng = 42;
     for (lfs_size_t i = 0; i < COUNT; i++) {
     for (lfs_size_t i = 0; i < COUNT; i++) {
@@ -494,7 +545,10 @@ code = '''
 defines.COUNT = 5
 defines.COUNT = 5
 defines.SIZE = [4, 32, 512, 8192]
 defines.SIZE = [4, 32, 512, 8192]
 defines.CHUNK = 2
 defines.CHUNK = 2
-if = 'LFS_VERSION_MAJOR == LFSP_VERSION_MAJOR'
+if = '''
+    LFS_DISK_VERSION_MAJOR == LFSP_DISK_VERSION_MAJOR
+        && DISK_VERSION == 0
+'''
 code = '''
 code = '''
     // create the previous version
     // create the previous version
     struct lfsp_config cfgp;
     struct lfsp_config cfgp;
@@ -537,6 +591,11 @@ code = '''
     lfs_t lfs;
     lfs_t lfs;
     lfs_mount(&lfs, cfg) => 0;
     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
     // write half COUNT files
     prng = 42;
     prng = 42;
     for (lfs_size_t i = 0; i < COUNT; i++) {
     for (lfs_size_t i = 0; i < COUNT; i++) {
@@ -636,7 +695,10 @@ code = '''
 
 
 # test we can mount in an old version
 # test we can mount in an old version
 [cases.test_compat_backward_mount]
 [cases.test_compat_backward_mount]
-if = 'LFS_VERSION == LFSP_VERSION'
+if = '''
+    LFS_DISK_VERSION == LFSP_DISK_VERSION
+        && DISK_VERSION == 0
+'''
 code = '''
 code = '''
     // create the new version
     // create the new version
     lfs_t lfs;
     lfs_t lfs;
@@ -651,13 +713,17 @@ code = '''
     memcpy(&cfgp, cfg, sizeof(cfgp));
     memcpy(&cfgp, cfg, sizeof(cfgp));
     lfsp_t lfsp;
     lfsp_t lfsp;
     lfsp_mount(&lfsp, &cfgp) => 0;
     lfsp_mount(&lfsp, &cfgp) => 0;
+
     lfsp_unmount(&lfsp) => 0;
     lfsp_unmount(&lfsp) => 0;
 '''
 '''
 
 
 # test we can read dirs in an old version
 # test we can read dirs in an old version
 [cases.test_compat_backward_read_dirs]
 [cases.test_compat_backward_read_dirs]
 defines.COUNT = 5
 defines.COUNT = 5
-if = 'LFS_VERSION == LFSP_VERSION'
+if = '''
+    LFS_DISK_VERSION == LFSP_DISK_VERSION
+        && DISK_VERSION == 0
+'''
 code = '''
 code = '''
     // create the new version
     // create the new version
     lfs_t lfs;
     lfs_t lfs;
@@ -709,7 +775,10 @@ code = '''
 defines.COUNT = 5
 defines.COUNT = 5
 defines.SIZE = [4, 32, 512, 8192]
 defines.SIZE = [4, 32, 512, 8192]
 defines.CHUNK = 4
 defines.CHUNK = 4
-if = 'LFS_VERSION == LFSP_VERSION'
+if = '''
+    LFS_DISK_VERSION == LFSP_DISK_VERSION
+        && DISK_VERSION == 0
+'''
 code = '''
 code = '''
     // create the new version
     // create the new version
     lfs_t lfs;
     lfs_t lfs;
@@ -791,7 +860,10 @@ code = '''
 defines.COUNT = 5
 defines.COUNT = 5
 defines.SIZE = [4, 32, 512, 8192]
 defines.SIZE = [4, 32, 512, 8192]
 defines.CHUNK = 4
 defines.CHUNK = 4
-if = 'LFS_VERSION == LFSP_VERSION'
+if = '''
+    LFS_DISK_VERSION == LFSP_DISK_VERSION
+        && DISK_VERSION == 0
+'''
 code = '''
 code = '''
     // create the new version
     // create the new version
     lfs_t lfs;
     lfs_t lfs;
@@ -898,7 +970,10 @@ code = '''
 # test we can write dirs in an old version
 # test we can write dirs in an old version
 [cases.test_compat_backward_write_dirs]
 [cases.test_compat_backward_write_dirs]
 defines.COUNT = 10
 defines.COUNT = 10
-if = 'LFS_VERSION == LFSP_VERSION'
+if = '''
+    LFS_DISK_VERSION == LFSP_DISK_VERSION
+        && DISK_VERSION == 0
+'''
 code = '''
 code = '''
     // create the new version
     // create the new version
     lfs_t lfs;
     lfs_t lfs;
@@ -957,7 +1032,10 @@ code = '''
 defines.COUNT = 5
 defines.COUNT = 5
 defines.SIZE = [4, 32, 512, 8192]
 defines.SIZE = [4, 32, 512, 8192]
 defines.CHUNK = 2
 defines.CHUNK = 2
-if = 'LFS_VERSION == LFSP_VERSION'
+if = '''
+    LFS_DISK_VERSION == LFSP_DISK_VERSION
+        && DISK_VERSION == 0
+'''
 code = '''
 code = '''
     // create the previous version
     // create the previous version
     lfs_t lfs;
     lfs_t lfs;
@@ -1071,7 +1149,10 @@ code = '''
 defines.COUNT = 5
 defines.COUNT = 5
 defines.SIZE = [4, 32, 512, 8192]
 defines.SIZE = [4, 32, 512, 8192]
 defines.CHUNK = 2
 defines.CHUNK = 2
-if = 'LFS_VERSION == LFSP_VERSION'
+if = '''
+    LFS_DISK_VERSION == LFSP_DISK_VERSION
+        && DISK_VERSION == 0
+'''
 code = '''
 code = '''
     // create the previous version
     // create the previous version
     lfs_t lfs;
     lfs_t lfs;
@@ -1280,7 +1361,10 @@ code = '''
 # test that we correctly bump the minor version
 # test that we correctly bump the minor version
 [cases.test_compat_minor_bump]
 [cases.test_compat_minor_bump]
 in = 'lfs.c'
 in = 'lfs.c'
-if = 'LFS_DISK_VERSION_MINOR > 0'
+if = '''
+    LFS_DISK_VERSION_MINOR > 0
+        && DISK_VERSION == 0
+'''
 code = '''
 code = '''
     // create a superblock
     // create a superblock
     lfs_t lfs;
     lfs_t lfs;
@@ -1316,45 +1400,54 @@ code = '''
 
 
     // mount should still work
     // mount should still work
     lfs_mount(&lfs, cfg) => 0;
     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;
     lfs_file_open(&lfs, &file, "test", LFS_O_RDONLY) => 0;
     uint8_t buffer[8];
     uint8_t buffer[8];
     lfs_file_read(&lfs, &file, buffer, 8) => 8;
     lfs_file_read(&lfs, &file, buffer, 8) => 8;
     assert(memcmp(buffer, "testtest", 8) == 0);
     assert(memcmp(buffer, "testtest", 8) == 0);
     lfs_file_close(&lfs, &file) => 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;
     lfs_unmount(&lfs) => 0;
 
 
     // if we write, we need to bump the minor version
     // if we write, we need to bump the minor version
     lfs_mount(&lfs, cfg) => 0;
     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_open(&lfs, &file, "test", LFS_O_WRONLY | LFS_O_TRUNC) => 0;
     lfs_file_write(&lfs, &file, "teeeeest", 8) => 8;
     lfs_file_write(&lfs, &file, "teeeeest", 8) => 8;
     lfs_file_close(&lfs, &file) => 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);
+    // minor version should be changed
+    lfs_fs_stat(&lfs, &fsinfo) => 0;
+    assert(fsinfo.disk_version == LFS_DISK_VERSION);
+
     lfs_unmount(&lfs) => 0;
     lfs_unmount(&lfs) => 0;
 
 
     // and of course mount should still work
     // and of course mount should still work
     lfs_mount(&lfs, cfg) => 0;
     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_open(&lfs, &file, "test", LFS_O_RDONLY) => 0;
     lfs_file_read(&lfs, &file, buffer, 8) => 8;
     lfs_file_read(&lfs, &file, buffer, 8) => 8;
     assert(memcmp(buffer, "teeeeest", 8) == 0);
     assert(memcmp(buffer, "teeeeest", 8) == 0);
     lfs_file_close(&lfs, &file) => 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;
     lfs_unmount(&lfs) => 0;
 '''
 '''

+ 4 - 1
tests/test_powerloss.toml

@@ -90,7 +90,10 @@ code = '''
 
 
 # partial prog, may not be byte in order!
 # partial prog, may not be byte in order!
 [cases.test_powerloss_partial_prog]
 [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_OFF = ["0", "PROG_SIZE-1", "PROG_SIZE/2"]
 defines.BYTE_VALUE = [0x33, 0xcc]
 defines.BYTE_VALUE = [0x33, 0xcc]
 in = "lfs.c"
 in = "lfs.c"

+ 48 - 0
tests/test_superblocks.toml

@@ -34,6 +34,54 @@ code = '''
     lfs_mount(&lfs, cfg) => LFS_ERR_CORRUPT;
     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
 # expanding superblock
 [cases.test_superblocks_expand]
 [cases.test_superblocks_expand]
 defines.BLOCK_CYCLES = [32, 33, 1]
 defines.BLOCK_CYCLES = [32, 33, 1]