From a34bcdb5bf5db0793f37492535cec5c3aba22183 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Thu, 24 Apr 2025 14:00:12 -0500 Subject: [PATCH] Allowed modification of unknown file types This drops the requirement that all file types are introduced with a related wcompat flag. Instead, the wcompat flag is only required if modification _would_ leak resources, and we treat unknown file types as though they are regular files. This allows modification of unknown file types without the risk of breaking anything. To compare with before the unknown-type rework: Before: > Unknown file types are allowed and may leak resources if modified, > so attempted modification (rename/remove) will error with > LFS_ERR_NOTSUP. Now: > Unknown file types are allowed but must not leak resources if > modified. If an unknown file type would leak resources, it should set > a related wcompat flag to only allow mounting RDONLY. Note this includes directories, which can leak bookmarks if removed, so filesystems using directories should set the LFSR_WCOMPAT_DIR flag. But we no longer need the LFSR_WCOMPAT_REG/LFSR_WCOMPAT_STICKYNOTE flags. --- The real tricky part was getting lfsr_rename to work with unknown types, as this broke the invariant that we only ever commit tags we know about. Fixing this required: - Fetching the non-unknown-mapped tag in lfsr_rename - Mapping all name tags to LFSR_TAG_NAME in lfsr_rbyd_appendrattr_ - Adopting LFSR_RATTR_NAME for bookmark name tags This was broken by the above lfsr_rbyd_appendrattr_ change, but it's probably good to handle these the same as other name tags anyways. This adds a bit of code, but not enough that I think this isn't worth it (or worth a build-time option): code stack ctx before: 35924 2440 640 after: 35992 (+0.0%) 2440 (+0.0%) 640 (+0.0%) --- lfs.c | 41 ++- scripts/dbgflags.py | 6 +- tests/test_mount.toml | 781 +++++++++++++++++++++++++++++++++++++++++- tests/test_paths.toml | 465 +++++++++++++++++++++++-- 4 files changed, 1246 insertions(+), 47 deletions(-) diff --git a/lfs.c b/lfs.c index f57b8916..84f85284 100644 --- a/lfs.c +++ b/lfs.c @@ -2331,7 +2331,12 @@ static inline lfsr_tag_t lfsr_rattr_dtag(lfsr_rattr_t rattr) { // lazily tag encoding can be bypassed with explicit data, this is // necessary to allow copies during compaction, relocation, etc if (rattr.count >= 0) { - return rattr.tag; + // map all name tags to LFSR_TAG_NAME for simplicity + if (lfsr_tag_suptype(rattr.tag) == LFSR_TAG_NAME) { + return LFSR_TAG_NAME; + } else { + return rattr.tag; + } } else { return LFSR_TAG_DATA; } @@ -3452,7 +3457,7 @@ static lfsr_data_t lfsr_data_frombtree(const lfsr_btree_t *btree, static lfsr_data_t lfsr_data_frommptr(const lfs_block_t mptr[static 2], uint8_t buffer[static LFSR_MPTR_DSIZE]); -// our core rbyd append algorithm +// encode rattrs static int lfsr_rbyd_appendrattr_(lfs_t *lfs, lfsr_rbyd_t *rbyd, lfsr_rattr_t rattr) { // tag must not be internal at this point @@ -3507,7 +3512,6 @@ static int lfsr_rbyd_appendrattr_(lfs_t *lfs, lfsr_rbyd_t *rbyd, // leb128? case LFSR_TAG_NAMELIMIT:; case LFSR_TAG_FILELIMIT:; - case LFSR_TAG_BOOKMARK:; case LFSR_TAG_DID:; // leb128s should not exceed 31-bits LFS_ASSERT(rattr.u.leb128 <= 0x7fffffff); @@ -3530,9 +3534,6 @@ static int lfsr_rbyd_appendrattr_(lfs_t *lfs, lfsr_rbyd_t *rbyd, // name? case LFSR_TAG_NAME:; - case LFSR_TAG_REG:; - case LFSR_TAG_DIR:; - case LFSR_TAG_STICKYNOTE:; const lfsr_name_t *name = rattr.u.etc; ctx.u.name.datas[0] = lfsr_data_fromleb128(name->did, ctx.u.name.buf); ctx.u.name.datas[1] = LFSR_DATA_BUF(name->name, name->name_len); @@ -3772,7 +3773,7 @@ static void lfsr_rbyd_p_recolor( } } -// core rbyd algorithm +// our core rbyd append algorithm static int lfsr_rbyd_appendrattr(lfs_t *lfs, lfsr_rbyd_t *rbyd, lfsr_srid_t rid, lfsr_rattr_t rattr) { // must fetch before mutating! @@ -10300,8 +10301,8 @@ int lfsr_mkdir(lfs_t *lfs, const char *path) { // commit our bookmark and a grm to self-remove in case of powerloss lfs_alloc_ckpoint(lfs); err = lfsr_mdir_commit(lfs, &mdir, LFSR_RATTRS( - LFSR_RATTR_LEB128( - LFSR_TAG_BOOKMARK, +1, did_))); + LFSR_RATTR_NAME( + LFSR_TAG_BOOKMARK, +1, did_, NULL, 0))); if (err) { return err; } @@ -10635,6 +10636,16 @@ int lfsr_rename(lfs_t *lfs, const char *old_path, const char *new_path) { } } + if (old_tag == LFSR_TAG_UNKNOWN) { + // lookup the actual tag + err = lfsr_rbyd_lookup(lfs, &old_mdir.rbyd, + lfsr_mrid(lfs, old_mdir.mid), LFSR_TAG_MASK8 | LFSR_TAG_NAME, + &old_tag, NULL); + if (err) { + return err; + } + } + // mark old entry for removal with a grm lfsr_grm_push(lfs, old_mdir.mid); @@ -13602,17 +13613,13 @@ static int lfs_deinit(lfs_t *lfs) { #define LFSR_WCOMPAT_NONSTANDARD 0x00000001 // Non-standard filesystem format #define LFSR_WCOMPAT_RDONLY 0x00000002 // Writing is disallowed -#define LFSR_WCOMPAT_REG 0x00000010 // Regular files in use -#define LFSR_WCOMPAT_DIR 0x00000020 // Directory files in use -#define LFSR_WCOMPAT_STICKYNOTE 0x00000040 // Stickynote files in use +#define LFSR_WCOMPAT_DIR 0x00000010 // Directory files in use #define LFSR_WCOMPAT_GCKSUM 0x00001000 // Global-checksum in use // internal #define LFSR_wcompat_OVERFLOW 0x80000000 // Can't represent all flags #define LFSR_WCOMPAT_COMPAT \ - (LFSR_WCOMPAT_REG \ - | LFSR_WCOMPAT_DIR \ - | LFSR_WCOMPAT_STICKYNOTE \ + (LFSR_WCOMPAT_DIR \ | LFSR_WCOMPAT_GCKSUM) #define LFSR_OCOMPAT_NONSTANDARD 0x00000001 // Non-standard filesystem format @@ -14294,9 +14301,9 @@ static int lfsr_formatinited(lfs_t *lfs) { LFSR_RATTR_LEB128( LFSR_TAG_FILELIMIT, 0, lfs->file_limit), - LFSR_RATTR_LEB128( + LFSR_RATTR_NAME( LFSR_TAG_BOOKMARK, +1, - 0))); + 0, NULL, 0))); if (err) { return err; } diff --git a/scripts/dbgflags.py b/scripts/dbgflags.py index 21ecb34b..0cb58e87 100755 --- a/scripts/dbgflags.py +++ b/scripts/dbgflags.py @@ -185,12 +185,8 @@ FLAGS = [ 0x00000001, "Non-standard filesystem format" ), ('WCOMPAT', 'RDONLY', 0x00000002, "Writing is disallowed" ), - ('WCOMPAT', 'REG', - 0x00000010, "Regular file types in use" ), ('WCOMPAT', 'DIR', - 0x00000020, "Directory file types in use" ), - ('WCOMPAT', 'STICKYNOTE', - 0x00000040, "Stickynote file types in use" ), + 0x00000010, "Directory file types in use" ), ('WCOMPAT', 'GCKSUM', 0x00001000, "Global-checksum in use" ), ('wcompat', 'OVERFLOW', diff --git a/tests/test_mount.toml b/tests/test_mount.toml index b8f58be3..cfbca8f4 100644 --- a/tests/test_mount.toml +++ b/tests/test_mount.toml @@ -1146,9 +1146,8 @@ code = ''' LFSR_DATA_BUF(path, lfsr_path_namelen(path))))) => 0; lfsr_unmount(&lfs) => 0; - // mount, note the LFS_M_RDONLY, new types should be added with - // new wcompat flags to prevent resource leaks - lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => 0; + // mount + lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // our file should appear as an unknown type struct lfs_info info; @@ -1182,9 +1181,785 @@ code = ''' lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; + // open/mkdir should error lfsr_file_open(&lfs, &file, "b", LFS_O_RDONLY) => LFS_ERR_NOTSUP; + lfsr_file_open(&lfs, &file, "b", + LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOTSUP; + + lfsr_mkdir(&lfs, "b") => LFS_ERR_EXIST; lfsr_unmount(&lfs) => 0; ''' +[cases.test_mount_incompat_unknown_type_rm] +in = 'lfs.c' +code = ''' + // create a superblock + lfs_t lfs; + lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; + + // create some files + lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "a", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, + "hi a!", strlen("hi a!")) => strlen("hi a!"); + lfsr_file_close(&lfs, &file) => 0; + lfsr_file_open(&lfs, &file, "b", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, + "oh no!", strlen("oh no!")) => strlen("oh no!"); + lfsr_file_close(&lfs, &file) => 0; + lfsr_file_open(&lfs, &file, "c", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, + "hi c!", strlen("hi c!")) => strlen("hi c!"); + lfsr_file_close(&lfs, &file) => 0; + lfsr_unmount(&lfs) => 0; + + // change a file's type to something unknown + lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; + const char *path = "b"; + lfsr_mdir_t mdir; + lfsr_did_t did; + lfsr_mtree_pathlookup(&lfs, &path, + &mdir, NULL, &did) => 0; + lfsr_mdir_commit(&lfs, &mdir, LFSR_RATTRS( + LFSR_RATTR_CAT( + LFSR_TAG_MASK8 | (LFSR_TAG_NAME + 0x13), 0, + lfsr_data_fromleb128(did, (uint8_t[LFSR_LEB128_DSIZE]){0}), + LFSR_DATA_BUF(path, lfsr_path_namelen(path))))) => 0; + lfsr_unmount(&lfs) => 0; + + // mount + lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; + + // our file should appear as an unknown type + struct lfs_info info; + lfsr_stat(&lfs, "b", &info) => 0; + assert(strcmp(info.name, "b") == 0); + assert(info.type == LFS_TYPE_UNKNOWN); + assert(info.size == 0); + + 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); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "a") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == strlen("hi a!")); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "b") == 0); + assert(info.type == LFS_TYPE_UNKNOWN); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "c") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == strlen("hi c!")); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + // removing unknown files should still work, if this would leak + // resources the new type should set a wcompat flag + lfsr_remove(&lfs, "b") => 0; + + // check that things look reasonable + lfsr_stat(&lfs, "b", &info) => LFS_ERR_NOENT; + + lfsr_dir_open(&lfs, &dir, "/") => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "a") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == strlen("hi a!")); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "c") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == strlen("hi c!")); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_mount_incompat_unknown_type_mv_src] +in = 'lfs.c' +code = ''' + // create a superblock + lfs_t lfs; + lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; + + // create some files + lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "a", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, + "hi a!", strlen("hi a!")) => strlen("hi a!"); + lfsr_file_close(&lfs, &file) => 0; + lfsr_file_open(&lfs, &file, "b", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, + "oh no!", strlen("oh no!")) => strlen("oh no!"); + lfsr_file_close(&lfs, &file) => 0; + lfsr_file_open(&lfs, &file, "c", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, + "hi c!", strlen("hi c!")) => strlen("hi c!"); + lfsr_file_close(&lfs, &file) => 0; + lfsr_unmount(&lfs) => 0; + + // change a file's type to something unknown + lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; + const char *path = "b"; + lfsr_mdir_t mdir; + lfsr_did_t did; + lfsr_mtree_pathlookup(&lfs, &path, + &mdir, NULL, &did) => 0; + lfsr_mdir_commit(&lfs, &mdir, LFSR_RATTRS( + LFSR_RATTR_CAT( + LFSR_TAG_MASK8 | (LFSR_TAG_NAME + 0x13), 0, + lfsr_data_fromleb128(did, (uint8_t[LFSR_LEB128_DSIZE]){0}), + LFSR_DATA_BUF(path, lfsr_path_namelen(path))))) => 0; + lfsr_unmount(&lfs) => 0; + + // mount + lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; + + // our file should appear as an unknown type + struct lfs_info info; + lfsr_stat(&lfs, "b", &info) => 0; + assert(strcmp(info.name, "b") == 0); + assert(info.type == LFS_TYPE_UNKNOWN); + assert(info.size == 0); + + 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); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "a") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == strlen("hi a!")); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "b") == 0); + assert(info.type == LFS_TYPE_UNKNOWN); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "c") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == strlen("hi c!")); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + // renaming unknown files should still work, if this would leak + // resources the new type should set a wcompat flag + lfsr_rename(&lfs, "b", "c") => 0; + + // check that things look reasonable after renaming/removing + lfsr_stat(&lfs, "c", &info) => 0; + assert(strcmp(info.name, "c") == 0); + assert(info.type == LFS_TYPE_UNKNOWN); + assert(info.size == 0); + + lfsr_dir_open(&lfs, &dir, "/") => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "a") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == strlen("hi a!")); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "c") == 0); + assert(info.type == LFS_TYPE_UNKNOWN); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_mount_incompat_unknown_type_mv_dst] +in = 'lfs.c' +code = ''' + // create a superblock + lfs_t lfs; + lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; + + // create some files + lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "a", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, + "hi a!", strlen("hi a!")) => strlen("hi a!"); + lfsr_file_close(&lfs, &file) => 0; + lfsr_file_open(&lfs, &file, "b", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, + "oh no!", strlen("oh no!")) => strlen("oh no!"); + lfsr_file_close(&lfs, &file) => 0; + lfsr_file_open(&lfs, &file, "c", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, + "hi c!", strlen("hi c!")) => strlen("hi c!"); + lfsr_file_close(&lfs, &file) => 0; + lfsr_unmount(&lfs) => 0; + + // change a file's type to something unknown + lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; + const char *path = "b"; + lfsr_mdir_t mdir; + lfsr_did_t did; + lfsr_mtree_pathlookup(&lfs, &path, + &mdir, NULL, &did) => 0; + lfsr_mdir_commit(&lfs, &mdir, LFSR_RATTRS( + LFSR_RATTR_CAT( + LFSR_TAG_MASK8 | (LFSR_TAG_NAME + 0x13), 0, + lfsr_data_fromleb128(did, (uint8_t[LFSR_LEB128_DSIZE]){0}), + LFSR_DATA_BUF(path, lfsr_path_namelen(path))))) => 0; + lfsr_unmount(&lfs) => 0; + + // mount + lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; + + // our file should appear as an unknown type + struct lfs_info info; + lfsr_stat(&lfs, "b", &info) => 0; + assert(strcmp(info.name, "b") == 0); + assert(info.type == LFS_TYPE_UNKNOWN); + assert(info.size == 0); + + 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); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "a") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == strlen("hi a!")); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "b") == 0); + assert(info.type == LFS_TYPE_UNKNOWN); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "c") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == strlen("hi c!")); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + // renaming unknown files should still work, if this would leak + // resources the new type should set a wcompat flag + lfsr_rename(&lfs, "c", "b") => 0; + + // check that things look reasonable after renaming/removing + lfsr_stat(&lfs, "b", &info) => 0; + assert(strcmp(info.name, "b") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == strlen("hi c!")); + + lfsr_dir_open(&lfs, &dir, "/") => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "a") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == strlen("hi a!")); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "b") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == strlen("hi c!")); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_mount_incompat_unknown_type_mv_src_dst] +in = 'lfs.c' +code = ''' + // create a superblock + lfs_t lfs; + lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; + + // create some files + lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "a", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, + "hi a!", strlen("hi a!")) => strlen("hi a!"); + lfsr_file_close(&lfs, &file) => 0; + lfsr_file_open(&lfs, &file, "b", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, + "oh no!", strlen("oh no!")) => strlen("oh no!"); + lfsr_file_close(&lfs, &file) => 0; + lfsr_file_open(&lfs, &file, "c", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, + "oh hi!", strlen("oh hi!")) => strlen("oh hi!"); + lfsr_file_close(&lfs, &file) => 0; + lfsr_unmount(&lfs) => 0; + + // change a file's type to something unknown + lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; + const char *path = "b"; + lfsr_mdir_t mdir; + lfsr_did_t did; + lfsr_mtree_pathlookup(&lfs, &path, + &mdir, NULL, &did) => 0; + lfsr_mdir_commit(&lfs, &mdir, LFSR_RATTRS( + LFSR_RATTR_CAT( + LFSR_TAG_MASK8 | (LFSR_TAG_NAME + 0x13), 0, + lfsr_data_fromleb128(did, (uint8_t[LFSR_LEB128_DSIZE]){0}), + LFSR_DATA_BUF(path, lfsr_path_namelen(path))))) => 0; + path = "c"; + lfsr_mtree_pathlookup(&lfs, &path, + &mdir, NULL, &did) => 0; + lfsr_mdir_commit(&lfs, &mdir, LFSR_RATTRS( + LFSR_RATTR_CAT( + LFSR_TAG_MASK8 | (LFSR_TAG_NAME + 0x13), 0, + lfsr_data_fromleb128(did, (uint8_t[LFSR_LEB128_DSIZE]){0}), + LFSR_DATA_BUF(path, lfsr_path_namelen(path))))) => 0; + lfsr_unmount(&lfs) => 0; + + // mount + lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; + + // our file should appear as an unknown type + struct lfs_info info; + lfsr_stat(&lfs, "b", &info) => 0; + assert(strcmp(info.name, "b") == 0); + assert(info.type == LFS_TYPE_UNKNOWN); + assert(info.size == 0); + + 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); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "a") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == strlen("hi a!")); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "b") == 0); + assert(info.type == LFS_TYPE_UNKNOWN); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "c") == 0); + assert(info.type == LFS_TYPE_UNKNOWN); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + // renaming unknown files should still work, if this would leak + // resources the new type should set a wcompat flag + lfsr_rename(&lfs, "b", "c") => 0; + + // check that things look reasonable after renaming/removing + lfsr_stat(&lfs, "c", &info) => 0; + assert(strcmp(info.name, "c") == 0); + assert(info.type == LFS_TYPE_UNKNOWN); + assert(info.size == 0); + + lfsr_dir_open(&lfs, &dir, "/") => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "a") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == strlen("hi a!")); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "c") == 0); + assert(info.type == LFS_TYPE_UNKNOWN); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_mount_incompat_unknown_type_mv_noop] +in = 'lfs.c' +code = ''' + // create a superblock + lfs_t lfs; + lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; + + // create some files + lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "a", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, + "hi a!", strlen("hi a!")) => strlen("hi a!"); + lfsr_file_close(&lfs, &file) => 0; + lfsr_file_open(&lfs, &file, "b", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, + "oh no!", strlen("oh no!")) => strlen("oh no!"); + lfsr_file_close(&lfs, &file) => 0; + lfsr_file_open(&lfs, &file, "c", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, + "hi c!", strlen("hi c!")) => strlen("hi c!"); + lfsr_file_close(&lfs, &file) => 0; + lfsr_unmount(&lfs) => 0; + + // change a file's type to something unknown + lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; + const char *path = "b"; + lfsr_mdir_t mdir; + lfsr_did_t did; + lfsr_mtree_pathlookup(&lfs, &path, + &mdir, NULL, &did) => 0; + lfsr_mdir_commit(&lfs, &mdir, LFSR_RATTRS( + LFSR_RATTR_CAT( + LFSR_TAG_MASK8 | (LFSR_TAG_NAME + 0x13), 0, + lfsr_data_fromleb128(did, (uint8_t[LFSR_LEB128_DSIZE]){0}), + LFSR_DATA_BUF(path, lfsr_path_namelen(path))))) => 0; + lfsr_unmount(&lfs) => 0; + + // mount + lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; + + // our file should appear as an unknown type + struct lfs_info info; + lfsr_stat(&lfs, "b", &info) => 0; + assert(strcmp(info.name, "b") == 0); + assert(info.type == LFS_TYPE_UNKNOWN); + assert(info.size == 0); + + 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); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "a") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == strlen("hi a!")); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "b") == 0); + assert(info.type == LFS_TYPE_UNKNOWN); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "c") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == strlen("hi c!")); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + // renaming unknown files should still work, if this would leak + // resources the new type should set a wcompat flag + lfsr_rename(&lfs, "b", "b") => 0; + + // check that things look reasonable after renaming/removing + lfsr_stat(&lfs, "b", &info) => 0; + assert(strcmp(info.name, "b") == 0); + assert(info.type == LFS_TYPE_UNKNOWN); + assert(info.size == 0); + + lfsr_dir_open(&lfs, &dir, "/") => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "a") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == strlen("hi a!")); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "b") == 0); + assert(info.type == LFS_TYPE_UNKNOWN); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "c") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == strlen("hi c!")); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_mount_incompat_unknown_type_mv_notdir] +in = 'lfs.c' +code = ''' + // create a superblock + lfs_t lfs; + lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; + + // create some files + lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "a", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, + "hi a!", strlen("hi a!")) => strlen("hi a!"); + lfsr_file_close(&lfs, &file) => 0; + lfsr_file_open(&lfs, &file, "b", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, + "oh no!", strlen("oh no!")) => strlen("oh no!"); + lfsr_file_close(&lfs, &file) => 0; + lfsr_mkdir(&lfs, "c") => 0; + lfsr_unmount(&lfs) => 0; + + // change a file's type to something unknown + lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; + const char *path = "b"; + lfsr_mdir_t mdir; + lfsr_did_t did; + lfsr_mtree_pathlookup(&lfs, &path, + &mdir, NULL, &did) => 0; + lfsr_mdir_commit(&lfs, &mdir, LFSR_RATTRS( + LFSR_RATTR_CAT( + LFSR_TAG_MASK8 | (LFSR_TAG_NAME + 0x13), 0, + lfsr_data_fromleb128(did, (uint8_t[LFSR_LEB128_DSIZE]){0}), + LFSR_DATA_BUF(path, lfsr_path_namelen(path))))) => 0; + lfsr_unmount(&lfs) => 0; + + // mount + lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; + + // our file should appear as an unknown type + struct lfs_info info; + lfsr_stat(&lfs, "b", &info) => 0; + assert(strcmp(info.name, "b") == 0); + assert(info.type == LFS_TYPE_UNKNOWN); + assert(info.size == 0); + + 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); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "a") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == strlen("hi a!")); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "b") == 0); + assert(info.type == LFS_TYPE_UNKNOWN); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "c") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + // renaming unknown files should still work, if this would leak + // resources the new type should set a wcompat flag + lfsr_rename(&lfs, "c", "b") => LFS_ERR_NOTDIR; + + // check that things look reasonable after renaming/removing + lfsr_stat(&lfs, "b", &info) => 0; + assert(strcmp(info.name, "b") == 0); + assert(info.type == LFS_TYPE_UNKNOWN); + assert(info.size == 0); + + lfsr_dir_open(&lfs, &dir, "/") => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "a") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == strlen("hi a!")); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "b") == 0); + assert(info.type == LFS_TYPE_UNKNOWN); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "c") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_mount_incompat_unknown_type_mv_isdir] +in = 'lfs.c' +code = ''' + // create a superblock + lfs_t lfs; + lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; + + // create some files + lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "a", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, + "hi a!", strlen("hi a!")) => strlen("hi a!"); + lfsr_file_close(&lfs, &file) => 0; + lfsr_file_open(&lfs, &file, "b", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, + "oh no!", strlen("oh no!")) => strlen("oh no!"); + lfsr_file_close(&lfs, &file) => 0; + lfsr_mkdir(&lfs, "c") => 0; + lfsr_unmount(&lfs) => 0; + + // change a file's type to something unknown + lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; + const char *path = "b"; + lfsr_mdir_t mdir; + lfsr_did_t did; + lfsr_mtree_pathlookup(&lfs, &path, + &mdir, NULL, &did) => 0; + lfsr_mdir_commit(&lfs, &mdir, LFSR_RATTRS( + LFSR_RATTR_CAT( + LFSR_TAG_MASK8 | (LFSR_TAG_NAME + 0x13), 0, + lfsr_data_fromleb128(did, (uint8_t[LFSR_LEB128_DSIZE]){0}), + LFSR_DATA_BUF(path, lfsr_path_namelen(path))))) => 0; + lfsr_unmount(&lfs) => 0; + + // mount + lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; + + // our file should appear as an unknown type + struct lfs_info info; + lfsr_stat(&lfs, "b", &info) => 0; + assert(strcmp(info.name, "b") == 0); + assert(info.type == LFS_TYPE_UNKNOWN); + assert(info.size == 0); + + 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); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "a") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == strlen("hi a!")); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "b") == 0); + assert(info.type == LFS_TYPE_UNKNOWN); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "c") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + // renaming unknown files should still work, if this would leak + // resources the new type should set a wcompat flag + lfsr_rename(&lfs, "b", "c") => LFS_ERR_ISDIR; + + // check that things look reasonable after renaming/removing + lfsr_stat(&lfs, "b", &info) => 0; + assert(strcmp(info.name, "b") == 0); + assert(info.type == LFS_TYPE_UNKNOWN); + assert(info.size == 0); + + lfsr_dir_open(&lfs, &dir, "/") => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "a") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == strlen("hi a!")); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "b") == 0); + assert(info.type == LFS_TYPE_UNKNOWN); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "c") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + lfsr_unmount(&lfs) => 0; +''' diff --git a/tests/test_paths.toml b/tests/test_paths.toml index 827c83b4..bb321778 100644 --- a/tests/test_paths.toml +++ b/tests/test_paths.toml @@ -4656,29 +4656,70 @@ code = ''' lfsr_dir_open(&lfs, &dir, "coffee/vietnamese") => LFS_ERR_NOTDIR; lfsr_dir_open(&lfs, &dir, "coffee/thai") => LFS_ERR_NOTDIR; - // note write operations should normally be rejected during mount - // by wcompat flags + // rename paths + lfsr_mkdir(&lfs, "espresso") => 0; + lfsr_rename(&lfs, + "coffee/drip", + "espresso/espresso") => 0; + lfsr_rename(&lfs, + "coffee/coldbrew", + "espresso/americano") => 0; + lfsr_rename(&lfs, + "coffee/turkish", + "espresso/macchiato") => 0; + lfsr_rename(&lfs, + "coffee/tubruk", + "espresso/latte") => 0; + lfsr_rename(&lfs, + "coffee/vietnamese", + "espresso/cappuccino") => 0; + lfsr_rename(&lfs, + "coffee/thai", + "espresso/mocha") => 0; // stat paths - lfsr_stat(&lfs, "coffee/drip", &info) => 0; - assert(strcmp(info.name, "drip") == 0); + lfsr_stat(&lfs, "espresso/espresso", &info) => 0; + assert(strcmp(info.name, "espresso") == 0); assert(info.type == LFS_TYPE_UNKNOWN); - lfsr_stat(&lfs, "coffee/coldbrew", &info) => 0; - assert(strcmp(info.name, "coldbrew") == 0); + lfsr_stat(&lfs, "espresso/americano", &info) => 0; + assert(strcmp(info.name, "americano") == 0); assert(info.type == LFS_TYPE_UNKNOWN); - lfsr_stat(&lfs, "coffee/turkish", &info) => 0; - assert(strcmp(info.name, "turkish") == 0); + lfsr_stat(&lfs, "espresso/macchiato", &info) => 0; + assert(strcmp(info.name, "macchiato") == 0); assert(info.type == LFS_TYPE_UNKNOWN); - lfsr_stat(&lfs, "coffee/tubruk", &info) => 0; - assert(strcmp(info.name, "tubruk") == 0); + lfsr_stat(&lfs, "espresso/latte", &info) => 0; + assert(strcmp(info.name, "latte") == 0); assert(info.type == LFS_TYPE_UNKNOWN); - lfsr_stat(&lfs, "coffee/vietnamese", &info) => 0; - assert(strcmp(info.name, "vietnamese") == 0); + lfsr_stat(&lfs, "espresso/cappuccino", &info) => 0; + assert(strcmp(info.name, "cappuccino") == 0); assert(info.type == LFS_TYPE_UNKNOWN); - lfsr_stat(&lfs, "coffee/thai", &info) => 0; - assert(strcmp(info.name, "thai") == 0); + lfsr_stat(&lfs, "espresso/mocha", &info) => 0; + assert(strcmp(info.name, "mocha") == 0); assert(info.type == LFS_TYPE_UNKNOWN); + lfsr_stat(&lfs, "coffee/drip", &info) => LFS_ERR_NOENT; + lfsr_stat(&lfs, "coffee/coldbrew", &info) => LFS_ERR_NOENT; + lfsr_stat(&lfs, "coffee/turkish", &info) => LFS_ERR_NOENT; + lfsr_stat(&lfs, "coffee/tubruk", &info) => LFS_ERR_NOENT; + lfsr_stat(&lfs, "coffee/vietnamese", &info) => LFS_ERR_NOENT; + lfsr_stat(&lfs, "coffee/thai", &info) => LFS_ERR_NOENT; + + // remove paths + lfsr_remove(&lfs, "espresso/espresso") => 0; + lfsr_remove(&lfs, "espresso/americano") => 0; + lfsr_remove(&lfs, "espresso/macchiato") => 0; + lfsr_remove(&lfs, "espresso/latte") => 0; + lfsr_remove(&lfs, "espresso/cappuccino") => 0; + lfsr_remove(&lfs, "espresso/mocha") => 0; + + // stat paths + lfsr_stat(&lfs, "espresso/espresso", &info) => LFS_ERR_NOENT; + lfsr_stat(&lfs, "espresso/americano", &info) => LFS_ERR_NOENT; + lfsr_stat(&lfs, "espresso/macchiato", &info) => LFS_ERR_NOENT; + lfsr_stat(&lfs, "espresso/latte", &info) => LFS_ERR_NOENT; + lfsr_stat(&lfs, "espresso/cappuccino", &info) => LFS_ERR_NOENT; + lfsr_stat(&lfs, "espresso/mocha", &info) => LFS_ERR_NOENT; + lfsr_unmount(&lfs) => 0; ''' @@ -4857,10 +4898,104 @@ code = ''' lfsr_file_close(&lfs, &file) => 0; } - // note write operations should normally be rejected during mount - // by wcompat flags + // rename paths + lfsr_mkdir(&lfs, "espresso") => 0; + // bad source + lfsr_rename(&lfs, + "drip/coffee", + "espresso/espresso") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coldbrew/coffee", + "espresso/americano") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "turkish/coffee", + "espresso/macchiato") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "tubruk/coffee", + "espresso/latte") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "vietnamese/coffee", + "espresso/cappuccino") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "thai/coffee", + "espresso/mocha") => LFS_ERR_NOTDIR; + + // bad destination + lfsr_rename(&lfs, + "coffee/drip", + "drip/espresso") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coffee/coldbrew", + "coldbrew/espresso") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coffee/turkish", + "turkish/espresso") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coffee/tubruk", + "tubruk/espresso") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coffee/vietnamese", + "vietnamese/espresso") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coffee/thai", + "thai/espresso") => LFS_ERR_NOTDIR; + + // bad source and bad destination + lfsr_rename(&lfs, + "drip/coffee", + "drip/espresso") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coldbrew/coffee", + "coldbrew/espresso") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "turkish/coffee", + "turkish/espresso") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "tubruk/coffee", + "tubruk/espresso") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "vietnamese/coffee", + "vietnamese/espresso") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "thai/coffee", + "thai/espresso") => LFS_ERR_NOTDIR; + + // here's a weird one, what happens if our rename is also a noop? + lfsr_rename(&lfs, + "drip/coffee", + "drip/coffee") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coldbrew/coffee", + "coldbrew/coffee") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "turkish/coffee", + "turkish/coffee") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "tubruk/coffee", + "tubruk/coffee") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "vietnamese/coffee", + "vietnamese/coffee") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "thai/coffee", + "thai/coffee") => LFS_ERR_NOTDIR; + + // remove paths + lfsr_stat(&lfs, "drip/espresso", &info) => LFS_ERR_NOTDIR; + lfsr_stat(&lfs, "coldbrew/espresso", &info) => LFS_ERR_NOTDIR; + lfsr_stat(&lfs, "turkish/espresso", &info) => LFS_ERR_NOTDIR; + lfsr_stat(&lfs, "tubruk/espresso", &info) => LFS_ERR_NOTDIR; + lfsr_stat(&lfs, "vietnamese/espresso", &info) => LFS_ERR_NOTDIR; + lfsr_stat(&lfs, "thai/espresso", &info) => LFS_ERR_NOTDIR; // stat paths + lfsr_stat(&lfs, "drip/espresso", &info) => LFS_ERR_NOTDIR; + lfsr_stat(&lfs, "coldbrew/espresso", &info) => LFS_ERR_NOTDIR; + lfsr_stat(&lfs, "turkish/espresso", &info) => LFS_ERR_NOTDIR; + lfsr_stat(&lfs, "tubruk/espresso", &info) => LFS_ERR_NOTDIR; + lfsr_stat(&lfs, "vietnamese/espresso", &info) => LFS_ERR_NOTDIR; + lfsr_stat(&lfs, "thai/espresso", &info) => LFS_ERR_NOTDIR; + lfsr_stat(&lfs, "coffee/drip", &info) => 0; assert(strcmp(info.name, "drip") == 0); assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG)); @@ -5005,10 +5140,104 @@ code = ''' lfsr_dir_open(&lfs, &dir, "coffee/vietnamese/////") => LFS_ERR_NOTDIR; lfsr_dir_open(&lfs, &dir, "coffee/thai//////") => LFS_ERR_NOTDIR; - // note write operations should normally be rejected during mount - // by wcompat flags + // rename paths + lfsr_mkdir(&lfs, "espresso") => 0; + // bad source + lfsr_rename(&lfs, + "coffee/drip//////", + "espresso/espresso") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coffee/coldbrew/////", + "espresso/americano") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coffee/turkish////", + "espresso/macchiato") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coffee/tubruk///", + "espresso/latte") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coffee/vietnamese//", + "espresso/cappuccino") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coffee/thai/", + "espresso/mocha") => LFS_ERR_NOTDIR; + + // bad destination + lfsr_rename(&lfs, + "coffee/drip", + "espresso/espresso/") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coffee/coldbrew", + "espresso/americano//") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coffee/turkish", + "espresso/macchiato///") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coffee/tubruk", + "espresso/latte////") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coffee/vietnamese", + "espresso/cappuccino/////") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coffee/thai", + "espresso/mocha//////") => LFS_ERR_NOTDIR; + + // bad source and bad destination + lfsr_rename(&lfs, + "coffee/drip//////", + "espresso/espresso/") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coffee/coldbrew/////", + "espresso/americano//") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coffee/turkish////", + "espresso/macchiato///") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coffee/tubruk///", + "espresso/latte////") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coffee/vietnamese//", + "espresso/cappuccino/////") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coffee/thai/", + "espresso/mocha//////") => LFS_ERR_NOTDIR; + + // here's a weird one, what happens if our rename is also a noop? + lfsr_rename(&lfs, + "coffee/drip//////", + "coffee/drip//////") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coffee/coldbrew/////", + "coffee/coldbrew/////") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coffee/turkish////", + "coffee/turkish////") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coffee/tubruk///", + "coffee/tubruk///") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coffee/vietnamese//", + "coffee/vietnamese//") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coffee/thai/", + "coffee/thai/") => LFS_ERR_NOTDIR; + + // remove paths + lfsr_remove(&lfs, "coffee/drip/") => LFS_ERR_NOTDIR; + lfsr_remove(&lfs, "coffee/coldbrew//") => LFS_ERR_NOTDIR; + lfsr_remove(&lfs, "coffee/turkish///") => LFS_ERR_NOTDIR; + lfsr_remove(&lfs, "coffee/tubruk////") => LFS_ERR_NOTDIR; + lfsr_remove(&lfs, "coffee/vietnamese/////") => LFS_ERR_NOTDIR; + lfsr_remove(&lfs, "coffee/thai//////") => LFS_ERR_NOTDIR; // stat paths + lfsr_stat(&lfs, "espresso/espresso", &info) => LFS_ERR_NOENT; + lfsr_stat(&lfs, "espresso/americano", &info) => LFS_ERR_NOENT; + lfsr_stat(&lfs, "espresso/macchiato", &info) => LFS_ERR_NOENT; + lfsr_stat(&lfs, "espresso/latte", &info) => LFS_ERR_NOENT; + lfsr_stat(&lfs, "espresso/cappuccino", &info) => LFS_ERR_NOENT; + lfsr_stat(&lfs, "espresso/mocha", &info) => LFS_ERR_NOENT; + lfsr_stat(&lfs, "coffee/drip", &info) => 0; assert(strcmp(info.name, "drip") == 0); assert(info.type == LFS_TYPE_UNKNOWN); @@ -5153,10 +5382,104 @@ code = ''' lfsr_dir_open(&lfs, &dir, "coffee/vietnamese/././././.") => LFS_ERR_NOTDIR; lfsr_dir_open(&lfs, &dir, "coffee/thai/./././././.") => LFS_ERR_NOTDIR; - // note write operations should normally be rejected during mount - // by wcompat flags + // rename paths + lfsr_mkdir(&lfs, "espresso") => 0; + // bad source + lfsr_rename(&lfs, + "coffee/drip/./././././.", + "espresso/espresso") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coffee/coldbrew/././././.", + "espresso/americano") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coffee/turkish/./././.", + "espresso/macchiato") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coffee/tubruk/././.", + "espresso/latte") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coffee/vietnamese/./.", + "espresso/cappuccino") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coffee/thai/.", + "espresso/mocha") => LFS_ERR_NOTDIR; + + // bad destination + lfsr_rename(&lfs, + "coffee/drip", + "espresso/espresso/.") => LFS_ERR_NOENT; + lfsr_rename(&lfs, + "coffee/coldbrew", + "espresso/americano/./.") => LFS_ERR_NOENT; + lfsr_rename(&lfs, + "coffee/turkish", + "espresso/macchiato/././.") => LFS_ERR_NOENT; + lfsr_rename(&lfs, + "coffee/tubruk", + "espresso/latte/./././.") => LFS_ERR_NOENT; + lfsr_rename(&lfs, + "coffee/vietnamese", + "espresso/cappuccino/././././.") => LFS_ERR_NOENT; + lfsr_rename(&lfs, + "coffee/thai", + "espresso/mocha/./././././.") => LFS_ERR_NOENT; + + // bad source and bad destination + lfsr_rename(&lfs, + "coffee/drip/./././././.", + "espresso/espresso/.") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coffee/coldbrew/././././.", + "espresso/americano/./.") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coffee/turkish/./././.", + "espresso/macchiato/././.") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coffee/tubruk/././.", + "espresso/latte/./././.") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coffee/vietnamese/./.", + "espresso/cappuccino/././././.") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coffee/thai/.", + "espresso/mocha/./././././.") => LFS_ERR_NOTDIR; + + // here's a weird one, what happens if our rename is also a noop? + lfsr_rename(&lfs, + "coffee/drip/./././././.", + "coffee/drip/./././././.") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coffee/coldbrew/././././.", + "coffee/coldbrew/././././.") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coffee/turkish/./././.", + "coffee/turkish/./././.") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coffee/tubruk/././.", + "coffee/tubruk/././.") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coffee/vietnamese/./.", + "coffee/vietnamese/./.") => LFS_ERR_NOTDIR; + lfsr_rename(&lfs, + "coffee/thai/.", + "coffee/thai/.") => LFS_ERR_NOTDIR; + + // remove paths + lfsr_remove(&lfs, "coffee/drip/.") => LFS_ERR_NOTDIR; + lfsr_remove(&lfs, "coffee/coldbrew/./.") => LFS_ERR_NOTDIR; + lfsr_remove(&lfs, "coffee/turkish/././.") => LFS_ERR_NOTDIR; + lfsr_remove(&lfs, "coffee/tubruk/./././.") => LFS_ERR_NOTDIR; + lfsr_remove(&lfs, "coffee/vietnamese/././././.") => LFS_ERR_NOTDIR; + lfsr_remove(&lfs, "coffee/thai/./././././.") => LFS_ERR_NOTDIR; // stat paths + lfsr_stat(&lfs, "espresso/espresso", &info) => LFS_ERR_NOENT; + lfsr_stat(&lfs, "espresso/americano", &info) => LFS_ERR_NOENT; + lfsr_stat(&lfs, "espresso/macchiato", &info) => LFS_ERR_NOENT; + lfsr_stat(&lfs, "espresso/latte", &info) => LFS_ERR_NOENT; + lfsr_stat(&lfs, "espresso/cappuccino", &info) => LFS_ERR_NOENT; + lfsr_stat(&lfs, "espresso/mocha", &info) => LFS_ERR_NOENT; + lfsr_stat(&lfs, "coffee/drip", &info) => 0; assert(strcmp(info.name, "drip") == 0); assert(info.type == LFS_TYPE_UNKNOWN); @@ -5281,11 +5604,109 @@ code = ''' lfsr_dir_open(&lfs, &dir, "coffee/vietnamese/../../../../..") => LFS_ERR_INVAL; lfsr_dir_open(&lfs, &dir, "coffee/thai/../../../../../..") => LFS_ERR_INVAL; - // note write operations should normally be rejected during mount - // by wcompat flags + // rename paths + lfsr_mkdir(&lfs, "espresso") => 0; + // bad source + lfsr_rename(&lfs, + "coffee/drip/../../../../../..", + "espresso/espresso") => LFS_ERR_INVAL; + lfsr_rename(&lfs, + "coffee/coldbrew/../../../../..", + "espresso/americano") => LFS_ERR_INVAL; + lfsr_rename(&lfs, + "coffee/turkish/../../../..", + "espresso/macchiato") => LFS_ERR_INVAL; + lfsr_rename(&lfs, + "coffee/tubruk/../../..", + "espresso/latte") => LFS_ERR_INVAL; + lfsr_rename(&lfs, + "coffee/vietnamese/../..", + "espresso/cappuccino") => LFS_ERR_INVAL; + // this one works + lfsr_rename(&lfs, + "coffee/thai/..", + "espresso/mocha") => 0; + lfsr_rename(&lfs, + "espresso/mocha", + "coffee") => 0; + + // bad destination + lfsr_rename(&lfs, + "coffee/drip", + "espresso/espresso/..") => LFS_ERR_ISDIR; + lfsr_rename(&lfs, + "coffee/coldbrew", + "espresso/americano/../..") => LFS_ERR_INVAL; + lfsr_rename(&lfs, + "coffee/turkish", + "espresso/macchiato/../../..") => LFS_ERR_INVAL; + lfsr_rename(&lfs, + "coffee/tubruk", + "espresso/latte/../../../..") => LFS_ERR_INVAL; + lfsr_rename(&lfs, + "coffee/vietnamese", + "espresso/cappuccino/../../../../..") => LFS_ERR_INVAL; + lfsr_rename(&lfs, + "coffee/thai", + "espresso/mocha/../../../../../..") => LFS_ERR_INVAL; + + // bad source and bad destination + lfsr_rename(&lfs, + "coffee/drip/../../../../../..", + "espresso/espresso/..") => LFS_ERR_INVAL; + lfsr_rename(&lfs, + "coffee/coldbrew/../../../../..", + "espresso/americano/../..") => LFS_ERR_INVAL; + lfsr_rename(&lfs, + "coffee/turkish/../../../..", + "espresso/macchiato/../../..") => LFS_ERR_INVAL; + lfsr_rename(&lfs, + "coffee/tubruk/../../..", + "espresso/latte/../../../..") => LFS_ERR_INVAL; + lfsr_rename(&lfs, + "coffee/vietnamese/../..", + "espresso/cappuccino/../../../../..") => LFS_ERR_INVAL; + lfsr_rename(&lfs, + "coffee/thai/..", + "espresso/mocha/../../../../../..") => LFS_ERR_INVAL; + + // here's a weird one, what happens if our rename is also a noop? + lfsr_rename(&lfs, + "coffee/drip/../../../../../..", + "coffee/drip/../../../../../..") => LFS_ERR_INVAL; + lfsr_rename(&lfs, + "coffee/coldbrew/../../../../..", + "coffee/coldbrew/../../../../..") => LFS_ERR_INVAL; + lfsr_rename(&lfs, + "coffee/turkish/../../../..", + "coffee/turkish/../../../..") => LFS_ERR_INVAL; + lfsr_rename(&lfs, + "coffee/tubruk/../../..", + "coffee/tubruk/../../..") => LFS_ERR_INVAL; + lfsr_rename(&lfs, + "coffee/vietnamese/../..", + "coffee/vietnamese/../..") => LFS_ERR_INVAL; + lfsr_rename(&lfs, + "coffee/thai/..", + "coffee/thai/..") => 0; + + // remove paths + lfsr_remove(&lfs, "coffee/drip/..") => LFS_ERR_NOTEMPTY; + lfsr_remove(&lfs, "coffee/coldbrew/../..") => LFS_ERR_INVAL; + lfsr_remove(&lfs, "coffee/turkish/../../..") => LFS_ERR_INVAL; + lfsr_remove(&lfs, "coffee/tubruk/../../../..") => LFS_ERR_INVAL; + lfsr_remove(&lfs, "coffee/vietnamese/../../../../..") => LFS_ERR_INVAL; + lfsr_remove(&lfs, "coffee/thai/../../../../../..") => LFS_ERR_INVAL; // stat paths + lfsr_stat(&lfs, "espresso/espresso", &info) => LFS_ERR_NOENT; + lfsr_stat(&lfs, "espresso/americano", &info) => LFS_ERR_NOENT; + lfsr_stat(&lfs, "espresso/macchiato", &info) => LFS_ERR_NOENT; + lfsr_stat(&lfs, "espresso/latte", &info) => LFS_ERR_NOENT; + lfsr_stat(&lfs, "espresso/cappuccino", &info) => LFS_ERR_NOENT; + lfsr_stat(&lfs, "espresso/mocha", &info) => LFS_ERR_NOENT; lfsr_stat(&lfs, "coffee/drip", &info) => 0; + assert(strcmp(info.name, "drip") == 0); assert(info.type == LFS_TYPE_UNKNOWN); lfsr_stat(&lfs, "coffee/coldbrew", &info) => 0;