From a6035071bede7308d30f38c26594b90e5c21395a Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Fri, 22 Nov 2024 17:07:51 -0600 Subject: [PATCH] paths: Fixed/doc trailing slash/dot POSIX incompatibilities - lfs_mkdir now accepts trailing slashes: - before: lfs_mkdir("a/") => LFS_ERR_NOENT - after: lfs_mkdir("a/") => 0 - lfs_stat, lfs_getattr, etc, now reject trailing slashes if the file is not a directory: - before: lfs_stat("reg_a/") => 0 - after: lfs_stat("reg_a/") => LFS_ERR_NOTDIR Note trailing slashes are accepted if the file is a directory: - before: lfs_stat("dir_a/") => 0 - after: lfs_stat("dir_a/") => 0 - lfs_file_open now returns LFS_ERR_NOTDIR if the file exists but the path contains trailing slashes: - before: lfs_file_open("reg_a/") => LFS_ERR_NOENT - after: lfs_file_open("reg_a/") => LFS_ERR_NOTDIR To make these work, the internal lfs_dir_find API required some interesting changes: - lfs_dir_find no longer sets id=0x3ff on not finding a parent entry in the path. Instead, lfs_path_islast can be used to determine if the modified path references a parent entry or child entry based on the remainder of the path string. Note this is only necessary for functions that create new entries (lfs_mkdir, lfs_rename, lfs_file_open). - Trailing slashes mean we can no longer rely on the modified path being NULL-terminated. lfs_path_namelen provides an alternative to strlen that stops at slash or NULL. - lfs_path_isdir also tells you if the modified path must reference a dir (contains trailing slashes). I considered handling this entirely in lfs_dir_find, but the behavior of entry-creating functions is too nuanced. At least lfs_dir_find returns LFS_ERR_NOTDIR if the file exists on disk. Like strlen, lfs_path_namelen/islast/isdir are all O(n) where n is the name length. This isn't great, but if you're using filenames large enough for this to actually matter... uh... open an issue on GitHub and we might improve this in the future. --- There are a couple POSIX incompatibilities that I think are not worth fixing: - Root modifications return EINVAL instead of EBUSY: - littlefs: remove("/") => EINVAL - POSIX: remove("/") => EBUSY Reason: This would be the only use of EBUSY in the system. - We accept modifications of directories with trailing dots: - littlefs: remove("a/.") => 0 - POSIX: remove("a/.") => EBUSY Reason: Not worth implementing. - We do not check for existence of directories followed by dotdots: - littlefs: stat("a/missing/..") => 0 - POSIX: stat("a/missing/..") => ENOENT Reason: Difficult to implement non-recursively. - We accept modifications of directories with trailing dotdots: - littlefs: rename("a/b/..", "c") => 0 - POSIX: rename("a/b/..", "c") => EBUSY Reason: Not worth implementing. These are at least now documented in tests/test_paths.toml, which isn't the greatest location, but it's at least something until a better document is created. Note that these don't really belong in SPEC.md because path parsing is a function of the driver and has no impact on disk. --- lfs.c | 68 ++++-- tests/test_paths.toml | 524 ++++++++++++++++++++---------------------- 2 files changed, 300 insertions(+), 292 deletions(-) diff --git a/lfs.c b/lfs.c index d35d5d6d..db7aae5c 100644 --- a/lfs.c +++ b/lfs.c @@ -282,6 +282,21 @@ static int lfs_bd_erase(lfs_t *lfs, lfs_block_t block) { /// Small type-level utilities /// + +// some operations on paths +static inline lfs_size_t lfs_path_namelen(const char *path) { + return strcspn(path, "/"); +} + +static inline bool lfs_path_islast(const char *path) { + lfs_size_t namelen = lfs_path_namelen(path); + return path[namelen + strspn(path + namelen, "/")] == '\0'; +} + +static inline bool lfs_path_isdir(const char *path) { + return path[lfs_path_namelen(path)] != '\0'; +} + // operations on block pairs static inline void lfs_pair_swap(lfs_block_t pair[2]) { lfs_block_t t = pair[0]; @@ -1461,13 +1476,16 @@ static int lfs_dir_find_match(void *data, return LFS_CMP_EQ; } +// lfs_dir_find tries to set path and id even if file is not found +// +// returns: +// - 0 if file is found +// - LFS_ERR_NOENT if file or parent is not found +// - LFS_ERR_NOTDIR if parent is not a dir static lfs_stag_t lfs_dir_find(lfs_t *lfs, lfs_mdir_t *dir, const char **path, uint16_t *id) { // we reduce path to a single name if we can find it const char *name = *path; - if (id) { - *id = 0x3ff; - } // default to root dir lfs_stag_t tag = LFS_MKTAG(LFS_TYPE_DIR, 0x3ff, 0); @@ -1476,8 +1494,10 @@ static lfs_stag_t lfs_dir_find(lfs_t *lfs, lfs_mdir_t *dir, while (true) { nextname: - // skip slashes - name += strspn(name, "/"); + // skip slashes if we're a directory + if (lfs_tag_type3(tag) == LFS_TYPE_DIR) { + name += strspn(name, "/"); + } lfs_size_t namelen = strcspn(name, "/"); // skip '.' and root '..' @@ -1519,7 +1539,7 @@ nextname: // update what we've found so far *path = name; - // only continue if we hit a directory + // only continue if we're a directory if (lfs_tag_type3(tag) != LFS_TYPE_DIR) { return LFS_ERR_NOTDIR; } @@ -1539,8 +1559,7 @@ nextname: tag = lfs_dir_fetchmatch(lfs, dir, dir->tail, LFS_MKTAG(0x780, 0, 0), LFS_MKTAG(LFS_TYPE_NAME, 0, namelen), - // are we last name? - (strchr(name, '/') == NULL) ? id : NULL, + id, lfs_dir_find_match, &(struct lfs_dir_find_match){ lfs, name, namelen}); if (tag < 0) { @@ -2603,12 +2622,12 @@ static int lfs_mkdir_(lfs_t *lfs, const char *path) { cwd.next = lfs->mlist; uint16_t id; err = lfs_dir_find(lfs, &cwd.m, &path, &id); - if (!(err == LFS_ERR_NOENT && id != 0x3ff)) { + if (!(err == LFS_ERR_NOENT && lfs_path_islast(path))) { return (err < 0) ? err : LFS_ERR_EXIST; } // check that name fits - lfs_size_t nlen = strlen(path); + lfs_size_t nlen = lfs_path_namelen(path); if (nlen > lfs->name_max) { return LFS_ERR_NAMETOOLONG; } @@ -3057,7 +3076,7 @@ static int lfs_file_opencfg_(lfs_t *lfs, lfs_file_t *file, // allocate entry for file if it doesn't exist lfs_stag_t tag = lfs_dir_find(lfs, &file->m, &path, &file->id); - if (tag < 0 && !(tag == LFS_ERR_NOENT && file->id != 0x3ff)) { + if (tag < 0 && !(tag == LFS_ERR_NOENT && lfs_path_islast(path))) { err = tag; goto cleanup; } @@ -3077,8 +3096,14 @@ static int lfs_file_opencfg_(lfs_t *lfs, lfs_file_t *file, goto cleanup; } + // don't allow trailing slashes + if (lfs_path_isdir(path)) { + err = LFS_ERR_ISDIR; + goto cleanup; + } + // check that name fits - lfs_size_t nlen = strlen(path); + lfs_size_t nlen = lfs_path_namelen(path); if (nlen > lfs->name_max) { err = LFS_ERR_NAMETOOLONG; goto cleanup; @@ -3842,6 +3867,12 @@ static int lfs_stat_(lfs_t *lfs, const char *path, struct lfs_info *info) { return (int)tag; } + // only allow trailing slashes on dirs + if (strchr(path, '/') != NULL + && lfs_tag_type3(tag) != LFS_TYPE_DIR) { + return LFS_ERR_NOTDIR; + } + return lfs_dir_getinfo(lfs, &cwd, lfs_tag_id(tag), info); } @@ -3944,7 +3975,7 @@ static int lfs_rename_(lfs_t *lfs, const char *oldpath, const char *newpath) { uint16_t newid; lfs_stag_t prevtag = lfs_dir_find(lfs, &newcwd, &newpath, &newid); if ((prevtag < 0 || lfs_tag_id(prevtag) == 0x3ff) && - !(prevtag == LFS_ERR_NOENT && newid != 0x3ff)) { + !(prevtag == LFS_ERR_NOENT && lfs_path_islast(newpath))) { return (prevtag < 0) ? (int)prevtag : LFS_ERR_INVAL; } @@ -3955,8 +3986,14 @@ static int lfs_rename_(lfs_t *lfs, const char *oldpath, const char *newpath) { struct lfs_mlist prevdir; prevdir.next = lfs->mlist; if (prevtag == LFS_ERR_NOENT) { + // if we're a file, don't allow trailing slashes + if (lfs_path_isdir(newpath) + && lfs_tag_type3(oldtag) != LFS_TYPE_DIR) { + return LFS_ERR_NOTDIR; + } + // check that name fits - lfs_size_t nlen = strlen(newpath); + lfs_size_t nlen = lfs_path_namelen(newpath); if (nlen > lfs->name_max) { return LFS_ERR_NAMETOOLONG; } @@ -4016,7 +4053,8 @@ static int lfs_rename_(lfs_t *lfs, const char *oldpath, const char *newpath) { {LFS_MKTAG_IF(prevtag != LFS_ERR_NOENT, LFS_TYPE_DELETE, newid, 0), NULL}, {LFS_MKTAG(LFS_TYPE_CREATE, newid, 0), NULL}, - {LFS_MKTAG(lfs_tag_type3(oldtag), newid, strlen(newpath)), newpath}, + {LFS_MKTAG(lfs_tag_type3(oldtag), + newid, lfs_path_namelen(newpath)), newpath}, {LFS_MKTAG(LFS_FROM_MOVE, newid, lfs_tag_id(oldtag)), &oldcwd}, {LFS_MKTAG_IF(samepair, LFS_TYPE_DELETE, newoldid, 0), NULL})); diff --git a/tests/test_paths.toml b/tests/test_paths.toml index 8ffb2658..69f1d7fe 100644 --- a/tests/test_paths.toml +++ b/tests/test_paths.toml @@ -1064,6 +1064,14 @@ code = ''' ''' # test trailing dots, these get a bit weird +# +# POSIX deviations: +# +# - We accept modifications of directories with trailing dots: +# - littlefs: remove("a/.") => 0 +# - POSIX: remove("a/.") => EBUSY +# Reason: Not worth implementing. +# [cases.test_paths_trailing_dots] defines.DIR = [false, true] code = ''' @@ -1214,26 +1222,6 @@ code = ''' // rename paths lfs_mkdir(&lfs, "espresso") => 0; if (DIR) { - // bad source - lfs_rename(&lfs, - "coffee/drip/./././././.", - "espresso/espresso") => LFS_ERR_INVAL; - lfs_rename(&lfs, - "coffee/coldbrew/././././.", - "espresso/americano") => LFS_ERR_INVAL; - lfs_rename(&lfs, - "coffee/turkish/./././.", - "espresso/macchiato") => LFS_ERR_INVAL; - lfs_rename(&lfs, - "coffee/tubruk/././.", - "espresso/latte") => LFS_ERR_INVAL; - lfs_rename(&lfs, - "coffee/vietnamese/./.", - "espresso/cappuccino") => LFS_ERR_INVAL; - lfs_rename(&lfs, - "coffee/thai/.", - "espresso/mocha") => LFS_ERR_INVAL; - // bad destination lfs_rename(&lfs, "coffee/drip", @@ -1274,6 +1262,69 @@ code = ''' "coffee/thai/.", "espresso/mocha/./././././.") => LFS_ERR_NOENT; + // this one works + lfs_rename(&lfs, + "coffee/drip/./././././.", + "espresso/espresso") => 0; + lfs_rename(&lfs, + "coffee/coldbrew/././././.", + "espresso/americano") => 0; + lfs_rename(&lfs, + "coffee/turkish/./././.", + "espresso/macchiato") => 0; + lfs_rename(&lfs, + "coffee/tubruk/././.", + "espresso/latte") => 0; + lfs_rename(&lfs, + "coffee/vietnamese/./.", + "espresso/cappuccino") => 0; + lfs_rename(&lfs, + "coffee/thai/.", + "espresso/mocha") => 0; + + // stat paths + lfs_stat(&lfs, "espresso/espresso/./././././.", &info) => 0; + assert(strcmp(info.name, "espresso") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_stat(&lfs, "espresso/americano/././././.", &info) => 0; + assert(strcmp(info.name, "americano") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_stat(&lfs, "espresso/macchiato/./././.", &info) => 0; + assert(strcmp(info.name, "macchiato") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_stat(&lfs, "espresso/latte/././.", &info) => 0; + assert(strcmp(info.name, "latte") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_stat(&lfs, "espresso/cappuccino/./.", &info) => 0; + assert(strcmp(info.name, "cappuccino") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_stat(&lfs, "espresso/mocha/.", &info) => 0; + assert(strcmp(info.name, "mocha") == 0); + assert(info.type == LFS_TYPE_DIR); + + lfs_stat(&lfs, "coffee/drip/./././././.", &info) => LFS_ERR_NOENT; + lfs_stat(&lfs, "coffee/coldbrew/././././.", &info) => LFS_ERR_NOENT; + lfs_stat(&lfs, "coffee/turkish/./././.", &info) => LFS_ERR_NOENT; + lfs_stat(&lfs, "coffee/tubruk/././.", &info) => LFS_ERR_NOENT; + lfs_stat(&lfs, "coffee/vietnamese/./.", &info) => LFS_ERR_NOENT; + lfs_stat(&lfs, "coffee/thai/.", &info) => LFS_ERR_NOENT; + + // remove paths + lfs_remove(&lfs, "espresso/espresso/.") => 0; + lfs_remove(&lfs, "espresso/americano/./.") => 0; + lfs_remove(&lfs, "espresso/macchiato/././.") => 0; + lfs_remove(&lfs, "espresso/latte/./././.") => 0; + lfs_remove(&lfs, "espresso/cappuccino/././././.") => 0; + lfs_remove(&lfs, "espresso/mocha/./././././.") => 0; + + // stat paths + lfs_stat(&lfs, "espresso/espresso/./././././.", &info) => LFS_ERR_NOENT; + lfs_stat(&lfs, "espresso/americano/././././.", &info) => LFS_ERR_NOENT; + lfs_stat(&lfs, "espresso/macchiato/./././.", &info) => LFS_ERR_NOENT; + lfs_stat(&lfs, "espresso/latte/././.", &info) => LFS_ERR_NOENT; + lfs_stat(&lfs, "espresso/cappuccino/./.", &info) => LFS_ERR_NOENT; + lfs_stat(&lfs, "espresso/mocha/.", &info) => LFS_ERR_NOENT; + } else { // bad source lfs_rename(&lfs, @@ -1334,52 +1385,43 @@ code = ''' lfs_rename(&lfs, "coffee/thai/.", "espresso/mocha/./././././.") => LFS_ERR_NOTDIR; - } - // remove paths - if (DIR) { - lfs_remove(&lfs, "coffee/drip/.") => LFS_ERR_INVAL; - lfs_remove(&lfs, "coffee/coldbrew/./.") => LFS_ERR_INVAL; - lfs_remove(&lfs, "coffee/turkish/././.") => LFS_ERR_INVAL; - lfs_remove(&lfs, "coffee/tubruk/./././.") => LFS_ERR_INVAL; - lfs_remove(&lfs, "coffee/vietnamese/././././.") => LFS_ERR_INVAL; - lfs_remove(&lfs, "coffee/thai/./././././.") => LFS_ERR_INVAL; - } else { + // remove paths lfs_remove(&lfs, "coffee/drip/.") => LFS_ERR_NOTDIR; lfs_remove(&lfs, "coffee/coldbrew/./.") => LFS_ERR_NOTDIR; lfs_remove(&lfs, "coffee/turkish/././.") => LFS_ERR_NOTDIR; lfs_remove(&lfs, "coffee/tubruk/./././.") => LFS_ERR_NOTDIR; lfs_remove(&lfs, "coffee/vietnamese/././././.") => LFS_ERR_NOTDIR; lfs_remove(&lfs, "coffee/thai/./././././.") => LFS_ERR_NOTDIR; + + // stat paths + lfs_stat(&lfs, "espresso/espresso", &info) => LFS_ERR_NOENT; + lfs_stat(&lfs, "espresso/americano", &info) => LFS_ERR_NOENT; + lfs_stat(&lfs, "espresso/macchiato", &info) => LFS_ERR_NOENT; + lfs_stat(&lfs, "espresso/latte", &info) => LFS_ERR_NOENT; + lfs_stat(&lfs, "espresso/cappuccino", &info) => LFS_ERR_NOENT; + lfs_stat(&lfs, "espresso/mocha", &info) => LFS_ERR_NOENT; + + lfs_stat(&lfs, "coffee/drip", &info) => 0; + assert(strcmp(info.name, "drip") == 0); + assert(info.type == LFS_TYPE_REG); + lfs_stat(&lfs, "coffee/coldbrew", &info) => 0; + assert(strcmp(info.name, "coldbrew") == 0); + assert(info.type == LFS_TYPE_REG); + lfs_stat(&lfs, "coffee/turkish", &info) => 0; + assert(strcmp(info.name, "turkish") == 0); + assert(info.type == LFS_TYPE_REG); + lfs_stat(&lfs, "coffee/tubruk", &info) => 0; + assert(strcmp(info.name, "tubruk") == 0); + assert(info.type == LFS_TYPE_REG); + lfs_stat(&lfs, "coffee/vietnamese", &info) => 0; + assert(strcmp(info.name, "vietnamese") == 0); + assert(info.type == LFS_TYPE_REG); + lfs_stat(&lfs, "coffee/thai", &info) => 0; + assert(strcmp(info.name, "thai") == 0); + assert(info.type == LFS_TYPE_REG); } - // stat paths - lfs_stat(&lfs, "espresso/espresso", &info) => LFS_ERR_NOENT; - lfs_stat(&lfs, "espresso/americano", &info) => LFS_ERR_NOENT; - lfs_stat(&lfs, "espresso/macchiato", &info) => LFS_ERR_NOENT; - lfs_stat(&lfs, "espresso/latte", &info) => LFS_ERR_NOENT; - lfs_stat(&lfs, "espresso/cappuccino", &info) => LFS_ERR_NOENT; - lfs_stat(&lfs, "espresso/mocha", &info) => LFS_ERR_NOENT; - - lfs_stat(&lfs, "coffee/drip", &info) => 0; - assert(strcmp(info.name, "drip") == 0); - assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG)); - lfs_stat(&lfs, "coffee/coldbrew", &info) => 0; - assert(strcmp(info.name, "coldbrew") == 0); - assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG)); - lfs_stat(&lfs, "coffee/turkish", &info) => 0; - assert(strcmp(info.name, "turkish") == 0); - assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG)); - lfs_stat(&lfs, "coffee/tubruk", &info) => 0; - assert(strcmp(info.name, "tubruk") == 0); - assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG)); - lfs_stat(&lfs, "coffee/vietnamese", &info) => 0; - assert(strcmp(info.name, "vietnamese") == 0); - assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG)); - lfs_stat(&lfs, "coffee/thai", &info) => 0; - assert(strcmp(info.name, "thai") == 0); - assert(info.type == ((DIR) ? LFS_TYPE_DIR : LFS_TYPE_REG)); - lfs_unmount(&lfs) => 0; ''' @@ -1576,6 +1618,19 @@ code = ''' ''' # test trailing dot dots, these get really weird +# +# POSIX deviations: +# +# - We do not check for existance of directories followed by dotdots: +# - littlefs: stat("a/missing/..") => 0 +# - POSIX: stat("a/missing/..") => ENOENT +# Reason: Difficult to implement non-recursively. +# +# - We accept modifications of directories with trailing dotdots: +# - littlefs: rename("a/b/..", "c") => 0 +# - POSIX: rename("a/b/..", "c") => EBUSY +# Reason: Not worth implementing. +# [cases.test_paths_trailing_dotdots] defines.DIR = [false, true] code = ''' @@ -1586,12 +1641,12 @@ code = ''' // create paths lfs_mkdir(&lfs, "coffee") => 0; if (DIR) { - lfs_mkdir(&lfs, "coffee/drip/..") => LFS_ERR_NOENT; - lfs_mkdir(&lfs, "coffee/coldbrew/../..") => LFS_ERR_NOENT; - lfs_mkdir(&lfs, "coffee/turkish/../../..") => LFS_ERR_NOENT; - lfs_mkdir(&lfs, "coffee/tubruk/../../../..") => LFS_ERR_NOENT; - lfs_mkdir(&lfs, "coffee/vietnamese/../../../../..") => LFS_ERR_NOENT; - lfs_mkdir(&lfs, "coffee/thai/../../../../../..") => LFS_ERR_NOENT; + lfs_mkdir(&lfs, "coffee/drip/..") => LFS_ERR_EXIST; + lfs_mkdir(&lfs, "coffee/coldbrew/../..") => LFS_ERR_EXIST; + lfs_mkdir(&lfs, "coffee/turkish/../../..") => LFS_ERR_EXIST; + lfs_mkdir(&lfs, "coffee/tubruk/../../../..") => LFS_ERR_EXIST; + lfs_mkdir(&lfs, "coffee/vietnamese/../../../../..") => LFS_ERR_EXIST; + lfs_mkdir(&lfs, "coffee/thai/../../../../../..") => LFS_ERR_EXIST; // still create so we have something to test lfs_mkdir(&lfs, "coffee/drip") => 0; @@ -1604,17 +1659,17 @@ code = ''' } else { lfs_file_t file; lfs_file_open(&lfs, &file, "coffee/drip/..", - LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOENT; + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST; lfs_file_open(&lfs, &file, "coffee/coldbrew/../..", - LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOENT; + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST; lfs_file_open(&lfs, &file, "coffee/turkish/../../..", - LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOENT; + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST; lfs_file_open(&lfs, &file, "coffee/tubruk/../../../..", - LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOENT; + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST; lfs_file_open(&lfs, &file, "coffee/vietnamese/../../../../..", - LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOENT; + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST; lfs_file_open(&lfs, &file, "coffee/thai/../../../../../..", - LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_NOENT; + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST; // still create so we have something to test lfs_file_open(&lfs, &file, "coffee/drip", @@ -1639,231 +1694,138 @@ code = ''' // stat paths struct lfs_info info; - if (DIR) { - lfs_stat(&lfs, "coffee/drip/../../../../../..", &info) => 0; - assert(strcmp(info.name, "/") == 0); - assert(info.type == LFS_TYPE_DIR); - lfs_stat(&lfs, "coffee/coldbrew/../../../../..", &info) => 0; - assert(strcmp(info.name, "/") == 0); - assert(info.type == LFS_TYPE_DIR); - lfs_stat(&lfs, "coffee/turkish/../../../..", &info) => 0; - assert(strcmp(info.name, "/") == 0); - assert(info.type == LFS_TYPE_DIR); - lfs_stat(&lfs, "coffee/tubruk/../../..", &info) => 0; - assert(strcmp(info.name, "/") == 0); - assert(info.type == LFS_TYPE_DIR); - lfs_stat(&lfs, "coffee/vietnamese/../..", &info) => 0; - assert(strcmp(info.name, "/") == 0); - assert(info.type == LFS_TYPE_DIR); - lfs_stat(&lfs, "coffee/thai/..", &info) => 0; - assert(strcmp(info.name, "coffee") == 0); - assert(info.type == LFS_TYPE_DIR); - } else { - lfs_stat(&lfs, "coffee/drip/../../../../../..", &info) => LFS_ERR_NOTDIR; - lfs_stat(&lfs, "coffee/coldbrew/../../../../..", &info) => LFS_ERR_NOTDIR; - lfs_stat(&lfs, "coffee/turkish/../../../..", &info) => LFS_ERR_NOTDIR; - lfs_stat(&lfs, "coffee/tubruk/../../..", &info) => LFS_ERR_NOTDIR; - lfs_stat(&lfs, "coffee/vietnamese/../..", &info) => LFS_ERR_NOTDIR; - lfs_stat(&lfs, "coffee/thai/..", &info) => LFS_ERR_NOTDIR; - } + lfs_stat(&lfs, "coffee/drip/../../../../../..", &info) => 0; + assert(strcmp(info.name, "/") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_stat(&lfs, "coffee/coldbrew/../../../../..", &info) => 0; + assert(strcmp(info.name, "/") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_stat(&lfs, "coffee/turkish/../../../..", &info) => 0; + assert(strcmp(info.name, "/") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_stat(&lfs, "coffee/tubruk/../../..", &info) => 0; + assert(strcmp(info.name, "/") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_stat(&lfs, "coffee/vietnamese/../..", &info) => 0; + assert(strcmp(info.name, "/") == 0); + assert(info.type == LFS_TYPE_DIR); + lfs_stat(&lfs, "coffee/thai/..", &info) => 0; + assert(strcmp(info.name, "coffee") == 0); + assert(info.type == LFS_TYPE_DIR); // file open paths, only works on files! - if (DIR) { - lfs_file_t file; - lfs_file_open(&lfs, &file, "coffee/drip/..", - LFS_O_RDONLY) => LFS_ERR_ISDIR; - lfs_file_open(&lfs, &file, "coffee/coldbrew/../..", - LFS_O_RDONLY) => LFS_ERR_ISDIR; - lfs_file_open(&lfs, &file, "coffee/turkish/../../..", - LFS_O_RDONLY) => LFS_ERR_ISDIR; - lfs_file_open(&lfs, &file, "coffee/tubruk/../../../..", - LFS_O_RDONLY) => LFS_ERR_ISDIR; - lfs_file_open(&lfs, &file, "coffee/vietnamese/../../../../..", - LFS_O_RDONLY) => LFS_ERR_ISDIR; - lfs_file_open(&lfs, &file, "coffee/thai/../../../../../..", - LFS_O_RDONLY) => LFS_ERR_ISDIR; - } else { - lfs_file_t file; - lfs_file_open(&lfs, &file, "coffee/drip/..", - LFS_O_RDONLY) => LFS_ERR_NOTDIR; - lfs_file_open(&lfs, &file, "coffee/coldbrew/../..", - LFS_O_RDONLY) => LFS_ERR_NOTDIR; - lfs_file_open(&lfs, &file, "coffee/turkish/../../..", - LFS_O_RDONLY) => LFS_ERR_NOTDIR; - lfs_file_open(&lfs, &file, "coffee/tubruk/../../../..", - LFS_O_RDONLY) => LFS_ERR_NOTDIR; - lfs_file_open(&lfs, &file, "coffee/vietnamese/../../../../..", - LFS_O_RDONLY) => LFS_ERR_NOTDIR; - lfs_file_open(&lfs, &file, "coffee/thai/../../../../../..", - LFS_O_RDONLY) => LFS_ERR_NOTDIR; - } + lfs_file_t file; + lfs_file_open(&lfs, &file, "coffee/drip/..", + LFS_O_RDONLY) => LFS_ERR_ISDIR; + lfs_file_open(&lfs, &file, "coffee/coldbrew/../..", + LFS_O_RDONLY) => LFS_ERR_ISDIR; + lfs_file_open(&lfs, &file, "coffee/turkish/../../..", + LFS_O_RDONLY) => LFS_ERR_ISDIR; + lfs_file_open(&lfs, &file, "coffee/tubruk/../../../..", + LFS_O_RDONLY) => LFS_ERR_ISDIR; + lfs_file_open(&lfs, &file, "coffee/vietnamese/../../../../..", + LFS_O_RDONLY) => LFS_ERR_ISDIR; + lfs_file_open(&lfs, &file, "coffee/thai/../../../../../..", + LFS_O_RDONLY) => LFS_ERR_ISDIR; // dir open paths, only works on dirs! - if (DIR) { - lfs_dir_t dir; - lfs_dir_open(&lfs, &dir, "coffee/drip/..") => 0; - lfs_dir_close(&lfs, &dir) => 0; - lfs_dir_open(&lfs, &dir, "coffee/coldbrew/../..") => 0; - lfs_dir_close(&lfs, &dir) => 0; - lfs_dir_open(&lfs, &dir, "coffee/turkish/../../..") => 0; - lfs_dir_close(&lfs, &dir) => 0; - lfs_dir_open(&lfs, &dir, "coffee/tubruk/../../../..") => 0; - lfs_dir_close(&lfs, &dir) => 0; - lfs_dir_open(&lfs, &dir, "coffee/vietnamese/../../../../..") => 0; - lfs_dir_close(&lfs, &dir) => 0; - lfs_dir_open(&lfs, &dir, "coffee/thai/../../../../../..") => 0; - lfs_dir_close(&lfs, &dir) => 0; - } else { - lfs_dir_t dir; - lfs_dir_open(&lfs, &dir, "coffee/drip/..") => LFS_ERR_NOTDIR; - lfs_dir_open(&lfs, &dir, "coffee/coldbrew/../..") => LFS_ERR_NOTDIR; - lfs_dir_open(&lfs, &dir, "coffee/turkish/../../..") => LFS_ERR_NOTDIR; - lfs_dir_open(&lfs, &dir, "coffee/tubruk/../../../..") => LFS_ERR_NOTDIR; - lfs_dir_open(&lfs, &dir, "coffee/vietnamese/../../../../..") => LFS_ERR_NOTDIR; - lfs_dir_open(&lfs, &dir, "coffee/thai/../../../../../..") => LFS_ERR_NOTDIR; - } + lfs_dir_t dir; + lfs_dir_open(&lfs, &dir, "coffee/drip/..") => 0; + lfs_dir_close(&lfs, &dir) => 0; + lfs_dir_open(&lfs, &dir, "coffee/coldbrew/../..") => 0; + lfs_dir_close(&lfs, &dir) => 0; + lfs_dir_open(&lfs, &dir, "coffee/turkish/../../..") => 0; + lfs_dir_close(&lfs, &dir) => 0; + lfs_dir_open(&lfs, &dir, "coffee/tubruk/../../../..") => 0; + lfs_dir_close(&lfs, &dir) => 0; + lfs_dir_open(&lfs, &dir, "coffee/vietnamese/../../../../..") => 0; + lfs_dir_close(&lfs, &dir) => 0; + lfs_dir_open(&lfs, &dir, "coffee/thai/../../../../../..") => 0; + lfs_dir_close(&lfs, &dir) => 0; // rename paths lfs_mkdir(&lfs, "espresso") => 0; + // bad source + lfs_rename(&lfs, + "coffee/drip/../../../../../..", + "espresso/espresso") => LFS_ERR_INVAL; + lfs_rename(&lfs, + "coffee/coldbrew/../../../../..", + "espresso/americano") => LFS_ERR_INVAL; + lfs_rename(&lfs, + "coffee/turkish/../../../..", + "espresso/macchiato") => LFS_ERR_INVAL; + lfs_rename(&lfs, + "coffee/tubruk/../../..", + "espresso/latte") => LFS_ERR_INVAL; + lfs_rename(&lfs, + "coffee/vietnamese/../..", + "espresso/cappuccino") => LFS_ERR_INVAL; + // this one works + lfs_rename(&lfs, + "coffee/thai/..", + "espresso/mocha") => 0; + lfs_rename(&lfs, + "espresso/mocha", + "coffee") => 0; + + // bad destination if (DIR) { - // bad source - lfs_rename(&lfs, - "coffee/drip/../../../../../..", - "espresso/espresso") => LFS_ERR_INVAL; - lfs_rename(&lfs, - "coffee/coldbrew/../../../../..", - "espresso/americano") => LFS_ERR_INVAL; - lfs_rename(&lfs, - "coffee/turkish/../../../..", - "espresso/macchiato") => LFS_ERR_INVAL; - lfs_rename(&lfs, - "coffee/tubruk/../../..", - "espresso/latte") => LFS_ERR_INVAL; - lfs_rename(&lfs, - "coffee/vietnamese/../..", - "espresso/cappuccino") => LFS_ERR_INVAL; - lfs_rename(&lfs, - "coffee/thai/..", - "espresso/mocha") => LFS_ERR_INVAL; - - // bad destination + // this one works lfs_rename(&lfs, "coffee/drip", - "espresso/espresso/..") => LFS_ERR_NOENT; + "espresso/espresso/..") => 0; lfs_rename(&lfs, - "coffee/coldbrew", - "espresso/americano/../..") => LFS_ERR_NOENT; - lfs_rename(&lfs, - "coffee/turkish", - "espresso/macchiato/../../..") => LFS_ERR_NOENT; - lfs_rename(&lfs, - "coffee/tubruk", - "espresso/latte/../../../..") => LFS_ERR_NOENT; - lfs_rename(&lfs, - "coffee/vietnamese", - "espresso/cappuccino/../../../../..") => LFS_ERR_NOENT; - lfs_rename(&lfs, - "coffee/thai", - "espresso/mocha/../../../../../..") => LFS_ERR_NOENT; - - // bad source and bad destination - lfs_rename(&lfs, - "coffee/drip/../../../../../..", - "espresso/espresso/..") => LFS_ERR_NOENT; - lfs_rename(&lfs, - "coffee/coldbrew/../../../../..", - "espresso/americano/../..") => LFS_ERR_NOENT; - lfs_rename(&lfs, - "coffee/turkish/../../../..", - "espresso/macchiato/../../..") => LFS_ERR_NOENT; - lfs_rename(&lfs, - "coffee/tubruk/../../..", - "espresso/latte/../../../..") => LFS_ERR_NOENT; - lfs_rename(&lfs, - "coffee/vietnamese/../..", - "espresso/cappuccino/../../../../..") => LFS_ERR_NOENT; - lfs_rename(&lfs, - "coffee/thai/..", - "espresso/mocha/../../../../../..") => LFS_ERR_NOENT; - + "espresso", + "coffee/drip") => 0; } else { - // bad source - lfs_rename(&lfs, - "coffee/drip/../../../../../..", - "espresso/espresso") => LFS_ERR_NOTDIR; - lfs_rename(&lfs, - "coffee/coldbrew/../../../../..", - "espresso/americano") => LFS_ERR_NOTDIR; - lfs_rename(&lfs, - "coffee/turkish/../../../..", - "espresso/macchiato") => LFS_ERR_NOTDIR; - lfs_rename(&lfs, - "coffee/tubruk/../../..", - "espresso/latte") => LFS_ERR_NOTDIR; - lfs_rename(&lfs, - "coffee/vietnamese/../..", - "espresso/cappuccino") => LFS_ERR_NOTDIR; - lfs_rename(&lfs, - "coffee/thai/..", - "espresso/mocha") => LFS_ERR_NOTDIR; - - // bad destination lfs_rename(&lfs, "coffee/drip", - "espresso/espresso/..") => LFS_ERR_NOTDIR; - lfs_rename(&lfs, - "coffee/coldbrew", - "espresso/americano/../..") => LFS_ERR_NOTDIR; - lfs_rename(&lfs, - "coffee/turkish", - "espresso/macchiato/../../..") => LFS_ERR_NOTDIR; - lfs_rename(&lfs, - "coffee/tubruk", - "espresso/latte/../../../..") => LFS_ERR_NOTDIR; - lfs_rename(&lfs, - "coffee/vietnamese", - "espresso/cappuccino/../../../../..") => LFS_ERR_NOTDIR; - lfs_rename(&lfs, - "coffee/thai", - "espresso/mocha/../../../../../..") => LFS_ERR_NOTDIR; - - // bad source and bad destination - lfs_rename(&lfs, - "coffee/drip/../../../../../..", - "espresso/espresso/..") => LFS_ERR_NOTDIR; - lfs_rename(&lfs, - "coffee/coldbrew/../../../../..", - "espresso/americano/../..") => LFS_ERR_NOTDIR; - lfs_rename(&lfs, - "coffee/turkish/../../../..", - "espresso/macchiato/../../..") => LFS_ERR_NOTDIR; - lfs_rename(&lfs, - "coffee/tubruk/../../..", - "espresso/latte/../../../..") => LFS_ERR_NOTDIR; - lfs_rename(&lfs, - "coffee/vietnamese/../..", - "espresso/cappuccino/../../../../..") => LFS_ERR_NOTDIR; - lfs_rename(&lfs, - "coffee/thai/..", - "espresso/mocha/../../../../../..") => LFS_ERR_NOTDIR; + "espresso/espresso/..") => LFS_ERR_ISDIR; } + lfs_rename(&lfs, + "coffee/coldbrew", + "espresso/americano/../..") => LFS_ERR_INVAL; + lfs_rename(&lfs, + "coffee/turkish", + "espresso/macchiato/../../..") => LFS_ERR_INVAL; + lfs_rename(&lfs, + "coffee/tubruk", + "espresso/latte/../../../..") => LFS_ERR_INVAL; + lfs_rename(&lfs, + "coffee/vietnamese", + "espresso/cappuccino/../../../../..") => LFS_ERR_INVAL; + lfs_rename(&lfs, + "coffee/thai", + "espresso/mocha/../../../../../..") => LFS_ERR_INVAL; + + // bad source and bad destination + lfs_rename(&lfs, + "coffee/drip/../../../../../..", + "espresso/espresso/..") => LFS_ERR_INVAL; + lfs_rename(&lfs, + "coffee/coldbrew/../../../../..", + "espresso/americano/../..") => LFS_ERR_INVAL; + lfs_rename(&lfs, + "coffee/turkish/../../../..", + "espresso/macchiato/../../..") => LFS_ERR_INVAL; + lfs_rename(&lfs, + "coffee/tubruk/../../..", + "espresso/latte/../../../..") => LFS_ERR_INVAL; + lfs_rename(&lfs, + "coffee/vietnamese/../..", + "espresso/cappuccino/../../../../..") => LFS_ERR_INVAL; + lfs_rename(&lfs, + "coffee/thai/..", + "espresso/mocha/../../../../../..") => LFS_ERR_INVAL; // remove paths - if (DIR) { - lfs_remove(&lfs, "coffee/drip/..") => LFS_ERR_INVAL; - lfs_remove(&lfs, "coffee/coldbrew/../..") => LFS_ERR_INVAL; - lfs_remove(&lfs, "coffee/turkish/../../..") => LFS_ERR_INVAL; - lfs_remove(&lfs, "coffee/tubruk/../../../..") => LFS_ERR_INVAL; - lfs_remove(&lfs, "coffee/vietnamese/../../../../..") => LFS_ERR_INVAL; - lfs_remove(&lfs, "coffee/thai/../../../../../..") => LFS_ERR_INVAL; - } else { - lfs_remove(&lfs, "coffee/drip/..") => LFS_ERR_NOTDIR; - lfs_remove(&lfs, "coffee/coldbrew/../..") => LFS_ERR_NOTDIR; - lfs_remove(&lfs, "coffee/turkish/../../..") => LFS_ERR_NOTDIR; - lfs_remove(&lfs, "coffee/tubruk/../../../..") => LFS_ERR_NOTDIR; - lfs_remove(&lfs, "coffee/vietnamese/../../../../..") => LFS_ERR_NOTDIR; - lfs_remove(&lfs, "coffee/thai/../../../../../..") => LFS_ERR_NOTDIR; - } + lfs_remove(&lfs, "coffee/drip/..") => LFS_ERR_NOTEMPTY; + lfs_remove(&lfs, "coffee/coldbrew/../..") => LFS_ERR_INVAL; + lfs_remove(&lfs, "coffee/turkish/../../..") => LFS_ERR_INVAL; + lfs_remove(&lfs, "coffee/tubruk/../../../..") => LFS_ERR_INVAL; + lfs_remove(&lfs, "coffee/vietnamese/../../../../..") => LFS_ERR_INVAL; + lfs_remove(&lfs, "coffee/thai/../../../../../..") => LFS_ERR_INVAL; // stat paths lfs_stat(&lfs, "espresso/espresso", &info) => LFS_ERR_NOENT; @@ -3048,6 +3010,14 @@ code = ''' ''' # root operations +# +# POSIX deviations: +# +# - Root modifications return EINVAL instead of EBUSY: +# - littlefs: remove("/") => EINVAL +# - POSIX: remove("/") => EBUSY +# Reason: This would be the only use of EBUSY in the system. +# [cases.test_paths_root] defines.DIR = [false, true] code = '''