Skip to content

Commit 7b8aaa0

Browse files
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.
1 parent 8ed63b2 commit 7b8aaa0

File tree

4 files changed

+285
-20
lines changed

4 files changed

+285
-20
lines changed

lfs.c

Lines changed: 65 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5233,38 +5233,67 @@ static int lfs_fs_gc_(lfs_t *lfs) {
52335233
#endif
52345234

52355235
#ifndef LFS_READONLY
5236+
static int lfs_fs_rewrite_block_count(lfs_t *lfs, lfs_size_t block_count) {
5237+
lfs->block_count = block_count;
5238+
5239+
// fetch the root
5240+
lfs_mdir_t root;
5241+
int err = lfs_dir_fetch(lfs, &root, lfs->root);
5242+
if (err) {
5243+
return err;
5244+
}
5245+
5246+
// update the superblock
5247+
lfs_superblock_t superblock;
5248+
lfs_stag_t tag = lfs_dir_get(lfs, &root, LFS_MKTAG(0x7ff, 0x3ff, 0),
5249+
LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)),
5250+
&superblock);
5251+
if (tag < 0) {
5252+
return tag;
5253+
}
5254+
lfs_superblock_fromle32(&superblock);
5255+
5256+
superblock.block_count = lfs->block_count;
5257+
5258+
lfs_superblock_tole32(&superblock);
5259+
err = lfs_dir_commit(lfs, &root, LFS_MKATTRS(
5260+
{tag, &superblock}));
5261+
if (err) {
5262+
return err;
5263+
}
5264+
return 0;
5265+
}
5266+
52365267
static int lfs_fs_grow_(lfs_t *lfs, lfs_size_t block_count) {
52375268
// shrinking is not supported
52385269
LFS_ASSERT(block_count >= lfs->block_count);
52395270

52405271
if (block_count > lfs->block_count) {
5241-
lfs->block_count = block_count;
5272+
return lfs_fs_rewrite_block_count(lfs, block_count);
5273+
}
52425274

5243-
// fetch the root
5244-
lfs_mdir_t root;
5245-
int err = lfs_dir_fetch(lfs, &root, lfs->root);
5246-
if (err) {
5247-
return err;
5248-
}
5275+
return 0;
5276+
}
52495277

5250-
// update the superblock
5251-
lfs_superblock_t superblock;
5252-
lfs_stag_t tag = lfs_dir_get(lfs, &root, LFS_MKTAG(0x7ff, 0x3ff, 0),
5253-
LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)),
5254-
&superblock);
5255-
if (tag < 0) {
5256-
return tag;
5257-
}
5258-
lfs_superblock_fromle32(&superblock);
5278+
static int lfs_shrink_check_block(void * data, lfs_block_t block) {
5279+
lfs_size_t threshold = *((lfs_size_t *) data);
5280+
if (block >= threshold) {
5281+
return LFS_ERR_NOTEMPTY;
5282+
}
5283+
return 0;
5284+
}
52595285

5260-
superblock.block_count = lfs->block_count;
5286+
static int lfs_fs_shrink_(lfs_t *lfs, lfs_size_t block_count) {
5287+
if (block_count != lfs->block_count) {
52615288

5262-
lfs_superblock_tole32(&superblock);
5263-
err = lfs_dir_commit(lfs, &root, LFS_MKATTRS(
5264-
{tag, &superblock}));
5289+
lfs_block_t threshold = block_count;
5290+
5291+
int err = lfs_fs_traverse_(lfs, lfs_shrink_check_block, &threshold, true);
52655292
if (err) {
52665293
return err;
52675294
}
5295+
5296+
return lfs_fs_rewrite_block_count(lfs, block_count);
52685297
}
52695298

52705299
return 0;
@@ -6485,6 +6514,22 @@ int lfs_fs_grow(lfs_t *lfs, lfs_size_t block_count) {
64856514
}
64866515
#endif
64876516

6517+
#ifndef LFS_READONLY
6518+
int lfs_fs_shrink(lfs_t *lfs, lfs_size_t block_count) {
6519+
int err = LFS_LOCK(lfs->cfg);
6520+
if (err) {
6521+
return err;
6522+
}
6523+
LFS_TRACE("lfs_fs_shrink(%p, %"PRIu32")", (void*)lfs, block_count);
6524+
6525+
err = lfs_fs_shrink_(lfs, block_count);
6526+
6527+
LFS_TRACE("lfs_fs_shrink -> %d", err);
6528+
LFS_UNLOCK(lfs->cfg);
6529+
return err;
6530+
}
6531+
#endif
6532+
64886533
#ifdef LFS_MIGRATE
64896534
int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg) {
64906535
int err = LFS_LOCK(cfg);

lfs.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -772,6 +772,17 @@ int lfs_fs_gc(lfs_t *lfs);
772772
int lfs_fs_grow(lfs_t *lfs, lfs_size_t block_count);
773773
#endif
774774

775+
#ifndef LFS_READONLY
776+
// Shrinks the filesystem to a new size, updating the superblock with the new
777+
// block count.
778+
//
779+
// Note: This first checks that none of the blocks that are being removed are in use
780+
// and will fail if it is the case
781+
//
782+
// Returns a negative error code on failure.
783+
int lfs_fs_shrink(lfs_t *lfs, lfs_size_t block_count);
784+
#endif
785+
775786
#ifndef LFS_READONLY
776787
#ifdef LFS_MIGRATE
777788
// Attempts to migrate a previous version of littlefs

tests/test_shrink.toml

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# simple shrink
2+
[cases.test_shrink_simple]
3+
defines.BLOCK_COUNT = [10, 15, 20]
4+
defines.AFTER_BLOCK_COUNT = [5, 10, 15, 19]
5+
if = "AFTER_BLOCK_COUNT <= BLOCK_COUNT"
6+
code = '''
7+
lfs_t lfs;
8+
lfs_format(&lfs, cfg) => 0;
9+
lfs_mount(&lfs, cfg) => 0;
10+
lfs_fs_shrink(&lfs, AFTER_BLOCK_COUNT) => 0;
11+
if (BLOCK_COUNT != AFTER_BLOCK_COUNT) {
12+
lfs_mount(&lfs, cfg) => LFS_ERR_INVAL;
13+
}
14+
lfs_t lfs2 = lfs;
15+
struct lfs_config cfg2 = *cfg;
16+
cfg2.block_count = AFTER_BLOCK_COUNT;
17+
lfs2.cfg = &cfg2;
18+
lfs_mount(&lfs2, &cfg2) => 0;
19+
lfs_unmount(&lfs2) => 0;
20+
'''
21+
22+
# shrinking full
23+
[cases.test_shrink_full]
24+
defines.BLOCK_COUNT = [10, 15, 20]
25+
defines.AFTER_BLOCK_COUNT = [5, 7, 10, 12, 15, 17, 20]
26+
defines.FILES_COUNT = [7, 8, 9, 10]
27+
if = "AFTER_BLOCK_COUNT <= BLOCK_COUNT && FILES_COUNT + 2 < BLOCK_COUNT"
28+
code = '''
29+
lfs_t lfs;
30+
lfs_format(&lfs, cfg) => 0;
31+
// create FILES_COUNT files of BLOCK_SIZE - 50 bytes (to avoid inlining)
32+
lfs_mount(&lfs, cfg) => 0;
33+
for (int i = 0; i < FILES_COUNT + 1; i++) {
34+
lfs_file_t file;
35+
char path[1024];
36+
sprintf(path, "file_%03d", i);
37+
lfs_file_open(&lfs, &file, path,
38+
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
39+
char wbuffer[BLOCK_SIZE];
40+
memset(wbuffer, 'b', BLOCK_SIZE);
41+
// Ensure one block is taken per file, but that files are not inlined.
42+
lfs_size_t size = BLOCK_SIZE - 0x40;
43+
sprintf(wbuffer, "Hi %03d", i);
44+
lfs_file_write(&lfs, &file, wbuffer, size) => size;
45+
lfs_file_close(&lfs, &file) => 0;
46+
}
47+
48+
int err = lfs_fs_shrink(&lfs, AFTER_BLOCK_COUNT);
49+
if (err == 0) {
50+
for (int i = 0; i < FILES_COUNT + 1; i++) {
51+
lfs_file_t file;
52+
char path[1024];
53+
sprintf(path, "file_%03d", i);
54+
lfs_file_open(&lfs, &file, path,
55+
LFS_O_RDONLY ) => 0;
56+
lfs_size_t size = BLOCK_SIZE - 0x40;
57+
char wbuffer[size];
58+
char wbuffer_ref[size];
59+
// Ensure one block is taken per file, but that files are not inlined.
60+
memset(wbuffer_ref, 'b', size);
61+
sprintf(wbuffer_ref, "Hi %03d", i);
62+
lfs_file_read(&lfs, &file, wbuffer, BLOCK_SIZE) => size;
63+
lfs_file_close(&lfs, &file) => 0;
64+
for (lfs_size_t j = 0; j < size; j++) {
65+
wbuffer[j] => wbuffer_ref[j];
66+
}
67+
}
68+
} else {
69+
assert(err == LFS_ERR_NOTEMPTY);
70+
}
71+
72+
lfs_unmount(&lfs) => 0;
73+
if (err == 0 ) {
74+
if ( AFTER_BLOCK_COUNT != BLOCK_COUNT ) {
75+
lfs_mount(&lfs, cfg) => LFS_ERR_INVAL;
76+
}
77+
78+
lfs_t lfs2 = lfs;
79+
struct lfs_config cfg2 = *cfg;
80+
cfg2.block_count = AFTER_BLOCK_COUNT;
81+
lfs2.cfg = &cfg2;
82+
lfs_mount(&lfs2, &cfg2) => 0;
83+
for (int i = 0; i < FILES_COUNT + 1; i++) {
84+
lfs_file_t file;
85+
char path[1024];
86+
sprintf(path, "file_%03d", i);
87+
lfs_file_open(&lfs2, &file, path,
88+
LFS_O_RDONLY ) => 0;
89+
lfs_size_t size = BLOCK_SIZE - 0x40;
90+
char wbuffer[size];
91+
char wbuffer_ref[size];
92+
// Ensure one block is taken per file, but that files are not inlined.
93+
memset(wbuffer_ref, 'b', size);
94+
sprintf(wbuffer_ref, "Hi %03d", i);
95+
lfs_file_read(&lfs2, &file, wbuffer, BLOCK_SIZE) => size;
96+
lfs_file_close(&lfs2, &file) => 0;
97+
for (lfs_size_t j = 0; j < size; j++) {
98+
wbuffer[j] => wbuffer_ref[j];
99+
}
100+
}
101+
lfs_unmount(&lfs2);
102+
}
103+
'''

tests/test_superblocks.toml

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,112 @@ code = '''
524524
lfs_unmount(&lfs) => 0;
525525
'''
526526

527+
528+
# mount and grow the filesystem
529+
[cases.test_superblocks_shrink]
530+
defines.BLOCK_COUNT = 'ERASE_COUNT'
531+
defines.BLOCK_COUNT_2 = ['ERASE_COUNT/2', 'ERASE_COUNT/4', '2']
532+
defines.KNOWN_BLOCK_COUNT = [true, false]
533+
code = '''
534+
lfs_t lfs;
535+
lfs_format(&lfs, cfg) => 0;
536+
537+
if (KNOWN_BLOCK_COUNT) {
538+
cfg->block_count = BLOCK_COUNT;
539+
} else {
540+
cfg->block_count = 0;
541+
}
542+
543+
// mount with block_size < erase_size
544+
lfs_mount(&lfs, cfg) => 0;
545+
struct lfs_fsinfo fsinfo;
546+
lfs_fs_stat(&lfs, &fsinfo) => 0;
547+
assert(fsinfo.block_size == BLOCK_SIZE);
548+
assert(fsinfo.block_count == BLOCK_COUNT);
549+
lfs_unmount(&lfs) => 0;
550+
551+
// same size is a noop
552+
lfs_mount(&lfs, cfg) => 0;
553+
lfs_fs_shrink(&lfs, BLOCK_COUNT) => 0;
554+
lfs_fs_stat(&lfs, &fsinfo) => 0;
555+
assert(fsinfo.block_size == BLOCK_SIZE);
556+
assert(fsinfo.block_count == BLOCK_COUNT);
557+
lfs_unmount(&lfs) => 0;
558+
559+
lfs_mount(&lfs, cfg) => 0;
560+
lfs_fs_stat(&lfs, &fsinfo) => 0;
561+
assert(fsinfo.block_size == BLOCK_SIZE);
562+
assert(fsinfo.block_count == BLOCK_COUNT);
563+
lfs_unmount(&lfs) => 0;
564+
565+
// grow to new size
566+
lfs_mount(&lfs, cfg) => 0;
567+
lfs_fs_shrink(&lfs, BLOCK_COUNT_2) => 0;
568+
lfs_fs_stat(&lfs, &fsinfo) => 0;
569+
assert(fsinfo.block_size == BLOCK_SIZE);
570+
assert(fsinfo.block_count == BLOCK_COUNT_2);
571+
lfs_unmount(&lfs) => 0;
572+
573+
if (KNOWN_BLOCK_COUNT) {
574+
cfg->block_count = BLOCK_COUNT_2;
575+
} else {
576+
cfg->block_count = 0;
577+
}
578+
579+
lfs_mount(&lfs, cfg) => 0;
580+
lfs_fs_stat(&lfs, &fsinfo) => 0;
581+
assert(fsinfo.block_size == BLOCK_SIZE);
582+
assert(fsinfo.block_count == BLOCK_COUNT_2);
583+
lfs_unmount(&lfs) => 0;
584+
585+
// mounting with the previous size should fail
586+
cfg->block_count = BLOCK_COUNT;
587+
lfs_mount(&lfs, cfg) => LFS_ERR_INVAL;
588+
589+
if (KNOWN_BLOCK_COUNT) {
590+
cfg->block_count = BLOCK_COUNT_2;
591+
} else {
592+
cfg->block_count = 0;
593+
}
594+
595+
// same size is a noop
596+
lfs_mount(&lfs, cfg) => 0;
597+
lfs_fs_shrink(&lfs, BLOCK_COUNT_2) => 0;
598+
lfs_fs_stat(&lfs, &fsinfo) => 0;
599+
assert(fsinfo.block_size == BLOCK_SIZE);
600+
assert(fsinfo.block_count == BLOCK_COUNT_2);
601+
lfs_unmount(&lfs) => 0;
602+
603+
lfs_mount(&lfs, cfg) => 0;
604+
lfs_fs_stat(&lfs, &fsinfo) => 0;
605+
assert(fsinfo.block_size == BLOCK_SIZE);
606+
assert(fsinfo.block_count == BLOCK_COUNT_2);
607+
lfs_unmount(&lfs) => 0;
608+
609+
// do some work
610+
lfs_mount(&lfs, cfg) => 0;
611+
lfs_fs_stat(&lfs, &fsinfo) => 0;
612+
assert(fsinfo.block_size == BLOCK_SIZE);
613+
assert(fsinfo.block_count == BLOCK_COUNT_2);
614+
lfs_file_t file;
615+
lfs_file_open(&lfs, &file, "test",
616+
LFS_O_CREAT | LFS_O_EXCL | LFS_O_WRONLY) => 0;
617+
lfs_file_write(&lfs, &file, "hello!", 6) => 6;
618+
lfs_file_close(&lfs, &file) => 0;
619+
lfs_unmount(&lfs) => 0;
620+
621+
lfs_mount(&lfs, cfg) => 0;
622+
lfs_fs_stat(&lfs, &fsinfo) => 0;
623+
assert(fsinfo.block_size == BLOCK_SIZE);
624+
assert(fsinfo.block_count == BLOCK_COUNT_2);
625+
lfs_file_open(&lfs, &file, "test", LFS_O_RDONLY) => 0;
626+
uint8_t buffer[256];
627+
lfs_file_read(&lfs, &file, buffer, sizeof(buffer)) => 6;
628+
lfs_file_close(&lfs, &file) => 0;
629+
assert(memcmp(buffer, "hello!", 6) == 0);
630+
lfs_unmount(&lfs) => 0;
631+
'''
632+
527633
# test that metadata_max does not cause problems for superblock compaction
528634
[cases.test_superblocks_metadata_max]
529635
defines.METADATA_MAX = [

0 commit comments

Comments
 (0)