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:
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