diff --git a/lfs.c b/lfs.c index d9970d60..fc2f78b1 100644 --- a/lfs.c +++ b/lfs.c @@ -8487,18 +8487,13 @@ static int lfsr_file_flushbuffer(lfs_t *lfs, lfsr_file_t *file) { // remove any data we're overwriting, note we need to account for // left_sibling changes - *attrs_++ = LFSR_ATTR( - lfs_min32( - file->buffer_pos + file->buffer_size - + right_weight - left_overlap, - file->inlined.u.rbyd.weight)-1, - RM, - -(lfs_min32( - file->buffer_pos + file->buffer_size - + right_weight - left_overlap, - file->inlined.u.rbyd.weight) - - file->buffer_pos), - NULL); + lfs_off_t rm_size = lfs_min32( + file->buffer_pos + file->buffer_size + + right_weight - left_overlap, + file->inlined.u.rbyd.weight - left_overlap) + - file->buffer_pos; + *attrs_++ = LFSR_ATTR(file->buffer_pos + rm_size - 1, + RM, -rm_size, NULL); if (lfsr_data_size(&right_data) == 0) { // append our buffer with any remaining weight @@ -8591,11 +8586,6 @@ failed:; return err; } -int lfsr_file_flush(lfs_t *lfs, lfsr_file_t *file) { - // TODO - return 0; -} - int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) { if (lfsr_file_iserrored(file->flags)) { // it's not safe to do anything if our file errored @@ -8682,6 +8672,43 @@ failed:; return err; } +lfs_soff_t lfsr_file_seek(lfs_t *lfs, lfsr_file_t *file, + lfs_soff_t off, uint8_t whence) { + // TODO check for out-of-range? + + // figure out our new file position + lfs_off_t pos_; + if (whence == LFS_SEEK_SET) { + pos_ = off; + } else if (whence == LFS_SEEK_CUR) { + pos_ = file->pos + off; + } else if (whence == LFS_SEEK_END) { + pos_ = file->size + off; + } else { + LFS_UNREACHABLE(); + } + + // out of range? + if (pos_ > lfs->size_limit) { + return LFS_ERR_INVAL; + } + + // update file position + file->pos = pos_; + return pos_; +} + +lfs_soff_t lfsr_file_tell(lfs_t *lfs, lfsr_file_t *file) { + (void)lfs; + return file->pos; +} + +lfs_soff_t lfsr_file_rewind(lfs_t *lfs, lfsr_file_t *file) { + (void)lfs; + file->pos = 0; + return 0; +} + lfs_soff_t lfsr_file_size(lfs_t *lfs, lfsr_file_t *file) { (void)lfs; return file->size; diff --git a/lfs.h b/lfs.h index 4b239a75..0fc0f3e1 100644 --- a/lfs.h +++ b/lfs.h @@ -781,6 +781,8 @@ lfs_ssize_t lfsr_file_write(lfs_t *lfs, lfsr_file_t *file, // Returns the new position of the file, or a negative error code on failure. lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file, lfs_soff_t off, int whence); +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 @@ -794,12 +796,14 @@ int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size); // Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_CUR) // Returns the position of the file, or a negative error code on failure. lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file); +lfs_soff_t lfsr_file_tell(lfs_t *lfs, lfsr_file_t *file); // Change the position of the file to the beginning of the file // // Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_SET) // Returns a negative error code on failure. int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file); +int lfsr_file_rewind(lfs_t *lfs, lfsr_file_t *file); // Return the size of the file // diff --git a/tests/test_ftree.toml b/tests/test_ftree.toml index f25cb444..864aa871 100644 --- a/tests/test_ftree.toml +++ b/tests/test_ftree.toml @@ -510,7 +510,7 @@ code = ''' # write files incrementally [cases.test_ftree_incr] defines.SIZE = ['CACHE_SIZE/2', '2*CACHE_SIZE'] -defines.CHUNK = [4, 1] +defines.CHUNK = ['CACHE_SIZE/2', '4', '1'] defines.SYNC = [false, true] defines.REMOUNT = [false, true] reentrant = true @@ -547,7 +547,7 @@ code = ''' lfsr_mount(&lfs, CFG) => 0; // note the switch to append here lfsr_file_open(&lfs, &file, "hello", - LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0; + LFS_O_WRONLY | LFS_O_APPEND) => 0; } } lfsr_file_close(&lfs, &file) => 0; @@ -595,6 +595,553 @@ code = ''' lfsr_unmount(&lfs) => 0; ''' +# overwrite files +[cases.test_ftree_overwrite] +defines.SIZE = ['CACHE_SIZE/2', '2*CACHE_SIZE'] +defines.CHUNK = ['CACHE_SIZE/2', '4', '1'] +# bit 0 => first chunk +# bit 1 => middle chunk +# bit 2 => last chunk +defines.MASK = [0, 1, 2, 3, 4, 5, 6, 7] +# 0 => in-order +# 1 => reversed +defines.ORDER = [0, 1] +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[8192]; + uint32_t prng = 42; + for (lfs_size_t i = 0; i < SIZE; i++) { + sim[i] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file, sim, 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_WRONLY) => 0; + } + + // write first chunk? + if (MASK & 1) { + if (ORDER == 0) { + 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 { + 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; + } + } + + // 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; + } + + // write second chunk? + if (MASK & 2) { + for (lfs_size_t i = 0; i < CHUNK; i++) { + sim[SIZE/2-CHUNK/2+i] = 'a' + (TEST_PRNG(&prng) % 26); + } + + lfsr_file_seek(&lfs, &file, SIZE/2 - CHUNK/2, LFS_SEEK_SET) + => SIZE/2 - CHUNK/2; + lfsr_file_write(&lfs, &file, &sim[SIZE/2-CHUNK/2], 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_WRONLY) => 0; + } + + // write third chunk? + if (MASK & 4) { + if (ORDER == 0) { + 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 { + 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; + } + } + + 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 == 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[8192]; + memset(rbuf, 0xaa, sizeof(rbuf)); + lfsr_file_read(&lfs, &file, rbuf, sizeof(rbuf)) => SIZE; + // does our file match our simulation? + assert(memcmp(rbuf, sim, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +# similar to overwrite files, but without underlying data +[cases.test_ftree_holes] +defines.SIZE = ['CACHE_SIZE/2', '2*CACHE_SIZE'] +defines.CHUNK = ['CACHE_SIZE/2', '4', '1'] +# bit 0 => first chunk +# bit 1 => middle chunk +# bit 2 => last chunk +defines.MASK = [4, 5, 6, 7] # TODO 0 1 2 3 ? need truncate? +# 0 => in-order +# 1 => reversed +defines.ORDER = [0] # TODO 1? +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[8192]; + uint32_t prng = 42; + memset(sim, 0, 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_WRONLY) => 0; + } + + // write first chunk? + if (MASK & 1) { + if (ORDER == 0) { + 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 { + 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; + } + } + + // 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; + } + + // write second chunk? + if (MASK & 2) { + for (lfs_size_t i = 0; i < CHUNK; i++) { + sim[SIZE/2-CHUNK/2+i] = 'a' + (TEST_PRNG(&prng) % 26); + } + + lfsr_file_seek(&lfs, &file, SIZE/2 - CHUNK/2, LFS_SEEK_SET) + => SIZE/2 - CHUNK/2; + lfsr_file_write(&lfs, &file, &sim[SIZE/2-CHUNK/2], 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_WRONLY) => 0; + } + + // write third chunk? + if (MASK & 4) { + if (ORDER == 0) { + 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 { + 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; + } + } + + 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 == 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[8192]; + memset(rbuf, 0xaa, sizeof(rbuf)); + lfsr_file_read(&lfs, &file, rbuf, sizeof(rbuf)) => SIZE; + // does our file match our simulation? + assert(memcmp(rbuf, sim, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +# writing any data structure backwards always reveals issues +[cases.test_ftree_reversed_overwrite] +defines.SIZE = ['CACHE_SIZE/2', '2*CACHE_SIZE'] +defines.CHUNK = ['CACHE_SIZE/2', '4', '1'] +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[8192]; + uint32_t prng = 42; + for (lfs_size_t i = 0; i < SIZE; i++) { + sim[i] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file, sim, 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_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; + + // 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; + } + } + 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 == 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[8192]; + memset(rbuf, 0xaa, sizeof(rbuf)); + lfsr_file_read(&lfs, &file, rbuf, sizeof(rbuf)) => SIZE; + // does our file match our simulation? + assert(memcmp(rbuf, sim, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +# writing any data structure backwards always reveals issues +[cases.test_ftree_reversed_holes] +defines.SIZE = ['CACHE_SIZE/2', '2*CACHE_SIZE'] +defines.CHUNK = ['CACHE_SIZE/2', '4', '1'] +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[8192]; + uint32_t prng = 42; + memset(sim, 0, 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_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; + + // 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; + } + } + 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 == 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[8192]; + memset(rbuf, 0xaa, sizeof(rbuf)); + lfsr_file_read(&lfs, &file, rbuf, sizeof(rbuf)) => SIZE; + // does our file match our simulation? + assert(memcmp(rbuf, sim, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; +'''