Files
littlefs/tests/t5_dirs.toml
T
Christopher Haster da810aca26 Implemented mtree path/dname lookup, rudimentary lfsr_mkdir/lfsr_dir_read
This makes it now possible to create directories in the new system.

The new system now uses a single global "mtree" to store all metadata
entries in the filesystem. In this system, a directory is simply a range
of metadata entries. This has a number of benefits, but does come with
its own problems:

1. We need to indicate which directory each file belongs to. To do this
   the file's name entry has been changed to a tuple of leb128-encoded
   directory-id + actual file name:

     01 66 69 6c 65 2e 74 78 74  .file.txt
      ^ '----------+----------'
      '------------|------------ leb128 directory-id
                   '------------ ascii/utf8 name

   If we include the directory-id as part of filename comparison, files
   should naturally be next to other files in the same directory.

2. We need a way allocate directory-ids for new directories. This turns
   out to be a bit more tricky than I expected.

   We can't use any mid/bid/rid inherent to the mtree, because these
   change on any file creation/deletion. And since we commit the did
   into the tree, that's not acceptable.

   Initially I though you could just find the largest did and increment,
   but this gives you no way to reclaim deleted dids. And sure, deleted
   dids have no storage consumption, but eventually you will overflow
   the did integer. Since this can suddenly happen in a filesystem
   that's been in a steady-state for years, that's pretty unnacceptable.

   One solution is to do a simple linear search over the mtree for an
   unused did. But with a runtime of O(n^2 log(n)), this raises
   performance concerns.

   Sidenote: It's interesting to note that the Linux kernel's allocation
   of process-ids, a very similar problem, is surprisingly complex and
   relies on a radix-tree of bitmaps (struct idr). This suggests I'm not
   missing an obvious solution somewhere.

   The solution I settled on here is to instead treat the set of dids as
   a sort of hash table:

   1. Hash the full directory path into a did.
   2. Perform a linear search until we have no collision.

     leb128(truncate28(crc32c("dir")))
          .--------'
          v
     9e cd c8 30 66 69 6c 65 2e 74 78 74  ...0file.txt
     '----+----' '----------+----------'
          '-----------------|------------ leb128 directory-id
                            '------------ ascii/utf8 name

   Worst case, this can still exhibit the worst case O(n^2 log(n))
   performance when we are close to full dids. However that seems
   unlikely to happen in practice, since we don't truncate our hashes,
   unlike normal hash tables. An additional 32-bit word for each file
   is a small price to pay for a low-chance of collisions.

   In the current implementation, I do truncate the hash to 28-bits.
   Since we encode the hash with leb128, and hashes are statistically
   random, this gives us better usage of the leb128 encoding. However
   it does limit a 32-bit littlefs to 256 Mi directories.

   Maybe this should be a configurable limit in the future.

   But that highlights another benefit of this scheme. It's easy to
   change in the future without disk changes.

3. We need a way to know if a directory-id is allocated, even if the
   directory is empty.

   For this we just introduce a new tag: LFSR_TAG_DSTART, which
   is an empty file entry that indicates the directory at the given did
   in the mtree is allocated.

   To create/delete these atomically with the reference in our parent
   directory, we can use the GRM system for atomic renames.

   Note this isn't implemented yet.

This is also the first time we finally get around to testing all of the
dname lookup functions, so this did find a few bugs, mostly around
reporting the root correctly.
2023-07-05 13:41:21 -05:00

1053 lines
34 KiB
TOML

# Directory tests
[cases.t5_dirs_mkdir]
code = '''
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
// make a directory
lfsr_mkdir(&lfs, "ardvark") => 0;
// check that our mkdir worked
lfsr_dir_t dir;
lfsr_dir_open(&lfs, &dir, "/") => 0;
struct lfs_info info;
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, "ardvark") == 0);
assert(info.type == LFS_TYPE_DIR);
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_unmount(&lfs) => 0;
'''
[cases.t5_dirs_mkdir_siblings]
code = '''
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
// make some directories
lfsr_mkdir(&lfs, "ardvark") => 0;
lfsr_mkdir(&lfs, "batman") => 0;
lfsr_mkdir(&lfs, "cantaloupe") => 0;
// check that our mkdir worked
lfsr_dir_t dir;
lfsr_dir_open(&lfs, &dir, "/") => 0;
struct lfs_info info;
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, "ardvark") == 0);
assert(info.type == LFS_TYPE_DIR);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "batman") == 0);
assert(info.type == LFS_TYPE_DIR);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "cantaloupe") == 0);
assert(info.type == LFS_TYPE_DIR);
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_unmount(&lfs) => 0;
'''
[cases.t5_dirs_mkdir_children]
code = '''
lfs_t lfs;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
// make some directories
lfsr_mkdir(&lfs, "ardvark") => 0;
lfsr_mkdir(&lfs, "ardvark/batman") => 0;
lfsr_mkdir(&lfs, "ardvark/batman/cantaloupe") => 0;
// check that our mkdir worked
lfsr_dir_t dir;
lfsr_dir_open(&lfs, &dir, "/") => 0;
struct lfs_info info;
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, "ardvark") == 0);
assert(info.type == LFS_TYPE_DIR);
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_open(&lfs, &dir, "/ardvark") => 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, "batman") == 0);
assert(info.type == LFS_TYPE_DIR);
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_open(&lfs, &dir, "/ardvark/batman") => 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, "cantaloupe") == 0);
assert(info.type == LFS_TYPE_DIR);
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_unmount(&lfs) => 0;
'''
#[cases.test_dirs_root]
#code = '''
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
# lfs_mount(&lfs, cfg) => 0;
# lfs_dir_t dir;
# lfs_dir_open(&lfs, &dir, "/") => 0;
# struct lfs_info info;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, ".") == 0);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, "..") == 0);
# lfs_dir_read(&lfs, &dir, &info) => 0;
# lfs_dir_close(&lfs, &dir) => 0;
# lfs_unmount(&lfs) => 0;
#'''
#
#[cases.test_dirs_many_creation]
#defines.N = 'range(3, 100, 3)'
#if = 'N < BLOCK_COUNT/2'
#code = '''
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# for (int i = 0; i < N; i++) {
# char path[1024];
# sprintf(path, "dir%03d", i);
# lfs_mkdir(&lfs, path) => 0;
# }
# lfs_unmount(&lfs) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# lfs_dir_t dir;
# lfs_dir_open(&lfs, &dir, "/") => 0;
# struct lfs_info info;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, ".") == 0);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, "..") == 0);
# for (int i = 0; i < N; i++) {
# char path[1024];
# sprintf(path, "dir%03d", i);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, path) == 0);
# }
# lfs_dir_read(&lfs, &dir, &info) => 0;
# lfs_dir_close(&lfs, &dir) => 0;
# lfs_unmount(&lfs) => 0;
#'''
#
#[cases.test_dirs_many_removal]
#defines.N = 'range(3, 100, 11)'
#if = 'N < BLOCK_COUNT/2'
#code = '''
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# for (int i = 0; i < N; i++) {
# char path[1024];
# sprintf(path, "removeme%03d", i);
# lfs_mkdir(&lfs, path) => 0;
# }
# lfs_unmount(&lfs) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# lfs_dir_t dir;
# lfs_dir_open(&lfs, &dir, "/") => 0;
# struct lfs_info info;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, ".") == 0);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, "..") == 0);
# for (int i = 0; i < N; i++) {
# char path[1024];
# sprintf(path, "removeme%03d", i);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, path) == 0);
# }
# lfs_dir_read(&lfs, &dir, &info) => 0;
# lfs_dir_close(&lfs, &dir) => 0;
# lfs_unmount(&lfs);
#
# lfs_mount(&lfs, cfg) => 0;
# for (int i = 0; i < N; i++) {
# char path[1024];
# sprintf(path, "removeme%03d", i);
# lfs_remove(&lfs, path) => 0;
# }
# lfs_unmount(&lfs);
#
# lfs_mount(&lfs, cfg) => 0;
# lfs_dir_open(&lfs, &dir, "/") => 0;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, ".") == 0);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, "..") == 0);
# lfs_dir_read(&lfs, &dir, &info) => 0;
# lfs_dir_close(&lfs, &dir) => 0;
# lfs_unmount(&lfs) => 0;
#'''
#
#[cases.test_dirs_many_rename]
#defines.N = 'range(3, 100, 11)'
#if = 'N < BLOCK_COUNT/2'
#code = '''
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# for (int i = 0; i < N; i++) {
# char path[1024];
# sprintf(path, "test%03d", i);
# lfs_mkdir(&lfs, path) => 0;
# }
# lfs_unmount(&lfs) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# lfs_dir_t dir;
# lfs_dir_open(&lfs, &dir, "/") => 0;
# struct lfs_info info;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, ".") == 0);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, "..") == 0);
# for (int i = 0; i < N; i++) {
# char path[1024];
# sprintf(path, "test%03d", i);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, path) == 0);
# }
# lfs_dir_read(&lfs, &dir, &info) => 0;
# lfs_dir_close(&lfs, &dir) => 0;
# lfs_unmount(&lfs);
#
# lfs_mount(&lfs, cfg) => 0;
# for (int i = 0; i < N; i++) {
# char oldpath[128];
# char newpath[128];
# sprintf(oldpath, "test%03d", i);
# sprintf(newpath, "tedd%03d", i);
# lfs_rename(&lfs, oldpath, newpath) => 0;
# }
# lfs_unmount(&lfs);
#
# lfs_mount(&lfs, cfg) => 0;
# lfs_dir_open(&lfs, &dir, "/") => 0;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, ".") == 0);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, "..") == 0);
# for (int i = 0; i < N; i++) {
# char path[1024];
# sprintf(path, "tedd%03d", i);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, path) == 0);
# }
# lfs_dir_read(&lfs, &dir, &info) => 0;
# lfs_dir_close(&lfs, &dir) => 0;
# lfs_unmount(&lfs);
#'''
#
#[cases.test_dirs_many_reentrant]
#defines.N = [5, 11]
#if = 'BLOCK_COUNT >= 4*N'
#reentrant = true
#code = '''
# lfs_t lfs;
# int err = lfs_mount(&lfs, cfg);
# if (err) {
# lfs_format(&lfs, cfg) => 0;
# lfs_mount(&lfs, cfg) => 0;
# }
#
# for (int i = 0; i < N; i++) {
# char path[1024];
# sprintf(path, "hi%03d", i);
# err = lfs_mkdir(&lfs, path);
# assert(err == 0 || err == LFS_ERR_EXIST);
# }
#
# for (int i = 0; i < N; i++) {
# char path[1024];
# sprintf(path, "hello%03d", i);
# err = lfs_remove(&lfs, path);
# assert(err == 0 || err == LFS_ERR_NOENT);
# }
#
# lfs_dir_t dir;
# lfs_dir_open(&lfs, &dir, "/") => 0;
# struct lfs_info info;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, ".") == 0);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, "..") == 0);
# for (int i = 0; i < N; i++) {
# char path[1024];
# sprintf(path, "hi%03d", i);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, path) == 0);
# }
# lfs_dir_read(&lfs, &dir, &info) => 0;
# lfs_dir_close(&lfs, &dir) => 0;
#
# for (int i = 0; i < N; i++) {
# char oldpath[128];
# char newpath[128];
# sprintf(oldpath, "hi%03d", i);
# sprintf(newpath, "hello%03d", i);
# // YES this can overwrite an existing newpath
# lfs_rename(&lfs, oldpath, newpath) => 0;
# }
#
# lfs_dir_open(&lfs, &dir, "/") => 0;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, ".") == 0);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, "..") == 0);
# for (int i = 0; i < N; i++) {
# char path[1024];
# sprintf(path, "hello%03d", i);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, path) == 0);
# }
# lfs_dir_read(&lfs, &dir, &info) => 0;
# lfs_dir_close(&lfs, &dir) => 0;
#
# for (int i = 0; i < N; i++) {
# char path[1024];
# sprintf(path, "hello%03d", i);
# lfs_remove(&lfs, path) => 0;
# }
#
# lfs_dir_open(&lfs, &dir, "/") => 0;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, ".") == 0);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, "..") == 0);
# lfs_dir_read(&lfs, &dir, &info) => 0;
# lfs_dir_close(&lfs, &dir) => 0;
# lfs_unmount(&lfs) => 0;
#'''
#
#[cases.test_dirs_file_creation]
#defines.N = 'range(3, 100, 11)'
#if = 'N < BLOCK_COUNT/2'
#code = '''
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# for (int i = 0; i < N; i++) {
# char path[1024];
# sprintf(path, "file%03d", i);
# lfs_file_t file;
# lfs_file_open(&lfs, &file, path,
# LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
# lfs_file_close(&lfs, &file) => 0;
# }
# lfs_unmount(&lfs) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# lfs_dir_t dir;
# lfs_dir_open(&lfs, &dir, "/") => 0;
# struct lfs_info info;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, ".") == 0);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, "..") == 0);
# for (int i = 0; i < N; i++) {
# char path[1024];
# sprintf(path, "file%03d", i);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_REG);
# assert(strcmp(info.name, path) == 0);
# }
# lfs_dir_read(&lfs, &dir, &info) => 0;
# lfs_dir_close(&lfs, &dir) => 0;
# lfs_unmount(&lfs);
#'''
#
#[cases.test_dirs_file_removal]
#defines.N = 'range(3, 100, 11)'
#if = 'N < BLOCK_COUNT/2'
#code = '''
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# for (int i = 0; i < N; i++) {
# char path[1024];
# sprintf(path, "removeme%03d", i);
# lfs_file_t file;
# lfs_file_open(&lfs, &file, path,
# LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
# lfs_file_close(&lfs, &file) => 0;
# }
# lfs_unmount(&lfs) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# lfs_dir_t dir;
# lfs_dir_open(&lfs, &dir, "/") => 0;
# struct lfs_info info;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, ".") == 0);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, "..") == 0);
# for (int i = 0; i < N; i++) {
# char path[1024];
# sprintf(path, "removeme%03d", i);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_REG);
# assert(strcmp(info.name, path) == 0);
# }
# lfs_dir_read(&lfs, &dir, &info) => 0;
# lfs_dir_close(&lfs, &dir) => 0;
# lfs_unmount(&lfs);
#
# lfs_mount(&lfs, cfg) => 0;
# for (int i = 0; i < N; i++) {
# char path[1024];
# sprintf(path, "removeme%03d", i);
# lfs_remove(&lfs, path) => 0;
# }
# lfs_unmount(&lfs);
#
# lfs_mount(&lfs, cfg) => 0;
# lfs_dir_open(&lfs, &dir, "/") => 0;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, ".") == 0);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, "..") == 0);
# lfs_dir_read(&lfs, &dir, &info) => 0;
# lfs_dir_close(&lfs, &dir) => 0;
# lfs_unmount(&lfs) => 0;
#'''
#
#[cases.test_dirs_file_rename]
#defines.N = 'range(3, 100, 11)'
#if = 'N < BLOCK_COUNT/2'
#code = '''
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# for (int i = 0; i < N; i++) {
# char path[1024];
# sprintf(path, "test%03d", i);
# lfs_file_t file;
# lfs_file_open(&lfs, &file, path,
# LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
# lfs_file_close(&lfs, &file) => 0;
# }
# lfs_unmount(&lfs) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# lfs_dir_t dir;
# lfs_dir_open(&lfs, &dir, "/") => 0;
# struct lfs_info info;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, ".") == 0);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, "..") == 0);
# for (int i = 0; i < N; i++) {
# char path[1024];
# sprintf(path, "test%03d", i);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_REG);
# assert(strcmp(info.name, path) == 0);
# }
# lfs_dir_read(&lfs, &dir, &info) => 0;
# lfs_dir_close(&lfs, &dir) => 0;
# lfs_unmount(&lfs);
#
# lfs_mount(&lfs, cfg) => 0;
# for (int i = 0; i < N; i++) {
# char oldpath[128];
# char newpath[128];
# sprintf(oldpath, "test%03d", i);
# sprintf(newpath, "tedd%03d", i);
# lfs_rename(&lfs, oldpath, newpath) => 0;
# }
# lfs_unmount(&lfs);
#
# lfs_mount(&lfs, cfg) => 0;
# lfs_dir_open(&lfs, &dir, "/") => 0;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, ".") == 0);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, "..") == 0);
# for (int i = 0; i < N; i++) {
# char path[1024];
# sprintf(path, "tedd%03d", i);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_REG);
# assert(strcmp(info.name, path) == 0);
# }
# lfs_dir_read(&lfs, &dir, &info) => 0;
# lfs_dir_close(&lfs, &dir) => 0;
# lfs_unmount(&lfs);
#'''
#
#[cases.test_dirs_file_reentrant]
#defines.N = [5, 25]
#if = 'N < BLOCK_COUNT/2'
#reentrant = true
#code = '''
# lfs_t lfs;
# int err = lfs_mount(&lfs, cfg);
# if (err) {
# lfs_format(&lfs, cfg) => 0;
# lfs_mount(&lfs, cfg) => 0;
# }
#
# for (int i = 0; i < N; i++) {
# char path[1024];
# sprintf(path, "hi%03d", i);
# lfs_file_t file;
# lfs_file_open(&lfs, &file, path, LFS_O_CREAT | LFS_O_WRONLY) => 0;
# lfs_file_close(&lfs, &file) => 0;
# }
#
# for (int i = 0; i < N; i++) {
# char path[1024];
# sprintf(path, "hello%03d", i);
# err = lfs_remove(&lfs, path);
# assert(err == 0 || err == LFS_ERR_NOENT);
# }
#
# lfs_dir_t dir;
# lfs_dir_open(&lfs, &dir, "/") => 0;
# struct lfs_info info;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, ".") == 0);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, "..") == 0);
# for (int i = 0; i < N; i++) {
# char path[1024];
# sprintf(path, "hi%03d", i);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_REG);
# assert(strcmp(info.name, path) == 0);
# }
# lfs_dir_read(&lfs, &dir, &info) => 0;
# lfs_dir_close(&lfs, &dir) => 0;
#
# for (int i = 0; i < N; i++) {
# char oldpath[128];
# char newpath[128];
# sprintf(oldpath, "hi%03d", i);
# sprintf(newpath, "hello%03d", i);
# // YES this can overwrite an existing newpath
# lfs_rename(&lfs, oldpath, newpath) => 0;
# }
#
# lfs_dir_open(&lfs, &dir, "/") => 0;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, ".") == 0);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, "..") == 0);
# for (int i = 0; i < N; i++) {
# char path[1024];
# sprintf(path, "hello%03d", i);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_REG);
# assert(strcmp(info.name, path) == 0);
# }
# lfs_dir_read(&lfs, &dir, &info) => 0;
# lfs_dir_close(&lfs, &dir) => 0;
#
# for (int i = 0; i < N; i++) {
# char path[1024];
# sprintf(path, "hello%03d", i);
# lfs_remove(&lfs, path) => 0;
# }
#
# lfs_dir_open(&lfs, &dir, "/") => 0;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, ".") == 0);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, "..") == 0);
# lfs_dir_read(&lfs, &dir, &info) => 0;
# lfs_dir_close(&lfs, &dir) => 0;
# lfs_unmount(&lfs) => 0;
#'''
#
#[cases.test_dirs_nested]
#code = '''
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
# lfs_mount(&lfs, cfg) => 0;
# lfs_mkdir(&lfs, "potato") => 0;
# lfs_file_t file;
# lfs_file_open(&lfs, &file, "burito",
# LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
# lfs_file_close(&lfs, &file) => 0;
# lfs_unmount(&lfs) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# lfs_mkdir(&lfs, "potato/baked") => 0;
# lfs_mkdir(&lfs, "potato/sweet") => 0;
# lfs_mkdir(&lfs, "potato/fried") => 0;
# lfs_unmount(&lfs) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# lfs_dir_t dir;
# lfs_dir_open(&lfs, &dir, "potato") => 0;
# struct lfs_info info;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(strcmp(info.name, ".") == 0);
# info.type => LFS_TYPE_DIR;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(strcmp(info.name, "..") == 0);
# info.type => LFS_TYPE_DIR;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(strcmp(info.name, "baked") == 0);
# info.type => LFS_TYPE_DIR;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(strcmp(info.name, "fried") == 0);
# info.type => LFS_TYPE_DIR;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(strcmp(info.name, "sweet") == 0);
# info.type => LFS_TYPE_DIR;
# lfs_dir_read(&lfs, &dir, &info) => 0;
# lfs_dir_close(&lfs, &dir) => 0;
# lfs_unmount(&lfs) => 0;
#
# // try removing?
# lfs_mount(&lfs, cfg) => 0;
# lfs_remove(&lfs, "potato") => LFS_ERR_NOTEMPTY;
# lfs_unmount(&lfs) => 0;
#
# // try renaming?
# lfs_mount(&lfs, cfg) => 0;
# lfs_rename(&lfs, "potato", "coldpotato") => 0;
# lfs_unmount(&lfs) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# lfs_rename(&lfs, "coldpotato", "warmpotato") => 0;
# lfs_rename(&lfs, "warmpotato", "hotpotato") => 0;
# lfs_unmount(&lfs) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# lfs_remove(&lfs, "potato") => LFS_ERR_NOENT;
# lfs_remove(&lfs, "coldpotato") => LFS_ERR_NOENT;
# lfs_remove(&lfs, "warmpotato") => LFS_ERR_NOENT;
# lfs_remove(&lfs, "hotpotato") => LFS_ERR_NOTEMPTY;
# lfs_unmount(&lfs) => 0;
#
# // try cross-directory renaming
# lfs_mount(&lfs, cfg) => 0;
# lfs_mkdir(&lfs, "coldpotato") => 0;
# lfs_rename(&lfs, "hotpotato/baked", "coldpotato/baked") => 0;
# lfs_rename(&lfs, "coldpotato", "hotpotato") => LFS_ERR_NOTEMPTY;
# lfs_remove(&lfs, "coldpotato") => LFS_ERR_NOTEMPTY;
# lfs_remove(&lfs, "hotpotato") => LFS_ERR_NOTEMPTY;
# lfs_rename(&lfs, "hotpotato/fried", "coldpotato/fried") => 0;
# lfs_rename(&lfs, "coldpotato", "hotpotato") => LFS_ERR_NOTEMPTY;
# lfs_remove(&lfs, "coldpotato") => LFS_ERR_NOTEMPTY;
# lfs_remove(&lfs, "hotpotato") => LFS_ERR_NOTEMPTY;
# lfs_rename(&lfs, "hotpotato/sweet", "coldpotato/sweet") => 0;
# lfs_rename(&lfs, "coldpotato", "hotpotato") => 0;
# lfs_remove(&lfs, "coldpotato") => LFS_ERR_NOENT;
# lfs_remove(&lfs, "hotpotato") => LFS_ERR_NOTEMPTY;
# lfs_unmount(&lfs) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# lfs_dir_open(&lfs, &dir, "hotpotato") => 0;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(strcmp(info.name, ".") == 0);
# info.type => LFS_TYPE_DIR;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(strcmp(info.name, "..") == 0);
# info.type => LFS_TYPE_DIR;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(strcmp(info.name, "baked") == 0);
# info.type => LFS_TYPE_DIR;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(strcmp(info.name, "fried") == 0);
# info.type => LFS_TYPE_DIR;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(strcmp(info.name, "sweet") == 0);
# info.type => LFS_TYPE_DIR;
# lfs_dir_read(&lfs, &dir, &info) => 0;
# lfs_dir_close(&lfs, &dir) => 0;
# lfs_unmount(&lfs) => 0;
#
# // final remove
# lfs_mount(&lfs, cfg) => 0;
# lfs_remove(&lfs, "hotpotato") => LFS_ERR_NOTEMPTY;
# lfs_remove(&lfs, "hotpotato/baked") => 0;
# lfs_remove(&lfs, "hotpotato") => LFS_ERR_NOTEMPTY;
# lfs_remove(&lfs, "hotpotato/fried") => 0;
# lfs_remove(&lfs, "hotpotato") => LFS_ERR_NOTEMPTY;
# lfs_remove(&lfs, "hotpotato/sweet") => 0;
# lfs_remove(&lfs, "hotpotato") => 0;
# lfs_unmount(&lfs) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# lfs_dir_open(&lfs, &dir, "/") => 0;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(strcmp(info.name, ".") == 0);
# info.type => LFS_TYPE_DIR;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(strcmp(info.name, "..") == 0);
# info.type => LFS_TYPE_DIR;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(strcmp(info.name, "burito") == 0);
# info.type => LFS_TYPE_REG;
# lfs_dir_read(&lfs, &dir, &info) => 0;
# lfs_dir_close(&lfs, &dir) => 0;
# lfs_unmount(&lfs) => 0;
#'''
#
#[cases.test_dirs_recursive_remove]
#defines.N = [10, 100]
#if = 'N < BLOCK_COUNT/2'
#code = '''
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
# lfs_mount(&lfs, cfg) => 0;
# lfs_mkdir(&lfs, "prickly-pear") => 0;
# for (int i = 0; i < N; i++) {
# char path[1024];
# sprintf(path, "prickly-pear/cactus%03d", i);
# lfs_mkdir(&lfs, path) => 0;
# }
# lfs_dir_t dir;
# lfs_dir_open(&lfs, &dir, "prickly-pear") => 0;
# struct lfs_info info;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, ".") == 0);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, "..") == 0);
# for (int i = 0; i < N; i++) {
# char path[1024];
# sprintf(path, "cactus%03d", i);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, path) == 0);
# }
# lfs_dir_read(&lfs, &dir, &info) => 0;
# lfs_dir_close(&lfs, &dir) => 0;
# lfs_unmount(&lfs);
#
# lfs_mount(&lfs, cfg) => 0;
# lfs_remove(&lfs, "prickly-pear") => LFS_ERR_NOTEMPTY;
#
# lfs_dir_open(&lfs, &dir, "prickly-pear") => 0;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, ".") == 0);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, "..") == 0);
# for (int i = 0; i < N; i++) {
# char path[1024];
# sprintf(path, "cactus%03d", i);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, path) == 0);
# sprintf(path, "prickly-pear/%s", info.name);
# lfs_remove(&lfs, path) => 0;
# }
# lfs_dir_read(&lfs, &dir, &info) => 0;
# lfs_dir_close(&lfs, &dir) => 0;
#
# lfs_remove(&lfs, "prickly-pear") => 0;
# lfs_remove(&lfs, "prickly-pear") => LFS_ERR_NOENT;
# lfs_unmount(&lfs) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# lfs_remove(&lfs, "prickly-pear") => LFS_ERR_NOENT;
# lfs_unmount(&lfs) => 0;
#'''
#
#[cases.test_dirs_other_errors]
#code = '''
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
# lfs_mount(&lfs, cfg) => 0;
# lfs_mkdir(&lfs, "potato") => 0;
# lfs_file_t file;
# lfs_file_open(&lfs, &file, "burito",
# LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
# lfs_file_close(&lfs, &file) => 0;
# lfs_unmount(&lfs) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
#
# lfs_mkdir(&lfs, "potato") => LFS_ERR_EXIST;
# lfs_mkdir(&lfs, "burito") => LFS_ERR_EXIST;
# lfs_file_open(&lfs, &file, "burito",
# LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
# lfs_file_open(&lfs, &file, "potato",
# LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
# lfs_dir_t dir;
# lfs_dir_open(&lfs, &dir, "tomato") => LFS_ERR_NOENT;
# lfs_dir_open(&lfs, &dir, "burito") => LFS_ERR_NOTDIR;
# lfs_file_open(&lfs, &file, "tomato", LFS_O_RDONLY) => LFS_ERR_NOENT;
# lfs_file_open(&lfs, &file, "potato", LFS_O_RDONLY) => LFS_ERR_ISDIR;
# lfs_file_open(&lfs, &file, "tomato", LFS_O_WRONLY) => LFS_ERR_NOENT;
# lfs_file_open(&lfs, &file, "potato", LFS_O_WRONLY) => LFS_ERR_ISDIR;
# lfs_file_open(&lfs, &file, "potato",
# LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
#
# lfs_mkdir(&lfs, "/") => LFS_ERR_EXIST;
# lfs_file_open(&lfs, &file, "/",
# LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;
# lfs_file_open(&lfs, &file, "/", LFS_O_RDONLY) => LFS_ERR_ISDIR;
# lfs_file_open(&lfs, &file, "/", LFS_O_WRONLY) => LFS_ERR_ISDIR;
# lfs_file_open(&lfs, &file, "/",
# LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_ISDIR;
#
# // check that errors did not corrupt directory
# lfs_dir_open(&lfs, &dir, "/") => 0;
# struct lfs_info info;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, ".") == 0);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, "..") == 0);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_REG);
# assert(strcmp(info.name, "burito") == 0);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, "potato") == 0);
# lfs_dir_read(&lfs, &dir, &info) => 0;
# lfs_dir_close(&lfs, &dir) => 0;
#
# lfs_unmount(&lfs) => 0;
#
# // or on disk
# lfs_mount(&lfs, cfg) => 0;
# lfs_dir_open(&lfs, &dir, "/") => 0;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, ".") == 0);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, "..") == 0);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_REG);
# assert(strcmp(info.name, "burito") == 0);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(info.type == LFS_TYPE_DIR);
# assert(strcmp(info.name, "potato") == 0);
# lfs_dir_read(&lfs, &dir, &info) => 0;
# lfs_dir_close(&lfs, &dir) => 0;
# lfs_unmount(&lfs) => 0;
#'''
#
#[cases.test_dirs_seek]
#defines.COUNT = [4, 128, 132]
#if = 'COUNT < BLOCK_COUNT/2'
#code = '''
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
# lfs_mount(&lfs, cfg) => 0;
# lfs_mkdir(&lfs, "hello") => 0;
# for (int i = 0; i < COUNT; i++) {
# char path[1024];
# sprintf(path, "hello/kitty%03d", i);
# lfs_mkdir(&lfs, path) => 0;
# }
# lfs_unmount(&lfs) => 0;
#
# for (int j = 2; j < COUNT; j++) {
# lfs_mount(&lfs, cfg) => 0;
# lfs_dir_t dir;
# lfs_dir_open(&lfs, &dir, "hello") => 0;
# struct lfs_info info;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(strcmp(info.name, ".") == 0);
# assert(info.type == LFS_TYPE_DIR);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(strcmp(info.name, "..") == 0);
# assert(info.type == LFS_TYPE_DIR);
#
# lfs_soff_t pos;
# for (int i = 0; i < j; i++) {
# char path[1024];
# sprintf(path, "kitty%03d", i);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(strcmp(info.name, path) == 0);
# assert(info.type == LFS_TYPE_DIR);
# pos = lfs_dir_tell(&lfs, &dir);
# assert(pos >= 0);
# }
#
# lfs_dir_seek(&lfs, &dir, pos) => 0;
# char path[1024];
# sprintf(path, "kitty%03d", j);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(strcmp(info.name, path) == 0);
# assert(info.type == LFS_TYPE_DIR);
#
# lfs_dir_rewind(&lfs, &dir) => 0;
# sprintf(path, "kitty%03u", 0);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(strcmp(info.name, ".") == 0);
# assert(info.type == LFS_TYPE_DIR);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(strcmp(info.name, "..") == 0);
# assert(info.type == LFS_TYPE_DIR);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(strcmp(info.name, path) == 0);
# assert(info.type == LFS_TYPE_DIR);
#
# lfs_dir_seek(&lfs, &dir, pos) => 0;
# sprintf(path, "kitty%03d", j);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(strcmp(info.name, path) == 0);
# assert(info.type == LFS_TYPE_DIR);
#
# lfs_dir_close(&lfs, &dir) => 0;
# lfs_unmount(&lfs) => 0;
# }
#'''
#
#[cases.test_dirs_toot_seek]
#defines.COUNT = [4, 128, 132]
#if = 'COUNT < BLOCK_COUNT/2'
#code = '''
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
# lfs_mount(&lfs, cfg) => 0;
# for (int i = 0; i < COUNT; i++) {
# char path[1024];
# sprintf(path, "hi%03d", i);
# lfs_mkdir(&lfs, path) => 0;
# }
# lfs_unmount(&lfs) => 0;
#
# for (int j = 2; j < COUNT; j++) {
# lfs_mount(&lfs, cfg) => 0;
# lfs_dir_t dir;
# lfs_dir_open(&lfs, &dir, "/") => 0;
# struct lfs_info info;
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(strcmp(info.name, ".") == 0);
# assert(info.type == LFS_TYPE_DIR);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(strcmp(info.name, "..") == 0);
# assert(info.type == LFS_TYPE_DIR);
#
# lfs_soff_t pos;
# for (int i = 0; i < j; i++) {
# char path[1024];
# sprintf(path, "hi%03d", i);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(strcmp(info.name, path) == 0);
# assert(info.type == LFS_TYPE_DIR);
# pos = lfs_dir_tell(&lfs, &dir);
# assert(pos >= 0);
# }
#
# lfs_dir_seek(&lfs, &dir, pos) => 0;
# char path[1024];
# sprintf(path, "hi%03d", j);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(strcmp(info.name, path) == 0);
# assert(info.type == LFS_TYPE_DIR);
#
# lfs_dir_rewind(&lfs, &dir) => 0;
# sprintf(path, "hi%03u", 0);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(strcmp(info.name, ".") == 0);
# assert(info.type == LFS_TYPE_DIR);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(strcmp(info.name, "..") == 0);
# assert(info.type == LFS_TYPE_DIR);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(strcmp(info.name, path) == 0);
# assert(info.type == LFS_TYPE_DIR);
#
# lfs_dir_seek(&lfs, &dir, pos) => 0;
# sprintf(path, "hi%03d", j);
# lfs_dir_read(&lfs, &dir, &info) => 1;
# assert(strcmp(info.name, path) == 0);
# assert(info.type == LFS_TYPE_DIR);
#
# lfs_dir_close(&lfs, &dir) => 0;
# lfs_unmount(&lfs) => 0;
# }
#'''
#