a9b81820b0
This adopts a previously discarded idea for compressed mids with a few
tweaks to avoiding decoding the bid/rid portions as much as possible.
The idea of compressed mids is to shove both the mid bid and mid rid
into a single integer, saving RAM and potentially helping filesystem
integration where a unique per-file integer is useful.
Unfortunately this has proven tricky. littlefs fundamentally needs two
ids, one "bid" to lookup which mdir our entry resides on, and one "rid"
to lookup the entry in the mdir. It's tempting to use two half-sized
integers (16-bit for example), but this risks surprising limitations
around the number of files when blocks are either really large or
really small.
Optimally, we'd limit the number of bits reserved for the rid to the
upper bound of number of rids that can fit in a single mdir. This would
allows for more bids when the block size is small, and more rids when
the block size is large. This should roughly approximate the limits of
a per-file integer.
With a bit of math we can estimate the upper bound to be <=block_size/16
with our current compaction strategy.
This idea was previously discarded due to the overhead of extracting the
bids/rids when we need them, but the RAM savings and file-to-integer
mapping is too useful to give up. When it became clear half-width
integers wasn't really going to work, compressed mids became the new
plan:
0bbbbbbb bbbbbbbb bbbbbbbb rrrrrrrr
^'-----------+-----------' '---+--'
'------------|-----------------|---- sign-bit, reserved for driver
| '---- nlog2(bs/16) bits for rid
| (8-bits for 4KiB blocks)
'---------------------- remaining bits for bid
(23-bits for 4KiB blocks)
To reduce the overhead of encoding/decode bids/rids a few extra features
were added to the internal mdir APIs:
1. The mtree has been changed to store mids directly. Giving each mdir
the upper bound as a weight. This allows direct lookup of mids
without any sort of bid decoding, though does bake the upper bound
estimate into the metadata of the filesystem, which isn't the
cleanest design, but if it works it works.
On the plus side, with this upper bound baked in to the filesystems,
GRMs can be encoded in a single leb128, which is nice. This may have
other savings if we ever store mids anywhere else in the filesystem.
2. rids are now mid relative in lfsr_mdir_lookup when non-negative. This
is implemented with a simple condition that is hopefully optimized
out when inlined, though there may be some room for improvement here.
3. rids are now mid relative in lfsr_mdir_commit. This was a bit tricky,
but we can leverage the existing mechanisms for bid-relative rids
used in the btree implementation.
The above changes make it so you can pass the mid around directly for
most of the mdir functions, hopefully reducing the mid decoding
overhead. This savings should only grow as more high-level filesystem
APIs are added.
Here is the resulting code/RAM changes for this entire change (from
before we adopted the mroot bit):
code stack structs
before: 20590 1784 908
after: 20890 (+1.4%) 1744 (-2.3%) 864 (-5.1%)
900 lines
28 KiB
TOML
900 lines
28 KiB
TOML
# Tests covering properties of the block allocator
|
|
after = 'test_mtree'
|
|
|
|
|
|
# TODO test all of these with weird block sizes? would be nice to make this
|
|
# easy via the test_runner, either by handling it there or letting a single
|
|
# config limit the block count by a couple blocks
|
|
|
|
# test that we can alloc
|
|
[cases.test_alloc_blocks]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// start allocating
|
|
lfs_alloc_ack(&lfs);
|
|
lfs_size_t alloced = 0;
|
|
while (true) {
|
|
lfs_block_t block;
|
|
int err = lfs_alloc(&lfs, &block);
|
|
assert(!err || err == LFS_ERR_NOSPC);
|
|
|
|
if (err == LFS_ERR_NOSPC) {
|
|
break;
|
|
}
|
|
alloced += 1;
|
|
|
|
// our allocator should stop at some point...
|
|
assert(alloced < 2*BLOCK_COUNT);
|
|
}
|
|
|
|
// excluding our mroot, we should have allocated exactly
|
|
// block_count-2 blocks
|
|
printf("alloced %d/%d blocks\n", alloced, (lfs_block_t)BLOCK_COUNT);
|
|
assert(alloced == BLOCK_COUNT-2);
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# test that we can realloc after an ack
|
|
[cases.test_alloc_reuse]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
|
|
// start allocating
|
|
lfs_alloc_ack(&lfs);
|
|
lfs_size_t alloced = 0;
|
|
while (true) {
|
|
lfs_block_t block;
|
|
int err = lfs_alloc(&lfs, &block);
|
|
assert(!err || err == LFS_ERR_NOSPC);
|
|
|
|
if (err == LFS_ERR_NOSPC) {
|
|
break;
|
|
}
|
|
alloced += 1;
|
|
|
|
// our allocator should stop at some point...
|
|
assert(alloced < 2*BLOCK_COUNT);
|
|
}
|
|
|
|
// excluding our mroot, we should have allocated exactly
|
|
// block_count-2 blocks
|
|
printf("alloced %d/%d blocks\n", alloced, (lfs_block_t)BLOCK_COUNT);
|
|
assert(alloced == BLOCK_COUNT-2);
|
|
|
|
// ack again, effectively releasing all the previously alloced blocks
|
|
lfs_alloc_ack(&lfs);
|
|
alloced = 0;
|
|
while (true) {
|
|
lfs_block_t block;
|
|
int err = lfs_alloc(&lfs, &block);
|
|
assert(!err || err == LFS_ERR_NOSPC);
|
|
|
|
if (err == LFS_ERR_NOSPC) {
|
|
break;
|
|
}
|
|
alloced += 1;
|
|
|
|
// our allocator should stop at some point...
|
|
assert(alloced < 2*BLOCK_COUNT);
|
|
}
|
|
|
|
// excluding our mroot, we should have allocated exactly
|
|
// block_count-2 blocks
|
|
printf("alloced %d/%d blocks\n", alloced, (lfs_block_t)BLOCK_COUNT);
|
|
assert(alloced == BLOCK_COUNT-2);
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# test that we can alloc an mtree, the difference between this and mtree tests
|
|
# is we expect this to be able to handle wrap-around
|
|
[cases.test_alloc_mtree]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, CFG) => 0;
|
|
lfs_alloc_ack(&lfs);
|
|
// remove root dstart for now
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(0, RM, -1, NULL))) => 0;
|
|
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, 0*lfsr_mbidweight(&lfs)+0,
|
|
&mdir) => 0;
|
|
|
|
lfs_size_t count = 0;
|
|
while (true) {
|
|
// at least try to catch infinite loops
|
|
assert(count < BLOCK_SIZE * BLOCK_COUNT/2);
|
|
|
|
// ack before each commit to reset the allocator
|
|
lfs_alloc_ack(&lfs);
|
|
|
|
// keep creating new metadata entries until we run out of space
|
|
int err = lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR(mdir.mid, REG, +1,
|
|
BUF(&alphas[count % 26], 1))));
|
|
assert(!err || err == LFS_ERR_NOSPC);
|
|
if (err == LFS_ERR_NOSPC) {
|
|
break;
|
|
}
|
|
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, 4) => 1;
|
|
assert(memcmp(buffer, &alphas[count % 26], 1) == 0);
|
|
|
|
mdir.mid += 1;
|
|
count += 1;
|
|
}
|
|
|
|
printf("alloced %d metadata entries in %d blocks\n",
|
|
count, (lfs_block_t)BLOCK_COUNT);
|
|
|
|
// test that all of our metadata entries are still there
|
|
lfs_size_t i = 0;
|
|
for (lfs_ssize_t mid = 0;
|
|
mid < lfs_smax32(
|
|
lfsr_mtree_weight(&lfs),
|
|
lfsr_mbidweight(&lfs));
|
|
mid += lfsr_mbidweight(&lfs)) {
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, mid, &mdir) => 0;
|
|
for (; (mdir.mid & lfsr_mridmask(&lfs))
|
|
< (lfs_ssize_t)mdir.u.m.weight;
|
|
mdir.mid += 1) {
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
|
buffer, 4) => 1;
|
|
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
|
|
i += 1;
|
|
}
|
|
}
|
|
assert(i == count);
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## allocator tests
|
|
## note for these to work there are a number constraints on the device geometry
|
|
#if = 'BLOCK_CYCLES == -1'
|
|
#
|
|
## parallel allocation test
|
|
#[cases.test_alloc_parallel]
|
|
#defines.FILES = 3
|
|
#defines.SIZE = '(((BLOCK_SIZE-8)*(BLOCK_COUNT-6)) / FILES)'
|
|
#code = '''
|
|
# const char *names[] = {"bacon", "eggs", "pancakes"};
|
|
# lfs_file_t files[FILES];
|
|
#
|
|
# lfs_t lfs;
|
|
# lfs_format(&lfs, cfg) => 0;
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# lfs_mkdir(&lfs, "breakfast") => 0;
|
|
# lfs_unmount(&lfs) => 0;
|
|
#
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# for (int n = 0; n < FILES; n++) {
|
|
# char path[1024];
|
|
# sprintf(path, "breakfast/%s", names[n]);
|
|
# lfs_file_open(&lfs, &files[n], path,
|
|
# LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
|
|
# }
|
|
# for (int n = 0; n < FILES; n++) {
|
|
# size_t size = strlen(names[n]);
|
|
# for (lfs_size_t i = 0; i < SIZE; i += size) {
|
|
# lfs_file_write(&lfs, &files[n], names[n], size) => size;
|
|
# }
|
|
# }
|
|
# for (int n = 0; n < FILES; n++) {
|
|
# lfs_file_close(&lfs, &files[n]) => 0;
|
|
# }
|
|
# lfs_unmount(&lfs) => 0;
|
|
#
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# for (int n = 0; n < FILES; n++) {
|
|
# char path[1024];
|
|
# sprintf(path, "breakfast/%s", names[n]);
|
|
# lfs_file_t file;
|
|
# lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
|
|
# size_t size = strlen(names[n]);
|
|
# for (lfs_size_t i = 0; i < SIZE; i += size) {
|
|
# uint8_t buffer[1024];
|
|
# lfs_file_read(&lfs, &file, buffer, size) => size;
|
|
# assert(memcmp(buffer, names[n], size) == 0);
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
# }
|
|
# lfs_unmount(&lfs) => 0;
|
|
#'''
|
|
#
|
|
## serial allocation test
|
|
#[cases.test_alloc_serial]
|
|
#defines.FILES = 3
|
|
#defines.SIZE = '(((BLOCK_SIZE-8)*(BLOCK_COUNT-6)) / FILES)'
|
|
#code = '''
|
|
# const char *names[] = {"bacon", "eggs", "pancakes"};
|
|
#
|
|
# lfs_t lfs;
|
|
# lfs_format(&lfs, cfg) => 0;
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# lfs_mkdir(&lfs, "breakfast") => 0;
|
|
# lfs_unmount(&lfs) => 0;
|
|
#
|
|
# for (int n = 0; n < FILES; n++) {
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# char path[1024];
|
|
# sprintf(path, "breakfast/%s", names[n]);
|
|
# lfs_file_t file;
|
|
# lfs_file_open(&lfs, &file, path,
|
|
# LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
|
|
# size_t size = strlen(names[n]);
|
|
# uint8_t buffer[1024];
|
|
# memcpy(buffer, names[n], size);
|
|
# for (int i = 0; i < SIZE; i += size) {
|
|
# lfs_file_write(&lfs, &file, buffer, size) => size;
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
# lfs_unmount(&lfs) => 0;
|
|
# }
|
|
#
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# for (int n = 0; n < FILES; n++) {
|
|
# char path[1024];
|
|
# sprintf(path, "breakfast/%s", names[n]);
|
|
# lfs_file_t file;
|
|
# lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
|
|
# size_t size = strlen(names[n]);
|
|
# for (int i = 0; i < SIZE; i += size) {
|
|
# uint8_t buffer[1024];
|
|
# lfs_file_read(&lfs, &file, buffer, size) => size;
|
|
# assert(memcmp(buffer, names[n], size) == 0);
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
# }
|
|
# lfs_unmount(&lfs) => 0;
|
|
#'''
|
|
#
|
|
## parallel allocation reuse test
|
|
#[cases.test_alloc_parallel_reuse]
|
|
#defines.FILES = 3
|
|
#defines.SIZE = '(((BLOCK_SIZE-8)*(BLOCK_COUNT-6)) / FILES)'
|
|
#defines.CYCLES = [1, 10]
|
|
#code = '''
|
|
# const char *names[] = {"bacon", "eggs", "pancakes"};
|
|
# lfs_file_t files[FILES];
|
|
#
|
|
# lfs_t lfs;
|
|
# lfs_format(&lfs, cfg) => 0;
|
|
#
|
|
# for (int c = 0; c < CYCLES; c++) {
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# lfs_mkdir(&lfs, "breakfast") => 0;
|
|
# lfs_unmount(&lfs) => 0;
|
|
#
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# for (int n = 0; n < FILES; n++) {
|
|
# char path[1024];
|
|
# sprintf(path, "breakfast/%s", names[n]);
|
|
# lfs_file_open(&lfs, &files[n], path,
|
|
# LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
|
|
# }
|
|
# for (int n = 0; n < FILES; n++) {
|
|
# size_t size = strlen(names[n]);
|
|
# for (int i = 0; i < SIZE; i += size) {
|
|
# lfs_file_write(&lfs, &files[n], names[n], size) => size;
|
|
# }
|
|
# }
|
|
# for (int n = 0; n < FILES; n++) {
|
|
# lfs_file_close(&lfs, &files[n]) => 0;
|
|
# }
|
|
# lfs_unmount(&lfs) => 0;
|
|
#
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# for (int n = 0; n < FILES; n++) {
|
|
# char path[1024];
|
|
# sprintf(path, "breakfast/%s", names[n]);
|
|
# lfs_file_t file;
|
|
# lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
|
|
# size_t size = strlen(names[n]);
|
|
# for (int i = 0; i < SIZE; i += size) {
|
|
# uint8_t buffer[1024];
|
|
# lfs_file_read(&lfs, &file, buffer, size) => size;
|
|
# assert(memcmp(buffer, names[n], size) == 0);
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
# }
|
|
# lfs_unmount(&lfs) => 0;
|
|
#
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# for (int n = 0; n < FILES; n++) {
|
|
# char path[1024];
|
|
# sprintf(path, "breakfast/%s", names[n]);
|
|
# lfs_remove(&lfs, path) => 0;
|
|
# }
|
|
# lfs_remove(&lfs, "breakfast") => 0;
|
|
# lfs_unmount(&lfs) => 0;
|
|
# }
|
|
#'''
|
|
#
|
|
## serial allocation reuse test
|
|
#[cases.test_alloc_serial_reuse]
|
|
#defines.FILES = 3
|
|
#defines.SIZE = '(((BLOCK_SIZE-8)*(BLOCK_COUNT-6)) / FILES)'
|
|
#defines.CYCLES = [1, 10]
|
|
#code = '''
|
|
# const char *names[] = {"bacon", "eggs", "pancakes"};
|
|
#
|
|
# lfs_t lfs;
|
|
# lfs_format(&lfs, cfg) => 0;
|
|
#
|
|
# for (int c = 0; c < CYCLES; c++) {
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# lfs_mkdir(&lfs, "breakfast") => 0;
|
|
# lfs_unmount(&lfs) => 0;
|
|
#
|
|
# for (int n = 0; n < FILES; n++) {
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# char path[1024];
|
|
# sprintf(path, "breakfast/%s", names[n]);
|
|
# lfs_file_t file;
|
|
# lfs_file_open(&lfs, &file, path,
|
|
# LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
|
|
# size_t size = strlen(names[n]);
|
|
# uint8_t buffer[1024];
|
|
# memcpy(buffer, names[n], size);
|
|
# for (int i = 0; i < SIZE; i += size) {
|
|
# lfs_file_write(&lfs, &file, buffer, size) => size;
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
# lfs_unmount(&lfs) => 0;
|
|
# }
|
|
#
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# for (int n = 0; n < FILES; n++) {
|
|
# char path[1024];
|
|
# sprintf(path, "breakfast/%s", names[n]);
|
|
# lfs_file_t file;
|
|
# lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
|
|
# size_t size = strlen(names[n]);
|
|
# for (int i = 0; i < SIZE; i += size) {
|
|
# uint8_t buffer[1024];
|
|
# lfs_file_read(&lfs, &file, buffer, size) => size;
|
|
# assert(memcmp(buffer, names[n], size) == 0);
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
# }
|
|
# lfs_unmount(&lfs) => 0;
|
|
#
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# for (int n = 0; n < FILES; n++) {
|
|
# char path[1024];
|
|
# sprintf(path, "breakfast/%s", names[n]);
|
|
# lfs_remove(&lfs, path) => 0;
|
|
# }
|
|
# lfs_remove(&lfs, "breakfast") => 0;
|
|
# lfs_unmount(&lfs) => 0;
|
|
# }
|
|
#'''
|
|
#
|
|
## exhaustion test
|
|
#[cases.test_alloc_exhaustion]
|
|
#code = '''
|
|
# lfs_t lfs;
|
|
# lfs_format(&lfs, cfg) => 0;
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# lfs_file_t file;
|
|
# lfs_file_open(&lfs, &file, "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
|
|
# size_t size = strlen("exhaustion");
|
|
# uint8_t buffer[1024];
|
|
# memcpy(buffer, "exhaustion", size);
|
|
# lfs_file_write(&lfs, &file, buffer, size) => size;
|
|
# lfs_file_sync(&lfs, &file) => 0;
|
|
#
|
|
# size = strlen("blahblahblahblah");
|
|
# memcpy(buffer, "blahblahblahblah", size);
|
|
# lfs_ssize_t res;
|
|
# while (true) {
|
|
# res = lfs_file_write(&lfs, &file, buffer, size);
|
|
# if (res < 0) {
|
|
# break;
|
|
# }
|
|
#
|
|
# res => size;
|
|
# }
|
|
# res => LFS_ERR_NOSPC;
|
|
#
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
# lfs_unmount(&lfs) => 0;
|
|
#
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# lfs_file_open(&lfs, &file, "exhaustion", LFS_O_RDONLY);
|
|
# size = strlen("exhaustion");
|
|
# lfs_file_size(&lfs, &file) => size;
|
|
# lfs_file_read(&lfs, &file, buffer, size) => size;
|
|
# memcmp(buffer, "exhaustion", size) => 0;
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
# lfs_unmount(&lfs) => 0;
|
|
#'''
|
|
#
|
|
## exhaustion wraparound test
|
|
#[cases.test_alloc_exhaustion_wraparound]
|
|
#defines.SIZE = '(((BLOCK_SIZE-8)*(BLOCK_COUNT-4)) / 3)'
|
|
#code = '''
|
|
# lfs_t lfs;
|
|
# lfs_format(&lfs, cfg) => 0;
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
#
|
|
# lfs_file_t file;
|
|
# lfs_file_open(&lfs, &file, "padding", LFS_O_WRONLY | LFS_O_CREAT);
|
|
# size_t size = strlen("buffering");
|
|
# uint8_t buffer[1024];
|
|
# memcpy(buffer, "buffering", size);
|
|
# for (int i = 0; i < SIZE; i += size) {
|
|
# lfs_file_write(&lfs, &file, buffer, size) => size;
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
# lfs_remove(&lfs, "padding") => 0;
|
|
#
|
|
# lfs_file_open(&lfs, &file, "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
|
|
# size = strlen("exhaustion");
|
|
# memcpy(buffer, "exhaustion", size);
|
|
# lfs_file_write(&lfs, &file, buffer, size) => size;
|
|
# lfs_file_sync(&lfs, &file) => 0;
|
|
#
|
|
# size = strlen("blahblahblahblah");
|
|
# memcpy(buffer, "blahblahblahblah", size);
|
|
# lfs_ssize_t res;
|
|
# while (true) {
|
|
# res = lfs_file_write(&lfs, &file, buffer, size);
|
|
# if (res < 0) {
|
|
# break;
|
|
# }
|
|
#
|
|
# res => size;
|
|
# }
|
|
# res => LFS_ERR_NOSPC;
|
|
#
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
# lfs_unmount(&lfs) => 0;
|
|
#
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# lfs_file_open(&lfs, &file, "exhaustion", LFS_O_RDONLY);
|
|
# size = strlen("exhaustion");
|
|
# lfs_file_size(&lfs, &file) => size;
|
|
# lfs_file_read(&lfs, &file, buffer, size) => size;
|
|
# memcmp(buffer, "exhaustion", size) => 0;
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
# lfs_remove(&lfs, "exhaustion") => 0;
|
|
# lfs_unmount(&lfs) => 0;
|
|
#'''
|
|
#
|
|
## dir exhaustion test
|
|
#[cases.test_alloc_dir_exhaustion]
|
|
#code = '''
|
|
# lfs_t lfs;
|
|
# lfs_format(&lfs, cfg) => 0;
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
#
|
|
# // find out max file size
|
|
# lfs_mkdir(&lfs, "exhaustiondir") => 0;
|
|
# size_t size = strlen("blahblahblahblah");
|
|
# uint8_t buffer[1024];
|
|
# memcpy(buffer, "blahblahblahblah", size);
|
|
# lfs_file_t file;
|
|
# lfs_file_open(&lfs, &file, "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
|
|
# int count = 0;
|
|
# int err;
|
|
# while (true) {
|
|
# err = lfs_file_write(&lfs, &file, buffer, size);
|
|
# if (err < 0) {
|
|
# break;
|
|
# }
|
|
#
|
|
# count += 1;
|
|
# }
|
|
# err => LFS_ERR_NOSPC;
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# lfs_remove(&lfs, "exhaustion") => 0;
|
|
# lfs_remove(&lfs, "exhaustiondir") => 0;
|
|
#
|
|
# // see if dir fits with max file size
|
|
# lfs_file_open(&lfs, &file, "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
|
|
# for (int i = 0; i < count; i++) {
|
|
# lfs_file_write(&lfs, &file, buffer, size) => size;
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# lfs_mkdir(&lfs, "exhaustiondir") => 0;
|
|
# lfs_remove(&lfs, "exhaustiondir") => 0;
|
|
# lfs_remove(&lfs, "exhaustion") => 0;
|
|
#
|
|
# // see if dir fits with > max file size
|
|
# lfs_file_open(&lfs, &file, "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
|
|
# for (int i = 0; i < count+1; i++) {
|
|
# lfs_file_write(&lfs, &file, buffer, size) => size;
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# lfs_mkdir(&lfs, "exhaustiondir") => LFS_ERR_NOSPC;
|
|
#
|
|
# lfs_remove(&lfs, "exhaustion") => 0;
|
|
# lfs_unmount(&lfs) => 0;
|
|
#'''
|
|
#
|
|
## what if we have a bad block during an allocation scan?
|
|
#[cases.test_alloc_bad_blocks]
|
|
#in = "lfs.c"
|
|
#defines.ERASE_CYCLES = 0xffffffff
|
|
#defines.BADBLOCK_BEHAVIOR = 'LFS_EMUBD_BADBLOCK_READERROR'
|
|
#code = '''
|
|
# lfs_t lfs;
|
|
# lfs_format(&lfs, cfg) => 0;
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# // first fill to exhaustion to find available space
|
|
# lfs_file_t file;
|
|
# lfs_file_open(&lfs, &file, "pacman", LFS_O_WRONLY | LFS_O_CREAT) => 0;
|
|
# uint8_t buffer[1024];
|
|
# strcpy((char*)buffer, "waka");
|
|
# size_t size = strlen("waka");
|
|
# lfs_size_t filesize = 0;
|
|
# while (true) {
|
|
# lfs_ssize_t res = lfs_file_write(&lfs, &file, buffer, size);
|
|
# assert(res == (lfs_ssize_t)size || res == LFS_ERR_NOSPC);
|
|
# if (res == LFS_ERR_NOSPC) {
|
|
# break;
|
|
# }
|
|
# filesize += size;
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
# // now fill all but a couple of blocks of the filesystem with data
|
|
# filesize -= 3*BLOCK_SIZE;
|
|
# lfs_file_open(&lfs, &file, "pacman", LFS_O_WRONLY | LFS_O_CREAT) => 0;
|
|
# strcpy((char*)buffer, "waka");
|
|
# size = strlen("waka");
|
|
# for (lfs_size_t i = 0; i < filesize/size; i++) {
|
|
# lfs_file_write(&lfs, &file, buffer, size) => size;
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
# // also save head of file so we can error during lookahead scan
|
|
# lfs_block_t fileblock = file.ctz.head;
|
|
# lfs_unmount(&lfs) => 0;
|
|
#
|
|
# // remount to force an alloc scan
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
#
|
|
# // but mark the head of our file as a "bad block", this is force our
|
|
# // scan to bail early
|
|
# lfs_emubd_setwear(cfg, fileblock, 0xffffffff) => 0;
|
|
# lfs_file_open(&lfs, &file, "ghost", LFS_O_WRONLY | LFS_O_CREAT) => 0;
|
|
# strcpy((char*)buffer, "chomp");
|
|
# size = strlen("chomp");
|
|
# while (true) {
|
|
# lfs_ssize_t res = lfs_file_write(&lfs, &file, buffer, size);
|
|
# assert(res == (lfs_ssize_t)size || res == LFS_ERR_CORRUPT);
|
|
# if (res == LFS_ERR_CORRUPT) {
|
|
# break;
|
|
# }
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# // now reverse the "bad block" and try to write the file again until we
|
|
# // run out of space
|
|
# lfs_emubd_setwear(cfg, fileblock, 0) => 0;
|
|
# lfs_file_open(&lfs, &file, "ghost", LFS_O_WRONLY | LFS_O_CREAT) => 0;
|
|
# strcpy((char*)buffer, "chomp");
|
|
# size = strlen("chomp");
|
|
# while (true) {
|
|
# lfs_ssize_t res = lfs_file_write(&lfs, &file, buffer, size);
|
|
# assert(res == (lfs_ssize_t)size || res == LFS_ERR_NOSPC);
|
|
# if (res == LFS_ERR_NOSPC) {
|
|
# break;
|
|
# }
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# lfs_unmount(&lfs) => 0;
|
|
#
|
|
# // check that the disk isn't hurt
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
# lfs_file_open(&lfs, &file, "pacman", LFS_O_RDONLY) => 0;
|
|
# strcpy((char*)buffer, "waka");
|
|
# size = strlen("waka");
|
|
# for (lfs_size_t i = 0; i < filesize/size; i++) {
|
|
# uint8_t rbuffer[4];
|
|
# lfs_file_read(&lfs, &file, rbuffer, size) => size;
|
|
# assert(memcmp(rbuffer, buffer, size) == 0);
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
# lfs_unmount(&lfs) => 0;
|
|
#'''
|
|
#
|
|
#
|
|
## Below, I don't like these tests. They're fragile and depend _heavily_
|
|
## on the geometry of the block device. But they are valuable. Eventually they
|
|
## should be removed and replaced with generalized tests.
|
|
#
|
|
## chained dir exhaustion test
|
|
#[cases.test_alloc_chained_dir_exhaustion]
|
|
#if = 'BLOCK_SIZE == 512'
|
|
#defines.BLOCK_COUNT = 1024
|
|
#code = '''
|
|
# lfs_t lfs;
|
|
# lfs_format(&lfs, cfg) => 0;
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
#
|
|
# // find out max file size
|
|
# lfs_mkdir(&lfs, "exhaustiondir") => 0;
|
|
# for (int i = 0; i < 10; i++) {
|
|
# char path[1024];
|
|
# sprintf(path, "dirwithanexhaustivelylongnameforpadding%d", i);
|
|
# lfs_mkdir(&lfs, path) => 0;
|
|
# }
|
|
# size_t size = strlen("blahblahblahblah");
|
|
# uint8_t buffer[1024];
|
|
# memcpy(buffer, "blahblahblahblah", size);
|
|
# lfs_file_t file;
|
|
# lfs_file_open(&lfs, &file, "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
|
|
# int count = 0;
|
|
# int err;
|
|
# while (true) {
|
|
# err = lfs_file_write(&lfs, &file, buffer, size);
|
|
# if (err < 0) {
|
|
# break;
|
|
# }
|
|
#
|
|
# count += 1;
|
|
# }
|
|
# err => LFS_ERR_NOSPC;
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# lfs_remove(&lfs, "exhaustion") => 0;
|
|
# lfs_remove(&lfs, "exhaustiondir") => 0;
|
|
# for (int i = 0; i < 10; i++) {
|
|
# char path[1024];
|
|
# sprintf(path, "dirwithanexhaustivelylongnameforpadding%d", i);
|
|
# lfs_remove(&lfs, path) => 0;
|
|
# }
|
|
#
|
|
# // see that chained dir fails
|
|
# lfs_file_open(&lfs, &file, "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
|
|
# for (int i = 0; i < count+1; i++) {
|
|
# lfs_file_write(&lfs, &file, buffer, size) => size;
|
|
# }
|
|
# lfs_file_sync(&lfs, &file) => 0;
|
|
#
|
|
# for (int i = 0; i < 10; i++) {
|
|
# char path[1024];
|
|
# sprintf(path, "dirwithanexhaustivelylongnameforpadding%d", i);
|
|
# lfs_mkdir(&lfs, path) => 0;
|
|
# }
|
|
#
|
|
# lfs_mkdir(&lfs, "exhaustiondir") => LFS_ERR_NOSPC;
|
|
#
|
|
# // shorten file to try a second chained dir
|
|
# while (true) {
|
|
# err = lfs_mkdir(&lfs, "exhaustiondir");
|
|
# if (err != LFS_ERR_NOSPC) {
|
|
# break;
|
|
# }
|
|
#
|
|
# lfs_ssize_t filesize = lfs_file_size(&lfs, &file);
|
|
# filesize > 0 => true;
|
|
#
|
|
# lfs_file_truncate(&lfs, &file, filesize - size) => 0;
|
|
# lfs_file_sync(&lfs, &file) => 0;
|
|
# }
|
|
# err => 0;
|
|
#
|
|
# lfs_mkdir(&lfs, "exhaustiondir2") => LFS_ERR_NOSPC;
|
|
#
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
# lfs_unmount(&lfs) => 0;
|
|
#'''
|
|
#
|
|
## split dir test
|
|
#[cases.test_alloc_split_dir]
|
|
#if = 'BLOCK_SIZE == 512'
|
|
#defines.BLOCK_COUNT = 1024
|
|
#code = '''
|
|
# lfs_t lfs;
|
|
# lfs_format(&lfs, cfg) => 0;
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
#
|
|
# // create one block hole for half a directory
|
|
# lfs_file_t file;
|
|
# lfs_file_open(&lfs, &file, "bump", LFS_O_WRONLY | LFS_O_CREAT) => 0;
|
|
# for (lfs_size_t i = 0; i < cfg->block_size; i += 2) {
|
|
# uint8_t buffer[1024];
|
|
# memcpy(&buffer[i], "hi", 2);
|
|
# }
|
|
# uint8_t buffer[1024];
|
|
# lfs_file_write(&lfs, &file, buffer, cfg->block_size) => cfg->block_size;
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# lfs_file_open(&lfs, &file, "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
|
|
# size_t size = strlen("blahblahblahblah");
|
|
# memcpy(buffer, "blahblahblahblah", size);
|
|
# for (lfs_size_t i = 0;
|
|
# i < (cfg->block_count-4)*(cfg->block_size-8);
|
|
# i += size) {
|
|
# lfs_file_write(&lfs, &file, buffer, size) => size;
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# // remount to force reset of lookahead
|
|
# lfs_unmount(&lfs) => 0;
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
#
|
|
# // open hole
|
|
# lfs_remove(&lfs, "bump") => 0;
|
|
#
|
|
# lfs_mkdir(&lfs, "splitdir") => 0;
|
|
# lfs_file_open(&lfs, &file, "splitdir/bump",
|
|
# LFS_O_WRONLY | LFS_O_CREAT) => 0;
|
|
# for (lfs_size_t i = 0; i < cfg->block_size; i += 2) {
|
|
# memcpy(&buffer[i], "hi", 2);
|
|
# }
|
|
# lfs_file_write(&lfs, &file, buffer, 2*cfg->block_size) => LFS_ERR_NOSPC;
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# lfs_unmount(&lfs) => 0;
|
|
#'''
|
|
#
|
|
## outdated lookahead test
|
|
#[cases.test_alloc_outdated_lookahead]
|
|
#if = 'BLOCK_SIZE == 512'
|
|
#defines.BLOCK_COUNT = 1024
|
|
#code = '''
|
|
# lfs_t lfs;
|
|
# lfs_format(&lfs, cfg) => 0;
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
#
|
|
# // fill completely with two files
|
|
# lfs_file_t file;
|
|
# lfs_file_open(&lfs, &file, "exhaustion1",
|
|
# LFS_O_WRONLY | LFS_O_CREAT) => 0;
|
|
# size_t size = strlen("blahblahblahblah");
|
|
# uint8_t buffer[1024];
|
|
# memcpy(buffer, "blahblahblahblah", size);
|
|
# for (lfs_size_t i = 0;
|
|
# i < ((cfg->block_count-2)/2)*(cfg->block_size-8);
|
|
# i += size) {
|
|
# lfs_file_write(&lfs, &file, buffer, size) => size;
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# lfs_file_open(&lfs, &file, "exhaustion2",
|
|
# LFS_O_WRONLY | LFS_O_CREAT) => 0;
|
|
# size = strlen("blahblahblahblah");
|
|
# memcpy(buffer, "blahblahblahblah", size);
|
|
# for (lfs_size_t i = 0;
|
|
# i < ((cfg->block_count-2+1)/2)*(cfg->block_size-8);
|
|
# i += size) {
|
|
# lfs_file_write(&lfs, &file, buffer, size) => size;
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# // remount to force reset of lookahead
|
|
# lfs_unmount(&lfs) => 0;
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
#
|
|
# // rewrite one file
|
|
# lfs_file_open(&lfs, &file, "exhaustion1",
|
|
# LFS_O_WRONLY | LFS_O_TRUNC) => 0;
|
|
# lfs_file_sync(&lfs, &file) => 0;
|
|
# size = strlen("blahblahblahblah");
|
|
# memcpy(buffer, "blahblahblahblah", size);
|
|
# for (lfs_size_t i = 0;
|
|
# i < ((cfg->block_count-2)/2)*(cfg->block_size-8);
|
|
# i += size) {
|
|
# lfs_file_write(&lfs, &file, buffer, size) => size;
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# // rewrite second file, this requires lookahead does not
|
|
# // use old population
|
|
# lfs_file_open(&lfs, &file, "exhaustion2",
|
|
# LFS_O_WRONLY | LFS_O_TRUNC) => 0;
|
|
# lfs_file_sync(&lfs, &file) => 0;
|
|
# size = strlen("blahblahblahblah");
|
|
# memcpy(buffer, "blahblahblahblah", size);
|
|
# for (lfs_size_t i = 0;
|
|
# i < ((cfg->block_count-2+1)/2)*(cfg->block_size-8);
|
|
# i += size) {
|
|
# lfs_file_write(&lfs, &file, buffer, size) => size;
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# lfs_unmount(&lfs) => 0;
|
|
#'''
|
|
#
|
|
## outdated lookahead and split dir test
|
|
#[cases.test_alloc_outdated_lookahead_split_dir]
|
|
#if = 'BLOCK_SIZE == 512'
|
|
#defines.BLOCK_COUNT = 1024
|
|
#code = '''
|
|
# lfs_t lfs;
|
|
# lfs_format(&lfs, cfg) => 0;
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
#
|
|
# // fill completely with two files
|
|
# lfs_file_t file;
|
|
# lfs_file_open(&lfs, &file, "exhaustion1",
|
|
# LFS_O_WRONLY | LFS_O_CREAT) => 0;
|
|
# size_t size = strlen("blahblahblahblah");
|
|
# uint8_t buffer[1024];
|
|
# memcpy(buffer, "blahblahblahblah", size);
|
|
# for (lfs_size_t i = 0;
|
|
# i < ((cfg->block_count-2)/2)*(cfg->block_size-8);
|
|
# i += size) {
|
|
# lfs_file_write(&lfs, &file, buffer, size) => size;
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# lfs_file_open(&lfs, &file, "exhaustion2",
|
|
# LFS_O_WRONLY | LFS_O_CREAT) => 0;
|
|
# size = strlen("blahblahblahblah");
|
|
# memcpy(buffer, "blahblahblahblah", size);
|
|
# for (lfs_size_t i = 0;
|
|
# i < ((cfg->block_count-2+1)/2)*(cfg->block_size-8);
|
|
# i += size) {
|
|
# lfs_file_write(&lfs, &file, buffer, size) => size;
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# // remount to force reset of lookahead
|
|
# lfs_unmount(&lfs) => 0;
|
|
# lfs_mount(&lfs, cfg) => 0;
|
|
#
|
|
# // rewrite one file with a hole of one block
|
|
# lfs_file_open(&lfs, &file, "exhaustion1",
|
|
# LFS_O_WRONLY | LFS_O_TRUNC) => 0;
|
|
# lfs_file_sync(&lfs, &file) => 0;
|
|
# size = strlen("blahblahblahblah");
|
|
# memcpy(buffer, "blahblahblahblah", size);
|
|
# for (lfs_size_t i = 0;
|
|
# i < ((cfg->block_count-2)/2 - 1)*(cfg->block_size-8);
|
|
# i += size) {
|
|
# lfs_file_write(&lfs, &file, buffer, size) => size;
|
|
# }
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# // try to allocate a directory, should fail!
|
|
# lfs_mkdir(&lfs, "split") => LFS_ERR_NOSPC;
|
|
#
|
|
# // file should not fail
|
|
# lfs_file_open(&lfs, &file, "notasplit",
|
|
# LFS_O_WRONLY | LFS_O_CREAT) => 0;
|
|
# lfs_file_write(&lfs, &file, "hi", 2) => 2;
|
|
# lfs_file_close(&lfs, &file) => 0;
|
|
#
|
|
# lfs_unmount(&lfs) => 0;
|
|
#'''
|