diff --git a/lfs.c b/lfs.c index bb937619..850f2e70 100644 --- a/lfs.c +++ b/lfs.c @@ -5056,6 +5056,54 @@ static int lfsr_btree_traversal_next(lfs_t *lfs, /// Metadata pair operations /// +// mids are a tuple of bid (index in the mtree) and rid (index in the mdir) +// shoved into an integer +// +// We want to maximize our encoding space, so we conservatively carve out +// only enough space to fit the maximum possible number of rids in an mdir. +// In theory, as the mdirs get larger, we need fewer mdirs for a filesystem of +// a given word size. +// +// - Each file neads 1 tag minimum +// - Each tag needs ~2 alts with our current compaction strategy +// - Each tag/alt encodes to a minimum of 4 bytes +// +// This gives us ~1*3*4 or ~12 bytes per rid minimum. Rounding down to the +// nearest power of 2 gives us ~block_size/8 rids per block at most. +// +#define LFSR_MID(_lfs, _bid, _rid) \ + (((_bid) << lfs_nlog2((_lfs)->cfg->block_size/8)) \ + | ((_rid) & ((1 << lfs_nlog2((_lfs)->cfg->block_size/8))-1))) + +// reserved values to represent "." and ".." entries +// +// note these need to be positive values to fit in the lfsr_soff_t returned +// by lfsr_dir_tell +// +// TODO is this really the best way to carve out space for these entries? +// TODO do we need an mdir_limit in the superconfig? since this makes +// mid=max problematic +enum { + LFSR_MID_DOT = 0x7fffffff & -2, + LFSR_MID_DOTDOT = 0x7fffffff & -1, +}; + +static inline lfs_ssize_t lfsr_mid_bid(lfs_t *lfs, lfsr_mid_t mid) { + // TODO can we get rid of this? + // adjust mid if mtree is inlined + if (lfsr_mtree_isinlined(lfs)) { + return LFSR_MID_MROOT; + } + // TODO should this be stored in lfs_t? + return mid >> lfs_nlog2(lfs->cfg->block_size/8); +} + +static inline lfs_size_t lfsr_mid_rid(lfs_t *lfs, lfsr_mid_t mid) { + // TODO should this be stored in lfs_t? + return mid & ((1 << lfs_nlog2(lfs->cfg->block_size/8))-1); +} + + // mptr things typedef struct lfsr_mptr { lfs_block_t blocks[2]; @@ -7798,7 +7846,7 @@ int lfsr_dir_open(lfs_t *lfs, lfsr_dir_t *dir, const char *path) { } } - // leave it up to rewind to initialize pos/mid/rid + // leave it up to rewind to initialize mid/dots err = lfsr_dir_rewind(lfs, dir); if (err) { return err; @@ -7818,16 +7866,16 @@ int lfsr_dir_close(lfs_t *lfs, lfsr_dir_t *dir) { int lfsr_dir_read(lfs_t *lfs, lfsr_dir_t *dir, struct lfs_info *info) { memset(info, 0, sizeof(struct lfs_info)); - // handle "." and ".." specially - if (dir->pos == 0) { + // handle dots specially + if (dir->dots == 0) { info->type = LFS_TYPE_DIR; strcpy(info->name, "."); - dir->pos += 1; + dir->dots += 1; return 0; - } else if (dir->pos == 1) { + } else if (dir->dots == 1) { info->type = LFS_TYPE_DIR; strcpy(info->name, ".."); - dir->pos += 1; + dir->dots += 1; return 0; } @@ -7847,67 +7895,95 @@ int lfsr_dir_read(lfs_t *lfs, lfsr_dir_t *dir, struct lfs_info *info) { return err; } - // found another directory's dstart? we must be done - if (tag == LFSR_TAG_DSTART) { - return LFS_ERR_NOENT; - } - - // fill in our info struct - info->type = lfsr_tag_filetype(tag); - - LFS_ASSERT(lfsr_data_size(data) <= LFS_NAME_MAX); - lfs_ssize_t d = lfsr_data_readleb128(lfs, data, 0, &(uint32_t){0}); + lfs_size_t did; + lfs_ssize_t d = lfsr_data_readleb128(lfs, data, 0, &did); if (d < 0) { return d; } + + // did mismatch? we must be done + if (did != dir->did) { + return LFS_ERR_NOENT; + } + + // get file name from the name entry + LFS_ASSERT(lfsr_data_size(data)-d <= LFS_NAME_MAX); d = lfsr_data_read(lfs, data, d, info->name, LFS_NAME_MAX); if (d < 0) { return d; } info->name[d] = '\0'; - // TODO size once we actually have regular files + // get file type from the tag + info->type = lfsr_tag_filetype(tag); + + // TODO get size once we actually have regular files // eagerly look up the next entry err = lfsr_mtree_seek(lfs, &dir->mdir.mdir, &dir->mdir.rid, 1); if (err && err != LFS_ERR_NOENT) { return err; } - dir->pos += 1; return 0; } int lfsr_dir_seek(lfs_t *lfs, lfsr_dir_t *dir, lfs_off_t off) { - // first rewind - int err = lfsr_dir_rewind(lfs, dir); + // off should never be negative, most likely an error went unchecked + // + // TODO is this the right approach? should we just assert if we detect + // a modified directory during seek? + // Note that aside from negative offsets, all other offset are accepted. + // This makes it so seeking to an outdated offset in the case of concurrent + // modification is at least not an assert, though it may repeat files in + // the directory. + LFS_ASSERT((lfs_soff_t)off > 0); + + // handle dots specially + if (off == LFSR_MID_DOT || off == LFSR_MID_DOTDOT) { + int err = lfsr_dir_rewind(lfs, dir); + if (err) { + return err; + } + + dir->dots = off - LFSR_MID_DOT; + return 0; + } + dir->dots = 2; + + // find our mdir + lfs_ssize_t mid = lfsr_mid_bid(lfs, off); + int err = lfsr_mtree_lookup(lfs, + lfs_smin32(mid, (lfs_ssize_t)lfsr_mtree_weight(lfs)-1), + &dir->mdir.mdir); if (err) { return err; } - // then seek to the requested offset, we leave it up to lfsr_mtree_seek - // to make this efficient - // - // note the -2 to adjust for "." and ".." entries - if (off > 2) { - err = lfsr_mtree_seek(lfs, &dir->mdir.mdir, &dir->mdir.rid, off - 2); - if (err && err != LFS_ERR_NOENT) { - return err; - } + // and update our rid, clamping to rbyd weight + lfs_ssize_t rid = lfsr_mid_rid(lfs, off); + if (mid > dir->mdir.mdir.mid + || rid >= (lfs_ssize_t)dir->mdir.mdir.rbyd.weight) { + dir->mdir.rid = dir->mdir.mdir.rbyd.weight; + } else { + dir->mdir.rid = rid; } - dir->pos = off; - return 0; } lfs_soff_t lfsr_dir_tell(lfs_t *lfs, lfsr_dir_t *dir) { - (void)lfs; - return dir->pos; + if (dir->dots == 0) { + return LFSR_MID_DOT; + } else if (dir->dots == 1) { + return LFSR_MID_DOTDOT; + } else { + return LFSR_MID(lfs, lfs_smax32(dir->mdir.mdir.mid, 0), dir->mdir.rid); + } } int lfsr_dir_rewind(lfs_t *lfs, lfsr_dir_t *dir) { - // reset pos - dir->pos = 0; + // reset dots + dir->dots = 0; // lookup our dstart in the mtree int err = lfsr_mtree_dnamelookup(lfs, dir->did, NULL, 0, diff --git a/lfs.h b/lfs.h index fcbdbe03..568d2119 100644 --- a/lfs.h +++ b/lfs.h @@ -47,6 +47,11 @@ typedef uint32_t lfs_block_t; typedef uint16_t lfsr_tag_t; typedef int16_t lfsr_stag_t; +// mids are a tuple of bid (index in the mtree) and rid (index in the mdir) +// shoved into an integer +typedef lfs_size_t lfsr_mid_t; +typedef lfs_ssize_t lfsr_smid_t; + // Maximum name size in bytes, may be redefined to reduce the size of the // info struct. Limited to <= 1022. Stored in superblock and must be // respected by other littlefs drivers. @@ -425,7 +430,7 @@ typedef struct lfs_dir { typedef struct lfsr_dir { lfsr_openedmdir_t mdir; lfs_size_t did; - lfs_off_t pos; + uint8_t dots; } lfsr_dir_t; // littlefs file type diff --git a/tests/t5_dirs.toml b/tests/t5_dirs.toml index 97e8e6eb..2a848b2c 100644 --- a/tests/t5_dirs.toml +++ b/tests/t5_dirs.toml @@ -5081,31 +5081,37 @@ code = ''' // read our directory // - // Note tell's value is not guaranteed! We can test the exact value only - // because these tests are tightly bound to the current littlefs version. + // we can't really make many assumptions about tell's value, so just + // check for no errors // lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, (PARENT ? "pricklypear" : "/")) => 0; - lfsr_dir_tell(&lfs, &dir) => 0; + lfs_soff_t off = lfsr_dir_tell(&lfs, &dir); + assert(off >= 0); struct lfs_info info; - lfsr_dir_read(&lfs, &dir, &info) => 0; + off = lfsr_dir_read(&lfs, &dir, &info); + assert(off >= 0); assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); - lfsr_dir_tell(&lfs, &dir) => 1; + off = lfsr_dir_tell(&lfs, &dir); + assert(off >= 0); 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++) { - lfsr_dir_tell(&lfs, &dir) => 2 + i; + off = lfsr_dir_tell(&lfs, &dir); + assert(off >= 0); char name[256]; sprintf(name, "dir%04d", i); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS_TYPE_DIR); } - lfsr_dir_tell(&lfs, &dir) => 2 + N; + off = lfsr_dir_tell(&lfs, &dir); + assert(off >= 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; - lfsr_dir_tell(&lfs, &dir) => 2 + N; + off = lfsr_dir_tell(&lfs, &dir); + assert(off >= 0); lfsr_dir_close(&lfs, &dir) => 0; lfsr_unmount(&lfs) => 0; @@ -5134,48 +5140,58 @@ code = ''' lfsr_dir_open(&lfs, &dir, (PARENT ? "pricklypear" : "/")) => 0; // read our directory once - lfsr_dir_tell(&lfs, &dir) => 0; + lfs_soff_t off = lfsr_dir_tell(&lfs, &dir); + assert(off >= 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_tell(&lfs, &dir) => 1; + off = lfsr_dir_tell(&lfs, &dir); + assert(off >= 0); 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++) { - lfsr_dir_tell(&lfs, &dir) => 2 + i; + off = lfsr_dir_tell(&lfs, &dir); + assert(off >= 0); char name[256]; sprintf(name, "dir%04d", i); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS_TYPE_DIR); } - lfsr_dir_tell(&lfs, &dir) => 2 + N; + off = lfsr_dir_tell(&lfs, &dir); + assert(off >= 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; - lfsr_dir_tell(&lfs, &dir) => 2 + N; + off = lfsr_dir_tell(&lfs, &dir); + assert(off >= 0); // now read it again lfsr_dir_rewind(&lfs, &dir) => 0; - lfsr_dir_tell(&lfs, &dir) => 0; + off = lfsr_dir_tell(&lfs, &dir); + assert(off >= 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); - lfsr_dir_tell(&lfs, &dir) => 1; + off = lfsr_dir_tell(&lfs, &dir); + assert(off >= 0); 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++) { - lfsr_dir_tell(&lfs, &dir) => 2 + i; + off = lfsr_dir_tell(&lfs, &dir); + assert(off >= 0); char name[256]; sprintf(name, "dir%04d", i); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS_TYPE_DIR); } - lfsr_dir_tell(&lfs, &dir) => 2 + N; + off = lfsr_dir_tell(&lfs, &dir); + assert(off >= 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; - lfsr_dir_tell(&lfs, &dir) => 2 + N; + off = lfsr_dir_tell(&lfs, &dir); + assert(off >= 0); lfsr_dir_close(&lfs, &dir) => 0; @@ -5205,45 +5221,53 @@ code = ''' lfsr_dir_open(&lfs, &dir, (PARENT ? "pricklypear" : "/")) => 0; // read our directory once - lfsr_dir_tell(&lfs, &dir) => 0; + lfs_soff_t offs[2+N+2]; + offs[0] = lfsr_dir_tell(&lfs, &dir); + assert(offs[0] >= 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_tell(&lfs, &dir) => 1; + offs[1] = lfsr_dir_tell(&lfs, &dir); + assert(offs[1] >= 0); 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++) { - lfsr_dir_tell(&lfs, &dir) => 2 + i; + offs[2+i] = lfsr_dir_tell(&lfs, &dir); + assert(offs[2+i] >= 0); char name[256]; sprintf(name, "dir%04d", i); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS_TYPE_DIR); } - lfsr_dir_tell(&lfs, &dir) => 2 + N; + offs[2+N+0] = lfsr_dir_tell(&lfs, &dir); + assert(offs[2+N+0] >= 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; - lfsr_dir_tell(&lfs, &dir) => 2 + N; + offs[2+N+1] = lfsr_dir_tell(&lfs, &dir); + assert(offs[2+N+1] >= 0); // now try to seek to each entry explicitly - lfsr_dir_seek(&lfs, &dir, 0) => 0; + lfsr_dir_seek(&lfs, &dir, offs[0]) => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); - lfsr_dir_seek(&lfs, &dir, 1) => 0; + lfsr_dir_seek(&lfs, &dir, offs[1]) => 0; 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++) { - lfsr_dir_seek(&lfs, &dir, 2 + i) => 0; + lfsr_dir_seek(&lfs, &dir, offs[2+i]) => 0; char name[256]; sprintf(name, "dir%04d", i); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS_TYPE_DIR); } - lfsr_dir_seek(&lfs, &dir, 2 + N) => 0; + lfsr_dir_seek(&lfs, &dir, offs[2+N+0]) => 0; + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_seek(&lfs, &dir, offs[2+N+1]) => 0; lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; @@ -5289,7 +5313,6 @@ code = ''' lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); - lfsr_dir_tell(&lfs, &dir) => 1; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); @@ -5297,6 +5320,8 @@ code = ''' assert(strcmp(info.name, "ardvark") == 0); assert(info.type == LFS_TYPE_DIR); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfs_soff_t off = lfsr_dir_tell(&lfs, &dir); + assert(off >= 0); // reading again should still return noent lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; @@ -5304,10 +5329,10 @@ code = ''' lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; // seeking past the end of the directory should still return noent - lfsr_dir_seek(&lfs, &dir, 4) => 0; + lfsr_dir_seek(&lfs, &dir, off) => 0; lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; - lfsr_dir_seek(&lfs, &dir, 40) => 0; + lfsr_dir_seek(&lfs, &dir, off + 1000) => 0; lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; @@ -5319,7 +5344,6 @@ code = ''' lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); - lfsr_dir_tell(&lfs, &dir) => 1; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); @@ -5379,7 +5403,6 @@ code = ''' lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); - lfsr_dir_tell(&lfs, &dir) => 1; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); @@ -5402,7 +5425,7 @@ code = ''' // updated correctly if (SEEK) { lfs_ssize_t off = lfsr_dir_tell(&lfs, &dir); - assert(off >= 2); + assert(off >= 0); lfsr_dir_rewind(&lfs, &dir) => 0; lfsr_dir_seek(&lfs, &dir, off) => 0; } @@ -5468,7 +5491,6 @@ code = ''' lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); - lfsr_dir_tell(&lfs, &dir) => 1; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); @@ -5491,7 +5513,7 @@ code = ''' // updated correctly if (SEEK) { lfs_ssize_t off = lfsr_dir_tell(&lfs, &dir); - assert(off >= 2); + assert(off >= 0); lfsr_dir_rewind(&lfs, &dir) => 0; lfsr_dir_seek(&lfs, &dir, off) => 0; } @@ -5569,7 +5591,6 @@ code = ''' lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); - lfsr_dir_tell(&lfs, &dir) => 1; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); @@ -5599,7 +5620,7 @@ code = ''' // updated correctly if (SEEK) { lfs_ssize_t off = lfsr_dir_tell(&lfs, &dir); - assert(off >= 2); + assert(off >= 0); lfsr_dir_rewind(&lfs, &dir) => 0; lfsr_dir_seek(&lfs, &dir, off) => 0; } @@ -5677,7 +5698,6 @@ code = ''' lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); - lfsr_dir_tell(&lfs, &dir) => 1; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); @@ -5704,7 +5724,7 @@ code = ''' // updated correctly if (SEEK) { lfs_ssize_t off = lfsr_dir_tell(&lfs, &dir); - assert(off >= 2); + assert(off >= 0); lfsr_dir_rewind(&lfs, &dir) => 0; lfsr_dir_seek(&lfs, &dir, off) => 0; } @@ -5778,7 +5798,6 @@ code = ''' lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); - lfsr_dir_tell(&lfs, &dir) => 1; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); @@ -5805,7 +5824,7 @@ code = ''' // updated correctly if (SEEK) { lfs_ssize_t off = lfsr_dir_tell(&lfs, &dir); - assert(off >= 2); + assert(off >= 0); lfsr_dir_rewind(&lfs, &dir) => 0; lfsr_dir_seek(&lfs, &dir, off) => 0; } @@ -5887,7 +5906,6 @@ code = ''' lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); - lfsr_dir_tell(&lfs, &dir) => 1; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); @@ -5927,7 +5945,7 @@ code = ''' // updated correctly if (SEEK) { lfs_ssize_t off = lfsr_dir_tell(&lfs, &dir); - assert(off >= 2); + assert(off >= 0); lfsr_dir_rewind(&lfs, &dir) => 0; lfsr_dir_seek(&lfs, &dir, off) => 0; } @@ -6043,7 +6061,7 @@ code = ''' // updated correctly if (SEEK) { lfs_ssize_t off = lfsr_dir_tell(&lfs, &dir); - assert(off == 2); + assert(off >= 0); lfsr_dir_rewind(&lfs, &dir) => 0; lfsr_dir_seek(&lfs, &dir, off) => 0; } @@ -6198,7 +6216,7 @@ code = ''' // updated correctly if (SEEK) { lfs_ssize_t off = lfsr_dir_tell(&lfs, &dir); - assert(off >= 2); + assert(off >= 0); lfsr_dir_rewind(&lfs, &dir) => 0; lfsr_dir_seek(&lfs, &dir, off) => 0; }