Skip to content

Draft: changes and bug fixes while using this in esp-idf. POSIX compliant readdir()#6

Open
Vincent-Dalstra wants to merge 17 commits into
jkent:mainfrom
Escape-This:dev
Open

Draft: changes and bug fixes while using this in esp-idf. POSIX compliant readdir()#6
Vincent-Dalstra wants to merge 17 commits into
jkent:mainfrom
Escape-This:dev

Conversation

@Vincent-Dalstra

Copy link
Copy Markdown

The changes I've made work, and are all unit-tested, but it's a bit messy: I should tidy up the commits more, and some of the code could be made more efficient (readdir(), specifically, when it hits the 'unhappy path').

It could be some time before I get it done, though, so I thought I may as well share the code as it is now. Could save someone some effort.

Do not merge this as it is. Revert 03d9707 first, at the very least.


Context: I was running lots of unit tests that interacted with a file system, using fatfs on an eMMC card - that's about as good as it gets, but its still much slower than memory. So using something like ramfs seems perfect! Unfortunately, swapping out fatfs for ramfs caused many tests to start failing.

These changes are the result of me (1) creating test cases that reveal bugs, or where behaviour does not adhere to the POSIX standards, and (2) fixing those bugs.

esp-idf version = v5.4.1


Main changes:

  • Accept trailing slash in directory names.
  • POSIX-compliant readdir() behaviour.
  • Do not change errno when an operation is successful.
  • Set errno=EISDIR when unlink() targets a directory.

This works when managed by the registry, but not as a git submodule, so
it needs to be disabled while developing.
This makes Unit-tests better, because they can allocate/cleanup every
single time.

However, it fails with ESP_ERR_INVALID_STATE each time. Could this be a
clue to why things are going wrong?
Bad sizeof operator. A classic c bug!

I added a runtime check that will catch that error if it were to
resurface again. It's also preferable to the previous behaviour of
'truncate silently if the path is too long'. Give people errors when
they do things wrong!
I had introduced this bug while adding an error message. I thought I was
helping, but did not consider the && in the if() statement.

Split the if(&&) statement into two if() statements, to make the control
flow clearer - and prevent the above from happening.
When using this component to substitute for the esp-idf's fatfs
component, I discovered various bugs and non-POSIX-compliant behaviour
that resulted in unit tests failing.
- Not handling trailing '/' in paths properly.
- Incorrect results from readdir() when the directory is modified.
- Setting errno when an operatioon succeeds.
- Setting ENFILE instead of EISDIR during unlink().

These errors were fixed using Test-Driven-Development, meaning there is
excellent test coverage.

Some of the original commit messages are kept below:

---

POSIX compliant readdir() behaviour when files are deleted

When a file is added or deleted from a directory after opendir(), POSIX
leaves unspecified whether the file will be returned from a subsequent
readdir() call.

That only applies to the the files that were added and removed, though.
All other files should be returned if-and-only-if they were not already
returned.

However, because the array of entries is shifted back when an element is
removed, removing a file behind the iterator would result in a file
being skipped!

To reliably detect changes, a copy of the directory handle is made
during opendir(), and entry pointer compared during each iteration to
see if anything was removed.

Use correct errno value for unlink()

Previously, when unlink() was called on a directory it would return
ENFILE, which I think was intended to represent "not a file", but
which really means "run out of file descriptors". Now returns the
correct value of EISDIR.

Check for errors during ramfs_rmtree()

Ignore trailing slash's in opendir(), mkdir() etc...

Fix opendir() returning NULL for empty directories.

Fix erroneously setting errno in soem situations.

Do not allow trailing slash when path refers to a file
THis was done to make the Unit-test app simpler; to speed up tests, the
the file system is generally left mounted after each test. But the ramfs
unit tests use a different path, and so try to setup a second
filesystem, exceeding the previous maximum of just 1.

Furthermore, it's only 24 bytes of static memory per additional slot, so
there's very little harm in increasing it a bit.
Remove lines with commented-out dependencies.

Add a note about how dependencies are small on purpose.
@jkent

jkent commented Apr 14, 2026

Copy link
Copy Markdown
Owner

Wow cool, thank you for your efforts. I will make time to review your changes this week! Sorry for the delayed response.

@Vincent-Dalstra

Copy link
Copy Markdown
Author

No worries. I was considering making something like this, so this saved me a lot of effort. I think my unit tests are about 10x faster, at a rough guess.

Replace assert() statements with the corresponding TEST_ASSERT_NULL or
TEST_ASSERT_NOT_NULL or TEST_ASSERT_EQUAL statements.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants