diff --git a/lfs.c b/lfs.c index fa8d2bd4..69b3bc32 100644 --- a/lfs.c +++ b/lfs.c @@ -3528,6 +3528,7 @@ static lfs_ssize_t lfsr_rbyd_estimate(lfs_t *lfs, const lfsr_rbyd_t *rbyd, return dsize; } + LFS_ASSERT(weight > 0); lower_rid += weight; lower_dsize += dsize; } else { @@ -3538,6 +3539,7 @@ static lfs_ssize_t lfsr_rbyd_estimate(lfs_t *lfs, const lfsr_rbyd_t *rbyd, return dsize; } + LFS_ASSERT(weight > 0); upper_rid -= weight; upper_dsize += dsize; } @@ -5593,12 +5595,58 @@ static int lfsr_mdir_commit__(lfs_t *lfs, lfsr_mdir_t *mdir, return err; } + // special case for shrubs, we need to copy these over + if (tag == LFSR_TAG_TRUNK) { + // TODO lfsr_rbyd_appendshrub? + lfsr_rbyd_t shrub = mdir__->rbyd; + err = lfsr_data_readtrunk(lfs, &data, + &shrub.trunk, (lfsr_rid_t*)&shrub.weight); + if (err) { + return err; + } + + // save our current trunk/weight + lfs_size_t trunk = rbyd_.trunk; + lfsr_srid_t weight = rbyd_.weight; + + // keep track of the start of our new tree + lfs_size_t off = rbyd_.eoff; + + // compact our inlined tree + err = lfsr_rbyd_appendcompactrbyd(lfs, &rbyd_, true, + -1, -1, &shrub); + if (err) { + return err; + } + + err = lfsr_rbyd_compact(lfs, &rbyd_, true, off); + if (err) { + return err; + } + + // restore mdir to the main trunk/weight, write our + // new shrub tag + lfs_swap32(&rbyd_.trunk, &trunk); + lfs_sswap32(&rbyd_.weight, &weight); + + uint8_t trunk_buf[LFSR_TRUNK_DSIZE]; + err = lfsr_rbyd_appendattr(lfs, &rbyd_, + rid - lfs_smax32(start_rid, 0), + LFSR_TAG_TRUNK, 0, lfsr_data_fromtrunk( + trunk, weight, + trunk_buf)); + if (err) { + return err; + } + // append the attr - err = lfsr_rbyd_appendattr(lfs, &rbyd_, - rid - lfs_smax32(start_rid, 0), - tag, 0, data); - if (err) { - return err; + } else { + err = lfsr_rbyd_appendattr(lfs, &rbyd_, + rid - lfs_smax32(start_rid, 0), + tag, 0, data); + if (err) { + return err; + } } } @@ -5615,6 +5663,9 @@ static int lfsr_mdir_commit__(lfs_t *lfs, lfsr_mdir_t *mdir, // // it is important that these rbyds share eoff/cksum/etc // + // TODO does allowing shrub clobbering here save a bit of RAM? + lfs_size_t trunk = rbyd_.trunk; + lfsr_srid_t weight = rbyd_.weight; rbyd_.trunk = bshrubcommit->bshrub->rbyd_.trunk; rbyd_.weight = bshrubcommit->bshrub->rbyd_.weight; @@ -5630,11 +5681,11 @@ static int lfsr_mdir_commit__(lfs_t *lfs, lfsr_mdir_t *mdir, } } - // revert mdir to main trunk/weight + // restore mdir to the main trunk/weight bshrubcommit->bshrub->rbyd_.trunk = rbyd_.trunk; bshrubcommit->bshrub->rbyd_.weight = rbyd_.weight; - rbyd_.trunk = mdir->rbyd.trunk; - rbyd_.weight = mdir->rbyd.weight; + rbyd_.trunk = trunk; + rbyd_.weight = weight; // lazily encode inlined trunks in case they change underneath // us due to mdir compactions diff --git a/tests/test_files.toml b/tests/test_files.toml index 777a9327..ce7eed7b 100644 --- a/tests/test_files.toml +++ b/tests/test_files.toml @@ -416,6 +416,81 @@ code = ''' lfsr_unmount(&lfs) => 0; ''' +# root is also not a file +[cases.test_files_root_not_file] +defines.REMOUNT = [false, true] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // try reading our root as a file + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "/", LFS_O_RDONLY) => LFS_ERR_INVAL; + + // try writing our root as a file + lfsr_file_open(&lfs, &file, "/", LFS_O_WRONLY) => LFS_ERR_INVAL; + lfsr_file_open(&lfs, &file, "/", + LFS_O_WRONLY | LFS_O_TRUNC) => LFS_ERR_INVAL; + lfsr_file_open(&lfs, &file, "/", + LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_INVAL; + lfsr_file_open(&lfs, &file, "/", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => LFS_ERR_INVAL; + + // try rename a file on top of our directory + lfsr_file_open(&lfs, &file, "not_hello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + uint8_t wbuf[8192]; + strcpy((char*)wbuf, "Hello World!"); + lfs_size_t wsize = strlen((const char*)wbuf); + lfsr_file_write(&lfs, &file, wbuf, wsize) => wsize; + lfsr_file_close(&lfs, &file) => 0; + + lfsr_rename(&lfs, "not_hello", "/") => LFS_ERR_INVAL; + + // remount? + if (REMOUNT) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // check our root with stat + struct lfs_info info; + lfsr_stat(&lfs, "/", &info) => 0; + assert(strcmp(info.name, "/") == 0); + assert(info.type == LFS_TYPE_DIR); + + // 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, "not_hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == wsize); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + // did we corrupt our renaming file? + // try reading our file + lfsr_file_open(&lfs, &file, "not_hello", LFS_O_RDONLY) => 0; + // is size correct? + lfsr_file_size(&lfs, &file) => wsize; + // try reading + uint8_t rbuf[8192]; + memset(rbuf, 0xaa, sizeof(rbuf)); + lfsr_file_read(&lfs, &file, rbuf, sizeof(rbuf)) => wsize; + assert(memcmp(rbuf, wbuf, wsize) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; +''' + # try writing larger files # # note: @@ -494,6 +569,275 @@ code = ''' lfsr_unmount(&lfs) => 0; ''' +# test removing files of various sizes +# +# to be honest, this doesn't really test much and is just included +# for completeness +# +[cases.test_files_rm] +defines.SIZE = [ + '0', + 'CACHE_SIZE/2', + '2*CACHE_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.REMOUNT = [false, true] +defines.CACHE_SIZE = 64 +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_WRONLY | LFS_O_CREAT) => 0; + uint8_t wbuf[SIZE]; + uint32_t prng = 42; + for (lfs_size_t i = 0; i < SIZE; i++) { + wbuf[i] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + // remount? + if (REMOUNT) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // remove our file + lfsr_remove(&lfs, "hello") => 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) => LFS_ERR_NOENT; + + // 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) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +# test renaming files of various sizes +[cases.test_files_mv] +defines.SIZE = [ + '0', + 'CACHE_SIZE/2', + '2*CACHE_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.N = [0, 128] +defines.REMOUNT = [false, true] +defines.CACHE_SIZE = 64 +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create a number of directories to distance our files + for (lfs_size_t i = 0; i < N; i++) { + char path[256]; + sprintf(path, "basalt%04d", i); + lfsr_mkdir(&lfs, path) => 0; + } + + // create a file + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "amethyst", LFS_O_WRONLY | LFS_O_CREAT) => 0; + uint8_t wbuf[SIZE]; + uint32_t prng = 42; + for (lfs_size_t i = 0; i < SIZE; i++) { + wbuf[i] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + // remount? + if (REMOUNT) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // rename the file + lfsr_rename(&lfs, "amethyst", "calcite") => 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, "calcite", &info) => 0; + assert(strcmp(info.name, "calcite") == 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); + for (lfs_size_t i = 0; i < N; i++) { + char path[256]; + sprintf(path, "basalt%04d", i); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, path) == 0); + assert(info.type == LFS_TYPE_DIR); + } + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "calcite") == 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, "calcite", 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; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +# test renaming files of various sizes over existing files +[cases.test_files_mv_replace] +defines.SIZE = [ + '0', + 'CACHE_SIZE/2', + '2*CACHE_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.N = [0, 128] +defines.REMOUNT = [false, true] +defines.CACHE_SIZE = 64 +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create a number of directories to distance our files + for (lfs_size_t i = 0; i < N; i++) { + char path[256]; + sprintf(path, "basalt%04d", i); + lfsr_mkdir(&lfs, path) => 0; + } + + // create a file + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "amethyst", LFS_O_WRONLY | LFS_O_CREAT) => 0; + uint8_t wbuf[SIZE]; + uint32_t prng = 42; + for (lfs_size_t i = 0; i < SIZE; i++) { + wbuf[i] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + // create another file + lfsr_file_open(&lfs, &file, "calcite", LFS_O_WRONLY | LFS_O_CREAT) => 0; + uint8_t wbuf_[SIZE]; + for (lfs_size_t i = 0; i < SIZE; i++) { + wbuf_[i] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file, wbuf_, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + // remount? + if (REMOUNT) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // rename the file + lfsr_rename(&lfs, "amethyst", "calcite") => 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, "amethyst", &info) => LFS_ERR_NOENT; + lfsr_stat(&lfs, "calcite", &info) => 0; + assert(strcmp(info.name, "calcite") == 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); + for (lfs_size_t i = 0; i < N; i++) { + char path[256]; + sprintf(path, "basalt%04d", i); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, path) == 0); + assert(info.type == LFS_TYPE_DIR); + } + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "calcite") == 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, "calcite", 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; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; +''' + # TODO # [cases.test_files_rm]