fs/vfs: Add ioctldir for volume ioctls via the mountpoint directory.#19512
Open
casaroli wants to merge 1 commit into
Open
fs/vfs: Add ioctldir for volume ioctls via the mountpoint directory.#19512casaroli wants to merge 1 commit into
casaroli wants to merge 1 commit into
Conversation
casaroli
requested review from
Donny9,
gustavonihei,
jerpelea,
pkarashchenko,
pussuw,
xiaoxiang781216 and
yamt
as code owners
July 23, 2026 16:49
|
acassis
previously approved these changes
Jul 23, 2026
casaroli
force-pushed
the
fs-dir-volume-ioctl
branch
from
July 24, 2026 16:46
579774d to
43caf61
Compare
casaroli
force-pushed
the
fs-dir-volume-ioctl
branch
from
July 24, 2026 16:51
43caf61 to
b3e79a4
Compare
FIOC_REFORMAT, FIOC_OPTIMIZE, FIOC_INTEGRITY and FIOC_DUMP act on a volume,
not on any one file, but the only route into a file system has been the
per-file ioctl method. A caller therefore has to open an unrelated file
just to name the volume it means.
For nxffs that is not merely awkward, it is a dead end. nxffs_ioctl()
refuses FIOC_REFORMAT while any file on the volume is open:
if (volume->ofiles)
{
ferr("ERROR: Open files\n");
ret = -EBUSY;
and every open file is on that list (nxffs_open.c). The descriptor used to
issue the command is itself such a file, so the check can never pass and
FIOC_REFORMAT is unreachable through the only interface that exposes it.
Add an optional ioctldir method to struct mountpt_operations, reached by
issuing the ioctl on a descriptor for the mountpoint directory:
fd = open("/mnt/nxffs", O_RDONLY | O_DIRECTORY);
ioctl(fd, FIOC_REFORMAT, 0);
It takes the same (mountpt, dir) pair as opendir/readdir/rewinddir, so it
reads as a member of the directory-operations family; the file system
recovers the volume from the mountpoint inode and may ignore dir. The
member is placed at the end of the structure rather than beside the other
directory operations on purpose: every file system initialises
mountpt_operations positionally, so a member inserted mid-structure would
force all of them to add a slot for a method they do not implement.
Appending keeps the change to one file system.
dir_ioctl() gives that method the first chance at every command when the
directory belongs to a mounted volume and the file system provides one, and
falls back to its own handling of FIOC_FILEPATH and BIOC_FLUSH when the file
system answers -ENOTTY. Trying the file system first is what lets a file
system override a command the VFS would otherwise answer generically; the
-ENOTTY fallback is what keeps the generic answers available to everyone
else. A file system that leaves the method NULL is unaffected: the VFS
answers exactly as before.
The existing per-file method could not simply be reused for this. It takes
a struct file, and every implementation that has an ioctl -- fat, romfs,
tmpfs, spiffs among them -- asserts on filep->f_priv and dereferences it,
so handing it a directory descriptor with no open file behind it would
fault. Making the entry point separate keeps that contract intact and
makes support explicit rather than assumed.
nxffs implements it, which is what makes its FIOC_REFORMAT reachable. The
per-file path is left in place and both share one implementation, so
nothing that works today stops working. spiffs, which has the same shape
of volume commands, can follow.
Measured on sim:nxffs, with one file written to the volume and then the
same sequence of ioctls issued on a file descriptor, on a descriptor for the
mountpoint directory, and on a descriptor for a pseudo file system directory.
Before:
FIOC_REFORMAT via file fd: ret=-1 errno=16 (EBUSY, as expected)
FIOC_REFORMAT via dir fd: ret=-1 errno=25
FIOC_FILEPATH via dir fd: ret=0 "/mnt/nxffs//"
BIOC_FLUSH via dir fd: ret=0
bogus cmd via dir fd: ret=-1 errno=25
FIOC_FILEPATH via /dev fd: ret=0 "/dev//"
bogus cmd via /dev fd: ret=-1 errno=25
name still in the raw MTD image afterwards: yes
After:
FIOC_REFORMAT via file fd: ret=-1 errno=16 (EBUSY, as expected)
FIOC_REFORMAT via dir fd: ret=0
FIOC_FILEPATH via dir fd: ret=0 "/mnt/nxffs//"
BIOC_FLUSH via dir fd: ret=0
bogus cmd via dir fd: ret=-1 errno=25
FIOC_FILEPATH via /dev fd: ret=0 "/dev//"
bogus cmd via /dev fd: ret=-1 errno=25
name still in the raw MTD image afterwards: no
Only the FIOC_REFORMAT line on the directory descriptor changes, and the raw
MTD image confirms the volume really was erased. FIOC_FILEPATH and
BIOC_FLUSH on a directory still answer even though nxffs is now consulted
ahead of them, an unrecognised command is still refused rather than
forwarded blindly, and a directory in the pseudo file system, which has no
ioctldir at all, is untouched.
Assisted-by: Claude Code:claude-opus-4-8
Assisted-by: Claude Code:claude-fable-5
Assisted-by: Claude Code:claude-opus-5
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
casaroli
force-pushed
the
fs-dir-volume-ioctl
branch
from
July 25, 2026 00:05
b3e79a4 to
d4c6cfb
Compare
xiaoxiang781216
approved these changes
Jul 25, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Some ioctl commands act on a volume rather than on a file —
FIOC_REFORMAT,FIOC_OPTIMIZE,FIOC_INTEGRITY,FIOC_DUMP. The only route into a filesystem, though, has been the per-file
ioctlmethod, so a caller has to openan unrelated file just to name the volume it means.
For NXFFS that is not merely awkward, it is a dead end.
nxffs_ioctl()refuses
FIOC_REFORMATwhile any file on the volume is open:and
nxffs_open()puts every opened file on exactly that list:The descriptor used to issue the command is itself an open file on the
volume, so the check can never pass.
FIOC_REFORMATis documented,implemented, and impossible to invoke.
This adds an optional
ioctldirmethod tostruct mountpt_operations,reached by issuing the ioctl on a descriptor for the mountpoint directory:
The method takes the same
(mountpt, dir)pair asopendir/readdir/rewinddir, so it reads as a member of the directory-operations family; afile system recovers the volume from the mountpoint inode and may ignore
dir(NXFFS does). It is placed at the end of the structure rather thanbeside the other directory operations on purpose — see Impact.
dir_ioctl()forwards any command it does not handle itself to that method,when the directory belongs to a mounted volume and the file system supplies
one. NXFFS implements it, which is what makes its
FIOC_REFORMATreachable.Why a new method rather than reusing
ioctl. The obvious alternative —have
dir_ioctl()call the existing per-file method — is not safe. Thatmethod takes a
struct file, and the implementations dereferencefilep->f_privafter asserting on it:A directory descriptor has no open file behind it, so forwarding into those
would assert or fault. A separate entry point keeps that contract intact and
makes support explicit rather than assumed.
include/nuttx/fs/fs.hioctldiradded tostruct mountpt_operationsfs/vfs/fs_dir.cdir_ioctl()forwards unhandled commands to itfs/nxffs/nxffs_ioctl.cfs/nxffs/nxffs_initialize.cfs/nxffs/nxffs.hDocumentation/components/filesystem/index.rstDocumentation/components/filesystem/nxffs.rstFIOC_REFORMATneeds this routeImpact
Users: additive. A new way to reach volume-wide ioctls; nothing that
works today changes. NXFFS keeps its per-file path, and both entry points
share one implementation.
FIOC_REFORMATon NXFFS becomes usable for thefirst time.
File systems: the new member is appended at the end of
struct mountpt_operations, so the positional initialisers every filesystem uses are unchanged — several already stop short of the end of the
structure today and continue to compile untouched. A file system that leaves
it
NULLbehaves exactly as before: the VFS answers-ENOTTYfor anycommand on a directory descriptor it does not handle itself.
Although
ioctldirbelongs conceptually with the directory operations(
opendir…rewinddir), it is placed at the end rather than beside themprecisely for this reason: all 21 in-tree filesystems initialise
mountpt_operationspositionally, so a member inserted mid-structure wouldforce every one of them to add a
NULLslot for a method it does notimplement. Appending keeps the change to the single filesystem that uses it.
Build / hardware / security: no build system changes, no hardware
dependency, no new configuration options. No change to any syscall
signature or user-visible ABI.
Documentation: updated in this PR, both the VFS method reference and the
NXFFS page.
Compatibility: no behavioural change for any existing caller. The
forwarding only happens for commands that previously returned
-ENOTTY.Testing
Host: macOS 15 (Darwin 25.5.0), Apple silicon.
Target:
sim:nxffs(simulator).A test was written that exercises both routes on one volume: create a file,
issue
FIOC_REFORMATthrough a descriptor on that file, then through adescriptor on the mountpoint directory, then issue an unrecognised command
on the directory to confirm it is still refused.
On
master— the directory route does not exist, so the command isunreachable by any means:
With this change, same test, same configuration:
The first line is unchanged, confirming the per-file path still behaves as
it always did. The second shows the command now succeeds. The third confirms
an unrecognised command on a directory descriptor is still refused with
-ENOTTY, so the forwarding did not turndir_ioctl()into a catch-all.The existing NXFFS test (
CONFIG_TESTING_NXFFS, 100 loops of fill/delete/verify against the simulated MTD) was run on
sim:nxffsand passesunchanged.
tools/checkpatch.sh -fpasses on every changed file.Follow-up, not in this PR
SPIFFS implements
FIOC_INTEGRITY,FIOC_REFORMAT,FIOC_OPTIMIZEandFIOC_DUMPwith the same shape and can adopt the method the same way. It isleft out here to keep the change reviewable, and because NXFFS is the case
where the command is currently unreachable rather than merely awkward.