Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

paths: Revisit path parsing, fix trailing slash behavior #1046

Merged
merged 9 commits into from
Dec 6, 2024

Conversation

geky
Copy link
Member

@geky geky commented Nov 24, 2024

This PR includes a number of tweaks to path parsing behavior to better align with POSIX. Better aligning with POSIX should make things less surprising for users, which is always a good thing.

This is the relevant section in POSIX: 4.16 Pathname Resolution.

Though note there are still some differences, listed below.

In addition to these changes, this PR includes a rewrite of the tests/test_paths.toml (+7269/-207 lines, +20 cases, +260 perms) to more aggressively cover path parsing corner cases. That being said, it's unreasonable to expect me to think of every possible corner case on the first (or nth?) try, so if you notice anything missing, please let me know.

I believe this also fixes all path-related issues reported so far (#1040, #729, #428), though it's likely I've overlooked some.

My current thinking is that most users are expecting POSIX behavior anyways, so it is reasonable to make these changes in a minor release, as long as there is sufficient documentation in the release notes. I haven't heard of any complaints related to the last set of tiny breaking changes (in v2.9) yet, though feedback is welcome.


Path changes

  • 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 path contains trailing slashes and the file is not a directory, or LFS_O_CREAT is specified and the file does not exist:

    before: lfs_file_open("reg_a/") => LFS_ERR_NOENT
    after:  lfs_file_open("reg_a/") => LFS_ERR_NOTDIR

    Note trailing slashes return LFS_ERR_ISDIR if the file is a directory:

    before: lfs_file_open("dir_a/") => LFS_ERR_ISDIR
    after:  lfs_file_open("dir_a/") => LFS_ERR_ISDIR

    Note LFS_ERR_NOENT takes priority if LFS_O_CREAT is not specified:

    before: lfs_file_open("missing_a/") => LFS_ERR_NOENT
    after:  lfs_file_open("missing_a/") => LFS_ERR_NOENT
  • Attempting to navigate above the root directory now returns LFS_ERR_INVAL:

    before: lfs_stat("/../a") => 0
    after:  lfs_stat("/../a") => LFS_ERR_INVAL

    This actually moves away from the behavior of other filesystems, but towards, in my opinion, more reasonable behavior. It should also be more consistent with future theoretical openat-like APIs.

  • The empty path ("") no longer aliases the root directory, now returns LFS_ERR_INVAL:

    before: lfs_stat("") => 0
    after:  lfs_stat("") => LFS_ERR_INVAL

POSIX deviations

While this gets us closer to POSIX, there are still some subtle differences in behaviors. These shouldn't affect most users, but are worth documenting. Most of these are due to 1. littlefs not actually creating . and .. entries on disk and 2. not using recursion during path resolution.

In my opinion these simply aren't worth trying to match POSIX exactly, but I'm curious if others disagree.

  • 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.


Related issues: #1040, #729, #428
Should supersede: #679
Related path issues found by: @rob-zeno, @XinStellaris, @PoppaChubby, @pavel-kirienko, @inf265, @Xywzel, @steverpalmer, and likely others
Feedback: Welcome

EDIT: Changed filenames in examples to hint the filetype.
EDIT: Tried to make the root ascension example a bit more clear.
EDIT: Changed lfs_file_open("missing/") to return LFS_ERR_NOTDIR
EDIT: Noted lfs_file_open("dir_a/") returns LFS_ERR_ISDIR
EDIT: Clarified that lfs_file_open("missing/") without LFS_O_CREAT returns LFS_ERR_NOENT

This should be a superset of the previous test_paths test suite, while
covering a couple more things (more APIs, more path synonyms, utf8,
non-printable ascii, non-utf8, etc).

Not yet tested are some corner cases with known bugs, mainly around
trailing slashes.
As expected these are failing and will need some work to pass.

The issue with lfs_file_open allowing trailing slashes was found by
rob-zeno, and the issue with lfs_mkdir disallowing trailing slashes was
found by XinStellaris, PoppaChubby, pavel-kirienko, inf265, Xywzel,
steverpalmer, and likely others.
@geky-bot
Copy link
Collaborator

geky-bot commented Nov 24, 2024

Tests passed ✓, Code: 17188 B (+0.7%), Stack: 1440 B (+0.0%), Structs: 812 B (+0.0%)
Code Stack Structs Coverage
Default 17188 B (+0.7%) 1440 B (+0.0%) 812 B (+0.0%) Lines 2411/2589 lines (+0.1%)
Readonly 6246 B (+0.8%) 448 B (+0.0%) 812 B (+0.0%) Branches 1269/1598 branches (+0.8%)
Threadsafe 18036 B (+0.6%) 1440 B (+0.0%) 820 B (+0.0%) Benchmarks
Multiversion 17248 B (+0.7%) 1440 B (+0.0%) 816 B (+0.0%) Readed 29369693876 B (+0.0%)
Migrate 18884 B (+0.7%) 1744 B (+0.0%) 816 B (+0.0%) Proged 1482874766 B (+0.0%)
Error-asserts 17872 B (+0.7%) 1432 B (+0.0%) 812 B (+0.0%) Erased 1568888832 B (+0.0%)

@geky geky force-pushed the fix-trailing-slashes branch from 09d8aae to 5b75910 Compare November 24, 2024 05:46
@geky-bot
Copy link
Collaborator

geky-bot commented Nov 24, 2024

Tests passed ✓, Code: 17188 B (+0.7%), Stack: 1440 B (+0.0%), Structs: 812 B (+0.0%)
Code Stack Structs Coverage
Default 17188 B (+0.7%) 1440 B (+0.0%) 812 B (+0.0%) Lines 2411/2589 lines (+0.1%)
Readonly 6246 B (+0.8%) 448 B (+0.0%) 812 B (+0.0%) Branches 1269/1598 branches (+0.8%)
Threadsafe 18036 B (+0.6%) 1440 B (+0.0%) 820 B (+0.0%) Benchmarks
Multiversion 17248 B (+0.7%) 1440 B (+0.0%) 816 B (+0.0%) Readed 29369693876 B (+0.0%)
Migrate 18884 B (+0.7%) 1744 B (+0.0%) 816 B (+0.0%) Proged 1482874766 B (+0.0%)
Error-asserts 17872 B (+0.7%) 1432 B (+0.0%) 812 B (+0.0%) Erased 1568888832 B (+0.0%)

@bmcdonnell-fb
Copy link

Overall, this seems like good stuff, based on the description.

  • lfs_file_open now returns LFS_ERR_NOTDIR if the file exists but the path contains trailing slashes:
    before: lfs_file_open("a/") => LFS_ERR_NOENT
    after:  lfs_file_open("a/") => LFS_ERR_NOTDIR

This one doesn't seem right to me. lfs_file_open() expects a non-dir file, right? I think maybe it should return LFS_ERR_INVAL in this case? Since the problem is that "a/" is (or would be) a dir, not a file.

Consider the following cases:

FS state Func call
a exists, is a dir lfs_file_open("a")
a exists, is a dir lfs_file_open("a/")
a exists, is a file lfs_file_open("a/")
a does not exist lfs_file_open("a/")

I think maybe all of these should return the same error?

@geky
Copy link
Member Author

geky commented Nov 25, 2024

This one doesn't seem right to me. lfs_file_open() expects a non-dir file, right? I think maybe it should return LFS_ERR_INVAL in this case? Since the problem is that "a/" is (or would be) a dir, not a file.

Yeah, I noticed this too and thought it was a bit funny:

FS state Func call Return value
a exists, is a dir lfs_file_open("dir_a") LFS_ERR_ISDIR
a exists, is a dir lfs_file_open("dir_a/") LFS_ERR_ISDIR
a exists, is a file lfs_file_open("reg_a/") LFS_ERR_NOTDIR
a does not exist lfs_file_open("missing_a/") LFS_ERR_ISDIR

But it is consistent with lfs_stat("reg_a/"):

lfs_stat("reg_a/") => LFS_ERR_NOTDIR

If lfs_stat("reg_a/") returned LFS_ERR_ISDIR that would be very confusing...


Actually, I think lfs_file_open("missing_a/") => LFS_ERR_ISDIR is the mistake. This should probably be LFS_ERR_NOTDIR:

FS state Func call Return value
a exists, is a dir lfs_file_open("dir_a") LFS_ERR_ISDIR
a exists, is a dir lfs_file_open("dir_a/") LFS_ERR_ISDIR
a exists, is a file lfs_file_open("reg_a/") LFS_ERR_NOTDIR
a does not exist lfs_file_open("missing_a/") LFS_ERR_NOTDIR

Then it would be consistent with the only other function that can "create" files, lfs_rename:

lfs_rename("reg_a", "missing_a/") => LFS_ERR_NOTDIR

For these weird corner cases I was mostly going off what my Linux + ext4 machine does, but reading the POSIX spec, I think this might be one of the cases where, as a retroactive spec, POSIX leaves this up to the implementation.

But I'll be honest, sometimes it's hard to know what exactly the spec is saying:

open, openat — open file

[EISDIR]
The named file is a directory and oflag includes O_WRONLY or O_RDWR, or includes O_CREAT without O_DIRECTORY, or includes O_EXEC when O_EXEC is not the same value as O_SEARCH.

[ENOTDIR]
A component of the path prefix names an existing file that is neither a directory nor a symbolic link to a directory; or O_CREAT and O_EXCL are not specified, the path argument contains at least one non-<slash> character and ends with one or more trailing <slash> characters, and the last pathname component names an existing file that is neither a directory nor a symbolic link to a directory; or O_DIRECTORY was specified and the path argument names a non-directory file; or the path argument names a non-directory file and O_SEARCH was specified when O_SEARCH is not the same value as O_EXEC.

Though POSIX open can also open directories, since it predates opendir, so things get a bit weird.

Will update.

@bmcdonnell-fb
Copy link

But it is consistent with lfs_stat("reg_a/"):

lfs_stat("reg_a/") => LFS_ERR_NOTDIR

But lfs_stat() supports dirs, right? And lfs_file_open() doesn't, right? So these are not necessarily analogous.

If lfs_stat("reg_a/") returned LFS_ERR_ISDIR that would be very confusing...

Fair.

Actually, I think lfs_file_open("missing_a/") => LFS_ERR_ISDIR is the mistake. This should probably be LFS_ERR_NOTDIR:

# FS state Func call You proposed return I propose return
1 dir_a exists, is a dir lfs_file_open("dir_a") LFS_ERR_ISDIR LFS_ERR_ISDIR
2 dir_a exists, is a dir lfs_file_open("dir_a/") LFS_ERR_ISDIR LFS_ERR_ISDIR
3 reg_a exists, is a file lfs_file_open("reg_a/") LFS_ERR_NOTDIR LFS_ERR_INVAL
4 missing_a does not exist lfs_file_open("missing_a/") LFS_ERR_NOTDIR However you indicate "file not found"...
  1. We agree. ISDIR accurately describes the problem.
  2. Combining problems 1 & 3 - can argue for either, as both would need to be resolved, but you're unlikely to change a dir into a file, so I think 1 is better aligned
  3. I'm on the fence.
    • On the one hand, I feel like we shouldn't use LFS_ERR_NOTDIR in any case here. The error message should indicate what the problem is. It's true that "reg_a/" is not a dir, but that's not why you can't lfs_file_open() it. You still couldn't open it if it were a dir (per 2).
    • OTOH, LFS_ERR_INVAL seems more correct in the sense of "true", but LFS_ERR_NOTDIR is more specific, though misleading. (Make it a dir, and you're back to 2.)
  4. Combining problem 3 w/ the file not existing. What does lfs_file_open("missing_a") return? I think this should be the same.

@geky
Copy link
Member Author

geky commented Nov 25, 2024

Combining problem 3 w/ the file not existing. What does lfs_file_open("missing_a") return? I think this should be the same.

This would be LFS_ERR_NOENT.

To make things more confusing, it depends on which flags you specify:

lfs_file_open("missing_a/", LFS_O_RDWR | LFS_O_CREAT) => LFS_ERR_NOTDIR

lfs_file_open actually already prioritizes LFS_ERR_NOENT, though this combo is missing from the tests (TODO note to self add NOENT/EXCL+trailing slash/dot/dotdot tests):

lfs_file_open("missing_a/", LFS_O_RDWR) => LFS_ERR_NOENT

A more complete table:

Func call Flags Proposed return value
lfs_file_open("dir_a") LFS_O_RDWR LFS_ERR_ISDIR
lfs_file_open("dir_a/") LFS_O_RDWR LFS_ERR_ISDIR
lfs_file_open("reg_a/") LFS_O_RDWR LFS_ERR_NOTDIR
lfs_file_open("missing_a/") LFS_O_RDWR LFS_ERR_NOENT
lfs_file_open("dir_a") LFS_O_RDWR | LFS_O_CREAT LFS_ERR_ISDIR
lfs_file_open("dir_a/") LFS_O_RDWR | LFS_O_CREAT LFS_ERR_ISDIR
lfs_file_open("reg_a/") LFS_O_RDWR | LFS_O_CREAT LFS_ERR_NOTDIR
lfs_file_open("missing_a/") LFS_O_RDWR | LFS_O_CREAT LFS_ERR_NOTDIR
lfs_file_open("dir_a") LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL LFS_ERR_EXIST
lfs_file_open("dir_a/") LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL LFS_ERR_EXIST
lfs_file_open("reg_a/") LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL LFS_ERR_NOTDIR
lfs_file_open("missing_a/") LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL LFS_ERR_NOTDIR
  1. I'm on the fence.
  • On the one hand, I feel like we shouldn't use LFS_ERR_NOTDIR in any case here. The error message should indicate what the problem is. It's true that "reg_a/" is not a dir, but that's not why you can't lfs_file_open() it. You still couldn't open it if it were a dir (per 2).
  • OTOH, LFS_ERR_INVAL seems more correct in the sense of "true", but LFS_ERR_NOTDIR is more specific, though misleading. (Make it a dir, and you're back to 2.)

My understanding of POSIX/Linux/Unix is that EINVAL is generally left as a last resort, otherwise it's too easy to use as a blanket error when more info would be useful to the user.

But lfs_stat() supports dirs, right? And lfs_file_open() doesn't, right? So these are not necessarily analogous.

That's true, though they do share the code path that decides if a trailing-slash is LFS_ERR_NOTDIR.

Thoughts on lfs_rename("reg_a", "missing_a/") => LFS_ERR_NOTDIR?

I think if we change lfs_file_open("missing_a/") we should change lfs_rename("reg_a", "missing_a/") to match. Though this would be another deviation from POSIX. POSIX is pretty explicit with rename (rename, renameat — rename file (emphasis mine)):

[ENOTDIR]
A component of either path prefix names an existing file that is neither a directory nor a symbolic link to a directory; or the old argument names a directory and the new argument names a non-directory file; or the old argument contains at least one non-<slash> character and ends with one or more trailing <slash> characters and the last pathname component names an existing file that is neither a directory nor a symbolic link to a directory; or the old argument names an existing non-directory file and the new argument names a nonexistent file, contains at least one non-<slash> character, and ends with one or more trailing <slash> characters; or the new argument names an existing non-directory file, contains at least one non-<slash> character, and ends with one or more trailing <slash> characters.

This doesn't really explain the motivation though. It could have just been an implementation quirk.


I need to think about this a bit more, I'm not entirely opposed to lfs_file_open("reg_a/") => LFS_ERR_ISDIR, though I think it does get a bit confusing with no dirs are involved. I'm also not entirely opposed to lfs_file_open("reg_a/") => LFS_ERR_INVAL, but it does lose information and doesn't seem very helpful. Worst case a user thinks it's a different arg that's the problem.

Another way of looking at LFS_ERR_NOTDIR is that it's the file that you're attempting to create that is not a directory. Though I can understand where this could be confusing.

On the other hand, these are realistically going to be pretty rare errors for users to encounter unintentionally...

geky added 7 commits November 25, 2024 15:39
- 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.
This changes the behavior of paths that attempt to navigate above root
to now return LFS_ERR_INVAL:

- before: lfs_stat("/../a") => 0
- after:  lfs_stat("/../a") => LFS_ERR_INVAL

This is a bit of an opinionated change while making other path
resolution tweaks.

In terms of POSIX-compatibility, it's a bit unclear exactly what dotdots
above the root should do.

POSIX notes:

> As a special case, in the root directory, dot-dot may refer to the
> root directory itself.

But the word choice of "may" implies it is up to the implementation.

I originally implement this as a root-loop simply because that is what
my Linux machine does, but I now think that's not the best option. Since
we're making other path-related tweaks, we might as well try to adopt
behavior that is, in my opinion, safer and less... weird...

This should also help make paths more consistent with future theoretical
openat-list APIs, where saturating at the current directory is sort of
the least expected behavior.
Unlike normal files, dots (".") should not change the depth when
attempting to skip dotdot ("..") entries.

A weird nuance in the path parser, but at least it had a relatively easy
fix.

Added test_paths_dot_dotdots to prevent a regression.
Before this, the empty path ("") was treated as an alias for the root.
This was unintentional and just a side-effect of how the path parser
worked.

Now, the empty path should always result in LFS_ERR_INVAL:

- before: lfs_stat("") => 0
- after:  lfs_stat("") => LFS_ERR_INVAL
These flags change the behavior of open quite significantly. It's useful
to cover these in our path tests so the behavior is locked down.
- test_paths_noent_trailing_slashes
- test_paths_noent_trailing_dots
- test_paths_noent_trailing_dotdots

These managed to slip through our path testing but should be tested, if
anything just to know exactly what errors these return.
- before: lfs_file_open("missing/") => LFS_ERR_ISDIR
- after:  lfs_file_open("missing/") => LFS_ERR_NOTDIR

As noted by bmcdonnell-fb, returning LFS_ERR_ISDIR here was inconsistent
with the case where the file exists:

  case                           before          after
  lfs_file_open("dir_a")      => LFS_ERR_ISDIR   LFS_ERR_ISDIR
  lfs_file_open("dir_a/")     => LFS_ERR_ISDIR   LFS_ERR_ISDIR
  lfs_file_open("reg_a/")     => LFS_ERR_NOTDIR  LFS_ERR_NOTDIR
  lfs_file_open("missing_a/") => LFS_ERR_ISDIR   LFS_ERR_NOTDIR

Note this is consistent with the behavior of lfs_stat:

  lfs_file_open("reg_a/") => LFS_ERR_NOTDIR
  lfs_stat("reg_a/")      => LFS_ERR_NOTDIR

And the only other function that can "create" files, lfs_rename:

  lfs_file_open("missing_a/")       => LFS_ERR_NOTDIR
  lfs_rename("reg_a", "missing_a/") => LFS_ERR_NOTDIR

There is some ongoing discussion about if these should return NOTDIR,
ISDIR, or INVAL, but this is at least an improvement over the
rename/open mismatch.
@geky geky force-pushed the fix-trailing-slashes branch from 21de7dc to 999ef66 Compare November 25, 2024 21:41
@geky
Copy link
Member Author

geky commented Nov 25, 2024

I've gone ahead and adopted LFS_ERR_NOTDIR for missing + trailing slashes, since it's at least an improvement over the open/rename mismatch.

Also updated tests, added the missing tests over NOENT /EXIST + trailing slashes, and updated the PR comment to match changes.

@geky-bot
Copy link
Collaborator

Tests passed ✓, Code: 17188 B, Stack: 1440 B, Structs: 812 B
Code Stack Structs Coverage
Default 17188 B 1440 B 812 B Lines 2411/2589 lines
Readonly 6246 B 448 B 812 B Branches 1269/1598 branches
Threadsafe 18036 B 1440 B 820 B Benchmarks
Multiversion 17248 B 1440 B 816 B Readed 29369693876 B
Migrate 18884 B 1744 B 816 B Proged 1482874766 B
Error-asserts 17872 B 1432 B 812 B Erased 1568888832 B

@geky
Copy link
Member Author

geky commented Dec 2, 2024

I think I'm going to leave lfs_file_open("reg_a/") as LFS_ERR_NOTDIR for now, if anything just because it matches POSIX rename. I agree with your assessment that LFS_ERR_NOTDIR is more specific than LFS_ERR_INVAL at the risk of being confusing, but we should only deviate from POSIX if there is a strong reason to.

POSIX has baggage and sometimes doesn't make the most sense, but it's what users are expecting.

If there are no other comments I'll bring this in and make a v2.10 release soon.

@BenBE
Copy link

BenBE commented Dec 2, 2024

From experiece I'd expect both lfs_file_open('missing') and lfs_file_open('missing/') to return the equivalent of ENOENT (AKA "file not found").

@bmcdonnell-fb
Copy link

@BenBE,

From experiece I'd expect both lfs_file_open('missing') and lfs_file_open('missing/') to return the equivalent of ENOENT (AKA "file not found").

Experience with what?

@bmcdonnell-fb
Copy link

My understanding of POSIX/Linux/Unix is that EINVAL is generally left as a last resort, otherwise it's too easy to use as a blanket error when more info would be useful to the user.

Fair.

On the other hand, these are realistically going to be pretty rare errors for users to encounter unintentionally...

I don't love the NOTDIR in these cases, but it doesn't seem like there's a "perfect" solution. You seem to be on a reasonable path, and I'm not too invested in the outcome of this one anymore.

Thanks as always @geky for your work and care on all this. :)

@BenBE
Copy link

BenBE commented Dec 2, 2024

@BenBE,

From experiece I'd expect both lfs_file_open('missing') and lfs_file_open('missing/') to return the equivalent of ENOENT (AKA "file not found").

Experience with what?

With other filesystems/OS. At least this would be my expectation. First test if it exists, then if it's also what you expect it to be.

@geky
Copy link
Member Author

geky commented Dec 2, 2024

From experiece I'd expect both lfs_file_open('missing') and lfs_file_open('missing/') to return the equivalent of ENOENT (AKA "file not found").

@BenBE To be clear LFS_ERR_NOENT does take priority. lfs_file_open only returns LFS_ERR_NOTDIR if LFS_O_CREAT is specified, since LFS_ERR_NOENT would not make sense in this case.

See the big table in #1046 (comment).

But clearly my explanation of the change is not worded well. Will update to hopefully make it more clear.

@geky
Copy link
Member Author

geky commented Dec 2, 2024

Should be updated now. I added an explicit note that lfs_file_open("missing_a/") returns LFS_ERR_NOENT if LFS_O_CREAT is not provided.

@geky geky added this to the v2.10 milestone Dec 6, 2024
@geky geky merged commit 2fcecc8 into devel Dec 6, 2024
32 checks passed
@geky
Copy link
Member Author

geky commented Dec 6, 2024

LGTM. Thanks all for the feedback. It's useful to know what people's thoughts are on these sort of changes.

@geky geky mentioned this pull request Dec 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants