fuse_set_conn_flag() collects the libfuse-side connection hints that are
not negotiated with the kernel. no_interrupt was the one such hint still
set only by direct field assignment.
Define FUSE_CONN_FLAG_NO_INTERRUPT alongside FUSE_CONN_FLAG_SINGLE_ISSUER
and map it onto conn->no_interrupt in the same switch, and convert the
example init() handlers to set it through the setter.
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
These example filesystems always send their reply from the per-queue
io-uring worker thread. The ones that run a helper thread use it only
for cache-invalidation notifications over /dev/fuse, not ring
submissions, so they too satisfy the io_uring_single_issuer contract.
Call fuse_set_conn_flag(conn, FUSE_CONN_FLAG_SINGLE_ISSUER) in their
init() handlers so they take the lock-free, registered-ring-fd fast
path. It is harmless when io-uring is not in use.
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
Mirror the fuse-side change commit abdd45f83c
("Make ioctl prototype conditional on FUSE_USE_VERSION. (#482)")'
for cuse too.
Crank versions to 3.19 as this is an ABI change.
Also increase the default FUSE_USE_VERSION to 30 in
include/cuse_lowlevel.h, 29 was below the libfuse3
min version.
Closes: https://github.com/libfuse/libfuse/issues/1406
Signed-off-by: Sam James <sam@gentoo.org>
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
Add -o writeback_cache to enable the kernel writeback cache, and
-o no_timeout to cache attributes and dentries indefinitely (a 1-day
timeout) instead of the default 0s, so the example can exercise caching
behaviour.
The MEMFS_ATTR_TIMEOUT/MEMFS_ENTRY_TIMEOUT macros select the default or
the long timeout based on no_timeout, leaving the handler call sites
unchanged. writeback_cache is negotiated from a new init handler via
fuse_set_feature_flag().
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
memfs only created regular files (via create) and directories (via
mkdir), so making a FIFO, socket, or device node failed with ENOSYS.
Add a mknod handler so these node types can be created.
The new dev_t rdev field on Inode stores the device number for
character and block special files and is reported back through
get_attr's st_rdev. The type bits in the mode argument select the
node kind, so the same handler covers all mknod-able types.
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
memfs_write passed offset and size straight to Inode::write_content,
which computes offset + size to size the content vector. A negative
offset converts to a huge unsigned value in that expression and aborts
the resize; an end position past the vector's index ceiling truncates
on the resize/iterator arithmetic (size_t/ptrdiff_t), which on a 32-bit
build is below the off_t range the kernel can hand us.
memfs_read only guarded offset >= content_size(). On a 64-bit build the
unsigned compare incidentally rejects a negative offset, but on 32-bit
the comparison is signed, so a negative offset slips through, truncates
in (size_t)offset, and indexes before the content buffer.
Validate at both handlers before touching the content store: reject a
negative offset with EINVAL, and in write reject an end position past
PTRDIFF_MAX (the std::vector size/index limit on both 32- and 64-bit
builds) with EFBIG.
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
Add a -o fuse_dio mount option that, on open and create, sets fi->direct_io
and fi->parallel_direct_writes. parallel_direct_writes only takes effect
alongside direct_io, so both are enabled together under the one option.
Useful for benchmarking the direct-I/O path, where the kernel no longer
serializes writes per inode and no longer clamps reads at i_size.
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
For benchmarking the FUSE interface itself rather than memfs's std::vector
content management, add a -o null_io mount option. Writes still consume
the incoming buffer (folding it into a volatile sink so the reads are not
elided) but discard it; reads return random data served from a scratch
buffer filled once at startup. The content store is never touched on
either path.
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
When memfs_rename overwrites an existing target, it called
remove_child(newname) -- which deletes the matched Dentry -- and then
dereferenced existing_dentry again via existing_dentry->get_inode() to
decrement the replaced inode's nlink, reading the freed Dentry: a
use-after-free. Resolve the target
inode before remove_child and use the saved pointer afterwards; the
inode stays alive through the Inodes map reference.
The same branch also decremented newparent's nlink twice when the target
was a directory: once explicitly and again inside remove_child(), which
already decrements a directory's parent. A directory-over-directory
rename therefore left newparent's link count one too low (rmdir
decrements the parent exactly once, via remove_child). Drop the
redundant explicit decrement.
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
memfs_link replies with fuse_reply_entry, which hands the kernel a
new lookup reference on the inode, but it only bumped nlink and never
bumped nlookup. Every other entry-replying handler (lookup, create,
mkdir) accounts for that reference. Without it, the kernel's accumulated
forget count for a hard-linked inode exceeds nlookup, and dec_lookup()
underflows and panics with "Lookup count mismatch detected" -- readily
reproduced by a concurrent stress loop that hard-links files. Increment
nlookup on the successful link reply.
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
memfs_rename locked the source then destination directory positionally.
Two concurrent renames in opposite directions could therefore deadlock
if the global inodes_mutex were ever relaxed to allow them to run
in parallel. Today the exclusive Inodes.lock() held for the whole
operation serializes them, so the deadlock is masked, but the positional
order is a latent hazard.
Order the two directory locks by inode number -- always lock the lower
ino first -- so any two renames acquire the per-directory locks in the
same order. The ordering locals are declared with the other rename
locals, before the first goto, because the validation-failure goto
would otherwise cross an initialized declaration into its scope. The
operation body still acts on the directories by role; both are locked
regardless of which was taken first.
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
Inodes::find() returned a raw Inode* after releasing its shared lock,
so every handler dereferenced an inode with nothing preventing a
concurrent forget from erasing and deleting it -- a use-after-free.
Related defects: forget erased an inode at nlookup==0 regardless of
nlink, freeing still-linked inodes; readdir dereferenced raw Dentry
pointers snapshotted at opendir; lookup dereferenced a Dentry after
dropping the parent lock; forget_multi resolved and decremented without
a lock and never erased at zero.
Make the inode table own std::shared_ptr<Inode> and have
find()/find_locked()/create() return shared_ptr, so a handler holds a
strong reference for the whole operation. A directory link (Dentry) now
holds a shared_ptr too, so a linked inode cannot be freed while
reachable. get_children()/DirHandle snapshot strong references, fixing
the readdir UAF. lookup copies the child's shared_ptr under the parent
lock before using it. An inode is erased only once it has no kernel
references and no links (nlookup==0 && nlink==0); forget and the
rewritten forget_multi both apply that guard under inodes_mutex.
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
memfs_link took inodes_mutex exclusively via Inodes.lock(), then resolved
the source and parent inodes with Inodes.find(), which takes a shared lock
on the same mutex -- a self-deadlock on a non-recursive shared_mutex. It
then called parent_inode->add_child(), which re-locks the parent inode
mutex already held via parent_inode->lock() -- a second self-deadlock.
The new Dentry was owned by a unique_ptr whose raw pointer was handed
to the parent and then freed at scope exit, leaving the parent with
a dangling dentry.
Resolve both inodes with find_locked() (inodes_mutex already held)
and link with add_child_locked() (parent inode mutex already held).
Heap-allocate the Dentry and hand ownership to the parent, which frees
it in remove_child(), matching create()/mkdir(). On add_child_locked()
failure, delete the dentry and undo the inc_nlink() before replying
the error.
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
memfs_mkdir's out_cleanup path called erase_locked() without holding
inodes_mutex, an unsynchronized map erase on the failure path. Use the
locking erase() variant.
memfs_create ignored add_child()'s return value. Its early find_child()
EEXIST check and the later add_child() are two separate parent-lock
acquisitions, so two concurrent create("foo") calls can both pass
the check, both allocate distinct inodes, and both reach add_child();
the loser gets EEXIST, which was silently discarded. That leaked the
loser's Dentry and orphaned its inode (left in the map, no link) while
still replying success. Capture the return and, on failure, delete
the dentry and erase the orphan inode before replying the error. The
inode was never linked or returned to the kernel, so the unconditional
erase() is correct.
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
Inodes::size() locked a separate std::mutex that protected nothing while
reading the inode map, which is guarded by inodes_mutex. Concurrent
create/erase could therefore reshape the map under an unsynchronized
size() read. Take a shared lock on inodes_mutex instead and drop the
stray mutex member, which had no other use.
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
The per-inode state is split across two mutexes for concurrency:
Inode::mutex (content, mtime, dentries, nlookup) and Inode::attr_mutex
(mode, uid, gid, atime). A chmod/chown/atime update should not serialize
behind a write() resizing the content vector, so the two domains keep
separate locks.
The original split was inconsistent, which was the actual bug:
write_content() updated content+mtime under mutex while truncate()
did the same under attr_mutex, and get_attr() read content.size()
under attr_mutex. So content and mtime were each touched under both
locks depending on the path, and a getattr could race a concurrent
write resizing the content. get_mode()/get_mtime() read mutable fields
with no lock at all; get_children() and is_empty() iterated the dentry
vector unlocked.
Give each field a single owning lock. content, mtime, size: mutex (truncate
and set_mtime move onto it). mode, uid, gid, atime: attr_mutex. ctime
is set once at construction and never mutated, so it stays lock-free.
get_attr() needs a consistent cross-domain snapshot, so it is the one
place that holds both, always in the order mutex -> attr_mutex; no
setter holds both, so there is no ABBA risk. memfs_setattr drops
its outer lock()/unlock() because the setters and the final get_attr
now self-lock; keeping it would deadlock the non-recursive mutex. A
concurrent getattr may then observe a partially-applied setattr, which
is acceptable for an example filesystem.
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
Add support for parsing FUSE_SECCTX extensions sent by the kernel
during file creation operations. This allows userspace filesystems
to receive and persist SELinux/AppArmor security labels on newly
created files.
Add example in passthrough_hp: When creating a file, iterates the fuse
req extension security contexts to find the SELinux security label and
apply it.
Signed-off-by: Kevin Chen <kchen@ddn.com>
Recent Meson versions warn when add_languages() is called without an
explicit 'native:' argument, because the historical default (configure
the language for "both sides") is being deprecated.
Both toolchains run on the developer's machine; the difference is the
architecture of the binaries they produce:
- native : true -> "build" toolchain: output runs on the developer's
machine itself (build-time helpers).
- native : false -> "host" toolchain: output runs on the target
system - in a cross build, the compiler from the
cross file; in a native build, the same as above.
The top-level meson.build declares only C via project(), so the C++
compiler used by the examples is registered here via add_languages().
The C++ examples (passthrough_hp, memfs_ll) link against libfuse and
run wherever libfuse runs, so 'native : false' is the correct intent
and matches what libfuse already does for C.
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
statx callers can tell the filesystem to fsync the file, so let's pass
that through to the backing fd.
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
This is the only example fuse server that exports a regular file instead
of a directory tree. Port it to be usable as a systemd fuse service so
that we can test that capability.
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Create a simple fuse server that can be run as a systemd service.
I plan to create some more single-file fuse server examples, so most of
the boilerplate code goes in a separate file.
Also suppress COMPLEX_MACRO in checkpatch, FUSE_OPT_KEY
macros have to stay as they are and checkpatch annotations
are not correct here.
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
This makes use of the bidirectional fusermount. Added is
doc/README.mount, which explains the new bidirectional
communication with fusermount.
Signed-off-by: Bernd Schubert <bschubert@ddn.com>
Existing example/ file systems do the fuse_daemonize() after
fuse_session_mount() - i.e. after the mount point is already
established. Though, these example/ daemons do not start
extra threads and do not need network initialization either.
fuse_daemonize() also does not allow to return notification
from the forked child to the parent.
Complex fuse file system daemons often want the order of
1) fork - parent watches, child does the work
Child:
2) start extra threads and system initialization (like network
connection and RDMA memory registration) from the fork child.
3) Start the fuse session after everything else succeeded
Parent:
Report child initialization success or failure
A new API is introduced to overcome the limitations of
fuse_daemonize()
fuse_daemonize_start() - fork, but foreground process does not
terminate yet and watches the background.
fuse_daemonize_success() / fuse_daemonize_fail() - background
daemon signals to the foreground process success or failure.
fuse_daemonize_active() - helper function for the high level
interface, which needs to handle both APIs. fuse_daemonize()
is called within fuse_main(), which now needs to know if the caller
actually already used the new API itself.
The object 'struct fuse_daemonize *' is allocated dynamically
and stored a global variable in fuse_daemonize.c, because
- high level fuse_main_real_versioned() needs to know
if already daemonized
- high level daemons do not have access to struct fuse_session
- FUSE_SYNC_INIT in later commits can only be done if the new
API (or a file system internal) daemonization is used.
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
Replace the FSEL_FILES macro and explicit char array with a string
literal for hex_map, deriving the file count from sizeof. Use size_t
for loop variables and fix the printf format specifier accordingly.
Signed-off-by: wangrzneu <wangrzneu@gmail.com>
* fuse: add FUSE_CAP_ALLOW_IDMAP support
Add FUSE_CAP_ALLOW_IDMAP capability flag to indicate that creation of
idmapped mounts is allowed. This maps to the kernel's FUSE_ALLOW_IDMAP
flag.
- Define FUSE_CAP_ALLOW_IDMAP in fuse_common.h using (1ULL << 32) to
ensure correct behavior on both 32-bit and 64-bit platforms.
- Set the capability in capable_ext when kernel reports FUSE_ALLOW_IDMAP.
- Send FUSE_ALLOW_IDMAP to kernel when userspace sets it in want_ext.
- Add the new FUSE_CAP_ALLOW_IDMAP capability to the printcap example
so it is printed when supported by the kernel.
Signed-off-by: Renzheng Wang <wangrzneu@gmail.com>
With 'umount -f' or
'echo 1 > /sys/fs/fuse/connections/NNN/abort'
libfuse might not receive FUSE_DESTROY and the
daemon might not know that it needs to exit.
Example: passthrough_hp might be blocked on the underlying file system
because that has some issues. The mount point would get stuck and
the user might call "umount -f" on the fuse mount point. Kernel
would then abort the connection, but libfuse/fuse-server would
still not exit and not even know that kernel had aborted the connection,
because FUSE_DESTROY might not arrive on kernel connection abort
anymore.
The new optional teardown watchdog polls on the fuse_session fd
(/dev/fuse) and starts a timer when the connection aborted.
When the timeout expires the watchdog thread exits, which will
terminate the entire program.
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
Under certain conditions, libfuse was yielding a zero d_ino from
high-level filesystems. This caused a number of bugs as other
software mis-handled these entries. To fix, ensure that direntries
stored in a fuse_dh always have either FUSE_UNKNOWN_INO or an
intentionally-set st_ino.
This bug was triggered if all the following conditions were met:
* High-level FS is readdirplus-capable, and does not set use_ino
or readdir_ino.
* FS does not use offsets in readdir.
* FS passes to the dir filler the FUSE_FILL_DIR_PLUS flag, and a
non-NULL struct stat with st_ino == 0.
* A directory is large enough to need multiple readdir calls.
* Adaptive readdirplus causes a readdirplus to be followed by a
regular readdir.
When this occurred, the fuse_dh was filled with entries with
st_ino == 0. On the initial readdirplus we were calling do_lookup()
to convert these to synthetic inode numbers, but on the subsequent
regular readdirs we were returning the zero inode numbers verbatim.
Historically, d_ino == 0 indicated that a direntry should be skipped.
Several tools have treated it this way, including Glibc before 2022
(or 2024 for readdir64_r), and current versions of Go. This has caused
a number of bugs:
* https://github.com/libfuse/libfuse/issues/1338
* https://github.com/golang/go/issues/76428
* https://github.com/restic/restic/pull/5607
* https://gitlab.gnome.org/World/deja-dup/-/issues/623
When libfuse receives st_ino == 0 in readdir, we should therefore
treat it as the FS having no opinion about the inode number. We should
only truly trust that it wants a zero inode if use_ino or readdir_ino
is true.
In addition to the fix, this commit adds a mode to passthrough to
return st_ino == 0 from readdir, and uses that to test libfuse's
behavior in test_examples.py.
Signed-off-by: Dave Vasilevsky <dave@vasilevsky.ca>
The incorrect removeal of the fs.debug check caused the message
"DEBUG: forget: cleaning up inode" to be printed even when debug
was not enabled.
Signed-off-by: Long Li <leo.lilong@huawei.com>
When multiple threads concurrently call forget_one() on the same inode,
a use-after-free memory issue can occur.
forget_one() forget_one()
---------------- ---------------
<inode.nlookup == 2>
inode.nlookup -= 1
inode.nlookup -= 1
<inode.nlookup == 0>
if (!inode.nlookup)
fs.inodes.erase()
if (!inode.nlookup) {} //UAF
Fix it by restoring the inode lock protection in forget_one().
Signed-off-by: Long Li <leo.lilong@huawei.com>
During fsstress stress testing using passthrough_hp as the backend, the
backend process crashes. The root cause is that when forget_one() and
do_lookup() concurrently process the same inode, do_lookup may return
either an invalid inode or a different inode reusing the same memory
address.
CPU0 CPU1
----------------------- --------------------
forget_one do_lookup
lock fs
inode = fs.inodes[id] //inode.fd > 0
unlock fs
lock inode
inode.nlookup -= n
<inode.nlookup equal to 0>
lock fs
unlock inode
fs.inodes.erase
unlock fs
lock inode
inode.nlookup++
unlock inode
<lookup a invalid inode>
This can lead to abnormalities in the inode nlookup count. Since the
value of inode.nlookup determines the inode's lifecycle, and considering
the locking order requirements between the inode lock and fs lock, using
the inode lock alone does not resolve the issue effectively. The fix is
to convert inode.nlookup to an atomic type, which removes the need for
write protection via inode lock, while using fs lock to guard the inode's
lifetime.
Signed-off-by: Long Li <leo.lilong@huawei.com>
fuse_log() did not have that attribute and so compilers
didn't give warnings for plain printf().
Add the attribute and fix related warnings.
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
fuse.h and fuse_lowlevel.h are already forward declaring struct statx,
there is no need for HAVE_STATX anymore. HAVE_STATX also bears the
risk to conflict with an application define. Alternatively it would
have been possible to change to HAVE_FUSE_STATX.
Get rid of the conditionals in the public header files and
also remove HAVE_STATX definition from the public
libfuse_config.h.
Edit by Bernd: Commit message and removal of HAVE_STATX from
public libfuse_config.h.
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
Commit f8fe398ee1 introduced an invalid
passthrough_hp.cc indentation and that was not detected by checkpatch,
as that only handles C files.
Signed-off-by: Bernd Schubert <bschubert@ddn.com>
With io-uring the req owns the payload buffer, the application
can directly access it and copy data into it.
fuse_buf_copy_one() already has a check for dstmem == srcmem
and skips data copies.
fuse_reply_data
fuse_reply_data_uring
fuse_buf_copy
fuse_buf_copy_one
Signed-off-by: Bernd Schubert <bschubert@ddn.com>
These nullptr initializations don't make sense - methods that
are not explicitly set are not implemented. I thought that C++20
would eventually avoid the nullptr, but looks like compilers
still give warnings - avoid them with a pragma.
Signed-off-by: Bernd Schubert <bschubert@ddn.com>
Remove redundant mutex lock acquisition in the truncate() method to
prevent deadlock. The issue occurs when memfs_setattr() already holds
the mutex lock and then calls truncate(), which attempts to acquire
the same lock again.
Signed-off-by: Long Li <leo.lilong@huawei.com>
This commit adds libfuse support for FUSE_STATX requests on
linux distributions.
Currently, statx is only supported on linux. To make the interface a
ergonomic as possible (eg using native 'struct statx' vs 'struct
fuse_statx'), this implementation gates the 'struct statx' changes
by #ifdef linux.
Signed-off-by: Joanne Koong <joannelkoong@gmail.com>
This only effects example/{passthrough_hp.cc,memfs_ll.cc} and is mainly
to avoid these warnings
../example/memfs_ll.cc:1100:1: warning: missing field 'statx' initializer
[-Wmissing-designated-field-initializers]
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
FreeBSD 14 introduced a new system call, fspacectl().
Currently, it supports one operation mode, SPACECTL_DEALLOC,
which is functionally equivalent to Linux fallocate() with
FALLOC_FL_KEEP_SIZE|FALLOC_FL_PUNCH_HOLE flags.
fspacectl() calls with SPACECTL_DEALLOC is supported on FUSE
filesystems via FUSE_FALLOCATE with the aforementioned flags.
Signed-off-by: CismonX <admin@cismon.net>