forked from Imagelibrary/littlefs
Add support for shrinking a filesystem
This PR adds a new `lfs_fs_shrink`, which functions similarly to `lfs_fs_grow`, but supports reducing the block count. This functions first checks that none of the removed block are in use. If it is the case, it will fail.
This commit is contained in:
89
lfs.c
89
lfs.c
@@ -5233,38 +5233,67 @@ 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);
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static int lfs_fs_shrink_(lfs_t *lfs, lfs_size_t block_count) {
|
||||
if (block_count != lfs->block_count) {
|
||||
|
||||
lfs_block_t threshold = block_count;
|
||||
|
||||
int err = lfs_fs_traverse_(lfs, lfs_shrink_check_block, &threshold, true);
|
||||
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 lfs_fs_rewrite_block_count(lfs, block_count);
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -6485,6 +6514,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);
|
||||
|
||||
11
lfs.h
11
lfs.h
@@ -772,6 +772,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
|
||||
|
||||
104
tests/test_shrink.toml
Normal file
104
tests/test_shrink.toml
Normal file
@@ -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);
|
||||
}
|
||||
'''
|
||||
@@ -524,6 +524,112 @@ code = '''
|
||||
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;
|
||||
'''
|
||||
|
||||
# test that metadata_max does not cause problems for superblock compaction
|
||||
[cases.test_superblocks_metadata_max]
|
||||
defines.METADATA_MAX = [
|
||||
|
||||
Reference in New Issue
Block a user