diff --git a/lfs.c b/lfs.c
index d35d5d6d..96282175 100644
--- a/lfs.c
+++ b/lfs.c
@@ -4521,7 +4521,9 @@ static int lfs_mount_(lfs_t *lfs, const struct lfs_config *cfg) {
}
// this is where we get the block_count from disk if block_count=0
- if (lfs->cfg->block_count
+
+ if ((lfs->cfg->flags & LFS_CFG_DISABLE_BLOCK_COUNT_CHECK) == 0
+ && lfs->cfg->block_count
&& superblock.block_count != lfs->cfg->block_count) {
LFS_ERROR("Invalid block count (%"PRIu32" != %"PRIu32")",
superblock.block_count, lfs->cfg->block_count);
@@ -4529,7 +4531,11 @@ static int lfs_mount_(lfs_t *lfs, const struct lfs_config *cfg) {
goto cleanup;
}
- lfs->block_count = superblock.block_count;
+ if (lfs->cfg->block_count) {
+ lfs->block_count = lfs->cfg->block_count;
+ } else {
+ lfs->block_count = superblock.block_count;
+ }
if (superblock.block_size != lfs->cfg->block_size) {
LFS_ERROR("Invalid block size (%"PRIu32" != %"PRIu32")",
@@ -5177,38 +5183,70 @@ static int lfs_fs_gc_(lfs_t *lfs) {
#endif
#ifndef LFS_READONLY
+static int lfs_fs_rewrite_block_count(lfs_t *lfs, lfs_size_t block_count) {
+ lfs->block_count = block_count;
+
+ // fetch the root
+ lfs_mdir_t root;
+ int err = lfs_dir_fetch(lfs, &root, lfs->root);
+ if (err) {
+ return err;
+ }
+
+ // update the superblock
+ lfs_superblock_t superblock;
+ lfs_stag_t tag = lfs_dir_get(lfs, &root, LFS_MKTAG(0x7ff, 0x3ff, 0),
+ LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)),
+ &superblock);
+ if (tag < 0) {
+ return tag;
+ }
+ lfs_superblock_fromle32(&superblock);
+
+ superblock.block_count = lfs->block_count;
+
+ lfs_superblock_tole32(&superblock);
+ err = lfs_dir_commit(lfs, &root, LFS_MKATTRS(
+ {tag, &superblock}));
+ if (err) {
+ return err;
+ }
+ return 0;
+}
+
static int lfs_fs_grow_(lfs_t *lfs, lfs_size_t block_count) {
// shrinking is not supported
LFS_ASSERT(block_count >= lfs->block_count);
if (block_count > lfs->block_count) {
- lfs->block_count = block_count;
+ return lfs_fs_rewrite_block_count(lfs, block_count);
+ }
- // fetch the root
- lfs_mdir_t root;
- int err = lfs_dir_fetch(lfs, &root, lfs->root);
- if (err) {
- return err;
- }
+ return 0;
+}
- // update the superblock
- lfs_superblock_t superblock;
- lfs_stag_t tag = lfs_dir_get(lfs, &root, LFS_MKTAG(0x7ff, 0x3ff, 0),
- LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)),
- &superblock);
- if (tag < 0) {
- return tag;
- }
- lfs_superblock_fromle32(&superblock);
+static int lfs_shrink_check_block(void * data, lfs_block_t block) {
+ lfs_size_t threshold = *((lfs_size_t *) data);
+ if (block >= threshold) {
+ return LFS_ERR_NOTEMPTY;
+ }
+ return 0;
+}
- superblock.block_count = lfs->block_count;
+static int lfs_fs_shrink_(lfs_t *lfs, lfs_size_t block_count) {
+ if (
+ block_count != lfs->block_count ||
+ (lfs->cfg->flags & LFS_CFG_DISABLE_BLOCK_COUNT_CHECK) == LFS_CFG_DISABLE_BLOCK_COUNT_CHECK
+ ) {
- lfs_superblock_tole32(&superblock);
- err = lfs_dir_commit(lfs, &root, LFS_MKATTRS(
- {tag, &superblock}));
+ lfs_block_t threshold = block_count;
+
+ int err = lfs_fs_traverse_(lfs, lfs_shrink_check_block, &threshold, true);
if (err) {
return err;
}
+
+ return lfs_fs_rewrite_block_count(lfs, block_count);
}
return 0;
@@ -6429,6 +6467,22 @@ int lfs_fs_grow(lfs_t *lfs, lfs_size_t block_count) {
}
#endif
+#ifndef LFS_READONLY
+int lfs_fs_shrink(lfs_t *lfs, lfs_size_t block_count) {
+ int err = LFS_LOCK(lfs->cfg);
+ if (err) {
+ return err;
+ }
+ LFS_TRACE("lfs_fs_shrink(%p, %"PRIu32")", (void*)lfs, block_count);
+
+ err = lfs_fs_shrink_(lfs, block_count);
+
+ LFS_TRACE("lfs_fs_shrink -> %d", err);
+ LFS_UNLOCK(lfs->cfg);
+ return err;
+}
+#endif
+
#ifdef LFS_MIGRATE
int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg) {
int err = LFS_LOCK(cfg);
diff --git a/lfs.h b/lfs.h
index 84738973..34e4ff31 100644
--- a/lfs.h
+++ b/lfs.h
@@ -283,6 +283,11 @@ struct lfs_config {
// Set to -1 to disable inlined files.
lfs_size_t inline_max;
+ // Configuration flags for the filesystem
+ //
+ // See variants of lfs_fs_flags
+ uint32_t flags;
+
#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
@@ -292,6 +297,10 @@ struct lfs_config {
#endif
};
+enum lfs_fs_flags {
+ LFS_CFG_DISABLE_BLOCK_COUNT_CHECK = 1, // Allow mounting a filesystem with a different block count in the config and the superblock
+};
+
// File info structure
struct lfs_info {
// Type of the file, either LFS_TYPE_REG or LFS_TYPE_DIR
@@ -772,6 +781,17 @@ int lfs_fs_gc(lfs_t *lfs);
int lfs_fs_grow(lfs_t *lfs, lfs_size_t block_count);
#endif
+#ifndef LFS_READONLY
+// Shrinks the filesystem to a new size, updating the superblock with the new
+// block count.
+//
+// Note: This first checks that none of the blocks that are being removed are in use
+// and will fail if it is the case
+//
+// Returns a negative error code on failure.
+int lfs_fs_shrink(lfs_t *lfs, lfs_size_t block_count);
+#endif
+
#ifndef LFS_READONLY
#ifdef LFS_MIGRATE
// Attempts to migrate a previous version of littlefs
diff --git a/tests/test_shrink.toml b/tests/test_shrink.toml
new file mode 100644
index 00000000..0c317d66
--- /dev/null
+++ b/tests/test_shrink.toml
@@ -0,0 +1,104 @@
+# simple shrink
+[cases.test_shrink_simple]
+defines.BLOCK_COUNT = [10, 15, 20]
+defines.AFTER_BLOCK_COUNT = [5, 10, 15, 19]
+if = "AFTER_BLOCK_COUNT <= BLOCK_COUNT"
+code = '''
+ lfs_t lfs;
+ lfs_format(&lfs, cfg) => 0;
+ lfs_mount(&lfs, cfg) => 0;
+ lfs_fs_shrink(&lfs, AFTER_BLOCK_COUNT) => 0;
+ lfs_unmount(&lfs);
+ if (BLOCK_COUNT != AFTER_BLOCK_COUNT) {
+ lfs_mount(&lfs, cfg) => LFS_ERR_INVAL;
+ }
+ lfs_t lfs2 = lfs;
+ struct lfs_config cfg2 = *cfg;
+ cfg2.block_count = AFTER_BLOCK_COUNT;
+ lfs2.cfg = &cfg2;
+ lfs_mount(&lfs2, &cfg2) => 0;
+ lfs_unmount(&lfs2) => 0;
+'''
+
+# shrinking full
+[cases.test_shrink_full]
+defines.BLOCK_COUNT = [10, 15, 20]
+defines.AFTER_BLOCK_COUNT = [5, 7, 10, 12, 15, 17, 20]
+defines.FILES_COUNT = [7, 8, 9, 10]
+if = "AFTER_BLOCK_COUNT <= BLOCK_COUNT && FILES_COUNT + 2 < BLOCK_COUNT"
+code = '''
+ lfs_t lfs;
+ lfs_format(&lfs, cfg) => 0;
+ // create FILES_COUNT files of BLOCK_SIZE - 50 bytes (to avoid inlining)
+ lfs_mount(&lfs, cfg) => 0;
+ for (int i = 0; i < FILES_COUNT + 1; i++) {
+ lfs_file_t file;
+ char path[1024];
+ sprintf(path, "file_%03d", i);
+ lfs_file_open(&lfs, &file, path,
+ LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
+ char wbuffer[BLOCK_SIZE];
+ memset(wbuffer, 'b', BLOCK_SIZE);
+ // Ensure one block is taken per file, but that files are not inlined.
+ lfs_size_t size = BLOCK_SIZE - 0x40;
+ sprintf(wbuffer, "Hi %03d", i);
+ lfs_file_write(&lfs, &file, wbuffer, size) => size;
+ lfs_file_close(&lfs, &file) => 0;
+ }
+
+ int err = lfs_fs_shrink(&lfs, AFTER_BLOCK_COUNT);
+ if (err == 0) {
+ for (int i = 0; i < FILES_COUNT + 1; i++) {
+ lfs_file_t file;
+ char path[1024];
+ sprintf(path, "file_%03d", i);
+ lfs_file_open(&lfs, &file, path,
+ LFS_O_RDONLY ) => 0;
+ lfs_size_t size = BLOCK_SIZE - 0x40;
+ char wbuffer[size];
+ char wbuffer_ref[size];
+ // Ensure one block is taken per file, but that files are not inlined.
+ memset(wbuffer_ref, 'b', size);
+ sprintf(wbuffer_ref, "Hi %03d", i);
+ lfs_file_read(&lfs, &file, wbuffer, BLOCK_SIZE) => size;
+ lfs_file_close(&lfs, &file) => 0;
+ for (lfs_size_t j = 0; j < size; j++) {
+ wbuffer[j] => wbuffer_ref[j];
+ }
+ }
+ } else {
+ assert(err == LFS_ERR_NOTEMPTY);
+ }
+
+ lfs_unmount(&lfs) => 0;
+ if (err == 0 ) {
+ if ( AFTER_BLOCK_COUNT != BLOCK_COUNT ) {
+ lfs_mount(&lfs, cfg) => LFS_ERR_INVAL;
+ }
+
+ lfs_t lfs2 = lfs;
+ struct lfs_config cfg2 = *cfg;
+ cfg2.block_count = AFTER_BLOCK_COUNT;
+ lfs2.cfg = &cfg2;
+ lfs_mount(&lfs2, &cfg2) => 0;
+ for (int i = 0; i < FILES_COUNT + 1; i++) {
+ lfs_file_t file;
+ char path[1024];
+ sprintf(path, "file_%03d", i);
+ lfs_file_open(&lfs2, &file, path,
+ LFS_O_RDONLY ) => 0;
+ lfs_size_t size = BLOCK_SIZE - 0x40;
+ char wbuffer[size];
+ char wbuffer_ref[size];
+ // Ensure one block is taken per file, but that files are not inlined.
+ memset(wbuffer_ref, 'b', size);
+ sprintf(wbuffer_ref, "Hi %03d", i);
+ lfs_file_read(&lfs2, &file, wbuffer, BLOCK_SIZE) => size;
+ lfs_file_close(&lfs2, &file) => 0;
+ for (lfs_size_t j = 0; j < size; j++) {
+ wbuffer[j] => wbuffer_ref[j];
+ }
+ }
+ lfs_unmount(&lfs2);
+ }
+'''
diff --git a/tests/test_superblocks.toml b/tests/test_superblocks.toml
index e93f02eb..704bd597 100644
--- a/tests/test_superblocks.toml
+++ b/tests/test_superblocks.toml
@@ -465,7 +465,13 @@ code = '''
lfs_unmount(&lfs) => 0;
if (KNOWN_BLOCK_COUNT) {
+ cfg->block_count = BLOCK_COUNT_2 + 1;
+ lfs_mount(&lfs, cfg) => LFS_ERR_INVAL;
+ cfg->flags |= LFS_CFG_DISABLE_BLOCK_COUNT_CHECK;
+ lfs_mount(&lfs, cfg) => 0;
+ lfs_unmount(&lfs) => 0;
cfg->block_count = BLOCK_COUNT_2;
+ cfg->flags &= ~LFS_CFG_DISABLE_BLOCK_COUNT_CHECK;
} else {
cfg->block_count = 0;
}
@@ -523,3 +529,108 @@ code = '''
assert(memcmp(buffer, "hello!", 6) == 0);
lfs_unmount(&lfs) => 0;
'''
+
+# mount and grow the filesystem
+[cases.test_superblocks_shrink]
+defines.BLOCK_COUNT = 'ERASE_COUNT'
+defines.BLOCK_COUNT_2 = ['ERASE_COUNT/2', 'ERASE_COUNT/4', '2']
+defines.KNOWN_BLOCK_COUNT = [true, false]
+code = '''
+ lfs_t lfs;
+ lfs_format(&lfs, cfg) => 0;
+
+ if (KNOWN_BLOCK_COUNT) {
+ cfg->block_count = BLOCK_COUNT;
+ } else {
+ cfg->block_count = 0;
+ }
+
+ // mount with block_size < erase_size
+ lfs_mount(&lfs, cfg) => 0;
+ struct lfs_fsinfo fsinfo;
+ lfs_fs_stat(&lfs, &fsinfo) => 0;
+ assert(fsinfo.block_size == BLOCK_SIZE);
+ assert(fsinfo.block_count == BLOCK_COUNT);
+ lfs_unmount(&lfs) => 0;
+
+ // same size is a noop
+ lfs_mount(&lfs, cfg) => 0;
+ lfs_fs_shrink(&lfs, BLOCK_COUNT) => 0;
+ lfs_fs_stat(&lfs, &fsinfo) => 0;
+ assert(fsinfo.block_size == BLOCK_SIZE);
+ assert(fsinfo.block_count == BLOCK_COUNT);
+ lfs_unmount(&lfs) => 0;
+
+ lfs_mount(&lfs, cfg) => 0;
+ lfs_fs_stat(&lfs, &fsinfo) => 0;
+ assert(fsinfo.block_size == BLOCK_SIZE);
+ assert(fsinfo.block_count == BLOCK_COUNT);
+ lfs_unmount(&lfs) => 0;
+
+ // grow to new size
+ lfs_mount(&lfs, cfg) => 0;
+ lfs_fs_shrink(&lfs, BLOCK_COUNT_2) => 0;
+ lfs_fs_stat(&lfs, &fsinfo) => 0;
+ assert(fsinfo.block_size == BLOCK_SIZE);
+ assert(fsinfo.block_count == BLOCK_COUNT_2);
+ lfs_unmount(&lfs) => 0;
+
+ if (KNOWN_BLOCK_COUNT) {
+ cfg->block_count = BLOCK_COUNT_2;
+ } else {
+ cfg->block_count = 0;
+ }
+
+ lfs_mount(&lfs, cfg) => 0;
+ lfs_fs_stat(&lfs, &fsinfo) => 0;
+ assert(fsinfo.block_size == BLOCK_SIZE);
+ assert(fsinfo.block_count == BLOCK_COUNT_2);
+ lfs_unmount(&lfs) => 0;
+
+ // mounting with the previous size should fail
+ cfg->block_count = BLOCK_COUNT;
+ lfs_mount(&lfs, cfg) => LFS_ERR_INVAL;
+
+ if (KNOWN_BLOCK_COUNT) {
+ cfg->block_count = BLOCK_COUNT_2;
+ } else {
+ cfg->block_count = 0;
+ }
+
+ // same size is a noop
+ lfs_mount(&lfs, cfg) => 0;
+ lfs_fs_shrink(&lfs, BLOCK_COUNT_2) => 0;
+ lfs_fs_stat(&lfs, &fsinfo) => 0;
+ assert(fsinfo.block_size == BLOCK_SIZE);
+ assert(fsinfo.block_count == BLOCK_COUNT_2);
+ lfs_unmount(&lfs) => 0;
+
+ lfs_mount(&lfs, cfg) => 0;
+ lfs_fs_stat(&lfs, &fsinfo) => 0;
+ assert(fsinfo.block_size == BLOCK_SIZE);
+ assert(fsinfo.block_count == BLOCK_COUNT_2);
+ lfs_unmount(&lfs) => 0;
+
+ // do some work
+ lfs_mount(&lfs, cfg) => 0;
+ lfs_fs_stat(&lfs, &fsinfo) => 0;
+ assert(fsinfo.block_size == BLOCK_SIZE);
+ assert(fsinfo.block_count == BLOCK_COUNT_2);
+ lfs_file_t file;
+ lfs_file_open(&lfs, &file, "test",
+ LFS_O_CREAT | LFS_O_EXCL | LFS_O_WRONLY) => 0;
+ lfs_file_write(&lfs, &file, "hello!", 6) => 6;
+ lfs_file_close(&lfs, &file) => 0;
+ lfs_unmount(&lfs) => 0;
+
+ lfs_mount(&lfs, cfg) => 0;
+ lfs_fs_stat(&lfs, &fsinfo) => 0;
+ assert(fsinfo.block_size == BLOCK_SIZE);
+ assert(fsinfo.block_count == BLOCK_COUNT_2);
+ lfs_file_open(&lfs, &file, "test", LFS_O_RDONLY) => 0;
+ uint8_t buffer[256];
+ lfs_file_read(&lfs, &file, buffer, sizeof(buffer)) => 6;
+ lfs_file_close(&lfs, &file) => 0;
+ assert(memcmp(buffer, "hello!", 6) == 0);
+ lfs_unmount(&lfs) => 0;
+'''