2835b17d14
This didn't really work out as well as I had hoped. There were a few
ideas on how to encode the bid/rid tuple without sacrificing the
(currently 31-bit) integer limit, but these just introduced too much
complexity.
Ideas:
1. In theory, as the mdirs increase in size, the quantity of mdirs needed
for a given number of files decreases. If we say the number of files
fits in an integer of a given size, than we can model the mapping to
mdirs and rids roughly as the number of bits in that integer split
between the two.
Since the block_size is known, the we can find a rather conservative,
yet useful, estimate of the upper bound of rids, which ends up
being ~16 bytes ((2 alts + 1 null + 1 tag) * 4 bytes).
And since our btrees are perfectly balanced, this encoding should only
waste 1 or 2 bits due to rounding to rounding and sign encoding for
special values.
bbbbbbbb bbbbbbbb bbbbbbbr rrrrrrrr
'-----------+-----------''----+---'
| '-- log2(block_size/32)-bit rid
'-------------------- remaining-bit bid
Unfortunately, while this works ok on paper, and maximize the use of
the bits we have available for the mid, the implementation ended up
awkward and difficult to use.
We need to either calculate the relatively complciated log2 of the
block_size on the fly, or cache the value, and use it to shift the
mid around to extract the bid/rid when needed.
Unfortunately, perhaps due to the it being easy to use the bid/rid
directly, we use and mutate the bid/rid quite a bit. We mutate when
updating the mdirs, when decoding grms, when seeking mdirs, etc. If
anything, updating the mid in total is rarer than updating the
bid/rid component in complicated situations.
Note to mention this required access to the lfs config to even begin
decoding, complicating the API and making the result less efficient.
Initial (unoptimized, and not even tested) code size showed ~+800
bytes. So I decided to scrap this.
Maybe it will be worth investigating dynamic rid sizes later, to
increase the possible mtree size for a given mid width. Not sure.
2. Probably one of the worst ideas I've had so far, but it would solve
the mid encoding problem, is to use some form a floating point to
encode the bid/rid pair:
.----------.
v .+-.
bbbbbbbb bbbbbbbb bbbrrrrr rrrrssss
'-----------+-------''----+---''-+'
| | '-- rid bits
| '--------- variable rid
'----------------------- variable bid
An even worse idea would be to use IEEE floating point here. Yes it
would work, and probably work annoyingly well, but we it risk
bringing in a lot of standard conforming backbending that we really
don't care about.
The idea here is to sacrifice some bits to encode the ratio of rid
bits to bid bits. The value of this over the using the block_size is
that we can decode the bid and rid using all of the bits in the
integer alone. Avoiding memory access (and worse debugging) to load
any external constants.
As a plus, all mids in the system would have the same exponent,
simplifying comparisons and other operations.
But this is just trying to solve complexity by adding more
complexity, so I'm not even going to try implementing it.
Still, it's an interesting idea...
In the end I've gone with the KISS implementation. Use half-width
integers, in this case uint16s, for both the bid and rid:
bbbbbbbb bbbbbbbb rrrrrrrr rrrrrrrr
'-------+-------' '-------+-------'
| '-- 16-bit rid
'-------------------- 16-bit bid
This suffers from weakened limits around the number of rids in a block
and number of mdirs in the mtree, which is unfortunate. Still it is
probably worth the tradeoff for the RAM savings and encoding simplicity.
If the mdir is reasonably sized, this does probably approach a decent
distribution of rids and bids in 32-bits. But for outlier cases with
very small and very large mdirs, it risks premature out of bounds
errors.
To protect against mtree errors, we will probably need an additional
configuration option in the form of an mdir limit. Conveniently this
would also provide a way to enforce 2-block mode.
rid errors, on the other hand, depend on block_size/32, so we may not
need another configuration option and can rely on the block_size
to determine if the rids can overflow.
This is probably worth revisiting in the future. Fortunately, with
mdir_limit and block_size configuration options, it should be possible
to increase these limits in the future if this mid bid/rid design
changes.
code stack
before: 22126 2136
after: 22326 (+0.9%) 2088 (-2.2%)
This code size increase was unexpected. Maybe non-32-bit-aligned integers
cost more to load in thumb? Unsure.
897 lines
28 KiB
TOML
897 lines
28 KiB
TOML
|
|
# Tests covering properties of the block allocator
|
|
|
|
# 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.t4_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.t4_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.t4_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, UNR, -1, NULL, 0))) => 0;
|
|
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(lfsr_mtree_weight(&lfs)-1, -1),
|
|
&mdir) => 0;
|
|
mdir.mid.rid = 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.rid, INLINED, +1, &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.rid, LFSR_TAG_INLINED,
|
|
buffer, 4) => 1;
|
|
assert(memcmp(buffer, &alphas[count % 26], 1) == 0);
|
|
|
|
count += 1;
|
|
mdir.mid.rid += 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 = (lfsr_mtree_isinlined(&lfs) ? -1 : 0);
|
|
mid < (lfs_ssize_t)lfsr_mtree_weight(&lfs);
|
|
mid++) {
|
|
lfsr_mdir_t mdir;
|
|
lfsr_mtree_lookup(&lfs, LFSR_MID(mid, -1), &mdir) => 0;
|
|
for (mdir.mid.rid = 0;
|
|
mdir.mid.rid < (lfs_ssize_t)mdir.m.rbyd.weight;
|
|
mdir.mid.rid++) {
|
|
uint8_t buffer[4];
|
|
lfsr_mdir_get(&lfs, &mdir, mdir.mid.rid, LFSR_TAG_INLINED,
|
|
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;
|
|
#'''
|