From 5adc1f54b764696c04d232919a3ee57d7f57d7ee Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Fri, 29 Sep 2023 00:58:29 -0500 Subject: [PATCH] Implemented and tested lfsr_file_truncate Not much to say here. We need to modify trees a bit, but at least it's relatively straightforward. --- lfs.c | 171 +++++++++++++-- lfs.h | 16 +- tests/test_files.toml | 468 +++++++++++++++++++++++++++++++++++------- 3 files changed, 568 insertions(+), 87 deletions(-) diff --git a/lfs.c b/lfs.c index 199e31a4..18b2568b 100644 --- a/lfs.c +++ b/lfs.c @@ -5322,6 +5322,7 @@ static int lfsr_mdir_commit__(lfs_t *lfs, lfsr_mdir_t *mdir, // us due to mdir compactions // // TODO should we preserve mode for all of these? + // TODO should we do the same for sprouts? } else if (lfsr_tag_key(attrs[i].tag) == LFSR_TAG_SHRUBTRUNK) { lfsr_file_t *file = (lfsr_file_t*)attrs[i].data.u.direct.buffer; @@ -8603,7 +8604,7 @@ static int lfsr_file_flushbuffer(lfs_t *lfs, lfsr_file_t *file) { lfsr_attr_t scratch_attrs[4]; lfsr_attr_t *attrs_ = scratch_attrs; - // have inlined data? + // have a sprout/null? if (!lfsr_file_hasshrub(file)) { // left data? this may create a hole if (file->buffer_pos > 0) { @@ -8747,8 +8748,8 @@ static int lfsr_file_flushbuffer(lfs_t *lfs, lfsr_file_t *file) { // commit our attributes int err = lfsr_mdir_commit(lfs, &file->m.mdir, LFSR_ATTRS( LFSR_ATTR_(file->m.mdir.mid, SHRUBATTRS, 0, SHRUBATTRS(file, - scratch_attrs, - attrs_ - scratch_attrs)))); + scratch_attrs, + attrs_ - scratch_attrs)))); if (err) { return err; } @@ -8767,6 +8768,11 @@ lfs_ssize_t lfsr_file_write(lfs_t *lfs, lfsr_file_t *file, LFS_ASSERT(lfsr_file_iswriteable(file)); LFS_ASSERT(size <= 0x7fffffff); + // would this write make our file larger than our size limit? + if (size > lfs->size_limit - file->pos) { + return LFS_ERR_FBIG; + } + // size=0 is a bit special and is gauranteed to have no effects on the // underlying file, this means no updating file pos or file size // @@ -8781,8 +8787,6 @@ lfs_ssize_t lfsr_file_write(lfs_t *lfs, lfsr_file_t *file, file->pos = file->size; } - // TODO do we need to prepare mutation? - lfs_off_t pos = file->pos; const uint8_t *buffer_ = buffer; int err; @@ -8855,7 +8859,7 @@ int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) { // inlined file in that case if (file->buffer_size >= file->size) { LFS_ASSERT(file->buffer_size == file->size); - LFS_ASSERT(file->buffer_pos == 0); + LFS_ASSERT(file->buffer_pos == 0 || file->buffer_size == 0); // commit our file's metadata err = lfsr_mdir_commit(lfs, &file->m.mdir, LFSR_ATTRS( @@ -8873,13 +8877,15 @@ int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) { // we risk runaway O(n^2) behavior file->buffer_size = 0; - // we need to look up the inlined data again... - // TODO deduplicate? - err = lfsr_mdir_lookup(lfs, &file->m.mdir, - file->m.mdir.mid, LFSR_TAG_INLINED, - NULL, &file->inlined.u.data); - if (err) { - return err; + if (file->size > 0) { + // we need to look up the inlined data again... + // TODO deduplicate? + err = lfsr_mdir_lookup(lfs, &file->m.mdir, + file->m.mdir.mid, LFSR_TAG_INLINED, + NULL, &file->inlined.u.data); + if (err) { + return err; + } } } else { // first make sure to flush our buffer @@ -8957,6 +8963,145 @@ lfs_soff_t lfsr_file_size(lfs_t *lfs, lfsr_file_t *file) { return file->size; } +int lfsr_file_truncate(lfs_t *lfs, lfsr_file_t *file, lfs_off_t size) { + // exceeds our size limit? + if (size > lfs->size_limit) { + return LFS_ERR_FBIG; + } + + // do nothing if our size does not change + if (file->size == size) { + return 0; + } + + // TODO make this recoverable on failure + + // mark as unsynced before we commit anything + file->flags |= LFS_F_UNSYNCED; + + lfsr_attr_t scratch_attrs[2]; + lfsr_attr_t *attrs_ = scratch_attrs; + + // if our truncated file is contained entirely in our buffer, + // revert to a sprout + lfs_size_t buffer_size = lfs_min32( + file->buffer_size, + size - lfs_min32(file->buffer_pos, size)); + if (buffer_size >= size) { + file->inlined.u.data = LFSR_DATA_DISK(0, 0, 0); + + // have a sprout/null? convert to a shrub + } else if (!lfsr_file_hasshrub(file)) { + *attrs_++ = LFSR_ATTR(0, + SHRUB(INLINED), +size, DISK( + file->inlined.u.data.u.disk.block, + file->inlined.u.data.u.disk.off, + lfs_min32( + lfsr_data_size(&file->inlined.u.data), + size))); + + // have a shrub? + } else if (lfsr_file_hasshrub(file)) { + // TODO can this be deduplicated with flushbuffer? some sort of + // lfsr_file_shrubcarveleft? + + // this should never happen, every route to zero-weight shrub + // should revert to an inlined file + LFS_ASSERT(file->inlined.u.rbyd.weight > 0); + LFS_ASSERT(size > 0); + + // left sibling? + lfs_soff_t left_overlap = 0; + lfsr_srid_t left_rid; + lfsr_tag_t left_tag; + lfsr_rid_t left_weight; + lfsr_data_t left_data; + int err = lfsr_rbyd_lookupnext(lfs, &file->inlined.u.rbyd, + lfs_min32( + size, + file->inlined.u.rbyd.weight)-1, 0, + &left_rid, &left_tag, &left_weight, &left_data); + if (err && err != LFS_ERR_NOENT) { + return err; + } + LFS_ASSERT(err != LFS_ERR_NOENT); + LFS_ASSERT(left_tag == LFSR_TAG_SHRUB(INLINED)); + + // this can be negative! + left_overlap = (left_rid+1) - size; + + // can we get away with a simple grow attr? this may + // create a hole + if (left_overlap != 0 + && size + >= left_rid-(left_weight-1) + + lfsr_data_size(&left_data)) { + *attrs_++ = LFSR_ATTR(left_rid, + SHRUB(GROW), -left_overlap, NULL); + + // need to carve out left data? + } else if (left_overlap > 0) { + *attrs_++ = LFSR_ATTR(left_rid, + SHRUB(GROW(INLINED)), -left_overlap, + DISK( + left_data.u.disk.block, + left_data.u.disk.off, + left_weight - left_overlap)); + } + + // remove any data we're truncating + *attrs_++ = LFSR_ATTR(file->inlined.u.rbyd.weight + - left_overlap - 1, + SHRUB(RM), -(file->inlined.u.rbyd.weight + - size - left_overlap), NULL); + } + + // commit any shrub changes + if (attrs_ > scratch_attrs) { + int err = lfsr_mdir_commit(lfs, &file->m.mdir, LFSR_ATTRS( + LFSR_ATTR_(file->m.mdir.mid, SHRUBATTRS, 0, SHRUBATTRS(file, + scratch_attrs, + attrs_ - scratch_attrs)))); + if (err) { + return err; + } + } + + // TODO update btree + + // update our buffer + file->buffer_size = buffer_size; + + // update our internal file size + // + // if this grows our file, leave it up to lfsr_file_sync to update + // on on-disk metadata + file->size = size; + + return 0; +} + +int lfsr_file_fruncate(lfs_t *lfs, lfsr_file_t *file, lfs_off_t size) { + // exceeds our size limit? + if (size > lfs->size_limit) { + return LFS_ERR_FBIG; + } + +// // TODO does this risk overflow? +// // if we're increasing the file size, just update our internal size/off +// // and leave it to lfsr_file_sync to do most of the work +// if (size > file->size) { +// file->off += +// file->size = size; +// return 0; +// } + + // TODO + LFS_ASSERT(false); + + return 0; +} + diff --git a/lfs.h b/lfs.h index 0fc0f3e1..dfb23470 100644 --- a/lfs.h +++ b/lfs.h @@ -785,10 +785,24 @@ lfs_soff_t lfsr_file_seek(lfs_t *lfs, lfsr_file_t *file, lfs_soff_t off, uint8_t whence); #ifndef LFS_READONLY -// Truncates the size of the file to the specified size +// Truncate/grow the size of the file to the specified size +// +// If size is larger than the current file size, a hole is created, appearing +// as if the file was filled with zeros. // // Returns a negative error code on failure. int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size); +int lfsr_file_truncate(lfs_t *lfs, lfsr_file_t *file, lfs_off_t size); +#endif + +#ifndef LFS_READONLY +// Truncate/grow the file, but from the front +// +// If size is larger than the current file size, a hole is created, appearing +// as if the file was filled with zeros. +// +// Returns a negative error code on failure. +int lfsr_file_fruncate(lfs_t *lfs, lfsr_file_t *file, lfs_off_t size); #endif // Return the position of the file diff --git a/tests/test_files.toml b/tests/test_files.toml index 7f540d99..52eab57b 100644 --- a/tests/test_files.toml +++ b/tests/test_files.toml @@ -644,15 +644,17 @@ code = ''' } // write first chunk? - if (MASK & 1) { - if (ORDER == 0) { + if (ORDER == 0) { + if (MASK & 0x1) { for (lfs_size_t i = 0; i < CHUNK; i++) { sim[i] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0; lfsr_file_write(&lfs, &file, &sim[0], CHUNK) => CHUNK; - } else { + } + } else { + if (MASK & 0x4) { for (lfs_size_t i = 0; i < CHUNK; i++) { sim[SIZE-CHUNK+i] = 'a' + (TEST_PRNG(&prng) % 26); } @@ -700,15 +702,17 @@ code = ''' } // write third chunk? - if (MASK & 4) { - if (ORDER == 0) { + if (ORDER == 0) { + if (MASK & 0x4) { for (lfs_size_t i = 0; i < CHUNK; i++) { sim[SIZE-CHUNK+i] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_seek(&lfs, &file, SIZE-CHUNK, LFS_SEEK_SET) => SIZE-CHUNK; lfsr_file_write(&lfs, &file, &sim[SIZE-CHUNK], CHUNK) => CHUNK; - } else { + } + } else { + if (MASK & 0x1) { for (lfs_size_t i = 0; i < CHUNK; i++) { sim[i] = 'a' + (TEST_PRNG(&prng) % 26); } @@ -774,7 +778,7 @@ defines.CHUNK = ['CACHE_SIZE/2', '4', '1'] defines.MASK = [0, 1, 2, 3, 4, 5, 6, 7] # 0 => in-order # 1 => reversed -defines.ORDER = [0] # TODO 1? +defines.ORDER = [0, 1] defines.SYNC = [false, true] defines.REMOUNT = [false, true] reentrant = true @@ -816,15 +820,17 @@ code = ''' } // write first chunk? - if (MASK & 1) { - if (ORDER == 0) { + if (ORDER == 0) { + if (MASK & 0x1) { for (lfs_size_t i = 0; i < CHUNK; i++) { sim[i] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0; lfsr_file_write(&lfs, &file, &sim[0], CHUNK) => CHUNK; - } else { + } + } else { + if (MASK & 0x4) { for (lfs_size_t i = 0; i < CHUNK; i++) { sim[SIZE-CHUNK+i] = 'a' + (TEST_PRNG(&prng) % 26); } @@ -872,15 +878,17 @@ code = ''' } // write third chunk? - if (MASK & 4) { - if (ORDER == 0) { + if (ORDER == 0) { + if (MASK & 0x4) { for (lfs_size_t i = 0; i < CHUNK; i++) { sim[SIZE-CHUNK+i] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_seek(&lfs, &file, SIZE-CHUNK, LFS_SEEK_SET) => SIZE-CHUNK; lfsr_file_write(&lfs, &file, &sim[SIZE-CHUNK], CHUNK) => CHUNK; - } else { + } + } else { + if (MASK & 0x1) { for (lfs_size_t i = 0; i < CHUNK; i++) { sim[i] = 'a' + (TEST_PRNG(&prng) % 26); } @@ -936,10 +944,10 @@ code = ''' lfsr_unmount(&lfs) => 0; ''' -# writing any data structure backwards always reveals issues -[cases.test_files_reversed_overwrite] -defines.SIZE = ['CACHE_SIZE/2', '2*CACHE_SIZE'] -defines.CHUNK = ['CACHE_SIZE/2', '4', '1'] +# simple truncate test +[cases.test_files_truncate] +defines.FROM = ['0', 'CACHE_SIZE/2', '2*CACHE_SIZE'] +defines.TO = ['0', 'CACHE_SIZE/2', '2*CACHE_SIZE'] defines.SYNC = [false, true] defines.REMOUNT = [false, true] reentrant = true @@ -957,12 +965,13 @@ code = ''' lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; // simulate our file in ram - uint8_t sim[SIZE]; + uint8_t sim[lfs_max32(FROM,TO)]; + memset(sim, 0, lfs_max32(FROM,TO)); uint32_t prng = 42; - for (lfs_size_t i = 0; i < SIZE; i++) { + for (lfs_size_t i = 0; i < FROM; i++) { sim[i] = 'a' + (TEST_PRNG(&prng) % 26); } - lfsr_file_write(&lfs, &file, sim, SIZE) => SIZE; + lfsr_file_write(&lfs, &file, sim, FROM) => FROM; // sync? if (SYNC) { @@ -977,27 +986,10 @@ code = ''' lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0; } - // write to file incrementally and backwards - for (lfs_size_t i = 0; i < SIZE; i += CHUNK) { - for (lfs_size_t j = 0; j < CHUNK; j++) { - sim[SIZE-i-CHUNK+j] = 'a' + (TEST_PRNG(&prng) % 26); - } - lfsr_file_seek(&lfs, &file, SIZE-i-CHUNK, LFS_SEEK_SET) => SIZE-i-CHUNK; - lfsr_file_write(&lfs, &file, &sim[SIZE-i-CHUNK], CHUNK) => CHUNK; + // truncate to new size + lfsr_file_truncate(&lfs, &file, TO) => 0; - // sync? - if (SYNC) { - lfsr_file_sync(&lfs, &file) => 0; - } - - // remount? - if (REMOUNT) { - lfsr_file_close(&lfs, &file) => 0; - lfsr_unmount(&lfs) => 0; - lfsr_mount(&lfs, CFG) => 0; - lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0; - } - } + // close lfsr_file_close(&lfs, &file) => 0; // remount? @@ -1011,7 +1003,7 @@ code = ''' lfsr_stat(&lfs, "hello", &info) => 0; assert(strcmp(info.name, "hello") == 0); assert(info.type == LFS_TYPE_REG); - assert(info.size == SIZE); + assert(info.size == TO); // and with dir read lfsr_dir_t dir; @@ -1025,29 +1017,147 @@ code = ''' lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "hello") == 0); assert(info.type == LFS_TYPE_REG); - assert(info.size == SIZE); + assert(info.size == TO); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // try reading our file lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0; // is size correct? - lfsr_file_size(&lfs, &file) => SIZE; + lfsr_file_size(&lfs, &file) => TO; // try reading - uint8_t rbuf[2*SIZE]; - memset(rbuf, 0xaa, 2*SIZE); - lfsr_file_read(&lfs, &file, rbuf, 2*SIZE) => SIZE; - // does our file match our simulation? - assert(memcmp(rbuf, sim, SIZE) == 0); + uint8_t rbuf[2*TO]; + memset(rbuf, 0xaa, 2*TO); + lfsr_file_read(&lfs, &file, rbuf, 2*TO) => TO; + assert(memcmp(rbuf, sim, TO) == 0); lfsr_file_close(&lfs, &file) => 0; lfsr_unmount(&lfs) => 0; ''' +# the main purpose of this test is to check that data is not hidden +# and then revealed by truncate, that would be bad +[cases.test_files_truncate_2] +defines.FROM = ['0', 'CACHE_SIZE/2', '2*CACHE_SIZE'] +defines.AND = ['0', 'CACHE_SIZE/2', '2*CACHE_SIZE'] +defines.TO = ['0', 'CACHE_SIZE/2', '2*CACHE_SIZE'] +defines.SYNC = [false, true] +defines.REMOUNT = [false, true] +reentrant = true +code = ''' + // format once per test + lfs_t lfs; + int err = lfsr_mount(&lfs, CFG); + if (err) { + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // create a file, truncating in case of powerloss + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "hello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + // simulate our file in ram + uint8_t sim[lfs_max32(FROM,TO)]; + memset(sim, 0, lfs_max32(FROM,TO)); + uint32_t prng = 42; + for (lfs_size_t i = 0; i < FROM; i++) { + sim[i] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file, sim, FROM) => FROM; + + // sync? + if (SYNC) { + lfsr_file_sync(&lfs, &file) => 0; + } + + // remount? + if (REMOUNT) { + lfsr_file_close(&lfs, &file) => 0; + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0; + } + + // truncate to intermediate size + lfsr_file_truncate(&lfs, &file, AND) => 0; + memset(&sim[AND], 0, lfs_max32(FROM,TO) + - lfs_min32(AND, lfs_max32(FROM,TO))); + + // sync? + if (SYNC) { + lfsr_file_sync(&lfs, &file) => 0; + } + + // remount? + if (REMOUNT) { + lfsr_file_close(&lfs, &file) => 0; + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0; + } + + // truncate to new size + lfsr_file_truncate(&lfs, &file, TO) => 0; + + // close + lfsr_file_close(&lfs, &file) => 0; + + // remount? + if (REMOUNT) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // check our file with stat + struct lfs_info info; + lfsr_stat(&lfs, "hello", &info) => 0; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == TO); + + // and with dir read + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "/") => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == TO); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + // try reading our file + lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0; + // is size correct? + lfsr_file_size(&lfs, &file) => TO; + // try reading + uint8_t rbuf[2*TO]; + memset(rbuf, 0xaa, 2*TO); + lfsr_file_read(&lfs, &file, rbuf, 2*TO) => TO; + assert(memcmp(rbuf, sim, TO) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +# TODO +# [cases.test_files_fruncate] +# [cases.test_files_fruncate_2] + # writing any data structure backwards always reveals issues -[cases.test_files_reversed_holes] +[cases.test_files_reversed] defines.SIZE = ['CACHE_SIZE/2', '2*CACHE_SIZE'] defines.CHUNK = ['CACHE_SIZE/2', '4', '1'] +# 0 => no init +# 1 => fill with data +# 2 => truncate to size +defines.INIT = [0, 1, 2] defines.SYNC = [false, true] defines.REMOUNT = [false, true] reentrant = true @@ -1067,7 +1177,17 @@ code = ''' // simulate our file in ram uint8_t sim[SIZE]; uint32_t prng = 42; - memset(sim, 0, SIZE); + if (INIT == 0) { + memset(sim, 0, SIZE); + } else if (INIT == 1) { + for (lfs_size_t i = 0; i < SIZE; i++) { + sim[i] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file, sim, SIZE) => SIZE; + } else if (INIT == 2) { + memset(sim, 0, SIZE); + lfsr_file_truncate(&lfs, &file, SIZE) => 0; + } // sync? if (SYNC) { @@ -1155,7 +1275,10 @@ defines.N = 100 defines.SEED = 'range(100)' defines.SIZE = ['CACHE_SIZE/2', '2*CACHE_SIZE'] defines.CHUNK = ['CACHE_SIZE/2', '4', '1'] -defines.INIT = [false, true] +# 0 => no init +# 1 => fill with data +# 2 => truncate to size +defines.INIT = [0, 1, 2] defines.SYNC = [false, true] defines.REMOUNT = [false, true] code = ''' @@ -1171,15 +1294,19 @@ code = ''' uint8_t sim[SIZE]; lfs_off_t size; uint32_t prng = SEED; - if (INIT) { + if (INIT == 0) { + memset(sim, 0, SIZE); + size = 0; + } else if (INIT == 1) { for (lfs_size_t i = 0; i < SIZE; i++) { sim[i] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, sim, SIZE) => SIZE; size = SIZE; - } else { + } else if (INIT == 2) { memset(sim, 0, SIZE); - size = 0; + lfsr_file_truncate(&lfs, &file, SIZE) => 0; + size = SIZE; } // sync? @@ -1275,7 +1402,10 @@ defines.SEED = 'range(100)' defines.SIZE = ['CACHE_SIZE/2', '2*CACHE_SIZE'] # chunk is more an upper limit here defines.CHUNK = ['CACHE_SIZE/2', '4'] -defines.INIT = [false, true] +# 0 => no init +# 1 => fill with data +# 2 => truncate to size +defines.INIT = [0, 1, 2] defines.SYNC = [false, true] defines.REMOUNT = [false, true] code = ''' @@ -1291,15 +1421,19 @@ code = ''' uint8_t sim[SIZE]; lfs_off_t size; uint32_t prng = SEED; - if (INIT) { + if (INIT == 0) { + memset(sim, 0, SIZE); + size = 0; + } else if (INIT == 1) { for (lfs_size_t i = 0; i < SIZE; i++) { sim[i] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, sim, SIZE) => SIZE; size = SIZE; - } else { + } else if (INIT == 2) { memset(sim, 0, SIZE); - size = 0; + lfsr_file_truncate(&lfs, &file, SIZE) => 0; + size = SIZE; } // sync? @@ -1469,7 +1603,10 @@ defines.WHENCE = ['LFS_SEEK_SET', 'LFS_SEEK_CUR', 'LFS_SEEK_END'] defines.SIZE = ['CACHE_SIZE/2', '2*CACHE_SIZE'] # chunk is more an upper limit here defines.CHUNK = ['CACHE_SIZE/2', '4'] -defines.INIT = [false, true] +# 0 => no init +# 1 => fill with data +# 2 => truncate to size +defines.INIT = [0, 1, 2] defines.SYNC = [false, true] code = ''' lfs_t lfs; @@ -1484,15 +1621,19 @@ code = ''' uint8_t sim[SIZE]; lfs_off_t size; uint32_t prng = SEED; - if (INIT) { + if (INIT == 0) { + memset(sim, 0, SIZE); + size = 0; + } else if (INIT == 1) { for (lfs_size_t i = 0; i < SIZE; i++) { sim[i] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, sim, SIZE) => SIZE; size = SIZE; - } else { + } else if (INIT == 2) { memset(sim, 0, SIZE); - size = 0; + lfsr_file_truncate(&lfs, &file, SIZE) => 0; + size = SIZE; } lfsr_file_close(&lfs, &file) => 0; @@ -1588,7 +1729,10 @@ defines.WHENCE = ['LFS_SEEK_SET', 'LFS_SEEK_CUR', 'LFS_SEEK_END'] defines.SIZE = ['CACHE_SIZE/2', '2*CACHE_SIZE'] # chunk is more an upper limit here defines.CHUNK = ['CACHE_SIZE/2', '4'] -defines.INIT = [false, true] +# 0 => no init +# 1 => fill with data +# 2 => truncate to size +defines.INIT = [0, 1, 2] defines.SYNC = [false, true] code = ''' lfs_t lfs; @@ -1603,15 +1747,19 @@ code = ''' uint8_t sim[SIZE]; lfs_off_t size; uint32_t prng = SEED; - if (INIT) { + if (INIT == 0) { + memset(sim, 0, SIZE); + size = 0; + } else if (INIT == 1) { for (lfs_size_t i = 0; i < SIZE; i++) { sim[i] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, sim, SIZE) => SIZE; size = SIZE; - } else { + } else if (INIT == 2) { memset(sim, 0, SIZE); - size = 0; + lfsr_file_truncate(&lfs, &file, SIZE) => 0; + size = SIZE; } lfsr_file_close(&lfs, &file) => 0; @@ -1727,7 +1875,10 @@ code = ''' [cases.test_files_seek_negative] defines.WHENCE = ['LFS_SEEK_SET', 'LFS_SEEK_CUR', 'LFS_SEEK_END'] defines.SIZE = ['CACHE_SIZE/2', '2*CACHE_SIZE'] -defines.INIT = [false, true] +# 0 => no init +# 1 => fill with data +# 2 => truncate to size +defines.INIT = [0, 1, 2] defines.MODE = ['LFS_O_RDONLY', 'LFS_O_WRONLY', 'LFS_O_RDWR'] code = ''' lfs_t lfs; @@ -1742,15 +1893,19 @@ code = ''' uint8_t sim[SIZE]; lfs_off_t size; uint32_t prng = 42; - if (INIT) { + if (INIT == 0) { + memset(sim, 0, SIZE); + size = 0; + } else if (INIT == 1) { for (lfs_size_t i = 0; i < SIZE; i++) { sim[i] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, sim, SIZE) => SIZE; size = SIZE; - } else { + } else if (INIT == 2) { memset(sim, 0, SIZE); - size = 0; + lfsr_file_truncate(&lfs, &file, SIZE) => 0; + size = SIZE; } lfsr_file_close(&lfs, &file) => 0; @@ -1803,11 +1958,178 @@ code = ''' lfsr_unmount(&lfs) => 0; ''' +# heavy fuzz test with rw seeks, truncate, and TODO fruncate +[cases.test_files_rwtf_fuzz] +defines.N = 100 +defines.SEED = 'range(100)' +defines.SIZE = ['CACHE_SIZE/2', '2*CACHE_SIZE'] +# chunk is more an upper limit here +defines.CHUNK = ['CACHE_SIZE/2', '4'] +# 0 => no init +# 1 => fill with data +# 2 => truncate to size +defines.INIT = [0, 1, 2] +defines.SYNC = [false, true] +defines.REMOUNT = [false, true] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create a file + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "hello", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + // simulate our file in ram + uint8_t sim[SIZE]; + lfs_off_t size; + uint32_t prng = SEED; + if (INIT == 0) { + memset(sim, 0, SIZE); + size = 0; + } else if (INIT == 1) { + for (lfs_size_t i = 0; i < SIZE; i++) { + sim[i] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file, sim, SIZE) => SIZE; + size = SIZE; + } else if (INIT == 2) { + memset(sim, 0, SIZE); + lfsr_file_truncate(&lfs, &file, SIZE) => 0; + size = SIZE; + } + + // sync? + if (SYNC) { + lfsr_file_sync(&lfs, &file) => 0; + } + + // remount? + if (REMOUNT) { + lfsr_file_close(&lfs, &file) => 0; + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + lfsr_file_open(&lfs, &file, "hello", LFS_O_RDWR) => 0; + } + + for (lfs_size_t i = 0; i < N; i++) { + // and if we are reading, writing, or truncating + uint8_t op = TEST_PRNG(&prng) % 3; + + // writing? + if (op == 0) { + // choose a random location + lfs_off_t off = TEST_PRNG(&prng) % SIZE; + // and a random size, up to the chunk size + lfs_size_t chunk = lfs_min32( + TEST_PRNG(&prng) % CHUNK, + SIZE - off); + + // seek + lfsr_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off; + + // update the sim + for (lfs_size_t j = 0; j < chunk; j++) { + sim[off+j] = 'a' + (TEST_PRNG(&prng) % 26); + } + if (chunk != 0) { + size = lfs_max32(size, off+chunk); + } + + // update the file + lfsr_file_write(&lfs, &file, &sim[off], chunk) => chunk; + + // sync? + if (SYNC) { + lfsr_file_sync(&lfs, &file) => 0; + } + + // remount? + if (REMOUNT) { + lfsr_file_close(&lfs, &file) => 0; + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + lfsr_file_open(&lfs, &file, "hello", LFS_O_RDWR) => 0; + } + + // reading? + } else if (op == 1) { + // choose a random location + lfs_off_t off = TEST_PRNG(&prng) % SIZE; + // and a random size, up to the chunk size + lfs_size_t chunk = lfs_min32( + TEST_PRNG(&prng) % CHUNK, + SIZE - off); + + // seek + lfsr_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off; + + // we may read less than chunk if we're past eof + lfs_off_t expected = lfs_min32( + chunk, + size - lfs_min32(off, size)); + + // read the file and assert we got the correct data + uint8_t rbuf[2*SIZE]; + memset(rbuf, 0xaa, 2*SIZE); + lfsr_file_read(&lfs, &file, rbuf, chunk) => expected; + assert(memcmp(rbuf, &sim[off], expected) == 0); + + // truncating? + } else if (op == 2) { + // choose a random new file size + lfs_off_t size_ = TEST_PRNG(&prng) % SIZE; + + // update the sim + memset(&sim[size_], 0, size - lfs_min32(size_, size)); + size = size_; + + // truncate the file + lfsr_file_truncate(&lfs, &file, size_) => 0; + } + } + lfsr_file_close(&lfs, &file) => 0; + + // check our file with stat + struct lfs_info info; + lfsr_stat(&lfs, "hello", &info) => 0; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == size); + + // and with dir read + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "/") => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == size); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + // try reading our file + lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0; + // is size correct? + lfsr_file_size(&lfs, &file) => size; + // try reading + uint8_t rbuf[2*SIZE]; + memset(rbuf, 0xaa, 2*SIZE); + lfsr_file_read(&lfs, &file, rbuf, 2*SIZE) => size; + // does our file match our simulation? + assert(memcmp(rbuf, sim, size) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; +''' + # TODO -# [cases.test_files_truncate] ? -# [cases.test_files_fruncate] ? -# [cases.test_files_no_hidden_data] # [cases.test_files_rwtf_fuzz] ? # [cases.test_files_push] ? # [cases.test_files_pop] ?