Skip to content

Block count disable and shrink #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 76 additions & 22 deletions lfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -4521,15 +4521,21 @@ 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);
err = LFS_ERR_INVAL;
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")",
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
20 changes: 20 additions & 0 deletions lfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
104 changes: 104 additions & 0 deletions tests/test_shrink.toml
Original file line number Diff line number Diff line change
@@ -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);
}
'''
Loading