fuse/libfuse/e2fsprogs/etc: containerize ext4 for safer operation
Subsystems (4)
PATCH v5 1/1 fuse/libfuse/e2fsprogs/etc: containerize ext4 for safer operation
Hi everyone,
This *would have been* the eight public draft of the gigantic patchset to connect the Linux fuse driver to fs-iomap for regular file IO operations to and from files whose contents persist to locally attached storage devices.
However, the previous submission was too large, and I didn't even send half the patches! I have therefore split the work into two sections. This first section covers setting up fuse servers to run as contained systemd services; I previously sent only the libfuse changes, without any of the surrounding pieces. Now I'm ready to send them all.
To summarize this patchbomb: fuse servers can now run as non-root users, with no privilege, no access to the network or hardware, etc. The only connection to the outside is an ephemeral AF_UNIX socket. The process on the other end is a helper program that acquires resources and calls fsmount().
Why would you want to do that? Most filesystem drivers are seriously vulnerable to metadata parsing attacks, as syzbot has shown repeatedly over almost a decade of its existence. Faulty code can lead to total kernel compromise, and I think there's a very strong incentive to move all that parsing out to userspace where we can containerize the fuse server process. Runtime filesystem metadata parsing is no longer a privileged (== risky) operation.
The consequences of a crashed driver is a dead mount, instead of a crashed or corrupt OS kernel.
Note that contained fuse filesystem servers are no faster than regular fuse. The redesign of the fuse IO path via iomap will be the subject of the second patchbomb. The containerization code only requires changes to libfuse and is ready to go today.
Since the seventh submission, I have made the following changes:
1) Added a couple of simple fuse service drivers to the example code
2) Adapted fuservicemount to be runnable as a setuid program so that unprivileged users can start up a containerized filesystem driver
3) Fixed some endianness handling errors in the socket protocol between the new mount helper and the fuse server
4) Added a high level fuse_main function so that fuse servers that use the high level api can containerize without a total rewrite
5) Adapted mount.fuse to call the new mount helper code so that mount -t fuse.XXX can try to start up a contained server
6) Cleaned up a lot of cppcheck complaints and refactored a bunch of repetitious code
7) Started using codex to try to find bugs and security problems with the new mount helper
There are a few unanswered questions:
a. How to integrate with the SYNC_INIT patches that Bernd is working on merging into libfuse
b. If /any/ of the new fsopen/fsconfig/fsmount/move_mount calls fail, do we fall back to the old mount syscall? Even after printing errors?
c. Are there any Linux systems where some inetd implementation can actually handle AF_UNIX sockets? Does it make sense to try to do the service isolation without the convenience of systemd directives?
d. meson/autoconf/cmake are a pain to deal with, hopefully the changes I made are correct
I have also converted a handful more fuse servers (fat, exfat, iso, http) to the new service architecture so that I can run a (virtual) Debian system with EFI completely off of containerized fuse servers. These will be sent at the end.
fstests:
https://git.kernel.org/pub/scm/linux/kernel/git/djwong/xfstests-dev.git/log/?h=fuse2fs_2026-04-22
--Darrick
Unreviewed patches in this patchbomb:
[PATCHSET v5] libfuse: run fuse servers as a contained service
[PATCH 02/13] mount_service: add systemd socket service mounting [PATCH 03/13] mount_service: create high level fuse helpers [PATCH 04/13] mount_service: use the new mount api for the mount [PATCH 05/13] mount_service: update mtab after a successful mount [PATCH 06/13] util: hoist the fuse.conf parsing and setuid mode [PATCH 07/13] util: fix checkpatch complaints in fuser_conf.[ch] [PATCH 08/13] mount_service: enable unprivileged users in a similar [PATCH 09/13] mount.fuse3: integrate systemd service startup [PATCH 10/13] mount_service: allow installation as a setuid program [PATCH 11/13] example/service_ll: create a sample systemd service [PATCH 12/13] example/service: create a sample systemd service for a [PATCH 13/13] nullfs: support fuse systemd service mode
[PATCHSET 1/2] libext2fs: fix some missed fsync calls
[PATCH 1/3] libext2fs: always fsync the device when flushing the [PATCH 2/3] libext2fs: always fsync the device when closing the unix [PATCH 3/3] libext2fs: only fsync the unix fd if we wrote to the
[PATCHSET v5 2/2] fuse4fs: run servers as a contained service
[PATCH 01/10] libext2fs: make it possible to extract the fd from an [PATCH 02/10] libext2fs: fix checking for valid fds in mmp.c [PATCH 03/10] unix_io: allow passing /dev/fd/XXX paths to the unixfd [PATCH 04/10] libext2fs: fix MMP code to work with unixfd IO manager [PATCH 05/10] libext2fs: bump libfuse API version to 3.19 [PATCH 06/10] fuse4fs: hoist some code out of fuse4fs_main [PATCH 07/10] fuse4fs: enable safe service mode
[PATCH 08/10] fuse4fs: set proc title when in fuse service mode [PATCH 09/10] fuse4fs: make MMP work correctly in safe service mode [PATCH 10/10] debian: update packaging for fuse4fs service
PATCH v5 1/1 libfuse: run fuse servers as a contained service
Hi all,
This patchset defines the necessary communication protocols and library code so that users can mount fuse servers that run in unprivileged systemd service containers. That in turn allows unprivileged untrusted mounts, because the worst that can happen is that a malicious image crashes the fuse server and the mount dies, instead of corrupting the kernel's memory.
v5: Refactor socket IO into helpers, tighten the security checks in
mount_service.c, always set nosuid/nodev for unprivileged mounts, use posix_spawnp in mount.fuse, restructure sample programs and hl library code to avoid the need for unmounting during startup
v4.1: fix various cppcheck/codecheck complaints
v4: fix a large number of security problems that only matter when the
mount helper is being run as a setuid program; fix protocol byteswapping problems; add CLOEXEC to all files being traded back and forth; add an umount command; and strengthen mount socket protocol checks.
v3: refactor the sample code to reduce duplication; fix all the
checkpatch complaints; examples actually build standalone;
fuservicemount handles utab now; cleaned up meson feature detection;
handle MS_ flags that don't translate to MOUNT_ATTR_*v2: cleaned up error code handling and logging; add some example fuse
service; fuservicemount3 can now be a setuid program to allow unprivileged userspace to fire up a contained filesystem driver. This could be opening Pandora's box...
v1: detach from fuse-iomap series
If you're going to start using this code, I strongly recommend pulling from my git trees, which are linked below.
With a bit of luck, this should all go splendidly. Comments and questions are, as always, welcome.
--D
kernel git tree:
https://git.kernel.org/cgit/linux/kernel/git/djwong/xfs-linux.git/log/?h=fuse-service-container
---
Commits in this patchset:
* Refactor mount code / move common functions to mount_util.c
* mount_service: add systemd socket service mounting helper
* mount_service: create high level fuse helpers
* mount_service: use the new mount api for the mount service
* mount_service: update mtab after a successful mount
* util: hoist the fuse.conf parsing and setuid mode enforcement code
* util: fix checkpatch complaints in fuser_conf.[ch]
* mount_service: enable unprivileged users in a similar manner as fusermount
* mount.fuse3: integrate systemd service startup
* mount_service: allow installation as a setuid program
* example/service_ll: create a sample systemd service fuse server
* example/service: create a sample systemd service for a high-level fuse server
* nullfs: support fuse systemd service mode
--- example/single_file.h | 191 ++ include/fuse.h | 34 include/fuse_service.h | 243 +++ include/fuse_service_priv.h | 161 ++ lib/fuse_i.h | 3 lib/mount_common_i.h | 22 lib/mount_util.h | 8 lib/util.h | 35 util/fuser_conf.h | 62 + util/mount_service.h | 49 + .github/workflows/install-ubuntu-dependencies.sh | 4 README.md | 3 doc/fuservicemount3.8 | 32 doc/meson.build | 3 example/meson.build | 26 example/null.c | 51 + example/null.socket.in | 15 example/[email protected] | 102 + example/service_hl.c | 224 ++ example/service_hl.socket.in | 15 example/[email protected] | 102 + example/service_ll.c | 313 +++ example/service_ll.socket.in | 15 example/[email protected] | 102 + example/single_file.c | 970 ++++++++++ include/meson.build | 4 lib/fuse_service.c | 1220 +++++++++++++ lib/fuse_service_stub.c | 106 + lib/fuse_versionscript | 18 lib/helper.c | 160 ++ lib/meson.build | 17 lib/mount.c | 72 + lib/mount_util.c | 9 meson.build | 53 + meson_options.txt | 9 test/ci-build.sh | 14 util/fuser_conf.c | 396 ++++ util/fusermount.c | 363 ---- util/fuservicemount.c | 65 + util/install_helper.sh | 6 util/meson.build | 24 util/mount.fuse.c | 169 ++ util/mount_service.c | 2111 ++++++++++++++++++++++ 43 files changed, 7197 insertions(+), 404 deletions(-) create mode 100644 example/single_file.h create mode 100644 include/fuse_service.h create mode 100644 include/fuse_service_priv.h create mode 100644 lib/mount_common_i.h create mode 100644 util/fuser_conf.h create mode 100644 util/mount_service.h create mode 100644 doc/fuservicemount3.8 create mode 100644 example/null.socket.in create mode 100644 example/[email protected] create mode 100644 example/service_hl.c create mode 100644 example/service_hl.socket.in create mode 100644 example/[email protected] create mode 100644 example/service_ll.c create mode 100644 example/service_ll.socket.in create mode 100644 example/[email protected] create mode 100644 example/single_file.c create mode 100644 lib/fuse_service.c create mode 100644 lib/fuse_service_stub.c create mode 100644 util/fuser_conf.c create mode 100644 util/fuservicemount.c create mode 100644 util/mount_service.c
PATCH 1/13 Refactor mount code / move common functions to mount_util.c
From: Bernd Schubert <[email protected]>
Also create the new "mount_i.h", which is independent of the the rest of libfuse.
This is preparation for the new mount API, which goes into a new file.
This is to allow fusermount to use the code from mount_fsmount.c. I.e. avoid code dup and add just re-use the new linux api mount functions from that file for fusermount.
--- lib/mount_common_i.h | 18 +++++++++++++++++ lib/mount_util.h | 8 ++++++++ lib/mount.c | 53 ++++++++++++++++++++++++++++++++++++-------------- lib/mount_util.c | 9 ++++++++ util/fusermount.c | 5 +---- 5 files changed, 74 insertions(+), 19 deletions(-) create mode 100644 lib/mount_common_i.h
lib/mount_common_i.h new file +18 -0
@@ -0,0 +1,18 @@ +/* +* FUSE: Filesystem in Userspace +* Copyright (C) 2001-2007 Miklos Szeredi <[email protected]> +* 2026 Bernd Schubert <[email protected]> +* +* This program can be distributed under the terms of the GNU LGPLv2. +* See the file LGPL2.txt +*/ + +#ifndef FUSE_MOUNT_COMMON_I_H_ +#define FUSE_MOUNT_COMMON_I_H_ + +struct mount_opts ; + +char *fuse_mnt_build_source (const struct mount_opts *mo); +char *fuse_mnt_build_type (const struct mount_opts *mo); + +#endif /* FUSE_MOUNT_COMMON_I_H_ */
lib/mount_util.h +8 -0
@@ -6,6 +6,9 @@See the file LGPL2.txt. */ +#ifndef FUSE_MOUNT_UTIL_H_ +#define FUSE_MOUNT_UTIL_H_ +#include <sys/types.h> int fuse_mnt_add_mount(const char *progname, const char *fsname,
@@ -16,3 +19,8 @@ int fuse_mnt_umount(const char *progname, const char *abs_mnt,char *fuse_mnt_resolve_path (const char *progname,const char *orig);int fuse_mnt_check_fuseblk (void );int fuse_mnt_parse_fuse_fd (const char *mountpoint); + +/* Helper functions for mount operations */ +const char *fuse_mnt_get_devname (void ); + +#endif /* FUSE_MOUNT_UTIL_H_ */
lib/mount.c +38 -15
85 lines not on this page · raw message
lib/mount_util.c +9 -0
@@ -377,3 +377,12 @@ int fuse_mnt_parse_fuse_fd(const char *mountpoint)return -1 ; } + +#define FUSE_KERN_DEVICE_ENV "FUSE_KERN_DEVICE" + +const char *fuse_mnt_get_devname (void ) +{ +const char *devname =getenv (FUSE_KERN_DEVICE_ENV ); + +return devname ? devname :"/dev/fuse" ; +}
util/fusermount.c +1 -4
@@ -47,9 +47,6 @@#endif #define FUSE_COMMFD_ENV "_FUSE_COMMFD" -#define FUSE_KERN_DEVICE_ENV "FUSE_KERN_DEVICE" - -#define FUSE_DEV "/dev/fuse"static const char *progname;
@@ -1262,7 +1259,7 @@ static int mount_fuse(const char *mnt, const char *opts, const char **type) {int res;int fd; -const char *dev =getenv (FUSE_KERN_DEVICE_ENV ) ?:FUSE_DEV ; +const char *dev =fuse_mnt_get_devname ();struct stat stbuf;char *source =NULL ;char *mnt_opts =NULL ;
PATCH 2/13 mount_service: add systemd socket service mounting helper
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
Create a mount helper program that can start a fuse server that runs as a socket-based systemd service, and a new libfuse module to wrap all the details of communicating between the mount helper and the containerized fuse server.
This enables untrusted ext4 mounts via systemd service containers, which avoids the problem of malicious filesystems compromising the integrity of the running kernel through memory corruption.
In theory this could also be supported via inetd and clones, though the author hasn't found one that supports AF_UNIX sockets.
--- include/fuse_service.h | 243 ++++ include/fuse_service_priv.h | 160 ++ lib/mount_common_i.h | 3 util/mount_service.h | 40 + .github/workflows/install-ubuntu-dependencies.sh | 4 doc/fuservicemount3.8 | 24 doc/meson.build | 3 include/meson.build | 4 lib/fuse_service.c | 1205 +++++++++++++++++++ lib/fuse_service_stub.c | 106 ++ lib/fuse_versionscript | 17 lib/helper.c | 51 + lib/meson.build | 17 lib/mount.c | 12 meson.build | 34 + meson_options.txt | 9 util/fuservicemount.c | 18 util/meson.build | 9 util/mount_service.c | 1427 ++++++++++++++++++++++ 19 files changed, 3384 insertions(+), 2 deletions(-) create mode 100644 include/fuse_service.h create mode 100644 include/fuse_service_priv.h create mode 100644 util/mount_service.h create mode 100644 doc/fuservicemount3.8 create mode 100644 lib/fuse_service.c create mode 100644 lib/fuse_service_stub.c create mode 100644 util/fuservicemount.c create mode 100644 util/mount_service.c
include/fuse_service.h new file +243 -0
@@ -0,0 +1,243 @@ +/* +* FUSE: Filesystem in Userspace +* Copyright (C) 2025-2026 Oracle. +* Author: Darrick J. Wong <[email protected]> +* +* This program can be distributed under the terms of the GNU LGPLv2. +* See the file LGPL2.txt. +*/ +#ifndef FUSE_SERVICE_H_ +#define FUSE_SERVICE_H_ + +/** @file +* +* Low level API +* +* IMPORTANT: you should define FUSE_USE_VERSION before including this +* header. To use the newest API define it to 319 (recommended for any +* new application). +*/ + +#ifndef FUSE_USE_VERSION +#error FUSE_USE_VERSION not defined +#endif + +#include "fuse_common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if FUSE_MAKE_VERSION (3 ,19 ) <=FUSE_USE_VERSION + +struct fuse_service ; + +/** +* Accept a socket created by mount.service for information exchange. +* +* @param sfp pointer to pointer to a service context. The pointer will always +* be initialized by this function; use fuse_service_accepted to +* find out if the fuse server is actually running as a service. +* @return 0 on success, or negative errno on failure +*/ +int fuse_service_accept (struct fuse_service **sfp); + +/** +* Has the fuse server accepted a service context? +* +* @param sf service context +* @return true if it has, false if not +*/ +static inline bool fuse_service_accepted (struct fuse_service *sf) +{ +return sf !=NULL ; +} + +/** +* Will the mount service helper accept the allow_other option? +* +* @param sf service context +* @return true if it has, false if not +*/ +bool fuse_service_can_allow_other (struct fuse_service *sf); + +/** +* Release all resources associated with the service context. +* +* @param sfp service context +*/ +void fuse_service_release (struct fuse_service *sf); + +/** +* Destroy a service context and release all resources +* +* @param sfp pointer to pointer to a service context +*/ +void fuse_service_destroy (struct fuse_service **sfp); + +/** +* Append the command line arguments from the mount service helper to an +* existing fuse_args structure. The fuse_args should have been initialized +* with the argc and argv passed to main(). +* +* @param sfp service context +* @param args arguments to modify (input+output) +* @return 0 on success, or negative errno on failure +*/ +int fuse_service_append_args (struct fuse_service *sf,struct fuse_args *args); + +/** +* Generate the effective fuse server command line from the args structure. +* The args structure should be the outcome from fuse_service_append_args. +* The resulting string is suitable for setproctitle and must be freed by the +* callre. +* +* @param argc argument count passed to main() +* @param argv argument vector passed to main() +* @param args fuse args structure +* @return effective command line string, or NULL +*/ +char *fuse_service_cmdline (int argc,char *argv[],struct fuse_args *args); + +struct fuse_cmdline_opts ; + +/** +* Utility function to parse common options for simple file systems +* using the low-level API. A help text that describes the available +* options can be printed with `fuse_cmdline_help`. A single +* non-option argument is treated as the mountpoint. Multiple +* non-option arguments will result in an error. +* +* If neither -o subtype= or -o fsname= options are given, a new +* subtype option will be added and set to the basename of the program +* (the fsname will remain unset, and then defaults to "fuse"). +* +* Known options will be removed from *args*, unknown options will +* remain. The mountpoint will not be checked here; that is the job of +* mount.service. +* +* @param args argument vector (input+output) +* @param opts output argument for parsed options +* @return 0 on success, -1 on failure +*/ +int fuse_service_parse_cmdline_opts (struct fuse_args *args, +struct fuse_cmdline_opts *opts); + +/** +* Don't complain if this file cannot be opened. +*/ +#define FUSE_SERVICE_REQUEST_FILE_QUIET (1U << 0) + +/** +* Ask the mount.service helper to open a file on behalf of the fuse server. +* +* @param sf service context +* @param path the path to file +* @param open_flags O_ flags +* @param create_mode mode with which to create the file +* @param request_flags set of FUSE_SERVICE_REQUEST_* flags +* @return 0 on success, or negative errno on failure +*/ +int fuse_service_request_file (struct fuse_service *sf,const char *path, +int open_flags,mode_t create_mode, +unsigned int request_flags); + +/** +* Ask the mount.service helper to open a block device on behalf of the fuse +* server. +* +* @param sf service context +* @param path the path to file +* @param open_flags O_ flags +* @param create_mode mode with which to create the file +* @param request_flags set of FUSE_SERVICE_REQUEST_* flags +* @param block_size set the block device block size to this value +* @return 0 on success, or negative errno on failure +*/ +int fuse_service_request_blockdev (struct fuse_service *sf,const char *path, +int open_flags,mode_t create_mode, +unsigned int request_flags, +unsigned int block_size); + +/** +* Receive a file previously requested. +* +* @param sf service context +* @param path to file +* @fdp pointer to file descriptor, which will be set a non-negative file +* descriptor value on success, or negative errno on failure +* @return 0 on success, or negative errno on socket communication failure +*/ +int fuse_service_receive_file (struct fuse_service *sf, +const char *path,int *fdp); + +/** +* Prevent the mount.service server from sending us any more open files. +* +* @param sf service context +* @return 0 on success, or negative errno on failure +*/ +int fuse_service_finish_file_requests (struct fuse_service *sf); + +/** +* Require that the filesystem mount point have the expected file format +* (S_IFDIR/S_IFREG). Can be overridden when calling +* fuse_service_session_mount. +* +* @param sf service context +* @param expected_fmt expected mode (S_IFDIR/S_IFREG) for mount point, or 0 +* to skip checks +*/ +void fuse_service_expect_mount_format (struct fuse_service *sf, +mode_t expected_fmt); + +/** +* Bind a FUSE file system to the fuse session inside a fuse service process, +* then ask the mount.service helper to mount the filesystem for us. The fuse +* client will begin sending requests to the fuse server immediately after +* this. Do not call fuse_daemonize() when running as a fuse service. +* +* @param sf service context +* @param se fuse session +* @param expected_fmt expected mode (S_IFDIR/S_IFREG) for mount point, or 0 +* to skip checks +* @param opts command line options +* @return 0 on success, or negative errno on failure +*/ +int fuse_service_session_mount (struct fuse_service *sf,struct fuse_session *se, +mode_t expected_fmt, +struct fuse_cmdline_opts *opts); + +/** +* Ask the mount helper to unmount th e filesystem. +* +* @param sf service context +* @return 0 on success, or negative errno on failure +*/ +int fuse_service_session_unmount (struct fuse_service *sf); + +/** +* Bid farewell to the mount.service helper. It is still necessary to call +* fuse_service_destroy after this. +* +* @param sf service context +* @param exitcode fuse server process exit status +* @return 0 on success, or negative errno on failure +*/ +int fuse_service_send_goodbye (struct fuse_service *sf,int exitcode); + +/** +* Exit routine for a fuse server running as a systemd service. +* +* @param ret 0 for success, nonzero for service failure. +* @return a value to be passed to exit() or returned from main +*/ +int fuse_service_exit (int ret); + +#endif /* FUSE_USE_VERSION >= FUSE_MAKE_VERSION(3, 19) */ + +#ifdef __cplusplus +} +#endif + +#endif /* FUSE_SERVICE_H_ */
include/fuse_service_priv.h new file +160 -0
160 lines not on this page · raw message
lib/mount_common_i.h +3 -0
@@ -14,5 +14,8 @@ struct mount_opts;char *fuse_mnt_build_source (const struct mount_opts *mo);char *fuse_mnt_build_type (const struct mount_opts *mo); +char *fuse_mnt_kernel_opts (const struct mount_opts *mo); +unsigned int fuse_mnt_flags (const struct mount_opts *mo); +#endif /* FUSE_MOUNT_COMMON_I_H_ */
util/mount_service.h new file +40 -0
@@ -0,0 +1,40 @@ +/* +* FUSE: Filesystem in Userspace +* Copyright (C) 2025-2026 Oracle. +* Author: Darrick J. Wong <[email protected]> +* +* This program can be distributed under the terms of the GNU GPLv2. +* See the file GPL2.txt. +*/ +#ifndef MOUNT_SERVICE_H_ +#define MOUNT_SERVICE_H_ + +/** +* Magic value that means that we couldn't connect to the mount service, +* so the caller should try to fall back to traditional means. +*/ +#define MOUNT_SERVICE_FALLBACK_NEEDED (2) + +/** +* Connect to a fuse service socket and try to mount the filesystem as +* specified with the CLI arguments. +* +* @argc argument count +* @argv vector of argument strings +* @return EXIT_SUCCESS for success, EXIT_FAILURE if mount fails, or +* MOUNT_SERVICE_FALLBACK_NEEDED if no service is available. +*/ +int mount_service_main (int argc,char *argv[]); + +/** +* Return the fuse filesystem subtype from a full fuse filesystem type +* specification. IOWs, fuse.Y -> Y; fuseblk.Z -> Z; or A -> A. The returned +* pointer is within the caller's string. The subtype must not contain a path +* separator. +* +* @param fstype full fuse filesystem type +* @return fuse subtype +*/ +const char *mount_service_subtype (const char *fstype); + +#endif /* MOUNT_SERVICE_H_ */
.github/workflows/install-ubuntu-dependencies.sh +4 -0
@@ -15,6 +15,8 @@ PACKAGES_CORE=( pkg-config python3 python3-pip + libsystemd-dev + systemd-dev ) PACKAGES_FULL=(
@@ -31,6 +33,8 @@ PACKAGES_FULL=( libudev-dev:i386 pkg-config:i386 python3-pytest + libsystemd-dev + systemd-dev ) PACKAGES_CODECHECKER=(
doc/fuservicemount3.8 new file +24 -0
@@ -0,0 +1,24 @@ +.TH fuservicemount3 "8" +.SH NAME +fuservicemount3 \- mount a FUSE filesystem that runs as a system socket service +.SH SYNOPSIS +.B fuservicemount3 +.B source +.B mountpoint +.BI -t " fstype" +[ +.I options +] +.SH DESCRIPTION +Mount a filesystem using a FUSE server that runs as a socket service. +These servers can be contained using the platform's service management +framework. +.SH "AUTHORS" +.LP +The author of the fuse socket service code is Darrick J. Wong <[email protected]>. +Debian GNU/Linux distribution. +.SH SEE ALSO +.BR fusermount3 (1) +.BR fusermount (1) +.BR mount (8) +.BR fuse (4)
doc/meson.build +3 -0
@@ -2,3 +2,6 @@ if not platform.endswith('bsd') and platform != 'dragonfly' install_man('fusermount3.1', 'mount.fuse3.8') endif +if private_cfg.get('HAVE_SERVICEMOUNT', false) + install_man('fuservicemount3.8') +endif
include/meson.build +4 -0
@@ -1,4 +1,8 @@ libfuse_headers = [ 'fuse.h', 'fuse_common.h', 'fuse_lowlevel.h', 'fuse_opt.h', 'cuse_lowlevel.h', 'fuse_log.h' ] +if private_cfg.get('HAVE_SERVICEMOUNT', false) + libfuse_headers += [ 'fuse_service.h' ] +endif + install_headers(libfuse_headers, subdir: 'fuse3')
lib/fuse_service.c new file +1205 -0
1,205 lines not on this page · raw message
lib/fuse_service_stub.c new file +106 -0
106 lines not on this page · raw message
lib/fuse_versionscript +17 -0
@@ -227,6 +227,23 @@ FUSE_3.19 { fuse_session_start_teardown_watchdog; fuse_session_stop_teardown_watchdog; fuse_lowlevel_notify_prune; + + fuse_service_accept; + fuse_service_append_args; + fuse_service_can_allow_other; + fuse_service_cmdline; + fuse_service_destroy; + fuse_service_exit; + fuse_service_expect_mount_format; + fuse_service_finish_file_requests; + fuse_service_parse_cmdline_opts; + fuse_service_receive_file; + fuse_service_release; + fuse_service_request_file; + fuse_service_request_blockdev; + fuse_service_send_goodbye; + fuse_service_session_mount; + fuse_service_session_unmount; } FUSE_3.18; # Local Variables:
lib/helper.c +51 -0
63 lines not on this page · raw message
lib/meson.build +15 -2
@@ -10,6 +10,12 @@ else libfuse_sources += [ 'mount_bsd.c' ] endif +if private_cfg.get('HAVE_SERVICEMOUNT', false) + libfuse_sources += [ 'fuse_service.c' ] +else + libfuse_sources += [ 'fuse_service_stub.c' ] +endif + deps = [ thread_dep ] if private_cfg.get('HAVE_ICONV') libfuse_sources += [ 'modules/iconv.c' ]
@@ -49,18 +55,25 @@ libfuse = library('fuse3', dependencies: deps, install: true, link_depends: 'fuse_versionscript', - c_args: [ '-DFUSE_USE_VERSION=317', + c_args: [ '-DFUSE_USE_VERSION=319', '-DFUSERMOUNT_DIR="@0@"'.format(fusermount_path) ], link_args: ['-Wl,--version-script,' + meson.current_source_dir() + '/fuse_versionscript' ]) +vars = [] +if private_cfg.get('HAVE_SERVICEMOUNT', false) + service_socket_dir = private_cfg.get_unquoted('FUSE_SERVICE_SOCKET_DIR', '') + vars += ['service_socket_dir=' + service_socket_dir] + vars += ['service_socket_perms=' + service_socket_perms] +endif pkg = import('pkgconfig') pkg.generate(libraries: [ libfuse, '-lpthread' ], libraries_private: '-ldl', version: meson.project_version(), name: 'fuse3', description: 'Filesystem in Userspace', - subdirs: 'fuse3') + subdirs: 'fuse3', + variables: vars) libfuse_dep = declare_dependency(include_directories: include_dirs, link_with: libfuse, dependencies: deps)
lib/mount.c +12 -0
@@ -750,3 +750,15 @@ char *fuse_mnt_build_type(const struct mount_opts *mo)return type; } + +char *fuse_mnt_kernel_opts (const struct mount_opts *mo) +{ +if (mo->kernel_opts) +return strdup (mo->kernel_opts); +return NULL ; +} + +unsigned int fuse_mnt_flags (const struct mount_opts *mo) +{ +return mo->flags; +}
meson.build +34 -0
@@ -69,6 +69,16 @@ args_default = [ '-D_GNU_SOURCE' ] # private_cfg = configuration_data() private_cfg.set_quoted('PACKAGE_VERSION', meson.project_version()) +service_socket_dir = get_option('service-socket-dir') +service_socket_perms = get_option('service-socket-perms') +if service_socket_dir == '' + service_socket_dir = '/run/filesystems' +endif +if service_socket_perms == '' + service_socket_perms = '0220' +endif +private_cfg.set_quoted('FUSE_SERVICE_SOCKET_DIR', service_socket_dir) +private_cfg.set('FUSE_SERVICE_SOCKET_PERMS', service_socket_perms) # Test for presence of some functions test_funcs = [ 'fork', 'fstatat', 'openat', 'readlinkat', 'pipe2',
@@ -118,6 +128,13 @@ special_funcs = { return -1; } } + ''', + 'systemd_headers': ''' + #include <systemd/sd-daemon.h> + + int main(int argc, char *argv[]) { + return SD_LISTEN_FDS_START; + } ''' }
@@ -180,6 +197,23 @@ if get_option('enable-io-uring') and liburing.found() and libnuma.found() endif endif +# Check for systemd support +systemd_system_unit_dir = get_option('systemd-system-unit-dir') +if systemd_system_unit_dir == '' + systemd = dependency('systemd', required: false) + if systemd.found() + systemd_system_unit_dir = systemd.get_variable(pkgconfig: 'systemd_system_unit_dir') + endif +endif + +if systemd_system_unit_dir == '' or private_cfg.get('HAVE_SYSTEMD_HEADERS', false) == false + warning('systemd service support will not be built') +else + private_cfg.set_quoted('SYSTEMD_SYSTEM_UNIT_DIR', systemd_system_unit_dir) + private_cfg.set('HAVE_SYSTEMD', true) + private_cfg.set('HAVE_SERVICEMOUNT', true) +endif + # # Compiler configuration #
meson_options.txt +9 -0
@@ -27,3 +27,12 @@ option('enable-usdt', type : 'boolean', value : false, option('enable-io-uring', type: 'boolean', value: true, description: 'Enable fuse-over-io-uring support') + +option('service-socket-dir', type : 'string', value : '', + description: 'Where to install fuse server sockets (if empty, /run/filesystems)') + +option('service-socket-perms', type : 'string', value : '', + description: 'Default fuse server socket permissions (if empty, 0220)') + +option('systemd-system-unit-dir', type : 'string', value : '', + description: 'Where to install systemd unit files (if empty, query pkg-config(1))')
util/fuservicemount.c new file +18 -0
@@ -0,0 +1,18 @@ +/* +* FUSE: Filesystem in Userspace +* Copyright (C) 2025-2026 Oracle. +* Author: Darrick J. Wong <[email protected]> +* +* This program can be distributed under the terms of the GNU GPLv2. +* See the file GPL2.txt. +* +* This program wraps the mounting of FUSE filesystems that run in systemd +*/ +#define _GNU_SOURCE +#include "fuse_config.h" +#include "mount_service.h" + +int main (int argc,char *argv[]) +{ +return mount_service_main (argc, argv); +}
util/meson.build +9 -0
@@ -6,6 +6,15 @@ executable('fusermount3', ['fusermount.c', '../lib/mount_util.c', '../lib/util.c install_dir: get_option('bindir'), c_args: '-DFUSE_CONF="@0@"'.format(fuseconf_path)) +if private_cfg.get('HAVE_SERVICEMOUNT', false) + executable('fuservicemount3', ['mount_service.c', 'fuservicemount.c', '../lib/mount_util.c'], + include_directories: include_dirs, + link_with: [ libfuse ], + install: true, + install_dir: get_option('sbindir'), + c_args: '-DFUSE_USE_VERSION=319') +endif + executable('mount.fuse3', ['mount.fuse.c'], include_directories: include_dirs, link_with: [ libfuse ],
util/mount_service.c new file +1427 -0
947 lines not on this page · raw message
The rest of this diff is not shown. View the raw message.
Part of the diff is not shown here. View the raw message.
Re: [PATCH 02/13] mount_service: add systemd socket service mounting helper
Quoted 682 lines · Darrick J. Wong
From: Darrick J. Wong <[email protected]>
Create a mount helper program that can start a fuse server that runs as a socket-based systemd service, and a new libfuse module to wrap all the details of communicating between the mount helper and the containerized fuse server.
This enables untrusted ext4 mounts via systemd service containers, which avoids the problem of malicious filesystems compromising the integrity of the running kernel through memory corruption.
In theory this could also be supported via inetd and clones, though the author hasn't found one that supports
AF_UNIXsockets.Signed-off-by "Darrick J. Wong" <[email protected]>--- include/fuse_service.h | 243 ++++ include/fuse_service_priv.h | 160 ++ lib/mount_common_i.h | 3 util/mount_service.h | 40 + .github/workflows/install-ubuntu-dependencies.sh | 4 doc/fuservicemount3.8 | 24 doc/meson.build | 3 include/meson.build | 4 lib/fuse_service.c | 1205 +++++++++++++++++++ lib/fuse_service_stub.c | 106 ++ lib/fuse_versionscript | 17 lib/helper.c | 51 + lib/meson.build | 17 lib/mount.c | 12 meson.build | 34 + meson_options.txt | 9 util/fuservicemount.c | 18 util/meson.build | 9 util/mount_service.c | 1427 ++++++++++++++++++++++ 19 files changed, 3384 insertions(+), 2 deletions(-) create mode 100644 include/fuse_service.h create mode 100644 include/fuse_service_priv.h create mode 100644 util/mount_service.h create mode 100644 doc/fuservicemount3.8 create mode 100644 lib/fuse_service.c create mode 100644 lib/fuse_service_stub.c create mode 100644 util/fuservicemount.c create mode 100644 util/mount_service.cinclude/fuse_service.h new file +243
-0@@ -0,0 +1,243 @@ +/* +* FUSE: Filesystem in Userspace +* Copyright (C) 2025-2026 Oracle. +* Author: Darrick J. Wong <[email protected]> +* +* This program can be distributed under the terms of the GNU LGPLv2. +* See the file LGPL2.txt. +*/ +#ifndef FUSE_SERVICE_H_ +#define FUSE_SERVICE_H_ + +/** @file +* +* Low level API +* +* IMPORTANT: you should define FUSE_USE_VERSION before including this +* header. To use the newest API define it to 319 (recommended for any +* new application). +*/ + +#ifndef FUSE_USE_VERSION +#error FUSE_USE_VERSION not defined +#endif + +#include "fuse_common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if FUSE_MAKE_VERSION (3 ,19 ) <=FUSE_USE_VERSION + +struct fuse_service ; + +/** +* Accept a socket created by mount.service for information exchange. +* +* @param sfp pointer to pointer to a service context. The pointer will always +* be initialized by this function; use fuse_service_accepted to +* find out if the fuse server is actually running as a service. +* @return 0 on success, or negative errno on failure +*/ +int fuse_service_accept (struct fuse_service **sfp); + +/** +* Has the fuse server accepted a service context? +* +* @param sf service context +* @return true if it has, false if not +*/ +static inline bool fuse_service_accepted (struct fuse_service *sf) +{ +return sf !=NULL ; +} + +/** +* Will the mount service helper accept the allow_other option? +* +* @param sf service context +* @return true if it has, false if not +*/ +bool fuse_service_can_allow_other (struct fuse_service *sf); + +/** +* Release all resources associated with the service context. +* +* @param sfp service context +*/ +void fuse_service_release (struct fuse_service *sf); + +/** +* Destroy a service context and release all resources +* +* @param sfp pointer to pointer to a service context +*/ +void fuse_service_destroy (struct fuse_service **sfp); + +/** +* Append the command line arguments from the mount service helper to an +* existing fuse_args structure. The fuse_args should have been initialized +* with the argc and argv passed to main(). +* +* @param sfp service context +* @param args arguments to modify (input+output) +* @return 0 on success, or negative errno on failure +*/ +int fuse_service_append_args (struct fuse_service *sf,struct fuse_args *args); + +/** +* Generate the effective fuse server command line from the args structure. +* The args structure should be the outcome from fuse_service_append_args. +* The resulting string is suitable for setproctitle and must be freed by the +* callre. +* +* @param argc argument count passed to main() +* @param argv argument vector passed to main() +* @param args fuse args structure +* @return effective command line string, or NULL +*/ +char *fuse_service_cmdline (int argc,char *argv[],struct fuse_args *args); + +struct fuse_cmdline_opts ; + +/** +* Utility function to parse common options for simple file systems +* using the low-level API. A help text that describes the available +* options can be printed with `fuse_cmdline_help`. A single +* non-option argument is treated as the mountpoint. Multiple +* non-option arguments will result in an error. +* +* If neither -o subtype= or -o fsname= options are given, a new +* subtype option will be added and set to the basename of the program +* (the fsname will remain unset, and then defaults to "fuse"). +* +* Known options will be removed from *args*, unknown options will +* remain. The mountpoint will not be checked here; that is the job of +* mount.service. +* +* @param args argument vector (input+output) +* @param opts output argument for parsed options +* @return 0 on success, -1 on failure +*/ +int fuse_service_parse_cmdline_opts (struct fuse_args *args, +struct fuse_cmdline_opts *opts); + +/** +* Don't complain if this file cannot be opened. +*/ +#define FUSE_SERVICE_REQUEST_FILE_QUIET (1U << 0) + +/** +* Ask the mount.service helper to open a file on behalf of the fuse server. +* +* @param sf service context +* @param path the path to file +* @param open_flags O_ flags +* @param create_mode mode with which to create the file +* @param request_flags set of FUSE_SERVICE_REQUEST_* flags +* @return 0 on success, or negative errno on failure +*/ +int fuse_service_request_file (struct fuse_service *sf,const char *path, +int open_flags,mode_t create_mode, +unsigned int request_flags); + +/** +* Ask the mount.service helper to open a block device on behalf of the fuse +* server. +* +* @param sf service context +* @param path the path to file +* @param open_flags O_ flags +* @param create_mode mode with which to create the file +* @param request_flags set of FUSE_SERVICE_REQUEST_* flags +* @param block_size set the block device block size to this value +* @return 0 on success, or negative errno on failure +*/ +int fuse_service_request_blockdev (struct fuse_service *sf,const char *path, +int open_flags,mode_t create_mode, +unsigned int request_flags, +unsigned int block_size); + +/** +* Receive a file previously requested. +* +* @param sf service context +* @param path to file +* @fdp pointer to file descriptor, which will be set a non-negative file +* descriptor value on success, or negative errno on failure +* @return 0 on success, or negative errno on socket communication failure +*/ +int fuse_service_receive_file (struct fuse_service *sf, +const char *path,int *fdp); + +/** +* Prevent the mount.service server from sending us any more open files. +* +* @param sf service context +* @return 0 on success, or negative errno on failure +*/ +int fuse_service_finish_file_requests (struct fuse_service *sf); + +/** +* Require that the filesystem mount point have the expected file format +* (S_IFDIR/S_IFREG). Can be overridden when calling +* fuse_service_session_mount. +* +* @param sf service context +* @param expected_fmt expected mode (S_IFDIR/S_IFREG) for mount point, or 0 +* to skip checks +*/ +void fuse_service_expect_mount_format (struct fuse_service *sf, +mode_t expected_fmt); + +/** +* Bind a FUSE file system to the fuse session inside a fuse service process, +* then ask the mount.service helper to mount the filesystem for us. The fuse +* client will begin sending requests to the fuse server immediately after +* this. Do not call fuse_daemonize() when running as a fuse service. +* +* @param sf service context +* @param se fuse session +* @param expected_fmt expected mode (S_IFDIR/S_IFREG) for mount point, or 0 +* to skip checks +* @param opts command line options +* @return 0 on success, or negative errno on failure +*/ +int fuse_service_session_mount (struct fuse_service *sf,struct fuse_session *se, +mode_t expected_fmt, +struct fuse_cmdline_opts *opts); + +/** +* Ask the mount helper to unmount th e filesystem. +* +* @param sf service context +* @return 0 on success, or negative errno on failure +*/ +int fuse_service_session_unmount (struct fuse_service *sf); + +/** +* Bid farewell to the mount.service helper. It is still necessary to call +* fuse_service_destroy after this. +* +* @param sf service context +* @param exitcode fuse server process exit status +* @return 0 on success, or negative errno on failure +*/ +int fuse_service_send_goodbye (struct fuse_service *sf,int exitcode); + +/** +* Exit routine for a fuse server running as a systemd service. +* +* @param ret 0 for success, nonzero for service failure. +* @return a value to be passed to exit() or returned from main +*/ +int fuse_service_exit (int ret); + +#endif /* FUSE_USE_VERSION >= FUSE_MAKE_VERSION(3, 19) */ + +#ifdef __cplusplus +} +#endif + +#endif /* FUSE_SERVICE_H_ */ include/fuse_service_priv.h new file +160
-0@@ -0,0 +1,160 @@ +/* +* FUSE: Filesystem in Userspace +* Copyright (C) 2025-2026 Oracle. +* Author: Darrick J. Wong <[email protected]> +* +* This program can be distributed under the terms of the GNU LGPLv2. +* See the file LGPL2.txt. +*/ +#ifndef FUSE_SERVICE_PRIV_H_ +#define FUSE_SERVICE_PRIV_H_ + +/* All numeric fields are network order (big-endian) when going across the socket */ + +struct fuse_service_memfd_arg { +uint32_t pos; +uint32_t len; +}; + +struct fuse_service_memfd_argv { +uint32_t magic; +uint32_t argc; +}; + +#define FUSE_SERVICE_MAX_CMD_SIZE (65536) + +#define FUSE_SERVICE_ARGS_MAGIC 0x41524753/* ARGS */ + +/* mount.service sends a hello to the server and it replies */ +#define FUSE_SERVICE_HELLO_CMD 0x53414654/* SAFT */ +#define FUSE_SERVICE_HELLO_REPLY 0x4c415354/* LAST */ + +/* fuse servers send commands to mount.service */ +#define FUSE_SERVICE_OPEN_CMD 0x4f50454e/* OPEN */ +#define FUSE_SERVICE_OPEN_BDEV_CMD 0x42444556/* BDEV */ +#define FUSE_SERVICE_FSOPEN_CMD 0x54595045/* TYPE */ +#define FUSE_SERVICE_SOURCE_CMD 0x4e414d45/* NAME */ +#define FUSE_SERVICE_MNTOPTS_CMD 0x4f505453/* OPTS */ +#define FUSE_SERVICE_MNTPT_CMD 0x4d4e5450/* MNTP */ +#define FUSE_SERVICE_MOUNT_CMD 0x444f4954/* DOIT */ +#define FUSE_SERVICE_UNMOUNT_CMD 0x554d4e54/* UMNT */ +#define FUSE_SERVICE_BYE_CMD 0x42594545/* BYEE */ + +/* mount.service sends replies to the fuse server */ +#define FUSE_SERVICE_OPEN_REPLY 0x46494c45/* FILE */ +#define FUSE_SERVICE_SIMPLE_REPLY 0x5245504c/* REPL */ + +struct fuse_service_packet { +uint32_t magic;/* FUSE_SERVICE_*_{CMD,REPLY} */ +}; + +#define FUSE_SERVICE_PROTO (1) +#define FUSE_SERVICE_MIN_PROTO (1) +#define FUSE_SERVICE_MAX_PROTO (1) + +#define FUSE_SERVICE_FLAG_ALLOW_OTHER (1U << 0) + +#define FUSE_SERVICE_FLAGS (FUSE_SERVICE_FLAG_ALLOW_OTHER) + +struct fuse_service_hello { +struct fuse_service_packet p; +uint16_t min_version; +uint16_t max_version; +uint32_t flags; +}; + +static inline bool check_null_endbyte (const void *p,size_t psz) +{ +return *((const char *)p + psz -1 ) ==0 ; +} + +struct fuse_service_hello_reply { +struct fuse_service_packet p; +uint16_t version; +uint16_t padding; +}; + +struct fuse_service_simple_reply { +struct fuse_service_packet p; +uint32_t error;/* positive errno */ +}; + +struct fuse_service_requested_file { +struct fuse_service_packet p; +uint32_t error;/* positive errno */ +char path[]; +}; + +static inline size_t sizeof_fuse_service_requested_file (size_t pathlen) +{ +return sizeof (struct fuse_service_requested_file ) + pathlen +1 ; +} + +#define FUSE_SERVICE_FSOPEN_FUSEBLK (1U << 0) +#define FUSE_SERVICE_FSOPEN_FLAGS (FUSE_SERVICE_FSOPEN_FUSEBLK) + +struct fuse_service_fsopen_command { +struct fuse_service_packet p; +uint32_t fsopen_flags; +}; + +#define FUSE_SERVICE_OPEN_QUIET (1U << 0) +#define FUSE_SERVICE_OPEN_FLAGS (FUSE_SERVICE_OPEN_QUIET) + +struct fuse_service_open_command { +struct fuse_service_packet p; +uint32_t open_flags; +uint32_t create_mode; +uint32_t request_flags; +uint32_t block_size; +char path[]; +}; + +static inline size_t sizeof_fuse_service_open_command (size_t pathlen) +{ +return sizeof (struct fuse_service_open_command ) + pathlen +1 ; +} + +struct fuse_service_string_command { +struct fuse_service_packet p; +char value[]; +}; + +static inline size_t sizeof_fuse_service_string_command (size_t len) +{ +return sizeof (struct fuse_service_string_command ) + len +1 ; +} + +struct fuse_service_mountpoint_command { +struct fuse_service_packet p; +uint16_t expected_fmt; +uint16_t padding; +char value[]; +}; + +static inline size_t sizeof_fuse_service_mountpoint_command (size_t len) +{ +return sizeof (struct fuse_service_mountpoint_command ) + len +1 ; +} + +struct fuse_service_bye_command { +struct fuse_service_packet p; +uint32_t exitcode; +}; + +struct fuse_service_mount_command { +struct fuse_service_packet p; +uint32_t ms_flags; +}; + +struct fuse_service_unmount_command { +struct fuse_service_packet p; +}; + +int fuse_parse_cmdline_service (struct fuse_args *args, +struct fuse_cmdline_opts *opts); + +#define FUSE_SERVICE_ARGV "argv" +#define FUSE_SERVICE_FUSEDEV "fusedev" + +#endif /* FUSE_SERVICE_PRIV_H_ */ lib/mount_common_i.h +3
-0@@ -14,5 +14,8 @@ struct mount_opts;char *fuse_mnt_build_source (const struct mount_opts *mo);char *fuse_mnt_build_type (const struct mount_opts *mo); +char *fuse_mnt_kernel_opts (const struct mount_opts *mo); +unsigned int fuse_mnt_flags (const struct mount_opts *mo); +#endif /* FUSE_MOUNT_COMMON_I_H_ */ util/mount_service.h new file +40
-0@@ -0,0 +1,40 @@ +/* +* FUSE: Filesystem in Userspace +* Copyright (C) 2025-2026 Oracle. +* Author: Darrick J. Wong <[email protected]> +* +* This program can be distributed under the terms of the GNU GPLv2. +* See the file GPL2.txt. +*/ +#ifndef MOUNT_SERVICE_H_ +#define MOUNT_SERVICE_H_ + +/** +* Magic value that means that we couldn't connect to the mount service, +* so the caller should try to fall back to traditional means. +*/ +#define MOUNT_SERVICE_FALLBACK_NEEDED (2) + +/** +* Connect to a fuse service socket and try to mount the filesystem as +* specified with the CLI arguments. +* +* @argc argument count +* @argv vector of argument strings +* @return EXIT_SUCCESS for success, EXIT_FAILURE if mount fails, or +* MOUNT_SERVICE_FALLBACK_NEEDED if no service is available. +*/ +int mount_service_main (int argc,char *argv[]); + +/** +* Return the fuse filesystem subtype from a full fuse filesystem type +* specification. IOWs, fuse.Y -> Y; fuseblk.Z -> Z; or A -> A. The returned +* pointer is within the caller's string. The subtype must not contain a path +* separator. +* +* @param fstype full fuse filesystem type +* @return fuse subtype +*/ +const char *mount_service_subtype (const char *fstype); + +#endif /* MOUNT_SERVICE_H_ */ .github/workflows/install-ubuntu-dependencies.sh +4
-0@@ -15,6 +15,8 @@ PACKAGES_CORE=( pkg-config python3 python3-pip + libsystemd-dev + systemd-dev ) PACKAGES_FULL=(@@ -31,6 +33,8 @@ PACKAGES_FULL=( libudev-dev:i386 pkg-config:i386 python3-pytest + libsystemd-dev + systemd-dev ) PACKAGES_CODECHECKER=(doc/fuservicemount3.8 new file +24
-0@@ -0,0 +1,24 @@ +.TH fuservicemount3 "8" +.SH NAME +fuservicemount3 \- mount a FUSE filesystem that runs as a system socket service +.SH SYNOPSIS +.B fuservicemount3 +.B source +.B mountpoint +.BI -t " fstype" +[ +.I options +] +.SH DESCRIPTION +Mount a filesystem using a FUSE server that runs as a socket service. +These servers can be contained using the platform's service management +framework. +.SH "AUTHORS" +.LP +The author of the fuse socket service code is Darrick J. Wong <[email protected]>. +Debian GNU/Linux distribution. +.SH SEE ALSO +.BR fusermount3 (1) +.BR fusermount (1) +.BR mount (8) +.BR fuse (4)doc/meson.build +3
-0@@ -2,3 +2,6 @@ if not platform.endswith('bsd') and platform != 'dragonfly' install_man('fusermount3.1', 'mount.fuse3.8') endif +if private_cfg.get('HAVE_SERVICEMOUNT', false) + install_man('fuservicemount3.8') +endifinclude/meson.build +4
-0@@ -1,4 +1,8 @@ libfuse_headers = [ 'fuse.h', 'fuse_common.h', 'fuse_lowlevel.h', 'fuse_opt.h', 'cuse_lowlevel.h', 'fuse_log.h' ] +if private_cfg.get('HAVE_SERVICEMOUNT', false) + libfuse_headers += [ 'fuse_service.h' ] +endif + install_headers(libfuse_headers, subdir: 'fuse3')lib/fuse_service.c new file +80
-0@@ -0,0 +1,1205 @@ +/* +* FUSE: Filesystem in Userspace +* Copyright (C) 2025-2026 Oracle. +* Author: Darrick J. Wong <[email protected]> +* +* Library functions to support fuse servers that can be run as "safe" systemd +* containers. +* +* This program can be distributed under the terms of the GNU LGPLv2. +* See the file LGPL2.txt +*/ + +#define _GNU_SOURCE +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <errno.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <systemd/sd-daemon.h> +#include <arpa/inet.h> +#include <limits.h> + +#include "fuse_config.h" +#include "fuse_i.h" +#include "fuse_service_priv.h" +#include "fuse_service.h" +#include "mount_common_i.h" + +struct fuse_service { +/* expected file format of the mount point */ +mode_t expected_fmt; + +/* socket fd */ +int sockfd; + +/* /dev/fuse device */ +int fusedevfd; + +/* memfd for cli arguments */ +int argvfd; + +/* do we own fusedevfd? */ +bool owns_fusedevfd; + +/* can we use allow_other? */ +bool allow_other; +}; + +static int __recv_fd (struct fuse_service *sf, +struct fuse_service_requested_file *buf, +ssize_t bufsize,int *fdp) +{ +struct iovec iov = { + .iov_base = buf, + .iov_len = bufsize, + }; +union { +struct cmsghdr cmsghdr; +char control[CMSG_SPACE (sizeof (int ))]; + } cmsgu; +struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen =1 , + .msg_control = cmsgu.control, + .msg_controllen =sizeof (cmsgu.control), + }; +struct cmsghdr *cmsg; +ssize_t size; + +memset (&cmsgu,0 ,sizeof (cmsgu)); + + size =recvmsg (sf->sockfd, &msg,MSG_TRUNC |MSG_CMSG_CLOEXEC ); +if (size <0 ) { +int error = errno; +
lib/fuse_service.c line 90
Quoted 10 lines
lib/fuse_service.c new file +10
-0+fuse_log (FUSE_LOG_ERR ,"fuse: service file reply: %s\n" , +strerror (error)); +return -error; + } +if (size > bufsize || + size < offsetof(struct fuse_service_requested_file , path)) { +fuse_log (FUSE_LOG_ERR ,"fuse: wrong service file reply size %zd, expected %zd\n" , + size, bufsize); +return -EBADMSG ; + }
Apparently SMACK will truncate the control data to "prohibit" the fd transfer, so it's necessary to check for MSG_CTRUNC here or else we end up with a garbage sockfd later on.
if (msg.msg_flags & MSG_CTRUNC) {
/* SMACK does this */
fuse_log(FUSE_LOG_ERR,
"fuse: service file reply control data truncated; did an LSM deny SCM_RIGHTS?\n");
return -EBADMSG;
}https://lore.kernel.org/linux-fsdevel/[email protected]/
The caller probably ought to check that the passed-back fd is a valid file descriptor number.
--D
Quoted 2,944 lines
lib/fuse_service.c new file +1115
-0+ + cmsg =CMSG_FIRSTHDR (&msg); +if (!cmsg) { +/* no control message means mount.service sent us an error */ +return 0 ; + } +if (cmsg->cmsg_len !=CMSG_LEN (sizeof (int ))) { +fuse_log (FUSE_LOG_ERR , +"fuse: wrong service file reply control data size %zd, expected %zd\n" , + cmsg->cmsg_len,CMSG_LEN (sizeof (int ))); +return -EBADMSG ; + } +if (cmsg->cmsg_level !=SOL_SOCKET || cmsg->cmsg_type !=SCM_RIGHTS ) { +fuse_log (FUSE_LOG_ERR , +"fuse: wrong service file reply control data level %d type %d, expected %d and %d\n" , + cmsg->cmsg_level, cmsg->cmsg_type,SOL_SOCKET , +SCM_RIGHTS ); +return -EBADMSG ; + } + +memcpy (fdp, (int *)CMSG_DATA (cmsg),sizeof (int )); +return 0 ; +} + +static ssize_t __send_packet (struct fuse_service *sf,void *ptr,size_t len) +{ +struct iovec iov = { + .iov_base = ptr, + .iov_len = len, + }; +struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen =1 , + }; + +return sendmsg (sf->sockfd, &msg,MSG_EOR |MSG_NOSIGNAL ); +} + +static ssize_t __recv_packet (struct fuse_service *sf,void *ptr,size_t len) +{ +struct iovec iov = { + .iov_base = ptr, + .iov_len = len, + }; +struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen =1 , + }; + +return recvmsg (sf->sockfd, &msg,MSG_TRUNC ); +} + +int fuse_service_receive_file (struct fuse_service *sf,const char *path, +int *fdp) +{ +struct fuse_service_requested_file *req; +const size_t req_sz =sizeof_fuse_service_requested_file (strlen (path)); +int fd = -ENOENT ; +int ret; + + *fdp = -ENOENT ; + + req =calloc (1 , req_sz +1 ); +if (!req) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: alloc service file reply: %s\n" , +strerror (error)); +return -error; + } + + ret =__recv_fd (sf, req, req_sz, &fd); +if (ret) + goto out_req; + +if (ntohl (req->p.magic) !=FUSE_SERVICE_OPEN_REPLY ) { +fuse_log (FUSE_LOG_ERR ,"fuse: service file reply contains wrong magic!\n" ); + ret = -EBADMSG ; + goto out_close; + } +if (strcmp (req->path, path)) { +fuse_log (FUSE_LOG_ERR ,"fuse: `%s': not the requested service file, got `%s'\n" , + path, req->path); + ret = -EBADMSG ; + goto out_close; + } + +if (req->error) { + *fdp = -ntohl (req->error); + goto out_close; + } + +if (fd == -ENOENT ) +fuse_log (FUSE_LOG_ERR ,"fuse: did not receive `%s' but no error?\n" , + path); + + *fdp = fd; + goto out_req; + +out_close: +close (fd); +out_req: +free (req); +return ret; +} + +#define FUSE_SERVICE_REQUEST_FILE_FLAGS (FUSE_SERVICE_REQUEST_FILE_QUIET) + +static int fuse_service_request_path (struct fuse_service *sf,const char *path, +mode_t expected_fmt,int open_flags, +mode_t create_mode, +unsigned int request_flags, +unsigned int block_size) +{ +struct fuse_service_open_command *cmd; +const size_t cmdsz =sizeof_fuse_service_open_command (strlen (path)); +ssize_t size; +unsigned int rqflags =0 ; +int ret; + +if (request_flags & ~FUSE_SERVICE_REQUEST_FILE_FLAGS ) { +fuse_log (FUSE_LOG_ERR ,"fuse: invalid fuse service file request flags 0x%x\n" , + request_flags); +return -EINVAL ; + } + +if (request_flags &FUSE_SERVICE_REQUEST_FILE_QUIET ) + rqflags |=FUSE_SERVICE_OPEN_QUIET ; + + cmd =calloc (1 , cmdsz); +if (!cmd) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: alloc service file request: %s\n" , +strerror (error)); +return -error; + } +if (S_ISBLK (expected_fmt)) { + cmd->p.magic =htonl (FUSE_SERVICE_OPEN_BDEV_CMD ); + cmd->block_size =htonl (block_size); + }else { + cmd->p.magic =htonl (FUSE_SERVICE_OPEN_CMD ); + } + cmd->open_flags =htonl (open_flags); + cmd->create_mode =htonl (create_mode); + cmd->request_flags =htonl (rqflags); +strcpy (cmd->path, path); + + size =__send_packet (sf, cmd, cmdsz); +if (size <0 ) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: request service file: %s\n" , +strerror (error)); + ret = -error; + goto out_free; + } + + ret =0 ; +out_free: +free (cmd); +return ret; +} + +int fuse_service_request_file (struct fuse_service *sf,const char *path, +int open_flags,mode_t create_mode, +unsigned int request_flags) +{ +return fuse_service_request_path (sf, path,S_IFREG , open_flags, + create_mode, request_flags,0 ); +} + +int fuse_service_request_blockdev (struct fuse_service *sf,const char *path, +int open_flags,mode_t create_mode, +unsigned int request_flags, +unsigned int block_size) +{ +return fuse_service_request_path (sf, path,S_IFBLK , open_flags, + create_mode, request_flags, + block_size); +} + +int fuse_service_send_goodbye (struct fuse_service *sf,int exitcode) +{ +struct fuse_service_bye_command c = { + .p.magic =htonl (FUSE_SERVICE_BYE_CMD ), + .exitcode =htonl (exitcode), + }; +ssize_t size; + +/* already gone? */ +if (sf->sockfd <0 ) +return 0 ; + + size =__send_packet (sf, &c,sizeof (c)); +if (size <0 ) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: send service goodbye: %s\n" , +strerror (error)); +return -error; + } + +shutdown (sf->sockfd,SHUT_RDWR ); +close (sf->sockfd); + sf->sockfd =-1 ; +return 0 ; +} + +static int count_listen_fds (void ) +{ +char *listen_fds; +char *listen_pid; +char *p; +long l; + +/* +* No environment variables means we're not running as a system socket +* service, so we'll back out without logging anything. +*/ + listen_fds =getenv ("LISTEN_FDS" ); + listen_pid =getenv ("LISTEN_PID" ); +if (!listen_fds || !listen_pid) +return 0 ; + +/* +* LISTEN_PID is the pid of the process to which systemd thinks it gave +* the socket fd. Hopefully that's us. +*/ + errno =0 ; + l =strtol (listen_pid, &p,10 ); +if (errno || *p !=0 || l !=getpid ()) +return 0 ; + +/* +* LISTEN_FDS is the number of sockets that were opened in this +* process. +*/ + errno =0 ; + l =strtol (listen_fds, &p,10 ); +if (errno || *p !=0 || l >INT_MAX || l <0 ) +return 0 ; + +return l; +} + +static int check_sendbuf_size (int sockfd) +{ +const size_t min_size =sizeof_fuse_service_open_command (PATH_MAX ); +int sendbuf_size =-1 ; +socklen_t optlen =sizeof (sendbuf_size); +int ret; + +/* +* If we can't query the maximum send buffer length, just keep going. +* Most likely we won't be sending huge open commands, and if we do, +* the sendmsg will fail there too. +*/ + ret =getsockopt (sockfd,SOL_SOCKET ,SO_SNDBUF , &sendbuf_size, &optlen); +if (ret || sendbuf_size <0 ) +return 0 ; + +if (sendbuf_size >= min_size) +return 0 ; + +fuse_log (FUSE_LOG_ERR ,"max socket send buffer is %d, need at least %zu.\n" , + sendbuf_size, min_size); +return -ENOBUFS ; +} + +static int find_socket_fd (int nr_fds) +{ +struct stat stbuf; +struct sockaddr_un urk; +socklen_t urklen =sizeof (urk); +int ret; + +if (nr_fds !=1 ) { +fuse_log (FUSE_LOG_ERR ,"fuse: can only handle 1 service socket, got %d.\n" , + nr_fds); +return -E2BIG ; + } + + ret =fstat (SD_LISTEN_FDS_START , &stbuf); +if (ret) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: service socket: %s\n" , +strerror (error)); +return -error; + } + +if (!S_ISSOCK (stbuf.st_mode)) { +fuse_log (FUSE_LOG_ERR ,"fuse: expected service fd %d to be a socket\n" , +SD_LISTEN_FDS_START ); +return -ENOTSOCK ; + } + + ret =getsockname (SD_LISTEN_FDS_START , &urk, &urklen); +if (ret <0 ) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: service socket family: %s\n" , +strerror (error)); +return -error; + } + +if (ret >0 || urk.sun_family !=AF_UNIX ) { +/* +* If getsockname wanted to return more data than fits in a +* sockaddr_un, then it's obviously not an AF_UNIX socket. +* +* If it filled the buffer exactly but the family isn't AF_UNIX +* then we also return false. +*/ +fuse_log (FUSE_LOG_ERR ,"fuse: service socket is not AF_UNIX\n" ); +return -EAFNOSUPPORT ; + } + + ret =check_sendbuf_size (SD_LISTEN_FDS_START ); +if (ret) +return ret; + +return SD_LISTEN_FDS_START ; +} + +static int negotiate_hello (struct fuse_service *sf) +{ +struct fuse_service_hello hello = { }; +struct fuse_service_hello_reply reply = { + .p.magic =htonl (FUSE_SERVICE_HELLO_REPLY ), + .version =htons (FUSE_SERVICE_PROTO ), + }; +uint32_t flags; +ssize_t size; + + size =__recv_packet (sf, &hello,sizeof (hello)); +if (size <0 ) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: receive service hello: %s\n" , +strerror (error)); +return -error; + } +if (size !=sizeof (hello)) { +fuse_log (FUSE_LOG_ERR ,"fuse: wrong service hello size %zd, expected %zd\n" , + size,sizeof (hello)); +return -EBADMSG ; + } + +if (ntohl (hello.p.magic) !=FUSE_SERVICE_HELLO_CMD ) { +fuse_log (FUSE_LOG_ERR ,"fuse: service server did not send hello command\n" ); +return -EBADMSG ; + } + +if (ntohs (hello.min_version) <FUSE_SERVICE_MIN_PROTO ) { +fuse_log (FUSE_LOG_ERR ,"fuse: unsupported min service protocol version %u\n" , +ntohs (hello.min_version)); +return -EOPNOTSUPP ; + } + +if (ntohs (hello.max_version) >FUSE_SERVICE_MAX_PROTO ) { +fuse_log (FUSE_LOG_ERR ,"fuse: unsupported max service protocol version %u\n" , +ntohs (hello.min_version)); +return -EOPNOTSUPP ; + } + + flags =ntohl (hello.flags); +if (flags & ~FUSE_SERVICE_FLAGS ) { +fprintf (stderr,"fuse: invalid hello flags: 0x%x\n" , + flags & ~FUSE_SERVICE_FLAGS ); +return -EINVAL ; + } + +if (flags &FUSE_SERVICE_FLAG_ALLOW_OTHER ) + sf->allow_other = true; + + size =__send_packet (sf, &reply,sizeof (reply)); +if (size <0 ) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: service hello reply: %s\n" , +strerror (error)); +return -error; + } + +return 0 ; +} + +int fuse_service_accept (struct fuse_service **sfp) +{ +struct fuse_service *sf; +int nr_fds; +int sockfd; +int flags; +int ret =0 ; + + *sfp =NULL ; + + nr_fds =count_listen_fds (); +if (nr_fds ==0 ) +return 0 ; + +/* Find the socket that connects us to mount.service */ + sockfd =find_socket_fd (nr_fds); +if (sockfd <0 ) +return sockfd; + + flags =fcntl (sockfd,F_GETFD ); +if (flags <0 ) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: service socket getfd: %s\n" , +strerror (error)); +return -error; + } + +if (!(flags &FD_CLOEXEC )) { + ret =fcntl (sockfd,F_SETFD , flags |FD_CLOEXEC ); +if (ret) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: service socket set cloexec: %s\n" , +strerror (error)); +return -error; + } + } + + sf =calloc (1 ,sizeof (struct fuse_service )); +if (!sf) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: service alloc: %s\n" , +strerror (error)); +return -error; + } + sf->sockfd = sockfd; + + ret =negotiate_hello (sf); +if (ret) + goto out_sf; + +/* Receive the two critical sockets */ + ret =fuse_service_receive_file (sf,FUSE_SERVICE_ARGV , &sf->argvfd); +if (ret <0 ) + goto out_sockfd; +if (sf->argvfd <0 ) { +fuse_log (FUSE_LOG_ERR ,"fuse: service mount options file: %s\n" , +strerror (-sf->argvfd)); + ret = sf->argvfd; + goto out_sockfd; + } + + ret =fuse_service_receive_file (sf,FUSE_SERVICE_FUSEDEV , + &sf->fusedevfd); +if (ret <0 ) + goto out_argvfd; +if (sf->fusedevfd <0 ) { +fuse_log (FUSE_LOG_ERR ,"fuse: service fuse device: %s\n" , +strerror (-sf->fusedevfd)); + ret = sf->fusedevfd; + goto out_argvfd; + } + + sf->owns_fusedevfd = true; + *sfp = sf; +return 0 ; + +out_argvfd: +close (sf->argvfd); +out_sockfd: +shutdown (sf->sockfd,SHUT_RDWR ); +close (sf->sockfd); +out_sf: +free (sf); +return ret; +} + +bool fuse_service_can_allow_other (struct fuse_service *sf) +{ +return sf->allow_other; +} + +int fuse_service_append_args (struct fuse_service *sf, +struct fuse_args *existing_args) +{ +struct fuse_service_memfd_argv memfd_args = { }; +struct fuse_args new_args = { + .allocated =1 , + }; +char *str =NULL ; +off_t memfd_pos =0 ; +ssize_t received; +unsigned int i; +int ret; + +/* Figure out how many arguments we're getting from the mount helper. */ + received =pread (sf->argvfd, &memfd_args,sizeof (memfd_args),0 ); +if (received <0 ) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: service args file: %s\n" , +strerror (error)); +return -error; + } +if (received <sizeof (memfd_args)) { +fuse_log (FUSE_LOG_ERR ,"fuse: service args file length unreadable\n" ); +return -EBADMSG ; + } +if (ntohl (memfd_args.magic) !=FUSE_SERVICE_ARGS_MAGIC ) { +fuse_log (FUSE_LOG_ERR ,"fuse: service args file corrupt\n" ); +return -EBADMSG ; + } + memfd_args.magic =htonl (memfd_args.magic); + memfd_args.argc =htonl (memfd_args.argc); + memfd_pos +=sizeof (memfd_args); + +/* Allocate a new array of argv string pointers */ + new_args.argv =calloc (memfd_args.argc + existing_args->argc, +sizeof (char *)); +if (!new_args.argv) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: service new args: %s\n" , +strerror (error)); +return -error; + } + +/* +* Copy the fuse server's CLI arguments. We'll leave new_args.argv[0] +* unset for now, because we'll set it in the next step with the fstype +* that the mount helper sent us. +*/ + new_args.argc++; +for (i =1 ; i < existing_args->argc; i++) { +if (existing_args->allocated) { + new_args.argv[new_args.argc] = existing_args->argv[i]; + existing_args->argv[i] =NULL ; + }else { +char *dup =strdup (existing_args->argv[i]); + +if (!dup) { +int error = errno; + +fuse_log (FUSE_LOG_ERR , +"fuse: service duplicate existing args: %s\n" , +strerror (error)); + ret = -error; + goto out_new_args; + } + + new_args.argv[new_args.argc] = dup; + } + + new_args.argc++; + } + +/* Copy the rest of the arguments from the helper */ +for (i =0 ; i < memfd_args.argc; i++) { +struct fuse_service_memfd_arg memfd_arg = { }; + +/* Read argv iovec */ + received =pread (sf->argvfd, &memfd_arg,sizeof (memfd_arg), + memfd_pos); +if (received <0 ) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: service args file iovec read: %s\n" , +strerror (error)); + ret = -error; + goto out_new_args; + } +if (received <sizeof (struct fuse_service_memfd_arg )) { +fuse_log (FUSE_LOG_ERR , +"fuse: service args file argv[%u] iovec short read %zd" , + i, received); + ret = -EBADMSG ; + goto out_new_args; + } + memfd_arg.pos =htonl (memfd_arg.pos); + memfd_arg.len =htonl (memfd_arg.len); + memfd_pos +=sizeof (memfd_arg); + +/* read arg string from file */ + str =calloc (1 , memfd_arg.len +1 ); +if (!str) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: service arg alloc: %s\n" , +strerror (error)); + ret = -error; + goto out_new_args; + } + + received =pread (sf->argvfd, str, memfd_arg.len, memfd_arg.pos); +if (received <0 ) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: service args file read: %s\n" , +strerror (error)); + ret = -error; + goto out_str; + } +if (received < memfd_arg.len) { +fuse_log (FUSE_LOG_ERR ,"fuse: service args file argv[%u] short read %zd" , + i, received); + ret = -EBADMSG ; + goto out_str; + } + +/* move string into the args structure */ +if (i ==0 ) { +/* the first argument is the fs type */ + new_args.argv[0 ] = str; + }else { + new_args.argv[new_args.argc] = str; + new_args.argc++; + } + str =NULL ; + } + +/* drop existing args, move new args to existing args */ +fuse_opt_free_args (existing_args); +memcpy (existing_args, &new_args,sizeof (*existing_args)); + +close (sf->argvfd); + sf->argvfd =-1 ; + +return 0 ; + +out_str: +free (str); +out_new_args: +fuse_opt_free_args (&new_args); +return ret; +} + +#ifdef SO_PASSRIGHTS +int fuse_service_finish_file_requests (struct fuse_service *sf) +{ +int zero =0 ; +int ret; + +/* +* Don't let a malicious mount helper send us more fds. If the kernel +* doesn't know about this new(ish) option that's ok, we'll trust the +* servicemount helper. +*/ + ret =setsockopt (sf->sockfd,SOL_SOCKET ,SO_PASSRIGHTS , &zero, +sizeof (zero)); +if (ret && errno ==ENOPROTOOPT ) + ret =0 ; +if (ret) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: disabling fd passing: %s\n" , +strerror (error)); +return -error; + } + +return 0 ; +} +#else +int fuse_service_finish_file_requests (struct fuse_service *sf) +{ + (void )sf; +return 0 ; +} +#endif + +static int send_fsopen (struct fuse_service *sf,const char *fstype, +int *errorp) +{ +struct fuse_service_simple_reply reply = { }; +struct fuse_service_fsopen_command c = { + .p.magic =htonl (FUSE_SERVICE_FSOPEN_CMD ), + }; +ssize_t size; + +if (!strncmp (fstype,"fuseblk" ,7 )) + c.fsopen_flags |=htonl (FUSE_SERVICE_FSOPEN_FUSEBLK ); + + size =__send_packet (sf, &c,sizeof (c)); +if (size <0 ) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: send service fsopen command: %s\n" , +strerror (error)); +return -error; + } + + size =__recv_packet (sf, &reply,sizeof (reply)); +if (size <0 ) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: service fsopen reply: %s\n" , +strerror (error)); +return -error; + } +if (size !=sizeof (reply)) { +fuse_log (FUSE_LOG_ERR ,"fuse: wrong service fsopen reply size %zd, expected %zd\n" , + size,sizeof (reply)); +return -EBADMSG ; + } + +if (ntohl (reply.p.magic) !=FUSE_SERVICE_SIMPLE_REPLY ) { +fuse_log (FUSE_LOG_ERR ,"fuse: service fsopen reply contains wrong magic!\n" ); +return -EBADMSG ; + } + + *errorp =ntohl (reply.error); +return 0 ; +} + +static int send_string (struct fuse_service *sf,uint32_t command, +const char *value,int *errorp) +{ +struct fuse_service_simple_reply reply = { }; +struct fuse_service_string_command *cmd; +const size_t cmdsz =sizeof_fuse_service_string_command (strlen (value)); +ssize_t size; + + cmd =calloc (1 , cmdsz); +if (!cmd) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: alloc service string send: %s\n" , +strerror (error)); +return -error; + } + cmd->p.magic =htonl (command); +strcpy (cmd->value, value); + + size =__send_packet (sf, cmd, cmdsz); +if (size <0 ) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: send service string: %s\n" , +strerror (error)); +return -error; + } +free (cmd); + + size =__recv_packet (sf, &reply,sizeof (reply)); +if (size <0 ) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: service string reply: %s\n" , +strerror (error)); +return -error; + } +if (size !=sizeof (reply)) { +fuse_log (FUSE_LOG_ERR ,"fuse: wrong service string reply size %zd, expected %zd\n" , + size,sizeof (reply)); +return -EBADMSG ; + } + +if (ntohl (reply.p.magic) !=FUSE_SERVICE_SIMPLE_REPLY ) { +fuse_log (FUSE_LOG_ERR ,"fuse: service string reply contains wrong magic!\n" ); +return -EBADMSG ; + } + + *errorp =ntohl (reply.error); +return 0 ; +} + +static int send_mountpoint (struct fuse_service *sf,mode_t expected_fmt, +const char *value,int *errorp) +{ +struct fuse_service_simple_reply reply = { }; +struct fuse_service_mountpoint_command *cmd; +const size_t cmdsz = +sizeof_fuse_service_mountpoint_command (strlen (value)); +ssize_t size; + + cmd =calloc (1 , cmdsz); +if (!cmd) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: alloc service mountpoint send: %s\n" , +strerror (error)); +return -error; + } + cmd->p.magic =htonl (FUSE_SERVICE_MNTPT_CMD ); + cmd->expected_fmt =htons (expected_fmt); +strcpy (cmd->value, value); + + size =__send_packet (sf, cmd, cmdsz); +if (size <0 ) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: send service mountpoint: %s\n" , +strerror (error)); +return -error; + } +free (cmd); + + size =__recv_packet (sf, &reply,sizeof (reply)); +if (size <0 ) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: service mountpoint reply: %s\n" , +strerror (error)); +return -error; + } +if (size !=sizeof (reply)) { +fuse_log (FUSE_LOG_ERR , +"fuse: wrong service mountpoint reply size %zd, expected %zd\n" , + size,sizeof (reply)); +return -EBADMSG ; + } + +if (ntohl (reply.p.magic) !=FUSE_SERVICE_SIMPLE_REPLY ) { +fuse_log (FUSE_LOG_ERR ,"fuse: service mountpoint reply contains wrong magic!\n" ); +return -EBADMSG ; + } + + *errorp =ntohl (reply.error); +return 0 ; +} + +static int send_mount (struct fuse_service *sf,unsigned int ms_flags, +int *errorp) +{ +struct fuse_service_simple_reply reply = { }; +struct fuse_service_mount_command c = { + .p.magic =htonl (FUSE_SERVICE_MOUNT_CMD ), + .ms_flags =htonl (ms_flags), + }; +ssize_t size; + + size =__send_packet (sf, &c,sizeof (c)); +if (size <0 ) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: send service mount command: %s\n" , +strerror (error)); +return -error; + } + + size =__recv_packet (sf, &reply,sizeof (reply)); +if (size <0 ) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: service mount reply: %s\n" , +strerror (error)); +return -error; + } +if (size !=sizeof (reply)) { +fuse_log (FUSE_LOG_ERR ,"fuse: wrong service mount reply size %zd, expected %zd\n" , + size,sizeof (reply)); +return -EBADMSG ; + } + +if (ntohl (reply.p.magic) !=FUSE_SERVICE_SIMPLE_REPLY ) { +fuse_log (FUSE_LOG_ERR ,"fuse: service mount reply contains wrong magic!\n" ); +return -EBADMSG ; + } + + *errorp =ntohl (reply.error); +return 0 ; +} + +void fuse_service_expect_mount_format (struct fuse_service *sf, +mode_t expected_fmt) +{ + sf->expected_fmt = expected_fmt; +} + +int fuse_service_session_mount (struct fuse_service *sf,struct fuse_session *se, +mode_t expected_fmt, +struct fuse_cmdline_opts *opts) +{ +char *fstype =fuse_mnt_build_type (se->mo); +char *source =fuse_mnt_build_source (se->mo); +char *mntopts =fuse_mnt_kernel_opts (se->mo); +char path[32 ]; +int ret; +int error =0 ; + +if (!fstype || !source) { +fuse_log (FUSE_LOG_ERR ,"fuse: cannot allocate service strings\n" ); + ret = -ENOMEM ; + goto out_strings; + } + +if (!expected_fmt) + expected_fmt = sf->expected_fmt; + +/* +* The fuse session takes the fusedev fd if this succeeds. It is +* required to use the "/dev/fd/XX" format. +*/ +snprintf (path,sizeof (path),"/dev/fd/%d" , sf->fusedevfd); + errno =0 ; + ret =fuse_session_mount (se, path); +if (ret) { +/* Try to return richer errors than fuse_session_mount's -1 */ + ret = errno ? -errno : -EINVAL ; + goto out_strings; + } + sf->owns_fusedevfd = false; + + ret =send_fsopen (sf, fstype, &error); +if (ret) + goto out_strings; +if (error) { +fuse_log (FUSE_LOG_ERR ,"fuse: service fsopen: %s\n" , +strerror (error)); + ret = -error; + goto out_strings; + } + + ret =send_string (sf,FUSE_SERVICE_SOURCE_CMD , source, &error); +if (ret) + goto out_strings; +if (error) { +fuse_log (FUSE_LOG_ERR ,"fuse: service fs source: %s\n" , +strerror (error)); + ret = -error; + goto out_strings; + } + + ret =send_mountpoint (sf, expected_fmt, opts->mountpoint, &error); +if (ret) + goto out_strings; +if (error) { +fuse_log (FUSE_LOG_ERR ,"fuse: service fs mountpoint: %s\n" , +strerror (error)); + ret = -error; + goto out_strings; + } + +if (mntopts) { + ret =send_string (sf,FUSE_SERVICE_MNTOPTS_CMD , mntopts, + &error); +if (ret) + goto out_strings; +if (error) { +fuse_log (FUSE_LOG_ERR ,"fuse: service fs mount options: %s\n" , +strerror (error)); + ret = -error; + goto out_strings; + } + } + + ret =send_mount (sf,fuse_mnt_flags (se->mo), &error); +if (ret) + goto out_strings; +if (error) { +fuse_log (FUSE_LOG_ERR ,"fuse: service mount: %s\n" , +strerror (error)); + ret = -error; + goto out_strings; + } + +/* +* foreground mode is needed so that systemd actually tracks the +* service correctly and doesn't try to kill it; and so that +* stdout/stderr don't get zapped. Change to the root directory so +* that the caller needn't call fuse_daemonize(). +*/ + opts->foreground =1 ; + (void )chdir ("/" ); + +out_strings: +free (mntopts); +free (source); +free (fstype); +return ret; +} + +int fuse_service_session_unmount (struct fuse_service *sf) +{ +struct fuse_service_simple_reply reply = { }; +struct fuse_service_unmount_command c = { + .p.magic =htonl (FUSE_SERVICE_UNMOUNT_CMD ), + }; +ssize_t size; + +/* already gone? */ +if (sf->sockfd <0 ) +return 0 ; + + size =__send_packet (sf, &c,sizeof (c)); +if (size <0 ) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: send service unmount: %s\n" , +strerror (error)); +return -error; + } + + size =__recv_packet (sf, &reply,sizeof (reply)); +if (size <0 ) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: service unmount reply: %s\n" , +strerror (error)); +return -error; + } +if (size !=sizeof (reply)) { +fuse_log (FUSE_LOG_ERR ,"fuse: wrong service unmount reply size %zd, expected %zd\n" , + size,sizeof (reply)); +return -EBADMSG ; + } + +if (ntohl (reply.p.magic) !=FUSE_SERVICE_SIMPLE_REPLY ) { +fuse_log (FUSE_LOG_ERR ,"fuse: service unmount reply contains wrong magic!\n" ); +return -EBADMSG ; + } + +if (reply.error) { +int error =ntohl (reply.error); + +fuse_log (FUSE_LOG_ERR ,"fuse: service unmount: %s\n" , +strerror (error)); +return -error; + } + +return 0 ; +} + +void fuse_service_release (struct fuse_service *sf) +{ +if (sf->owns_fusedevfd) +close (sf->fusedevfd); + sf->owns_fusedevfd = false; + sf->fusedevfd =-1 ; +close (sf->argvfd); + sf->argvfd =-1 ; +shutdown (sf->sockfd,SHUT_RDWR ); +close (sf->sockfd); + sf->sockfd =-1 ; +} + +void fuse_service_destroy (struct fuse_service **sfp) +{ +struct fuse_service *sf = *sfp; + +if (sf) { +fuse_service_release (*sfp); +free (sf); + } + + *sfp =NULL ; +} + +char *fuse_service_cmdline (int argc,char *argv[],struct fuse_args *args) +{ +char *p, *dst; +size_t len =1 ; +ssize_t ret; +char *argv0; +unsigned int i; + +/* Try to preserve argv[0] */ +if (argc >0 ) + argv0 = argv[0 ]; +else if (args->argc >0 ) + argv0 = args->argv[0 ]; +else +return NULL ; + +/* Pick up the alleged fstype from args->argv[0] */ +if (args->argc ==0 ) +return NULL ; + + len +=strlen (argv0) +1 ; + len +=3 ;/* " -t" */ +for (i =0 ; i < args->argc; i++) + len +=strlen (args->argv[i]) +1 ; + + p =calloc (1 , len); +if (!p) +return NULL ; + dst = p; + +/* Format: argv0 -t alleged_fstype [all other options...] */ + ret =sprintf (dst,"%s -t" , argv0); + dst += ret; +for (i =0 ; i < args->argc; i++) { + ret =sprintf (dst," %s" , args->argv[i]); + dst += ret; + } + +return p; +} + +int fuse_service_parse_cmdline_opts (struct fuse_args *args, +struct fuse_cmdline_opts *opts) +{ +return fuse_parse_cmdline_service (args, opts); +} + +int fuse_service_exit (int ret) +{ +/* +* We have to sleep 2 seconds here because journald uses the pid to +* connect our log messages to the systemd service. This is critical +* for capturing all the log messages if the service fails, because +* failure analysis tools use the service name to gather log messages +* for reporting. +*/ +sleep (2 ); + +/* +* If we're being run as a service, the return code must fit the LSB +* init script action error guidelines, which is to say that we +* compress all errors to 1 ("generic or unspecified error", LSB 5.0 +* section 22.2) and hope the admin will scan the log for what actually +* happened. +*/ +return ret !=0 ?EXIT_FAILURE :EXIT_SUCCESS ; +}lib/fuse_service_stub.c new file +106
-0@@ -0,0 +1,106 @@ +/* +* FUSE: Filesystem in Userspace +* Copyright (C) 2025-2026 Oracle. +* Author: Darrick J. Wong <[email protected]> +* +* Stub functions for platforms where we cannot have fuse servers run as "safe" +* systemd containers. +* +* This program can be distributed under the terms of the GNU LGPLv2. +* See the file LGPL2.txt +*/ + +/* we don't use any parameters at all */ +#pragma GCC diagnostic ignored "-Wunused-parameter" + +#define _GNU_SOURCE +#include <errno.h> + +#include "fuse_config.h" +#include "fuse_i.h" +#include "fuse_service.h" + +int fuse_service_receive_file (struct fuse_service *sf,const char *path, +int *fdp) +{ +return -EOPNOTSUPP ; +} + +int fuse_service_request_file (struct fuse_service *sf,const char *path, +int open_flags,mode_t create_mode, +unsigned int request_flags) +{ +return -EOPNOTSUPP ; +} + +int fuse_service_request_blockdev (struct fuse_service *sf,const char *path, +int open_flags,mode_t create_mode, +unsigned int request_flags, +unsigned int block_size) +{ +return -EOPNOTSUPP ; +} + +int fuse_service_send_goodbye (struct fuse_service *sf,int error) +{ +return -EOPNOTSUPP ; +} + +int fuse_service_accept (struct fuse_service **sfp) +{ + *sfp =NULL ; +return 0 ; +} + +int fuse_service_append_args (struct fuse_service *sf, +struct fuse_args *existing_args) +{ +return -EOPNOTSUPP ; +} + +char *fuse_service_cmdline (int argc,char *argv[],struct fuse_args *args) +{ +return NULL ; +} + +int fuse_service_finish_file_requests (struct fuse_service *sf) +{ +return -EOPNOTSUPP ; +} + +void fuse_service_expect_mount_format (struct fuse_service *sf, +mode_t expected_fmt) +{ +} + +int fuse_service_session_mount (struct fuse_service *sf,struct fuse_session *se, +mode_t expected_fmt, +struct fuse_cmdline_opts *opts) +{ +return -EOPNOTSUPP ; +} + +int fuse_service_session_unmount (struct fuse_service *sf) +{ +return -EOPNOTSUPP ; +} + +void fuse_service_release (struct fuse_service *sf) +{ +} + +void fuse_service_destroy (struct fuse_service **sfp) +{ + *sfp =NULL ; +} + +int fuse_service_parse_cmdline_opts (struct fuse_args *args, +struct fuse_cmdline_opts *opts) +{ +return -1 ; +} + +int fuse_service_exit (int ret) +{ +return ret; +}lib/fuse_versionscript +17
-0@@ -227,6 +227,23 @@ FUSE_3.19 { fuse_session_start_teardown_watchdog; fuse_session_stop_teardown_watchdog; fuse_lowlevel_notify_prune; + + fuse_service_accept; + fuse_service_append_args; + fuse_service_can_allow_other; + fuse_service_cmdline; + fuse_service_destroy; + fuse_service_exit; + fuse_service_expect_mount_format; + fuse_service_finish_file_requests; + fuse_service_parse_cmdline_opts; + fuse_service_receive_file; + fuse_service_release; + fuse_service_request_file; + fuse_service_request_blockdev; + fuse_service_send_goodbye; + fuse_service_session_mount; + fuse_service_session_unmount; } FUSE_3.18; # Local Variables:lib/helper.c +51
-0@@ -26,6 +26,11 @@#include <errno.h> #include <sys/param.h> +#ifdef HAVE_SERVICEMOUNT +# include <linux/types.h> +# include "fuse_service_priv.h" +#endif +#define FUSE_HELPER_OPT (t, p) \ { t, offsetof(struct fuse_cmdline_opts, p), 1 }@@ -228,6 +233,52 @@ int fuse_parse_cmdline_312(struct fuse_args *args,return 0 ; } +#ifdef HAVE_SERVICEMOUNT +static int fuse_helper_opt_proc_service (void *data,const char *arg,int key, +struct fuse_args *outargs) +{ + (void ) outargs; +struct fuse_cmdline_opts *opts = data; + +switch (key) { +case FUSE_OPT_KEY_NONOPT : +if (!opts->mountpoint) +return fuse_opt_add_opt (&opts->mountpoint, arg); + +fuse_log (FUSE_LOG_ERR ,"fuse: invalid argument `%s'\n" , arg); +return -1 ; +default : +/* Pass through unknown options */ +return 1 ; + } +} + +int fuse_parse_cmdline_service (struct fuse_args *args, +struct fuse_cmdline_opts *opts) +{ +memset (opts,0 ,sizeof (struct fuse_cmdline_opts )); + + opts->max_idle_threads =UINT_MAX ;/* new default in fuse version 3.12 */ + opts->max_threads =10 ; + +if (fuse_opt_parse (args, opts, fuse_helper_opts, + fuse_helper_opt_proc_service) ==-1 ) +return -1 ; + +/* +* *Linux*: if neither -o subtype nor -o fsname are specified, +* set subtype to program's basename. +* *FreeBSD*: if fsname is not specified, set to program's +* basename. +*/ +if (!opts->nodefault_subtype) +if (add_default_subtype (args->argv[0 ], args) ==-1 ) +return -1 ; + +return 0 ; +} +#endif +/** * struct fuse_cmdline_opts got extended in libfuse-3.12 */ lib/meson.build +15
-2@@ -10,6 +10,12 @@ else libfuse_sources += [ 'mount_bsd.c' ] endif +if private_cfg.get('HAVE_SERVICEMOUNT', false) + libfuse_sources += [ 'fuse_service.c' ] +else + libfuse_sources += [ 'fuse_service_stub.c' ] +endif + deps = [ thread_dep ] if private_cfg.get('HAVE_ICONV') libfuse_sources += [ 'modules/iconv.c' ]@@ -49,18 +55,25 @@ libfuse = library('fuse3', dependencies: deps, install: true, link_depends: 'fuse_versionscript', - c_args: [ '-DFUSE_USE_VERSION=317', + c_args: [ '-DFUSE_USE_VERSION=319', '-DFUSERMOUNT_DIR="@0@"'.format(fusermount_path) ], link_args: ['-Wl,--version-script,' + meson.current_source_dir() + '/fuse_versionscript' ]) +vars = [] +if private_cfg.get('HAVE_SERVICEMOUNT', false) + service_socket_dir = private_cfg.get_unquoted('FUSE_SERVICE_SOCKET_DIR', '') + vars += ['service_socket_dir=' + service_socket_dir] + vars += ['service_socket_perms=' + service_socket_perms] +endif pkg = import('pkgconfig') pkg.generate(libraries: [ libfuse, '-lpthread' ], libraries_private: '-ldl', version: meson.project_version(), name: 'fuse3', description: 'Filesystem in Userspace', - subdirs: 'fuse3') + subdirs: 'fuse3', + variables: vars) libfuse_dep = declare_dependency(include_directories: include_dirs, link_with: libfuse, dependencies: deps)lib/mount.c +12
-0@@ -750,3 +750,15 @@ char *fuse_mnt_build_type(const struct mount_opts *mo)return type; } + +char *fuse_mnt_kernel_opts (const struct mount_opts *mo) +{ +if (mo->kernel_opts) +return strdup (mo->kernel_opts); +return NULL ; +} + +unsigned int fuse_mnt_flags (const struct mount_opts *mo) +{ +return mo->flags; +}meson.build +34
-0@@ -69,6 +69,16 @@ args_default = [ '-D_GNU_SOURCE' ] # private_cfg = configuration_data() private_cfg.set_quoted('PACKAGE_VERSION', meson.project_version()) +service_socket_dir = get_option('service-socket-dir') +service_socket_perms = get_option('service-socket-perms') +if service_socket_dir == '' + service_socket_dir = '/run/filesystems' +endif +if service_socket_perms == '' + service_socket_perms = '0220' +endif +private_cfg.set_quoted('FUSE_SERVICE_SOCKET_DIR', service_socket_dir) +private_cfg.set('FUSE_SERVICE_SOCKET_PERMS', service_socket_perms) # Test for presence of some functions test_funcs = [ 'fork', 'fstatat', 'openat', 'readlinkat', 'pipe2',@@ -118,6 +128,13 @@ special_funcs = { return -1; } } + ''', + 'systemd_headers': ''' + #include <systemd/sd-daemon.h> + + int main(int argc, char *argv[]) { + return SD_LISTEN_FDS_START; + } ''' }@@ -180,6 +197,23 @@ if get_option('enable-io-uring') and liburing.found() and libnuma.found() endif endif +# Check for systemd support +systemd_system_unit_dir = get_option('systemd-system-unit-dir') +if systemd_system_unit_dir == '' + systemd = dependency('systemd', required: false) + if systemd.found() + systemd_system_unit_dir = systemd.get_variable(pkgconfig: 'systemd_system_unit_dir') + endif +endif + +if systemd_system_unit_dir == '' or private_cfg.get('HAVE_SYSTEMD_HEADERS', false) == false + warning('systemd service support will not be built') +else + private_cfg.set_quoted('SYSTEMD_SYSTEM_UNIT_DIR', systemd_system_unit_dir) + private_cfg.set('HAVE_SYSTEMD', true) + private_cfg.set('HAVE_SERVICEMOUNT', true) +endif + # # Compiler configuration #meson_options.txt +9
-0@@ -27,3 +27,12 @@ option('enable-usdt', type : 'boolean', value : false, option('enable-io-uring', type: 'boolean', value: true, description: 'Enable fuse-over-io-uring support') + +option('service-socket-dir', type : 'string', value : '', + description: 'Where to install fuse server sockets (if empty, /run/filesystems)') + +option('service-socket-perms', type : 'string', value : '', + description: 'Default fuse server socket permissions (if empty, 0220)') + +option('systemd-system-unit-dir', type : 'string', value : '', + description: 'Where to install systemd unit files (if empty, query pkg-config(1))')util/fuservicemount.c new file +18
-0@@ -0,0 +1,18 @@ +/* +* FUSE: Filesystem in Userspace +* Copyright (C) 2025-2026 Oracle. +* Author: Darrick J. Wong <[email protected]> +* +* This program can be distributed under the terms of the GNU GPLv2. +* See the file GPL2.txt. +* +* This program wraps the mounting of FUSE filesystems that run in systemd +*/ +#define _GNU_SOURCE +#include "fuse_config.h" +#include "mount_service.h" + +int main (int argc,char *argv[]) +{ +return mount_service_main (argc, argv); +}util/meson.build +9
-0@@ -6,6 +6,15 @@ executable('fusermount3', ['fusermount.c', '../lib/mount_util.c', '../lib/util.c install_dir: get_option('bindir'), c_args: '-DFUSE_CONF="@0@"'.format(fuseconf_path)) +if private_cfg.get('HAVE_SERVICEMOUNT', false) + executable('fuservicemount3', ['mount_service.c', 'fuservicemount.c', '../lib/mount_util.c'], + include_directories: include_dirs, + link_with: [ libfuse ], + install: true, + install_dir: get_option('sbindir'), + c_args: '-DFUSE_USE_VERSION=319') +endif + executable('mount.fuse3', ['mount.fuse.c'], include_directories: include_dirs, link_with: [ libfuse ],util/mount_service.c new file +1427
-0@@ -0,0 +1,1427 @@ +/* +* FUSE: Filesystem in Userspace +* Copyright (C) 2025-2026 Oracle. +* Author: Darrick J. Wong <[email protected]> +* +* This program can be distributed under the terms of the GNU GPLv2. +* See the file GPL2.txt. +* +* This program does the mounting of FUSE filesystems that run in systemd +*/ +#define _GNU_SOURCE +#include "fuse_config.h" +#include <stdint.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <stdbool.h> +#include <limits.h> +#include <arpa/inet.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/mman.h> +#include <sys/mount.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <linux/fs.h> + +#include "mount_util.h" +#include "util.h" +#include "fuse_i.h" +#include "fuse_service_priv.h" +#include "mount_service.h" + +struct mount_service { +/* prefix for printing error messages */ +const char *msgtag; + +/* fuse subtype based on -t cli argument */ +char *subtype; + +/* source argument to mount() */ +char *source; + +/* target argument (aka mountpoint) to mount() */ +char *mountpoint; + +/* mountpoint that we pass to mount() */ +char *real_mountpoint; + +/* resolved path to mountpoint that we use for mtab updates */ +char *resv_mountpoint; + +/* mount options */ +char *mntopts; + +/* socket fd */ +int sockfd; + +/* /dev/fuse device */ +int fusedevfd; + +/* memfd for cli arguments */ +int argvfd; + +/* fd for mount point */ +int mountfd; + +/* did we actually mount successfully? */ +bool mounted; + +/* has the fsopen command already been submitted? */ +bool fsopened; + +/* is this a fuseblk mount? */ +bool fuseblk; +}; + +static ssize_t __send_fd (struct mount_service *mo, +struct fuse_service_requested_file *req, +size_t req_sz,int fd) +{ +union { +struct cmsghdr cmsghdr; +char control[CMSG_SPACE (sizeof (int ))]; + } cmsgu; +struct iovec iov = { + .iov_base = req, + .iov_len = req_sz, + }; +struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen =1 , + .msg_control = cmsgu.control, + .msg_controllen =sizeof (cmsgu.control), + }; +struct cmsghdr *cmsg =CMSG_FIRSTHDR (&msg); + +if (!cmsg) { + errno =EINVAL ; +return -1 ; + } + +memset (&cmsgu,0 ,sizeof (cmsgu)); + cmsg->cmsg_len =CMSG_LEN (sizeof (int )); + cmsg->cmsg_level =SOL_SOCKET ; + cmsg->cmsg_type =SCM_RIGHTS ; + + *((int *)CMSG_DATA (cmsg)) = fd; + +return sendmsg (mo->sockfd, &msg,MSG_EOR |MSG_NOSIGNAL ); +} + +static ssize_t __send_packet (struct mount_service *mo,void *ptr,size_t len) +{ +struct iovec iov = { + .iov_base = ptr, + .iov_len = len, + }; +struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen =1 , + }; + +return sendmsg (mo->sockfd, &msg,MSG_EOR |MSG_NOSIGNAL ); +} + +static ssize_t __recv_packet_size (struct mount_service *mo) +{ +struct iovec iov = { }; +struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen =1 , + }; +return recvmsg (mo->sockfd, &msg,MSG_PEEK |MSG_TRUNC ); +} + +static ssize_t __recv_packet (struct mount_service *mo,void *ptr,size_t len) +{ +struct iovec iov = { + .iov_base = ptr, + .iov_len = len, + }; +struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen =1 , + }; + +return recvmsg (mo->sockfd, &msg,MSG_TRUNC ); +} + +/* +* Filter out the subtype of the filesystem (e.g. fuse.Y[.Z] -> Y[.Z]). The +* fuse server determines if it's appropriate to set the "blockdev" mount +* option (aka fuseblk). +*/ +const char *mount_service_subtype (const char *fstype) +{ +const char *subtype; + +if (!strncmp (fstype,"fuse." ,5 )) + subtype = fstype +5 ; +else if (!strncmp (fstype,"fuseblk." ,8 )) + subtype = fstype +8 ; +else + subtype = fstype; + +if (strchr (subtype,'/' ) !=NULL ) { +fprintf (stderr, +"%s: fs subtype cannot contain path separators\n" , + fstype); +return NULL ; + } + +return subtype; +} + +static int mount_service_init (struct mount_service *mo,int argc,char *argv[]) +{ +char *fstype =NULL ; +const char *subtype; +int i; + + mo->sockfd =-1 ; + mo->argvfd =-1 ; + mo->fusedevfd =-1 ; + mo->mountfd =-1 ; + +for (i =0 ; i < argc; i++) { +if (!strcmp (argv[i],"-t" ) && i +1 < argc) { + fstype = argv[i +1 ]; +break ; + } + } +if (!fstype) { +fprintf (stderr,"%s: cannot determine filesystem type.\n" , + mo->msgtag); +return -1 ; + } + + subtype =mount_service_subtype (fstype); +if (!subtype) +return -1 ; + + mo->subtype =strdup (subtype); +if (!mo->subtype) { +int error = errno; + +fprintf (stderr,"%s: cannot alloc memory for fs subtype: %s\n" , + mo->msgtag,strerror (error)); +return -1 ; + } + +return 0 ; +} + +#ifdef SO_PASSRIGHTS +static int try_drop_passrights (struct mount_service *mo,int sockfd) +{ +int zero =0 ; +int ret; + +/* +* Don't let a malicious mount helper send us any fds. We don't trust +* the fuse server not to pollute our fd namespace, so we'll end now. +*/ + ret =setsockopt (sockfd,SOL_SOCKET ,SO_PASSRIGHTS , &zero, +sizeof (zero)); +if (ret) { +fprintf (stderr,"%s: disabling fd passing: %s\n" , + mo->msgtag,strerror (errno)); +return -1 ; + } + +return 0 ; +} +#else +# define try_drop_passrights (...) (0) +#endif + +static int check_sendbuf_size (struct mount_service *mo,int sockfd) +{ +const size_t min_size =sizeof_fuse_service_open_command (PATH_MAX ); +int sendbuf_size =-1 ; +socklen_t optlen =sizeof (sendbuf_size); +int ret; + +/* +* If we can't query the maximum send buffer length, just keep going. +* Most likely we won't be sending huge open commands, and if we do, +* the sendmsg will fail there too. +*/ + ret =getsockopt (sockfd,SOL_SOCKET ,SO_SNDBUF , &sendbuf_size, &optlen); +if (ret || sendbuf_size <0 ) +return 0 ; + +if (sendbuf_size >= min_size) +return 0 ; + +fprintf (stderr,"%s: max socket send buffer is %d, need at least %zu.\n" , + mo->msgtag, sendbuf_size, min_size); +return MOUNT_SERVICE_FALLBACK_NEEDED ; +} + +static int mount_service_connect (struct mount_service *mo) +{ +struct sockaddr_un name = { + .sun_family =AF_UNIX , + }; +int sockfd; +ssize_t written; +int ret; + + written =snprintf (name.sun_path,sizeof (name.sun_path), +FUSE_SERVICE_SOCKET_DIR "/%s" , mo->subtype); +if (written >=sizeof (name.sun_path)) { +fprintf (stderr,"%s: filesystem type name `%s' is too long.\n" , + mo->msgtag, mo->subtype); +return -1 ; + } + + sockfd =socket (AF_UNIX ,SOCK_SEQPACKET |SOCK_CLOEXEC ,0 ); +if (sockfd <0 ) { +int error = errno; + +fprintf (stderr,"%s: opening %s service socket: %s\n" , + mo->msgtag, mo->subtype,strerror (error)); +return -1 ; + } + + ret =check_sendbuf_size (mo, sockfd); +if (ret) +return ret; + + ret =connect (sockfd, (const struct sockaddr *)&name,sizeof (name)); +if (ret && (errno ==ENOENT || errno ==ECONNREFUSED )) { +fprintf (stderr,"%s: no safe filesystem driver for %s available.\n" , + mo->msgtag, mo->subtype); +close (sockfd); +return MOUNT_SERVICE_FALLBACK_NEEDED ; + } +if (ret) { +int error = errno; + +fprintf (stderr,"%s: %s: %s\n" , + mo->msgtag, name.sun_path,strerror (error)); + goto out; + } + + ret =try_drop_passrights (mo, sockfd); +if (ret) + goto out; + + mo->sockfd = sockfd; +return 0 ; +out: +close (sockfd); +return -1 ; +} + +static int mount_service_send_hello (struct mount_service *mo) +{ +struct fuse_service_hello hello = { + .p.magic =htonl (FUSE_SERVICE_HELLO_CMD ), + .min_version =htons (FUSE_SERVICE_MIN_PROTO ), + .max_version =htons (FUSE_SERVICE_MAX_PROTO ), + }; +struct fuse_service_hello_reply reply = { }; +ssize_t size; + +if (getuid () ==0 ) + hello.flags |=htonl (FUSE_SERVICE_FLAG_ALLOW_OTHER ); + + size =__send_packet (mo, &hello,sizeof (hello)); +if (size <0 ) { +fprintf (stderr,"%s: send hello: %s\n" , + mo->msgtag,strerror (errno)); +return -1 ; + } + + size =__recv_packet (mo, &reply,sizeof (reply)); +if (size <0 ) { +fprintf (stderr,"%s: hello reply: %s\n" , + mo->msgtag,strerror (errno)); +return -1 ; + } +if (size !=sizeof (reply)) { +fprintf (stderr,"%s: wrong hello reply size %zd, expected %zu\n" , + mo->msgtag, size,sizeof (reply)); +return -1 ; + } + +if (ntohl (reply.p.magic) !=FUSE_SERVICE_HELLO_REPLY ) { +fprintf (stderr,"%s: %s service server did not reply to hello\n" , + mo->msgtag, mo->subtype); +return -1 ; + } + +if (ntohs (reply.version) <FUSE_SERVICE_MIN_PROTO || +ntohs (reply.version) >FUSE_SERVICE_MAX_PROTO ) { +fprintf (stderr,"%s: unsupported protocol version %u\n" , + mo->msgtag,ntohs (reply.version)); +return -1 ; + } + +if (reply.padding) { +fprintf (stderr,"%s: nonzero value in padding field\n" , + mo->msgtag); +return -1 ; + } + +return 0 ; +} + +static int mount_service_capture_arg (struct mount_service *mo, +struct fuse_service_memfd_argv *args, +const char *string,off_t *array_pos, +off_t *string_pos) +{ +const size_t string_len =strlen (string) +1 ; +struct fuse_service_memfd_arg arg = { + .pos =htonl (*string_pos), + .len =htonl (string_len), + }; +ssize_t written; + + written =pwrite (mo->argvfd, string, string_len, *string_pos); +if (written <0 ) { +fprintf (stderr,"%s: memfd argv write: %s\n" , + mo->msgtag,strerror (errno)); +return -1 ; + } +if (written < string_len) { +fprintf (stderr,"%s: memfd argv[%u] wrote %zd, expected %zu\n" , + mo->msgtag, args->argc, written, string_len); +return -1 ; + } + + written =pwrite (mo->argvfd, &arg,sizeof (arg), *array_pos); +if (written <0 ) { +fprintf (stderr,"%s: memfd arg write: %s\n" , + mo->msgtag,strerror (errno)); +return -1 ; + } +if (written <sizeof (arg)) { +fprintf (stderr,"%s: memfd arg[%u] wrote %zd, expected %zu\n" , + mo->msgtag, args->argc, written,sizeof (arg)); +return -1 ; + } + + args->argc++; + *string_pos += string_len; + *array_pos +=sizeof (arg); + +return 0 ; +} + +static int mount_service_capture_args (struct mount_service *mo,int argc, +char *argv[]) +{ +struct fuse_service_memfd_argv args = { + .magic =htonl (FUSE_SERVICE_ARGS_MAGIC ), + }; +off_t array_pos =sizeof (struct fuse_service_memfd_argv ); +off_t string_pos = array_pos + + (argc *sizeof (struct fuse_service_memfd_arg )); +ssize_t written; +int i; +int ret; + +if (argc <0 ) { +fprintf (stderr,"%s: argc cannot be negative\n" , + mo->msgtag); +return -1 ; + } + +/* +* Create the memfd in which we'll stash arguments, and set the write +* pointer for the names. +*/ + mo->argvfd =memfd_create ("fuse service argv" ,MFD_CLOEXEC ); +if (mo->argvfd <0 ) { +fprintf (stderr,"%s: argvfd create: %s\n" , + mo->msgtag,strerror (errno)); +return -1 ; + } + +/* +* Write the alleged subtype as if it were argv[0], then write the rest +* of the argv arguments. +*/ + ret =mount_service_capture_arg (mo, &args, mo->subtype, &array_pos, + &string_pos); +if (ret) +return ret; + +for (i =1 ; i < argc; i++) { +/* skip the -t(ype) argument */ +if (!strcmp (argv[i],"-t" ) && i +1 < argc) { + i++; +continue ; + } + + ret =mount_service_capture_arg (mo, &args, argv[i], + &array_pos, &string_pos); +if (ret) +return ret; + } + +/* Now write the header */ + args.argc =htonl (args.argc); + written =pwrite (mo->argvfd, &args,sizeof (args),0 ); +if (written <0 ) { +fprintf (stderr,"%s: memfd argv write: %s\n" , + mo->msgtag,strerror (errno)); +return -1 ; + } +if (written <sizeof (args)) { +fprintf (stderr,"%s: memfd argv wrote %zd, expected %zu\n" , + mo->msgtag, written,sizeof (args)); +return -1 ; + } + +return 0 ; +} + +static int mount_service_send_file (struct mount_service *mo, +const char *path,int fd) +{ +struct fuse_service_requested_file *req; +const size_t req_sz = +sizeof_fuse_service_requested_file (strlen (path)); +ssize_t written; +int ret =0 ; + + req =calloc (1 , req_sz); +if (!req) { +fprintf (stderr,"%s: alloc send file reply: %s\n" , + mo->msgtag,strerror (errno)); +return -1 ; + } + req->p.magic =htonl (FUSE_SERVICE_OPEN_REPLY ); + req->error =0 ; +strcpy (req->path, path); + + written =__send_fd (mo, req, req_sz, fd); +if (written <0 ) { +fprintf (stderr,"%s: send file reply: %s\n" , + mo->msgtag,strerror (errno)); + ret =-1 ; + goto out_req; + } +if (written < req_sz) { +fprintf (stderr,"%s: send file reply wrote %zd, expected %zu\n" , + mo->msgtag, written, req_sz); + ret =-1 ; + goto out_req; + } + +out_req: +free (req); +return ret; +} + +static int mount_service_send_file_error (struct mount_service *mo,int error, +const char *path) +{ +struct fuse_service_requested_file *req; +const size_t req_sz = +sizeof_fuse_service_requested_file (strlen (path)); +ssize_t written; +int ret =0 ; + + req =calloc (1 , req_sz); +if (!req) { +fprintf (stderr,"%s: alloc send file error: %s\n" , + mo->msgtag,strerror (errno)); +return -1 ; + } + req->p.magic =htonl (FUSE_SERVICE_OPEN_REPLY ); + req->error =htonl (error); +strcpy (req->path, path); + + written =__send_packet (mo, req, req_sz); +if (written <0 ) { +fprintf (stderr,"%s: send file error: %s\n" , + mo->msgtag,strerror (errno)); + ret =-1 ; + goto out_req; + } +if (written < req_sz) { +fprintf (stderr,"%s: send file error wrote %zd, expected %zu\n" , + mo->msgtag, written, req_sz); + ret =-1 ; + goto out_req; + } + +out_req: +free (req); +return ret; +} + +static int mount_service_send_required_files (struct mount_service *mo, +const char *fusedev) +{ +int ret; + + mo->fusedevfd =open (fusedev,O_RDWR |O_CLOEXEC ); +if (mo->fusedevfd <0 ) { +int error = errno; + +fprintf (stderr,"%s: %s: %s\n" , + mo->msgtag, fusedev,strerror (error)); +return -1 ; + } + + ret =mount_service_send_file (mo,FUSE_SERVICE_ARGV , mo->argvfd); +if (ret) + goto out_fusedevfd; + +close (mo->argvfd); + mo->argvfd =-1 ; + +return mount_service_send_file (mo,FUSE_SERVICE_FUSEDEV , + mo->fusedevfd); + +out_fusedevfd: +close (mo->fusedevfd); + mo->fusedevfd =-1 ; +return ret; +} + +static int mount_service_receive_command (struct mount_service *mo, +struct fuse_service_packet **commandp, +size_t *commandsz) +{ +struct fuse_service_packet *command; +ssize_t alleged_size, size; + + alleged_size =__recv_packet_size (mo); +if (alleged_size <0 ) { +fprintf (stderr,"%s: peek service command: %s\n" , + mo->msgtag,strerror (errno)); +return -1 ; + } +if (alleged_size ==0 ) { +/* fuse server probably exited early */ +fprintf (stderr,"%s: fuse server exited without saying goodbye!\n" , + mo->msgtag); +return -1 ; + } +if (alleged_size <sizeof (struct fuse_service_packet )) { +fprintf (stderr,"%s: wrong command packet size %zd, expected at least %zu\n" , + mo->msgtag, alleged_size, +sizeof (struct fuse_service_packet )); +return -1 ; + } +if (alleged_size >FUSE_SERVICE_MAX_CMD_SIZE ) { +fprintf (stderr,"%s: wrong command packet size %zd, expected less than %d\n" , + mo->msgtag, alleged_size,FUSE_SERVICE_MAX_CMD_SIZE ); +return -1 ; + } + + command =calloc (1 , alleged_size +1 ); +if (!command) { +fprintf (stderr,"%s: alloc service command: %s\n" , + mo->msgtag,strerror (errno)); +return -1 ; + } + + size =__recv_packet (mo, command, alleged_size); +if (size <0 ) { +fprintf (stderr,"%s: receive service command: %s\n" , + mo->msgtag,strerror (errno)); +free (command); +return -1 ; + } +if (size != alleged_size) { +fprintf (stderr,"%s: wrong service command size %zd, expected %zd\n" , + mo->msgtag, size, alleged_size); +free (command); +return -1 ; + } + + *commandp = command; + *commandsz = size; +return 0 ; +} + +static int mount_service_send_reply (struct mount_service *mo,int error) +{ +struct fuse_service_simple_reply reply = { + .p.magic =htonl (FUSE_SERVICE_SIMPLE_REPLY ), + .error =htonl (error), + }; +ssize_t size; + + size =__send_packet (mo, &reply,sizeof (reply)); +if (size <0 ) { +fprintf (stderr,"%s: send service reply: %s\n" , + mo->msgtag,strerror (errno)); +return -1 ; + } + +return 0 ; +} + +static int prepare_bdev (struct mount_service *mo, +struct fuse_service_open_command *oc,int fd) +{ +struct stat stbuf; +int ret; + + ret =fstat (fd, &stbuf); +if (ret) { +int error = errno; + +fprintf (stderr,"%s: %s: %s\n" , + mo->msgtag, oc->path,strerror (error)); +return -error; + } + +if (!S_ISBLK (stbuf.st_mode)) { +fprintf (stderr,"%s: %s: %s\n" , + mo->msgtag, oc->path,strerror (ENOTBLK )); +return -ENOTBLK ; + } + +if (oc->block_size) { +int block_size =ntohl (oc->block_size); + + ret =ioctl (fd,BLKBSZSET , &block_size); +if (ret) { +int error = errno; + +fprintf (stderr,"%s: %s: %s\n" , + mo->msgtag, oc->path,strerror (error)); +return -error; + } + } + +return 0 ; +} + +static int mount_service_open_path (struct mount_service *mo, +mode_t expected_fmt, +struct fuse_service_packet *p,size_t psz) +{ +struct fuse_service_open_command *oc = + container_of(p, struct fuse_service_open_command, p); +uint32_t request_flags; +int open_flags; +int ret; +int fd; + +if (psz <sizeof_fuse_service_open_command (1 )) { +fprintf (stderr,"%s: open command too small\n" , + mo->msgtag); +return mount_service_send_file_error (mo,EINVAL ,"?" ); + } + +if (!check_null_endbyte (p, psz)) { +fprintf (stderr,"%s: open command must be null terminated\n" , + mo->msgtag); +return mount_service_send_file_error (mo,EINVAL ,"?" ); + } + + request_flags =ntohl (oc->request_flags); +if (request_flags & ~FUSE_SERVICE_OPEN_FLAGS ) { +fprintf (stderr,"%s: open flags 0x%x not recognized\n" , + mo->msgtag, request_flags & ~FUSE_SERVICE_OPEN_FLAGS ); +return mount_service_send_file_error (mo,EINVAL , oc->path); + } + + open_flags =ntohl (oc->open_flags) |O_CLOEXEC ; + fd =open (oc->path, open_flags,ntohl (oc->create_mode)); +if (fd <0 ) { +int error = errno; + +/* +* Don't print a busy device error report because the +* filesystem might decide to retry. +*/ +if (error !=EBUSY && !(request_flags &FUSE_SERVICE_OPEN_QUIET )) +fprintf (stderr,"%s: %s: %s\n" , + mo->msgtag, oc->path,strerror (error)); +return mount_service_send_file_error (mo, error, oc->path); + } + +if (S_ISBLK (expected_fmt)) { + ret =prepare_bdev (mo, oc, fd); +if (ret <0 ) { +close (fd); +return mount_service_send_file_error (mo, -ret, + oc->path); + } + } + + ret =mount_service_send_file (mo, oc->path, fd); +close (fd); +return ret; +} + +static int mount_service_handle_open_cmd (struct mount_service *mo, +struct fuse_service_packet *p, +size_t psz) +{ +return mount_service_open_path (mo,0 , p, psz); +} + +static int mount_service_handle_open_bdev_cmd (struct mount_service *mo, +struct fuse_service_packet *p, +size_t psz) +{ +return mount_service_open_path (mo,S_IFBLK , p, psz); +} + +static inline const char *fsname (const struct mount_service *mo) +{ +return mo->fuseblk ?"fuseblk" :"fuse" ; +} + +static int mount_service_handle_fsopen_cmd (struct mount_service *mo, +const struct fuse_service_packet *p, +size_t psz) +{ +struct fuse_service_fsopen_command *oc = + container_of(p, struct fuse_service_fsopen_command, p); +uint32_t fsopen_flags; + +if (psz !=sizeof (struct fuse_service_fsopen_command )) { +fprintf (stderr,"%s: fsopen command wrong size %zu, expected %zu\n" , + mo->msgtag, psz,sizeof (*oc)); +return mount_service_send_reply (mo,EINVAL ); + } + +if (mo->fsopened) { +fprintf (stderr,"%s: fsopen command respecified\n" , + mo->msgtag); +return mount_service_send_reply (mo,EINVAL ); + } + + fsopen_flags =ntohl (oc->fsopen_flags); +if (fsopen_flags & ~FUSE_SERVICE_FSOPEN_FLAGS ) { +fprintf (stderr,"%s: unknown fsopen flags, 0x%x\n" , + mo->msgtag, fsopen_flags & ~FUSE_SERVICE_FSOPEN_FLAGS ); +return mount_service_send_reply (mo,EINVAL ); + } + +if (fsopen_flags &FUSE_SERVICE_FSOPEN_FUSEBLK ) { +if (getuid () !=0 ) { +fprintf (stderr,"%s: fuseblk requires root privilege\n" , + mo->msgtag); +return mount_service_send_reply (mo,EPERM ); + } + + mo->fuseblk = true; + } + mo->fsopened = true; + +return mount_service_send_reply (mo,0 ); +} + +static int mount_service_handle_source_cmd (struct mount_service *mo, +const struct fuse_service_packet *p, +size_t psz) +{ +struct fuse_service_string_command *oc = + container_of(p, struct fuse_service_string_command, p); + +if (psz <sizeof_fuse_service_string_command (1 )) { +fprintf (stderr,"%s: source command too small\n" , + mo->msgtag); +return mount_service_send_reply (mo,EINVAL ); + } + +if (!check_null_endbyte (p, psz)) { +fprintf (stderr,"%s: source command must be null terminated\n" , + mo->msgtag); +return mount_service_send_reply (mo,EINVAL ); + } + +if (mo->source) { +fprintf (stderr,"%s: source respecified!\n" , + mo->msgtag); +return mount_service_send_reply (mo,EINVAL ); + } + + mo->source =strdup (oc->value); +if (!mo->source) { +int error = errno; + +fprintf (stderr,"%s: alloc source string: %s\n" , + mo->msgtag,strerror (error)); +return mount_service_send_reply (mo, error); + } + +return mount_service_send_reply (mo,0 ); +} + +static int mount_service_handle_mntopts_cmd (struct mount_service *mo, +const struct fuse_service_packet *p, +size_t psz) +{ +struct fuse_service_string_command *oc = + container_of(p, struct fuse_service_string_command, p); + +if (psz <sizeof_fuse_service_string_command (1 )) { +fprintf (stderr,"%s: mount options command too small\n" , + mo->msgtag); +return mount_service_send_reply (mo,EINVAL ); + } + +if (!check_null_endbyte (p, psz)) { +fprintf (stderr,"%s: mount options command must be null terminated\n" , + mo->msgtag); +return mount_service_send_reply (mo,EINVAL ); + } + +if (mo->mntopts) { +fprintf (stderr,"%s: mount options respecified!\n" , + mo->msgtag); +return mount_service_send_reply (mo,EINVAL ); + } + + mo->mntopts =strdup (oc->value); +if (!mo->mntopts) { +int error = errno; + +fprintf (stderr,"%s: alloc mount options string: %s\n" , + mo->msgtag,strerror (error)); +return mount_service_send_reply (mo, error); + } + +return mount_service_send_reply (mo,0 ); +} + +static int attach_to_mountpoint (struct mount_service *mo,mode_t expected_fmt, +char *mntpt) +{ +struct stat stbuf; +char *res_mntpt; +int mountfd =-1 ; +int error; +int ret; + +/* +* Open the alleged mountpoint, make sure it's a dir or a file. +*/ + mountfd =open (mntpt,O_RDONLY |O_CLOEXEC ); +if (mountfd <0 ) { + error = errno; +fprintf (stderr,"%s: %s: %s\n" , mo->msgtag, mntpt, +strerror (error)); + goto out_error; + } + +/* +* Make sure we can access the mountpoint and that it's either a +* directory or a regular file. Linux can handle mounting atop special +* files, but we don't care to do such crazy things. +*/ + ret =fstat (mountfd, &stbuf); +if (ret) { + error = errno; +fprintf (stderr,"%s: %s: %s\n" , mo->msgtag, mntpt, +strerror (error)); + goto out_mountfd; + } + +if (!S_ISDIR (stbuf.st_mode) && !S_ISREG (stbuf.st_mode)) { + error =EACCES ; +fprintf (stderr,"%s: %s: Mount point must be directory or regular file.\n" , + mo->msgtag, mntpt); + goto out_mountfd; + } + +/* +* Resolve the (possibly relative) mountpoint path before chdir'ing +* onto it. +*/ + res_mntpt =fuse_mnt_resolve_path (mo->msgtag, mntpt); +if (!res_mntpt) { + error =EACCES ; + fprintf(stderr, "%s: %s: Could not resolve path to mount point.\n",Lines of this file are not shown. View the raw message.
The rest of this diff is not shown. View the raw message.
Part of the diff is not shown here. View the raw message.
Re: [PATCH 02/13] mount_service: add systemd socket service mounting helper
Quoted 665 lines · Darrick J. Wong
Quoted 664 lines · Darrick J. Wong
From: Darrick J. Wong <[email protected]>
Create a mount helper program that can start a fuse server that runs as a socket-based systemd service, and a new libfuse module to wrap all the details of communicating between the mount helper and the containerized fuse server.
This enables untrusted ext4 mounts via systemd service containers, which avoids the problem of malicious filesystems compromising the integrity of the running kernel through memory corruption.
In theory this could also be supported via inetd and clones, though the author hasn't found one that supports
AF_UNIXsockets.Signed-off-by "Darrick J. Wong" <[email protected]>--- include/fuse_service.h | 243 ++++ include/fuse_service_priv.h | 160 ++ lib/mount_common_i.h | 3 util/mount_service.h | 40 + .github/workflows/install-ubuntu-dependencies.sh | 4 doc/fuservicemount3.8 | 24 doc/meson.build | 3 include/meson.build | 4 lib/fuse_service.c | 1205 +++++++++++++++++++ lib/fuse_service_stub.c | 106 ++ lib/fuse_versionscript | 17 lib/helper.c | 51 + lib/meson.build | 17 lib/mount.c | 12 meson.build | 34 + meson_options.txt | 9 util/fuservicemount.c | 18 util/meson.build | 9 util/mount_service.c | 1427 ++++++++++++++++++++++ 19 files changed, 3384 insertions(+), 2 deletions(-) create mode 100644 include/fuse_service.h create mode 100644 include/fuse_service_priv.h create mode 100644 util/mount_service.h create mode 100644 doc/fuservicemount3.8 create mode 100644 lib/fuse_service.c create mode 100644 lib/fuse_service_stub.c create mode 100644 util/fuservicemount.c create mode 100644 util/mount_service.cinclude/fuse_service.h new file +243
-0@@ -0,0 +1,243 @@ +/* +* FUSE: Filesystem in Userspace +* Copyright (C) 2025-2026 Oracle. +* Author: Darrick J. Wong <[email protected]> +* +* This program can be distributed under the terms of the GNU LGPLv2. +* See the file LGPL2.txt. +*/ +#ifndef FUSE_SERVICE_H_ +#define FUSE_SERVICE_H_ + +/** @file +* +* Low level API +* +* IMPORTANT: you should define FUSE_USE_VERSION before including this +* header. To use the newest API define it to 319 (recommended for any +* new application). +*/ + +#ifndef FUSE_USE_VERSION +#error FUSE_USE_VERSION not defined +#endif + +#include "fuse_common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if FUSE_MAKE_VERSION (3 ,19 ) <=FUSE_USE_VERSION + +struct fuse_service ; + +/** +* Accept a socket created by mount.service for information exchange. +* +* @param sfp pointer to pointer to a service context. The pointer will always +* be initialized by this function; use fuse_service_accepted to +* find out if the fuse server is actually running as a service. +* @return 0 on success, or negative errno on failure +*/ +int fuse_service_accept (struct fuse_service **sfp); + +/** +* Has the fuse server accepted a service context? +* +* @param sf service context +* @return true if it has, false if not +*/ +static inline bool fuse_service_accepted (struct fuse_service *sf) +{ +return sf !=NULL ; +} + +/** +* Will the mount service helper accept the allow_other option? +* +* @param sf service context +* @return true if it has, false if not +*/ +bool fuse_service_can_allow_other (struct fuse_service *sf); + +/** +* Release all resources associated with the service context. +* +* @param sfp service context +*/ +void fuse_service_release (struct fuse_service *sf); + +/** +* Destroy a service context and release all resources +* +* @param sfp pointer to pointer to a service context +*/ +void fuse_service_destroy (struct fuse_service **sfp); + +/** +* Append the command line arguments from the mount service helper to an +* existing fuse_args structure. The fuse_args should have been initialized +* with the argc and argv passed to main(). +* +* @param sfp service context +* @param args arguments to modify (input+output) +* @return 0 on success, or negative errno on failure +*/ +int fuse_service_append_args (struct fuse_service *sf,struct fuse_args *args); + +/** +* Generate the effective fuse server command line from the args structure. +* The args structure should be the outcome from fuse_service_append_args. +* The resulting string is suitable for setproctitle and must be freed by the +* callre. +* +* @param argc argument count passed to main() +* @param argv argument vector passed to main() +* @param args fuse args structure +* @return effective command line string, or NULL +*/ +char *fuse_service_cmdline (int argc,char *argv[],struct fuse_args *args); + +struct fuse_cmdline_opts ; + +/** +* Utility function to parse common options for simple file systems +* using the low-level API. A help text that describes the available +* options can be printed with `fuse_cmdline_help`. A single +* non-option argument is treated as the mountpoint. Multiple +* non-option arguments will result in an error. +* +* If neither -o subtype= or -o fsname= options are given, a new +* subtype option will be added and set to the basename of the program +* (the fsname will remain unset, and then defaults to "fuse"). +* +* Known options will be removed from *args*, unknown options will +* remain. The mountpoint will not be checked here; that is the job of +* mount.service. +* +* @param args argument vector (input+output) +* @param opts output argument for parsed options +* @return 0 on success, -1 on failure +*/ +int fuse_service_parse_cmdline_opts (struct fuse_args *args, +struct fuse_cmdline_opts *opts); + +/** +* Don't complain if this file cannot be opened. +*/ +#define FUSE_SERVICE_REQUEST_FILE_QUIET (1U << 0) + +/** +* Ask the mount.service helper to open a file on behalf of the fuse server. +* +* @param sf service context +* @param path the path to file +* @param open_flags O_ flags +* @param create_mode mode with which to create the file +* @param request_flags set of FUSE_SERVICE_REQUEST_* flags +* @return 0 on success, or negative errno on failure +*/ +int fuse_service_request_file (struct fuse_service *sf,const char *path, +int open_flags,mode_t create_mode, +unsigned int request_flags); + +/** +* Ask the mount.service helper to open a block device on behalf of the fuse +* server. +* +* @param sf service context +* @param path the path to file +* @param open_flags O_ flags +* @param create_mode mode with which to create the file +* @param request_flags set of FUSE_SERVICE_REQUEST_* flags +* @param block_size set the block device block size to this value +* @return 0 on success, or negative errno on failure +*/ +int fuse_service_request_blockdev (struct fuse_service *sf,const char *path, +int open_flags,mode_t create_mode, +unsigned int request_flags, +unsigned int block_size); + +/** +* Receive a file previously requested. +* +* @param sf service context +* @param path to file +* @fdp pointer to file descriptor, which will be set a non-negative file +* descriptor value on success, or negative errno on failure +* @return 0 on success, or negative errno on socket communication failure +*/ +int fuse_service_receive_file (struct fuse_service *sf, +const char *path,int *fdp); + +/** +* Prevent the mount.service server from sending us any more open files. +* +* @param sf service context +* @return 0 on success, or negative errno on failure +*/ +int fuse_service_finish_file_requests (struct fuse_service *sf); + +/** +* Require that the filesystem mount point have the expected file format +* (S_IFDIR/S_IFREG). Can be overridden when calling +* fuse_service_session_mount. +* +* @param sf service context +* @param expected_fmt expected mode (S_IFDIR/S_IFREG) for mount point, or 0 +* to skip checks +*/ +void fuse_service_expect_mount_format (struct fuse_service *sf, +mode_t expected_fmt); + +/** +* Bind a FUSE file system to the fuse session inside a fuse service process, +* then ask the mount.service helper to mount the filesystem for us. The fuse +* client will begin sending requests to the fuse server immediately after +* this. Do not call fuse_daemonize() when running as a fuse service. +* +* @param sf service context +* @param se fuse session +* @param expected_fmt expected mode (S_IFDIR/S_IFREG) for mount point, or 0 +* to skip checks +* @param opts command line options +* @return 0 on success, or negative errno on failure +*/ +int fuse_service_session_mount (struct fuse_service *sf,struct fuse_session *se, +mode_t expected_fmt, +struct fuse_cmdline_opts *opts); + +/** +* Ask the mount helper to unmount th e filesystem. +* +* @param sf service context +* @return 0 on success, or negative errno on failure +*/ +int fuse_service_session_unmount (struct fuse_service *sf); + +/** +* Bid farewell to the mount.service helper. It is still necessary to call +* fuse_service_destroy after this. +* +* @param sf service context +* @param exitcode fuse server process exit status +* @return 0 on success, or negative errno on failure +*/ +int fuse_service_send_goodbye (struct fuse_service *sf,int exitcode); + +/** +* Exit routine for a fuse server running as a systemd service. +* +* @param ret 0 for success, nonzero for service failure. +* @return a value to be passed to exit() or returned from main +*/ +int fuse_service_exit (int ret); + +#endif /* FUSE_USE_VERSION >= FUSE_MAKE_VERSION(3, 19) */ + +#ifdef __cplusplus +} +#endif + +#endif /* FUSE_SERVICE_H_ */ include/fuse_service_priv.h new file +160
-0@@ -0,0 +1,160 @@ +/* +* FUSE: Filesystem in Userspace +* Copyright (C) 2025-2026 Oracle. +* Author: Darrick J. Wong <[email protected]> +* +* This program can be distributed under the terms of the GNU LGPLv2. +* See the file LGPL2.txt. +*/ +#ifndef FUSE_SERVICE_PRIV_H_ +#define FUSE_SERVICE_PRIV_H_ + +/* All numeric fields are network order (big-endian) when going across the socket */ + +struct fuse_service_memfd_arg { +uint32_t pos; +uint32_t len; +}; + +struct fuse_service_memfd_argv { +uint32_t magic; +uint32_t argc; +}; + +#define FUSE_SERVICE_MAX_CMD_SIZE (65536) + +#define FUSE_SERVICE_ARGS_MAGIC 0x41524753/* ARGS */ + +/* mount.service sends a hello to the server and it replies */ +#define FUSE_SERVICE_HELLO_CMD 0x53414654/* SAFT */ +#define FUSE_SERVICE_HELLO_REPLY 0x4c415354/* LAST */ + +/* fuse servers send commands to mount.service */ +#define FUSE_SERVICE_OPEN_CMD 0x4f50454e/* OPEN */ +#define FUSE_SERVICE_OPEN_BDEV_CMD 0x42444556/* BDEV */ +#define FUSE_SERVICE_FSOPEN_CMD 0x54595045/* TYPE */ +#define FUSE_SERVICE_SOURCE_CMD 0x4e414d45/* NAME */ +#define FUSE_SERVICE_MNTOPTS_CMD 0x4f505453/* OPTS */ +#define FUSE_SERVICE_MNTPT_CMD 0x4d4e5450/* MNTP */ +#define FUSE_SERVICE_MOUNT_CMD 0x444f4954/* DOIT */ +#define FUSE_SERVICE_UNMOUNT_CMD 0x554d4e54/* UMNT */ +#define FUSE_SERVICE_BYE_CMD 0x42594545/* BYEE */ + +/* mount.service sends replies to the fuse server */ +#define FUSE_SERVICE_OPEN_REPLY 0x46494c45/* FILE */ +#define FUSE_SERVICE_SIMPLE_REPLY 0x5245504c/* REPL */ + +struct fuse_service_packet { +uint32_t magic;/* FUSE_SERVICE_*_{CMD,REPLY} */ +}; + +#define FUSE_SERVICE_PROTO (1) +#define FUSE_SERVICE_MIN_PROTO (1) +#define FUSE_SERVICE_MAX_PROTO (1) + +#define FUSE_SERVICE_FLAG_ALLOW_OTHER (1U << 0) + +#define FUSE_SERVICE_FLAGS (FUSE_SERVICE_FLAG_ALLOW_OTHER) + +struct fuse_service_hello { +struct fuse_service_packet p; +uint16_t min_version; +uint16_t max_version; +uint32_t flags; +}; + +static inline bool check_null_endbyte (const void *p,size_t psz) +{ +return *((const char *)p + psz -1 ) ==0 ; +} + +struct fuse_service_hello_reply { +struct fuse_service_packet p; +uint16_t version; +uint16_t padding; +}; + +struct fuse_service_simple_reply { +struct fuse_service_packet p; +uint32_t error;/* positive errno */ +}; + +struct fuse_service_requested_file { +struct fuse_service_packet p; +uint32_t error;/* positive errno */ +char path[]; +}; + +static inline size_t sizeof_fuse_service_requested_file (size_t pathlen) +{ +return sizeof (struct fuse_service_requested_file ) + pathlen +1 ; +} + +#define FUSE_SERVICE_FSOPEN_FUSEBLK (1U << 0) +#define FUSE_SERVICE_FSOPEN_FLAGS (FUSE_SERVICE_FSOPEN_FUSEBLK) + +struct fuse_service_fsopen_command { +struct fuse_service_packet p; +uint32_t fsopen_flags; +}; + +#define FUSE_SERVICE_OPEN_QUIET (1U << 0) +#define FUSE_SERVICE_OPEN_FLAGS (FUSE_SERVICE_OPEN_QUIET) + +struct fuse_service_open_command { +struct fuse_service_packet p; +uint32_t open_flags; +uint32_t create_mode; +uint32_t request_flags; +uint32_t block_size; +char path[]; +}; + +static inline size_t sizeof_fuse_service_open_command (size_t pathlen) +{ +return sizeof (struct fuse_service_open_command ) + pathlen +1 ; +} + +struct fuse_service_string_command { +struct fuse_service_packet p; +char value[]; +}; + +static inline size_t sizeof_fuse_service_string_command (size_t len) +{ +return sizeof (struct fuse_service_string_command ) + len +1 ; +} + +struct fuse_service_mountpoint_command { +struct fuse_service_packet p; +uint16_t expected_fmt; +uint16_t padding; +char value[]; +}; + +static inline size_t sizeof_fuse_service_mountpoint_command (size_t len) +{ +return sizeof (struct fuse_service_mountpoint_command ) + len +1 ; +} + +struct fuse_service_bye_command { +struct fuse_service_packet p; +uint32_t exitcode; +}; + +struct fuse_service_mount_command { +struct fuse_service_packet p; +uint32_t ms_flags; +}; + +struct fuse_service_unmount_command { +struct fuse_service_packet p; +}; + +int fuse_parse_cmdline_service (struct fuse_args *args, +struct fuse_cmdline_opts *opts); + +#define FUSE_SERVICE_ARGV "argv" +#define FUSE_SERVICE_FUSEDEV "fusedev" + +#endif /* FUSE_SERVICE_PRIV_H_ */ lib/mount_common_i.h +3
-0@@ -14,5 +14,8 @@ struct mount_opts;char *fuse_mnt_build_source (const struct mount_opts *mo);char *fuse_mnt_build_type (const struct mount_opts *mo); +char *fuse_mnt_kernel_opts (const struct mount_opts *mo); +unsigned int fuse_mnt_flags (const struct mount_opts *mo); +#endif /* FUSE_MOUNT_COMMON_I_H_ */ util/mount_service.h new file +40
-0@@ -0,0 +1,40 @@ +/* +* FUSE: Filesystem in Userspace +* Copyright (C) 2025-2026 Oracle. +* Author: Darrick J. Wong <[email protected]> +* +* This program can be distributed under the terms of the GNU GPLv2. +* See the file GPL2.txt. +*/ +#ifndef MOUNT_SERVICE_H_ +#define MOUNT_SERVICE_H_ + +/** +* Magic value that means that we couldn't connect to the mount service, +* so the caller should try to fall back to traditional means. +*/ +#define MOUNT_SERVICE_FALLBACK_NEEDED (2) + +/** +* Connect to a fuse service socket and try to mount the filesystem as +* specified with the CLI arguments. +* +* @argc argument count +* @argv vector of argument strings +* @return EXIT_SUCCESS for success, EXIT_FAILURE if mount fails, or +* MOUNT_SERVICE_FALLBACK_NEEDED if no service is available. +*/ +int mount_service_main (int argc,char *argv[]); + +/** +* Return the fuse filesystem subtype from a full fuse filesystem type +* specification. IOWs, fuse.Y -> Y; fuseblk.Z -> Z; or A -> A. The returned +* pointer is within the caller's string. The subtype must not contain a path +* separator. +* +* @param fstype full fuse filesystem type +* @return fuse subtype +*/ +const char *mount_service_subtype (const char *fstype); + +#endif /* MOUNT_SERVICE_H_ */ .github/workflows/install-ubuntu-dependencies.sh +4
-0@@ -15,6 +15,8 @@ PACKAGES_CORE=( pkg-config python3 python3-pip + libsystemd-dev + systemd-dev ) PACKAGES_FULL=(@@ -31,6 +33,8 @@ PACKAGES_FULL=( libudev-dev:i386 pkg-config:i386 python3-pytest + libsystemd-dev + systemd-dev ) PACKAGES_CODECHECKER=(doc/fuservicemount3.8 new file +24
-0@@ -0,0 +1,24 @@ +.TH fuservicemount3 "8" +.SH NAME +fuservicemount3 \- mount a FUSE filesystem that runs as a system socket service +.SH SYNOPSIS +.B fuservicemount3 +.B source +.B mountpoint +.BI -t " fstype" +[ +.I options +] +.SH DESCRIPTION +Mount a filesystem using a FUSE server that runs as a socket service. +These servers can be contained using the platform's service management +framework. +.SH "AUTHORS" +.LP +The author of the fuse socket service code is Darrick J. Wong <[email protected]>. +Debian GNU/Linux distribution. +.SH SEE ALSO +.BR fusermount3 (1) +.BR fusermount (1) +.BR mount (8) +.BR fuse (4)doc/meson.build +3
-0@@ -2,3 +2,6 @@ if not platform.endswith('bsd') and platform != 'dragonfly' install_man('fusermount3.1', 'mount.fuse3.8') endif +if private_cfg.get('HAVE_SERVICEMOUNT', false) + install_man('fuservicemount3.8') +endifinclude/meson.build +4
-0@@ -1,4 +1,8 @@ libfuse_headers = [ 'fuse.h', 'fuse_common.h', 'fuse_lowlevel.h', 'fuse_opt.h', 'cuse_lowlevel.h', 'fuse_log.h' ] +if private_cfg.get('HAVE_SERVICEMOUNT', false) + libfuse_headers += [ 'fuse_service.h' ] +endif + install_headers(libfuse_headers, subdir: 'fuse3')lib/fuse_service.c new file +62
-0@@ -0,0 +1,1205 @@ +/* +* FUSE: Filesystem in Userspace +* Copyright (C) 2025-2026 Oracle. +* Author: Darrick J. Wong <[email protected]> +* +* Library functions to support fuse servers that can be run as "safe" systemd +* containers. +* +* This program can be distributed under the terms of the GNU LGPLv2. +* See the file LGPL2.txt +*/ + +#define _GNU_SOURCE +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <errno.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <systemd/sd-daemon.h> +#include <arpa/inet.h> +#include <limits.h> + +#include "fuse_config.h" +#include "fuse_i.h" +#include "fuse_service_priv.h" +#include "fuse_service.h" +#include "mount_common_i.h" + +struct fuse_service { +/* expected file format of the mount point */ +mode_t expected_fmt; + +/* socket fd */ +int sockfd; + +/* /dev/fuse device */ +int fusedevfd; + +/* memfd for cli arguments */ +int argvfd; + +/* do we own fusedevfd? */ +bool owns_fusedevfd; + +/* can we use allow_other? */ +bool allow_other; +}; + +static int __recv_fd (struct fuse_service *sf, +struct fuse_service_requested_file *buf, +ssize_t bufsize,int *fdp) +{ +struct iovec iov = { + .iov_base = buf, + .iov_len = bufsize, + }; +union {
Quoted 8 lines
lib/fuse_service.c line 70
Quoted 8 lines
lib/fuse_service.c new file +8
-0+struct cmsghdr cmsghdr; +char control[CMSG_SPACE (sizeof (int ))]; + } cmsgu; +struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen =1 , + .msg_control = cmsgu.control, + .msg_controllen =sizeof (cmsgu.control),
Looking at the dbus source code, apparently CMSG_SPACE can round up the buffer size for alignment reasons. Hence one is supposed to use CMSG_LEN here, not sizeof().
Quoted 32 lines
Quoted 10 lines
lib/fuse_service.c new file +10
-0+ }; +struct cmsghdr *cmsg; +ssize_t size; + +memset (&cmsgu,0 ,sizeof (cmsgu)); + + size =recvmsg (sf->sockfd, &msg,MSG_TRUNC |MSG_CMSG_CLOEXEC ); +if (size <0 ) { +int error = errno; +lib/fuse_service.c line 90
Quoted 10 lines
lib/fuse_service.c new file +10
-0+fuse_log (FUSE_LOG_ERR ,"fuse: service file reply: %s\n" , +strerror (error)); +return -error; + } +if (size > bufsize || + size < offsetof(struct fuse_service_requested_file , path)) { +fuse_log (FUSE_LOG_ERR ,"fuse: wrong service file reply size %zd, expected %zd\n" , + size, bufsize); +return -EBADMSG ; + }Apparently SMACK will truncate the control data to "prohibit" the fd transfer, so it's necessary to check for
MSG_CTRUNChere or else we end up with a garbage sockfd later on.if (msg.msg_flags & MSG_CTRUNC) { /* SMACK does this */ fuse_log(FUSE_LOG_ERR, "fuse: service file reply control data truncated; did an LSM deny SCM_RIGHTS?\n"); return -EBADMSG; }
Quoted 4 lines
https://lore.kernel.org/linux-fsdevel/[email protected]/
The caller probably ought to check that the passed-back fd is a valid file descriptor number.
Following up: yes, we should do that. The kernel could easily write a negative number into cmsgu.control (aka the fd number space) and we'd naïvely return that invalid fd to the caller. There doesn't seem to be much documentation on SCM_RIGHTS, but maybe I missed some? Anyway there doesn't seem to be any documented restriction on the kernel returning obviously garbage numbers.
--D
Quoted 2,945 lines
Quoted 2,944 lines
lib/fuse_service.c new file +1115
-0+ + cmsg =CMSG_FIRSTHDR (&msg); +if (!cmsg) { +/* no control message means mount.service sent us an error */ +return 0 ; + } +if (cmsg->cmsg_len !=CMSG_LEN (sizeof (int ))) { +fuse_log (FUSE_LOG_ERR , +"fuse: wrong service file reply control data size %zd, expected %zd\n" , + cmsg->cmsg_len,CMSG_LEN (sizeof (int ))); +return -EBADMSG ; + } +if (cmsg->cmsg_level !=SOL_SOCKET || cmsg->cmsg_type !=SCM_RIGHTS ) { +fuse_log (FUSE_LOG_ERR , +"fuse: wrong service file reply control data level %d type %d, expected %d and %d\n" , + cmsg->cmsg_level, cmsg->cmsg_type,SOL_SOCKET , +SCM_RIGHTS ); +return -EBADMSG ; + } + +memcpy (fdp, (int *)CMSG_DATA (cmsg),sizeof (int )); +return 0 ; +} + +static ssize_t __send_packet (struct fuse_service *sf,void *ptr,size_t len) +{ +struct iovec iov = { + .iov_base = ptr, + .iov_len = len, + }; +struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen =1 , + }; + +return sendmsg (sf->sockfd, &msg,MSG_EOR |MSG_NOSIGNAL ); +} + +static ssize_t __recv_packet (struct fuse_service *sf,void *ptr,size_t len) +{ +struct iovec iov = { + .iov_base = ptr, + .iov_len = len, + }; +struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen =1 , + }; + +return recvmsg (sf->sockfd, &msg,MSG_TRUNC ); +} + +int fuse_service_receive_file (struct fuse_service *sf,const char *path, +int *fdp) +{ +struct fuse_service_requested_file *req; +const size_t req_sz =sizeof_fuse_service_requested_file (strlen (path)); +int fd = -ENOENT ; +int ret; + + *fdp = -ENOENT ; + + req =calloc (1 , req_sz +1 ); +if (!req) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: alloc service file reply: %s\n" , +strerror (error)); +return -error; + } + + ret =__recv_fd (sf, req, req_sz, &fd); +if (ret) + goto out_req; + +if (ntohl (req->p.magic) !=FUSE_SERVICE_OPEN_REPLY ) { +fuse_log (FUSE_LOG_ERR ,"fuse: service file reply contains wrong magic!\n" ); + ret = -EBADMSG ; + goto out_close; + } +if (strcmp (req->path, path)) { +fuse_log (FUSE_LOG_ERR ,"fuse: `%s': not the requested service file, got `%s'\n" , + path, req->path); + ret = -EBADMSG ; + goto out_close; + } + +if (req->error) { + *fdp = -ntohl (req->error); + goto out_close; + } + +if (fd == -ENOENT ) +fuse_log (FUSE_LOG_ERR ,"fuse: did not receive `%s' but no error?\n" , + path); + + *fdp = fd; + goto out_req; + +out_close: +close (fd); +out_req: +free (req); +return ret; +} + +#define FUSE_SERVICE_REQUEST_FILE_FLAGS (FUSE_SERVICE_REQUEST_FILE_QUIET) + +static int fuse_service_request_path (struct fuse_service *sf,const char *path, +mode_t expected_fmt,int open_flags, +mode_t create_mode, +unsigned int request_flags, +unsigned int block_size) +{ +struct fuse_service_open_command *cmd; +const size_t cmdsz =sizeof_fuse_service_open_command (strlen (path)); +ssize_t size; +unsigned int rqflags =0 ; +int ret; + +if (request_flags & ~FUSE_SERVICE_REQUEST_FILE_FLAGS ) { +fuse_log (FUSE_LOG_ERR ,"fuse: invalid fuse service file request flags 0x%x\n" , + request_flags); +return -EINVAL ; + } + +if (request_flags &FUSE_SERVICE_REQUEST_FILE_QUIET ) + rqflags |=FUSE_SERVICE_OPEN_QUIET ; + + cmd =calloc (1 , cmdsz); +if (!cmd) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: alloc service file request: %s\n" , +strerror (error)); +return -error; + } +if (S_ISBLK (expected_fmt)) { + cmd->p.magic =htonl (FUSE_SERVICE_OPEN_BDEV_CMD ); + cmd->block_size =htonl (block_size); + }else { + cmd->p.magic =htonl (FUSE_SERVICE_OPEN_CMD ); + } + cmd->open_flags =htonl (open_flags); + cmd->create_mode =htonl (create_mode); + cmd->request_flags =htonl (rqflags); +strcpy (cmd->path, path); + + size =__send_packet (sf, cmd, cmdsz); +if (size <0 ) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: request service file: %s\n" , +strerror (error)); + ret = -error; + goto out_free; + } + + ret =0 ; +out_free: +free (cmd); +return ret; +} + +int fuse_service_request_file (struct fuse_service *sf,const char *path, +int open_flags,mode_t create_mode, +unsigned int request_flags) +{ +return fuse_service_request_path (sf, path,S_IFREG , open_flags, + create_mode, request_flags,0 ); +} + +int fuse_service_request_blockdev (struct fuse_service *sf,const char *path, +int open_flags,mode_t create_mode, +unsigned int request_flags, +unsigned int block_size) +{ +return fuse_service_request_path (sf, path,S_IFBLK , open_flags, + create_mode, request_flags, + block_size); +} + +int fuse_service_send_goodbye (struct fuse_service *sf,int exitcode) +{ +struct fuse_service_bye_command c = { + .p.magic =htonl (FUSE_SERVICE_BYE_CMD ), + .exitcode =htonl (exitcode), + }; +ssize_t size; + +/* already gone? */ +if (sf->sockfd <0 ) +return 0 ; + + size =__send_packet (sf, &c,sizeof (c)); +if (size <0 ) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: send service goodbye: %s\n" , +strerror (error)); +return -error; + } + +shutdown (sf->sockfd,SHUT_RDWR ); +close (sf->sockfd); + sf->sockfd =-1 ; +return 0 ; +} + +static int count_listen_fds (void ) +{ +char *listen_fds; +char *listen_pid; +char *p; +long l; + +/* +* No environment variables means we're not running as a system socket +* service, so we'll back out without logging anything. +*/ + listen_fds =getenv ("LISTEN_FDS" ); + listen_pid =getenv ("LISTEN_PID" ); +if (!listen_fds || !listen_pid) +return 0 ; + +/* +* LISTEN_PID is the pid of the process to which systemd thinks it gave +* the socket fd. Hopefully that's us. +*/ + errno =0 ; + l =strtol (listen_pid, &p,10 ); +if (errno || *p !=0 || l !=getpid ()) +return 0 ; + +/* +* LISTEN_FDS is the number of sockets that were opened in this +* process. +*/ + errno =0 ; + l =strtol (listen_fds, &p,10 ); +if (errno || *p !=0 || l >INT_MAX || l <0 ) +return 0 ; + +return l; +} + +static int check_sendbuf_size (int sockfd) +{ +const size_t min_size =sizeof_fuse_service_open_command (PATH_MAX ); +int sendbuf_size =-1 ; +socklen_t optlen =sizeof (sendbuf_size); +int ret; + +/* +* If we can't query the maximum send buffer length, just keep going. +* Most likely we won't be sending huge open commands, and if we do, +* the sendmsg will fail there too. +*/ + ret =getsockopt (sockfd,SOL_SOCKET ,SO_SNDBUF , &sendbuf_size, &optlen); +if (ret || sendbuf_size <0 ) +return 0 ; + +if (sendbuf_size >= min_size) +return 0 ; + +fuse_log (FUSE_LOG_ERR ,"max socket send buffer is %d, need at least %zu.\n" , + sendbuf_size, min_size); +return -ENOBUFS ; +} + +static int find_socket_fd (int nr_fds) +{ +struct stat stbuf; +struct sockaddr_un urk; +socklen_t urklen =sizeof (urk); +int ret; + +if (nr_fds !=1 ) { +fuse_log (FUSE_LOG_ERR ,"fuse: can only handle 1 service socket, got %d.\n" , + nr_fds); +return -E2BIG ; + } + + ret =fstat (SD_LISTEN_FDS_START , &stbuf); +if (ret) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: service socket: %s\n" , +strerror (error)); +return -error; + } + +if (!S_ISSOCK (stbuf.st_mode)) { +fuse_log (FUSE_LOG_ERR ,"fuse: expected service fd %d to be a socket\n" , +SD_LISTEN_FDS_START ); +return -ENOTSOCK ; + } + + ret =getsockname (SD_LISTEN_FDS_START , &urk, &urklen); +if (ret <0 ) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: service socket family: %s\n" , +strerror (error)); +return -error; + } + +if (ret >0 || urk.sun_family !=AF_UNIX ) { +/* +* If getsockname wanted to return more data than fits in a +* sockaddr_un, then it's obviously not an AF_UNIX socket. +* +* If it filled the buffer exactly but the family isn't AF_UNIX +* then we also return false. +*/ +fuse_log (FUSE_LOG_ERR ,"fuse: service socket is not AF_UNIX\n" ); +return -EAFNOSUPPORT ; + } + + ret =check_sendbuf_size (SD_LISTEN_FDS_START ); +if (ret) +return ret; + +return SD_LISTEN_FDS_START ; +} + +static int negotiate_hello (struct fuse_service *sf) +{ +struct fuse_service_hello hello = { }; +struct fuse_service_hello_reply reply = { + .p.magic =htonl (FUSE_SERVICE_HELLO_REPLY ), + .version =htons (FUSE_SERVICE_PROTO ), + }; +uint32_t flags; +ssize_t size; + + size =__recv_packet (sf, &hello,sizeof (hello)); +if (size <0 ) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: receive service hello: %s\n" , +strerror (error)); +return -error; + } +if (size !=sizeof (hello)) { +fuse_log (FUSE_LOG_ERR ,"fuse: wrong service hello size %zd, expected %zd\n" , + size,sizeof (hello)); +return -EBADMSG ; + } + +if (ntohl (hello.p.magic) !=FUSE_SERVICE_HELLO_CMD ) { +fuse_log (FUSE_LOG_ERR ,"fuse: service server did not send hello command\n" ); +return -EBADMSG ; + } + +if (ntohs (hello.min_version) <FUSE_SERVICE_MIN_PROTO ) { +fuse_log (FUSE_LOG_ERR ,"fuse: unsupported min service protocol version %u\n" , +ntohs (hello.min_version)); +return -EOPNOTSUPP ; + } + +if (ntohs (hello.max_version) >FUSE_SERVICE_MAX_PROTO ) { +fuse_log (FUSE_LOG_ERR ,"fuse: unsupported max service protocol version %u\n" , +ntohs (hello.min_version)); +return -EOPNOTSUPP ; + } + + flags =ntohl (hello.flags); +if (flags & ~FUSE_SERVICE_FLAGS ) { +fprintf (stderr,"fuse: invalid hello flags: 0x%x\n" , + flags & ~FUSE_SERVICE_FLAGS ); +return -EINVAL ; + } + +if (flags &FUSE_SERVICE_FLAG_ALLOW_OTHER ) + sf->allow_other = true; + + size =__send_packet (sf, &reply,sizeof (reply)); +if (size <0 ) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: service hello reply: %s\n" , +strerror (error)); +return -error; + } + +return 0 ; +} + +int fuse_service_accept (struct fuse_service **sfp) +{ +struct fuse_service *sf; +int nr_fds; +int sockfd; +int flags; +int ret =0 ; + + *sfp =NULL ; + + nr_fds =count_listen_fds (); +if (nr_fds ==0 ) +return 0 ; + +/* Find the socket that connects us to mount.service */ + sockfd =find_socket_fd (nr_fds); +if (sockfd <0 ) +return sockfd; + + flags =fcntl (sockfd,F_GETFD ); +if (flags <0 ) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: service socket getfd: %s\n" , +strerror (error)); +return -error; + } + +if (!(flags &FD_CLOEXEC )) { + ret =fcntl (sockfd,F_SETFD , flags |FD_CLOEXEC ); +if (ret) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: service socket set cloexec: %s\n" , +strerror (error)); +return -error; + } + } + + sf =calloc (1 ,sizeof (struct fuse_service )); +if (!sf) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: service alloc: %s\n" , +strerror (error)); +return -error; + } + sf->sockfd = sockfd; + + ret =negotiate_hello (sf); +if (ret) + goto out_sf; + +/* Receive the two critical sockets */ + ret =fuse_service_receive_file (sf,FUSE_SERVICE_ARGV , &sf->argvfd); +if (ret <0 ) + goto out_sockfd; +if (sf->argvfd <0 ) { +fuse_log (FUSE_LOG_ERR ,"fuse: service mount options file: %s\n" , +strerror (-sf->argvfd)); + ret = sf->argvfd; + goto out_sockfd; + } + + ret =fuse_service_receive_file (sf,FUSE_SERVICE_FUSEDEV , + &sf->fusedevfd); +if (ret <0 ) + goto out_argvfd; +if (sf->fusedevfd <0 ) { +fuse_log (FUSE_LOG_ERR ,"fuse: service fuse device: %s\n" , +strerror (-sf->fusedevfd)); + ret = sf->fusedevfd; + goto out_argvfd; + } + + sf->owns_fusedevfd = true; + *sfp = sf; +return 0 ; + +out_argvfd: +close (sf->argvfd); +out_sockfd: +shutdown (sf->sockfd,SHUT_RDWR ); +close (sf->sockfd); +out_sf: +free (sf); +return ret; +} + +bool fuse_service_can_allow_other (struct fuse_service *sf) +{ +return sf->allow_other; +} + +int fuse_service_append_args (struct fuse_service *sf, +struct fuse_args *existing_args) +{ +struct fuse_service_memfd_argv memfd_args = { }; +struct fuse_args new_args = { + .allocated =1 , + }; +char *str =NULL ; +off_t memfd_pos =0 ; +ssize_t received; +unsigned int i; +int ret; + +/* Figure out how many arguments we're getting from the mount helper. */ + received =pread (sf->argvfd, &memfd_args,sizeof (memfd_args),0 ); +if (received <0 ) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: service args file: %s\n" , +strerror (error)); +return -error; + } +if (received <sizeof (memfd_args)) { +fuse_log (FUSE_LOG_ERR ,"fuse: service args file length unreadable\n" ); +return -EBADMSG ; + } +if (ntohl (memfd_args.magic) !=FUSE_SERVICE_ARGS_MAGIC ) { +fuse_log (FUSE_LOG_ERR ,"fuse: service args file corrupt\n" ); +return -EBADMSG ; + } + memfd_args.magic =htonl (memfd_args.magic); + memfd_args.argc =htonl (memfd_args.argc); + memfd_pos +=sizeof (memfd_args); + +/* Allocate a new array of argv string pointers */ + new_args.argv =calloc (memfd_args.argc + existing_args->argc, +sizeof (char *)); +if (!new_args.argv) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: service new args: %s\n" , +strerror (error)); +return -error; + } + +/* +* Copy the fuse server's CLI arguments. We'll leave new_args.argv[0] +* unset for now, because we'll set it in the next step with the fstype +* that the mount helper sent us. +*/ + new_args.argc++; +for (i =1 ; i < existing_args->argc; i++) { +if (existing_args->allocated) { + new_args.argv[new_args.argc] = existing_args->argv[i]; + existing_args->argv[i] =NULL ; + }else { +char *dup =strdup (existing_args->argv[i]); + +if (!dup) { +int error = errno; + +fuse_log (FUSE_LOG_ERR , +"fuse: service duplicate existing args: %s\n" , +strerror (error)); + ret = -error; + goto out_new_args; + } + + new_args.argv[new_args.argc] = dup; + } + + new_args.argc++; + } + +/* Copy the rest of the arguments from the helper */ +for (i =0 ; i < memfd_args.argc; i++) { +struct fuse_service_memfd_arg memfd_arg = { }; + +/* Read argv iovec */ + received =pread (sf->argvfd, &memfd_arg,sizeof (memfd_arg), + memfd_pos); +if (received <0 ) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: service args file iovec read: %s\n" , +strerror (error)); + ret = -error; + goto out_new_args; + } +if (received <sizeof (struct fuse_service_memfd_arg )) { +fuse_log (FUSE_LOG_ERR , +"fuse: service args file argv[%u] iovec short read %zd" , + i, received); + ret = -EBADMSG ; + goto out_new_args; + } + memfd_arg.pos =htonl (memfd_arg.pos); + memfd_arg.len =htonl (memfd_arg.len); + memfd_pos +=sizeof (memfd_arg); + +/* read arg string from file */ + str =calloc (1 , memfd_arg.len +1 ); +if (!str) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: service arg alloc: %s\n" , +strerror (error)); + ret = -error; + goto out_new_args; + } + + received =pread (sf->argvfd, str, memfd_arg.len, memfd_arg.pos); +if (received <0 ) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: service args file read: %s\n" , +strerror (error)); + ret = -error; + goto out_str; + } +if (received < memfd_arg.len) { +fuse_log (FUSE_LOG_ERR ,"fuse: service args file argv[%u] short read %zd" , + i, received); + ret = -EBADMSG ; + goto out_str; + } + +/* move string into the args structure */ +if (i ==0 ) { +/* the first argument is the fs type */ + new_args.argv[0 ] = str; + }else { + new_args.argv[new_args.argc] = str; + new_args.argc++; + } + str =NULL ; + } + +/* drop existing args, move new args to existing args */ +fuse_opt_free_args (existing_args); +memcpy (existing_args, &new_args,sizeof (*existing_args)); + +close (sf->argvfd); + sf->argvfd =-1 ; + +return 0 ; + +out_str: +free (str); +out_new_args: +fuse_opt_free_args (&new_args); +return ret; +} + +#ifdef SO_PASSRIGHTS +int fuse_service_finish_file_requests (struct fuse_service *sf) +{ +int zero =0 ; +int ret; + +/* +* Don't let a malicious mount helper send us more fds. If the kernel +* doesn't know about this new(ish) option that's ok, we'll trust the +* servicemount helper. +*/ + ret =setsockopt (sf->sockfd,SOL_SOCKET ,SO_PASSRIGHTS , &zero, +sizeof (zero)); +if (ret && errno ==ENOPROTOOPT ) + ret =0 ; +if (ret) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: disabling fd passing: %s\n" , +strerror (error)); +return -error; + } + +return 0 ; +} +#else +int fuse_service_finish_file_requests (struct fuse_service *sf) +{ + (void )sf; +return 0 ; +} +#endif + +static int send_fsopen (struct fuse_service *sf,const char *fstype, +int *errorp) +{ +struct fuse_service_simple_reply reply = { }; +struct fuse_service_fsopen_command c = { + .p.magic =htonl (FUSE_SERVICE_FSOPEN_CMD ), + }; +ssize_t size; + +if (!strncmp (fstype,"fuseblk" ,7 )) + c.fsopen_flags |=htonl (FUSE_SERVICE_FSOPEN_FUSEBLK ); + + size =__send_packet (sf, &c,sizeof (c)); +if (size <0 ) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: send service fsopen command: %s\n" , +strerror (error)); +return -error; + } + + size =__recv_packet (sf, &reply,sizeof (reply)); +if (size <0 ) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: service fsopen reply: %s\n" , +strerror (error)); +return -error; + } +if (size !=sizeof (reply)) { +fuse_log (FUSE_LOG_ERR ,"fuse: wrong service fsopen reply size %zd, expected %zd\n" , + size,sizeof (reply)); +return -EBADMSG ; + } + +if (ntohl (reply.p.magic) !=FUSE_SERVICE_SIMPLE_REPLY ) { +fuse_log (FUSE_LOG_ERR ,"fuse: service fsopen reply contains wrong magic!\n" ); +return -EBADMSG ; + } + + *errorp =ntohl (reply.error); +return 0 ; +} + +static int send_string (struct fuse_service *sf,uint32_t command, +const char *value,int *errorp) +{ +struct fuse_service_simple_reply reply = { }; +struct fuse_service_string_command *cmd; +const size_t cmdsz =sizeof_fuse_service_string_command (strlen (value)); +ssize_t size; + + cmd =calloc (1 , cmdsz); +if (!cmd) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: alloc service string send: %s\n" , +strerror (error)); +return -error; + } + cmd->p.magic =htonl (command); +strcpy (cmd->value, value); + + size =__send_packet (sf, cmd, cmdsz); +if (size <0 ) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: send service string: %s\n" , +strerror (error)); +return -error; + } +free (cmd); + + size =__recv_packet (sf, &reply,sizeof (reply)); +if (size <0 ) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: service string reply: %s\n" , +strerror (error)); +return -error; + } +if (size !=sizeof (reply)) { +fuse_log (FUSE_LOG_ERR ,"fuse: wrong service string reply size %zd, expected %zd\n" , + size,sizeof (reply)); +return -EBADMSG ; + } + +if (ntohl (reply.p.magic) !=FUSE_SERVICE_SIMPLE_REPLY ) { +fuse_log (FUSE_LOG_ERR ,"fuse: service string reply contains wrong magic!\n" ); +return -EBADMSG ; + } + + *errorp =ntohl (reply.error); +return 0 ; +} + +static int send_mountpoint (struct fuse_service *sf,mode_t expected_fmt, +const char *value,int *errorp) +{ +struct fuse_service_simple_reply reply = { }; +struct fuse_service_mountpoint_command *cmd; +const size_t cmdsz = +sizeof_fuse_service_mountpoint_command (strlen (value)); +ssize_t size; + + cmd =calloc (1 , cmdsz); +if (!cmd) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: alloc service mountpoint send: %s\n" , +strerror (error)); +return -error; + } + cmd->p.magic =htonl (FUSE_SERVICE_MNTPT_CMD ); + cmd->expected_fmt =htons (expected_fmt); +strcpy (cmd->value, value); + + size =__send_packet (sf, cmd, cmdsz); +if (size <0 ) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: send service mountpoint: %s\n" , +strerror (error)); +return -error; + } +free (cmd); + + size =__recv_packet (sf, &reply,sizeof (reply)); +if (size <0 ) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: service mountpoint reply: %s\n" , +strerror (error)); +return -error; + } +if (size !=sizeof (reply)) { +fuse_log (FUSE_LOG_ERR , +"fuse: wrong service mountpoint reply size %zd, expected %zd\n" , + size,sizeof (reply)); +return -EBADMSG ; + } + +if (ntohl (reply.p.magic) !=FUSE_SERVICE_SIMPLE_REPLY ) { +fuse_log (FUSE_LOG_ERR ,"fuse: service mountpoint reply contains wrong magic!\n" ); +return -EBADMSG ; + } + + *errorp =ntohl (reply.error); +return 0 ; +} + +static int send_mount (struct fuse_service *sf,unsigned int ms_flags, +int *errorp) +{ +struct fuse_service_simple_reply reply = { }; +struct fuse_service_mount_command c = { + .p.magic =htonl (FUSE_SERVICE_MOUNT_CMD ), + .ms_flags =htonl (ms_flags), + }; +ssize_t size; + + size =__send_packet (sf, &c,sizeof (c)); +if (size <0 ) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: send service mount command: %s\n" , +strerror (error)); +return -error; + } + + size =__recv_packet (sf, &reply,sizeof (reply)); +if (size <0 ) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: service mount reply: %s\n" , +strerror (error)); +return -error; + } +if (size !=sizeof (reply)) { +fuse_log (FUSE_LOG_ERR ,"fuse: wrong service mount reply size %zd, expected %zd\n" , + size,sizeof (reply)); +return -EBADMSG ; + } + +if (ntohl (reply.p.magic) !=FUSE_SERVICE_SIMPLE_REPLY ) { +fuse_log (FUSE_LOG_ERR ,"fuse: service mount reply contains wrong magic!\n" ); +return -EBADMSG ; + } + + *errorp =ntohl (reply.error); +return 0 ; +} + +void fuse_service_expect_mount_format (struct fuse_service *sf, +mode_t expected_fmt) +{ + sf->expected_fmt = expected_fmt; +} + +int fuse_service_session_mount (struct fuse_service *sf,struct fuse_session *se, +mode_t expected_fmt, +struct fuse_cmdline_opts *opts) +{ +char *fstype =fuse_mnt_build_type (se->mo); +char *source =fuse_mnt_build_source (se->mo); +char *mntopts =fuse_mnt_kernel_opts (se->mo); +char path[32 ]; +int ret; +int error =0 ; + +if (!fstype || !source) { +fuse_log (FUSE_LOG_ERR ,"fuse: cannot allocate service strings\n" ); + ret = -ENOMEM ; + goto out_strings; + } + +if (!expected_fmt) + expected_fmt = sf->expected_fmt; + +/* +* The fuse session takes the fusedev fd if this succeeds. It is +* required to use the "/dev/fd/XX" format. +*/ +snprintf (path,sizeof (path),"/dev/fd/%d" , sf->fusedevfd); + errno =0 ; + ret =fuse_session_mount (se, path); +if (ret) { +/* Try to return richer errors than fuse_session_mount's -1 */ + ret = errno ? -errno : -EINVAL ; + goto out_strings; + } + sf->owns_fusedevfd = false; + + ret =send_fsopen (sf, fstype, &error); +if (ret) + goto out_strings; +if (error) { +fuse_log (FUSE_LOG_ERR ,"fuse: service fsopen: %s\n" , +strerror (error)); + ret = -error; + goto out_strings; + } + + ret =send_string (sf,FUSE_SERVICE_SOURCE_CMD , source, &error); +if (ret) + goto out_strings; +if (error) { +fuse_log (FUSE_LOG_ERR ,"fuse: service fs source: %s\n" , +strerror (error)); + ret = -error; + goto out_strings; + } + + ret =send_mountpoint (sf, expected_fmt, opts->mountpoint, &error); +if (ret) + goto out_strings; +if (error) { +fuse_log (FUSE_LOG_ERR ,"fuse: service fs mountpoint: %s\n" , +strerror (error)); + ret = -error; + goto out_strings; + } + +if (mntopts) { + ret =send_string (sf,FUSE_SERVICE_MNTOPTS_CMD , mntopts, + &error); +if (ret) + goto out_strings; +if (error) { +fuse_log (FUSE_LOG_ERR ,"fuse: service fs mount options: %s\n" , +strerror (error)); + ret = -error; + goto out_strings; + } + } + + ret =send_mount (sf,fuse_mnt_flags (se->mo), &error); +if (ret) + goto out_strings; +if (error) { +fuse_log (FUSE_LOG_ERR ,"fuse: service mount: %s\n" , +strerror (error)); + ret = -error; + goto out_strings; + } + +/* +* foreground mode is needed so that systemd actually tracks the +* service correctly and doesn't try to kill it; and so that +* stdout/stderr don't get zapped. Change to the root directory so +* that the caller needn't call fuse_daemonize(). +*/ + opts->foreground =1 ; + (void )chdir ("/" ); + +out_strings: +free (mntopts); +free (source); +free (fstype); +return ret; +} + +int fuse_service_session_unmount (struct fuse_service *sf) +{ +struct fuse_service_simple_reply reply = { }; +struct fuse_service_unmount_command c = { + .p.magic =htonl (FUSE_SERVICE_UNMOUNT_CMD ), + }; +ssize_t size; + +/* already gone? */ +if (sf->sockfd <0 ) +return 0 ; + + size =__send_packet (sf, &c,sizeof (c)); +if (size <0 ) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: send service unmount: %s\n" , +strerror (error)); +return -error; + } + + size =__recv_packet (sf, &reply,sizeof (reply)); +if (size <0 ) { +int error = errno; + +fuse_log (FUSE_LOG_ERR ,"fuse: service unmount reply: %s\n" , +strerror (error)); +return -error; + } +if (size !=sizeof (reply)) { +fuse_log (FUSE_LOG_ERR ,"fuse: wrong service unmount reply size %zd, expected %zd\n" , + size,sizeof (reply)); +return -EBADMSG ; + } + +if (ntohl (reply.p.magic) !=FUSE_SERVICE_SIMPLE_REPLY ) { +fuse_log (FUSE_LOG_ERR ,"fuse: service unmount reply contains wrong magic!\n" ); +return -EBADMSG ; + } + +if (reply.error) { +int error =ntohl (reply.error); + +fuse_log (FUSE_LOG_ERR ,"fuse: service unmount: %s\n" , +strerror (error)); +return -error; + } + +return 0 ; +} + +void fuse_service_release (struct fuse_service *sf) +{ +if (sf->owns_fusedevfd) +close (sf->fusedevfd); + sf->owns_fusedevfd = false; + sf->fusedevfd =-1 ; +close (sf->argvfd); + sf->argvfd =-1 ; +shutdown (sf->sockfd,SHUT_RDWR ); +close (sf->sockfd); + sf->sockfd =-1 ; +} + +void fuse_service_destroy (struct fuse_service **sfp) +{ +struct fuse_service *sf = *sfp; + +if (sf) { +fuse_service_release (*sfp); +free (sf); + } + + *sfp =NULL ; +} + +char *fuse_service_cmdline (int argc,char *argv[],struct fuse_args *args) +{ +char *p, *dst; +size_t len =1 ; +ssize_t ret; +char *argv0; +unsigned int i; + +/* Try to preserve argv[0] */ +if (argc >0 ) + argv0 = argv[0 ]; +else if (args->argc >0 ) + argv0 = args->argv[0 ]; +else +return NULL ; + +/* Pick up the alleged fstype from args->argv[0] */ +if (args->argc ==0 ) +return NULL ; + + len +=strlen (argv0) +1 ; + len +=3 ;/* " -t" */ +for (i =0 ; i < args->argc; i++) + len +=strlen (args->argv[i]) +1 ; + + p =calloc (1 , len); +if (!p) +return NULL ; + dst = p; + +/* Format: argv0 -t alleged_fstype [all other options...] */ + ret =sprintf (dst,"%s -t" , argv0); + dst += ret; +for (i =0 ; i < args->argc; i++) { + ret =sprintf (dst," %s" , args->argv[i]); + dst += ret; + } + +return p; +} + +int fuse_service_parse_cmdline_opts (struct fuse_args *args, +struct fuse_cmdline_opts *opts) +{ +return fuse_parse_cmdline_service (args, opts); +} + +int fuse_service_exit (int ret) +{ +/* +* We have to sleep 2 seconds here because journald uses the pid to +* connect our log messages to the systemd service. This is critical +* for capturing all the log messages if the service fails, because +* failure analysis tools use the service name to gather log messages +* for reporting. +*/ +sleep (2 ); + +/* +* If we're being run as a service, the return code must fit the LSB +* init script action error guidelines, which is to say that we +* compress all errors to 1 ("generic or unspecified error", LSB 5.0 +* section 22.2) and hope the admin will scan the log for what actually +* happened. +*/ +return ret !=0 ?EXIT_FAILURE :EXIT_SUCCESS ; +}lib/fuse_service_stub.c new file +106
-0@@ -0,0 +1,106 @@ +/* +* FUSE: Filesystem in Userspace +* Copyright (C) 2025-2026 Oracle. +* Author: Darrick J. Wong <[email protected]> +* +* Stub functions for platforms where we cannot have fuse servers run as "safe" +* systemd containers. +* +* This program can be distributed under the terms of the GNU LGPLv2. +* See the file LGPL2.txt +*/ + +/* we don't use any parameters at all */ +#pragma GCC diagnostic ignored "-Wunused-parameter" + +#define _GNU_SOURCE +#include <errno.h> + +#include "fuse_config.h" +#include "fuse_i.h" +#include "fuse_service.h" + +int fuse_service_receive_file (struct fuse_service *sf,const char *path, +int *fdp) +{ +return -EOPNOTSUPP ; +} + +int fuse_service_request_file (struct fuse_service *sf,const char *path, +int open_flags,mode_t create_mode, +unsigned int request_flags) +{ +return -EOPNOTSUPP ; +} + +int fuse_service_request_blockdev (struct fuse_service *sf,const char *path, +int open_flags,mode_t create_mode, +unsigned int request_flags, +unsigned int block_size) +{ +return -EOPNOTSUPP ; +} + +int fuse_service_send_goodbye (struct fuse_service *sf,int error) +{ +return -EOPNOTSUPP ; +} + +int fuse_service_accept (struct fuse_service **sfp) +{ + *sfp =NULL ; +return 0 ; +} + +int fuse_service_append_args (struct fuse_service *sf, +struct fuse_args *existing_args) +{ +return -EOPNOTSUPP ; +} + +char *fuse_service_cmdline (int argc,char *argv[],struct fuse_args *args) +{ +return NULL ; +} + +int fuse_service_finish_file_requests (struct fuse_service *sf) +{ +return -EOPNOTSUPP ; +} + +void fuse_service_expect_mount_format (struct fuse_service *sf, +mode_t expected_fmt) +{ +} + +int fuse_service_session_mount (struct fuse_service *sf,struct fuse_session *se, +mode_t expected_fmt, +struct fuse_cmdline_opts *opts) +{ +return -EOPNOTSUPP ; +} + +int fuse_service_session_unmount (struct fuse_service *sf) +{ +return -EOPNOTSUPP ; +} + +void fuse_service_release (struct fuse_service *sf) +{ +} + +void fuse_service_destroy (struct fuse_service **sfp) +{ + *sfp =NULL ; +} + +int fuse_service_parse_cmdline_opts (struct fuse_args *args, +struct fuse_cmdline_opts *opts) +{ +return -1 ; +} + +int fuse_service_exit (int ret) +{ +return ret; +}lib/fuse_versionscript +17
-0@@ -227,6 +227,23 @@ FUSE_3.19 { fuse_session_start_teardown_watchdog; fuse_session_stop_teardown_watchdog; fuse_lowlevel_notify_prune; + + fuse_service_accept; + fuse_service_append_args; + fuse_service_can_allow_other; + fuse_service_cmdline; + fuse_service_destroy; + fuse_service_exit; + fuse_service_expect_mount_format; + fuse_service_finish_file_requests; + fuse_service_parse_cmdline_opts; + fuse_service_receive_file; + fuse_service_release; + fuse_service_request_file; + fuse_service_request_blockdev; + fuse_service_send_goodbye; + fuse_service_session_mount; + fuse_service_session_unmount; } FUSE_3.18; # Local Variables:lib/helper.c +51
-0@@ -26,6 +26,11 @@#include <errno.h> #include <sys/param.h> +#ifdef HAVE_SERVICEMOUNT +# include <linux/types.h> +# include "fuse_service_priv.h" +#endif +#define FUSE_HELPER_OPT (t, p) \ { t, offsetof(struct fuse_cmdline_opts, p), 1 }@@ -228,6 +233,52 @@ int fuse_parse_cmdline_312(struct fuse_args *args,return 0 ; } +#ifdef HAVE_SERVICEMOUNT +static int fuse_helper_opt_proc_service (void *data,const char *arg,int key, +struct fuse_args *outargs) +{ + (void ) outargs; +struct fuse_cmdline_opts *opts = data; + +switch (key) { +case FUSE_OPT_KEY_NONOPT : +if (!opts->mountpoint) +return fuse_opt_add_opt (&opts->mountpoint, arg); + +fuse_log (FUSE_LOG_ERR ,"fuse: invalid argument `%s'\n" , arg); +return -1 ; +default : +/* Pass through unknown options */ +return 1 ; + } +} + +int fuse_parse_cmdline_service (struct fuse_args *args, +struct fuse_cmdline_opts *opts) +{ +memset (opts,0 ,sizeof (struct fuse_cmdline_opts )); + + opts->max_idle_threads =UINT_MAX ;/* new default in fuse version 3.12 */ + opts->max_threads =10 ; + +if (fuse_opt_parse (args, opts, fuse_helper_opts, + fuse_helper_opt_proc_service) ==-1 ) +return -1 ; + +/* +* *Linux*: if neither -o subtype nor -o fsname are specified, +* set subtype to program's basename. +* *FreeBSD*: if fsname is not specified, set to program's +* basename. +*/ +if (!opts->nodefault_subtype) +if (add_default_subtype (args->argv[0 ], args) ==-1 ) +return -1 ; + +return 0 ; +} +#endif +/** * struct fuse_cmdline_opts got extended in libfuse-3.12 */ lib/meson.build +15
-2@@ -10,6 +10,12 @@ else libfuse_sources += [ 'mount_bsd.c' ] endif +if private_cfg.get('HAVE_SERVICEMOUNT', false) + libfuse_sources += [ 'fuse_service.c' ] +else + libfuse_sources += [ 'fuse_service_stub.c' ] +endif + deps = [ thread_dep ] if private_cfg.get('HAVE_ICONV') libfuse_sources += [ 'modules/iconv.c' ]@@ -49,18 +55,25 @@ libfuse = library('fuse3', dependencies: deps, install: true, link_depends: 'fuse_versionscript', - c_args: [ '-DFUSE_USE_VERSION=317', + c_args: [ '-DFUSE_USE_VERSION=319', '-DFUSERMOUNT_DIR="@0@"'.format(fusermount_path) ], link_args: ['-Wl,--version-script,' + meson.current_source_dir() + '/fuse_versionscript' ]) +vars = [] +if private_cfg.get('HAVE_SERVICEMOUNT', false) + service_socket_dir = private_cfg.get_unquoted('FUSE_SERVICE_SOCKET_DIR', '') + vars += ['service_socket_dir=' + service_socket_dir] + vars += ['service_socket_perms=' + service_socket_perms] +endif pkg = import('pkgconfig') pkg.generate(libraries: [ libfuse, '-lpthread' ], libraries_private: '-ldl', version: meson.project_version(), name: 'fuse3', description: 'Filesystem in Userspace', - subdirs: 'fuse3') + subdirs: 'fuse3', + variables: vars) libfuse_dep = declare_dependency(include_directories: include_dirs, link_with: libfuse, dependencies: deps)lib/mount.c +12
-0@@ -750,3 +750,15 @@ char *fuse_mnt_build_type(const struct mount_opts *mo)return type; } + +char *fuse_mnt_kernel_opts (const struct mount_opts *mo) +{ +if (mo->kernel_opts) +return strdup (mo->kernel_opts); +return NULL ; +} + +unsigned int fuse_mnt_flags (const struct mount_opts *mo) +{ +return mo->flags; +}meson.build +34
-0@@ -69,6 +69,16 @@ args_default = [ '-D_GNU_SOURCE' ] # private_cfg = configuration_data() private_cfg.set_quoted('PACKAGE_VERSION', meson.project_version()) +service_socket_dir = get_option('service-socket-dir') +service_socket_perms = get_option('service-socket-perms') +if service_socket_dir == '' + service_socket_dir = '/run/filesystems' +endif +if service_socket_perms == '' + service_socket_perms = '0220' +endif +private_cfg.set_quoted('FUSE_SERVICE_SOCKET_DIR', service_socket_dir) +private_cfg.set('FUSE_SERVICE_SOCKET_PERMS', service_socket_perms) # Test for presence of some functions test_funcs = [ 'fork', 'fstatat', 'openat', 'readlinkat', 'pipe2',@@ -118,6 +128,13 @@ special_funcs = { return -1; } } + ''', + 'systemd_headers': ''' + #include <systemd/sd-daemon.h> + + int main(int argc, char *argv[]) { + return SD_LISTEN_FDS_START; + } ''' }@@ -180,6 +197,23 @@ if get_option('enable-io-uring') and liburing.found() and libnuma.found() endif endif +# Check for systemd support +systemd_system_unit_dir = get_option('systemd-system-unit-dir') +if systemd_system_unit_dir == '' + systemd = dependency('systemd', required: false) + if systemd.found() + systemd_system_unit_dir = systemd.get_variable(pkgconfig: 'systemd_system_unit_dir') + endif +endif + +if systemd_system_unit_dir == '' or private_cfg.get('HAVE_SYSTEMD_HEADERS', false) == false + warning('systemd service support will not be built') +else + private_cfg.set_quoted('SYSTEMD_SYSTEM_UNIT_DIR', systemd_system_unit_dir) + private_cfg.set('HAVE_SYSTEMD', true) + private_cfg.set('HAVE_SERVICEMOUNT', true) +endif + # # Compiler configuration #meson_options.txt +9
-0@@ -27,3 +27,12 @@ option('enable-usdt', type : 'boolean', value : false, option('enable-io-uring', type: 'boolean', value: true, description: 'Enable fuse-over-io-uring support') + +option('service-socket-dir', type : 'string', value : '', + description: 'Where to install fuse server sockets (if empty, /run/filesystems)') + +option('service-socket-perms', type : 'string', value : '', + description: 'Default fuse server socket permissions (if empty, 0220)') + +option('systemd-system-unit-dir', type : 'string', value : '', + description: 'Where to install systemd unit files (if empty, query pkg-config(1))')util/fuservicemount.c new file +18
-0@@ -0,0 +1,18 @@ +/* +* FUSE: Filesystem in Userspace +* Copyright (C) 2025-2026 Oracle. +* Author: Darrick J. Wong <[email protected]> +* +* This program can be distributed under the terms of the GNU GPLv2. +* See the file GPL2.txt. +* +* This program wraps the mounting of FUSE filesystems that run in systemd +*/ +#define _GNU_SOURCE +#include "fuse_config.h" +#include "mount_service.h" + +int main (int argc,char *argv[]) +{ +return mount_service_main (argc, argv); +}util/meson.build +9
-0@@ -6,6 +6,15 @@ executable('fusermount3', ['fusermount.c', '../lib/mount_util.c', '../lib/util.c install_dir: get_option('bindir'), c_args: '-DFUSE_CONF="@0@"'.format(fuseconf_path)) +if private_cfg.get('HAVE_SERVICEMOUNT', false) + executable('fuservicemount3', ['mount_service.c', 'fuservicemount.c', '../lib/mount_util.c'], + include_directories: include_dirs, + link_with: [ libfuse ], + install: true, + install_dir: get_option('sbindir'), + c_args: '-DFUSE_USE_VERSION=319') +endif + executable('mount.fuse3', ['mount.fuse.c'], include_directories: include_dirs, link_with: [ libfuse ],util/mount_service.c new file +1427
-0@@ -0,0 +1,1427 @@ +/* +* FUSE: Filesystem in Userspace +* Copyright (C) 2025-2026 Oracle. +* Author: Darrick J. Wong <[email protected]> +* +* This program can be distributed under the terms of the GNU GPLv2. +* See the file GPL2.txt. +* +* This program does the mounting of FUSE filesystems that run in systemd +*/ +#define _GNU_SOURCE +#include "fuse_config.h" +#include <stdint.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <stdbool.h> +#include <limits.h> +#include <arpa/inet.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/mman.h> +#include <sys/mount.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <linux/fs.h> + +#include "mount_util.h" +#include "util.h" +#include "fuse_i.h" +#include "fuse_service_priv.h" +#include "mount_service.h" + +struct mount_service { +/* prefix for printing error messages */ +const char *msgtag; + +/* fuse subtype based on -t cli argument */ +char *subtype; + +/* source argument to mount() */ +char *source; + +/* target argument (aka mountpoint) to mount() */ +char *mountpoint; + +/* mountpoint that we pass to mount() */ +char *real_mountpoint; + +/* resolved path to mountpoint that we use for mtab updates */ +char *resv_mountpoint; + +/* mount options */ +char *mntopts; + +/* socket fd */ +int sockfd; + +/* /dev/fuse device */ +int fusedevfd; + +/* memfd for cli arguments */ +int argvfd; + +/* fd for mount point */ +int mountfd; + +/* did we actually mount successfully? */ +bool mounted; + +/* has the fsopen command already been submitted? */ +bool fsopened; + +/* is this a fuseblk mount? */ +bool fuseblk; +}; + +static ssize_t __send_fd (struct mount_service *mo, +struct fuse_service_requested_file *req, +size_t req_sz,int fd) +{ +union { +struct cmsghdr cmsghdr; +char control[CMSG_SPACE (sizeof (int ))]; + } cmsgu; +struct iovec iov = { + .iov_base = req, + .iov_len = req_sz, + }; +struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen =1 , + .msg_control = cmsgu.control, + .msg_controllen =sizeof (cmsgu.control), + }; +struct cmsghdr *cmsg =CMSG_FIRSTHDR (&msg); + +if (!cmsg) { + errno =EINVAL ; +return -1 ; + } + +memset (&cmsgu,0 ,sizeof (cmsgu)); + cmsg->cmsg_len =CMSG_LEN (sizeof (int )); + cmsg->cmsg_level =SOL_SOCKET ; + cmsg->cmsg_type =SCM_RIGHTS ; + + *((int *)CMSG_DATA (cmsg)) = fd; + +return sendmsg (mo->sockfd, &msg,MSG_EOR |MSG_NOSIGNAL ); +} + +static ssize_t __send_packet (struct mount_service *mo,void *ptr,size_t len) +{ +struct iovec iov = { + .iov_base = ptr, + .iov_len = len, + }; +struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen =1 , + }; + +return sendmsg (mo->sockfd, &msg,MSG_EOR |MSG_NOSIGNAL ); +} + +static ssize_t __recv_packet_size (struct mount_service *mo) +{ +struct iovec iov = { }; +struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen =1 , + }; +return recvmsg (mo->sockfd, &msg,MSG_PEEK |MSG_TRUNC ); +} + +static ssize_t __recv_packet (struct mount_service *mo,void *ptr,size_t len) +{ +struct iovec iov = { + .iov_base = ptr, + .iov_len = len, + }; +struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen =1 , + }; + +return recvmsg (mo->sockfd, &msg,MSG_TRUNC ); +} + +/* +* Filter out the subtype of the filesystem (e.g. fuse.Y[.Z] -> Y[.Z]). The +* fuse server determines if it's appropriate to set the "blockdev" mount +* option (aka fuseblk). +*/ +const char *mount_service_subtype (const char *fstype) +{ +const char *subtype; + +if (!strncmp (fstype,"fuse." ,5 )) + subtype = fstype +5 ; +else if (!strncmp (fstype,"fuseblk." ,8 )) + subtype = fstype +8 ; +else + subtype = fstype; + +if (strchr (subtype,'/' ) !=NULL ) { +fprintf (stderr, +"%s: fs subtype cannot contain path separators\n" , + fstype); +return NULL ; + } + +return subtype; +} + +static int mount_service_init (struct mount_service *mo,int argc,char *argv[]) +{ +char *fstype =NULL ; +const char *subtype; +int i; + + mo->sockfd =-1 ; + mo->argvfd =-1 ; + mo->fusedevfd =-1 ; + mo->mountfd =-1 ; + +for (i =0 ; i < argc; i++) { +if (!strcmp (argv[i],"-t" ) && i +1 < argc) { + fstype = argv[i +1 ]; +break ; + } + } +if (!fstype) { +fprintf (stderr,"%s: cannot determine filesystem type.\n" , + mo->msgtag); +return -1 ; + } + + subtype =mount_service_subtype (fstype); +if (!subtype) +return -1 ; + + mo->subtype =strdup (subtype); +if (!mo->subtype) { +int error = errno; + +fprintf (stderr,"%s: cannot alloc memory for fs subtype: %s\n" , + mo->msgtag,strerror (error)); +return -1 ; + } + +return 0 ; +} + +#ifdef SO_PASSRIGHTS +static int try_drop_passrights (struct mount_service *mo,int sockfd) +{ +int zero =0 ; +int ret; + +/* +* Don't let a malicious mount helper send us any fds. We don't trust +* the fuse server not to pollute our fd namespace, so we'll end now. +*/ + ret =setsockopt (sockfd,SOL_SOCKET ,SO_PASSRIGHTS , &zero, +sizeof (zero)); +if (ret) { +fprintf (stderr,"%s: disabling fd passing: %s\n" , + mo->msgtag,strerror (errno)); +return -1 ; + } + +return 0 ; +} +#else +# define try_drop_passrights (...) (0) +#endif + +static int check_sendbuf_size (struct mount_service *mo,int sockfd) +{ +const size_t min_size =sizeof_fuse_service_open_command (PATH_MAX ); +int sendbuf_size =-1 ; +socklen_t optlen =sizeof (sendbuf_size); +int ret; + +/* +* If we can't query the maximum send buffer length, just keep going. +* Most likely we won't be sending huge open commands, and if we do, +* the sendmsg will fail there too. +*/ + ret =getsockopt (sockfd,SOL_SOCKET ,SO_SNDBUF , &sendbuf_size, &optlen); +if (ret || sendbuf_size <0 ) +return 0 ; + +if (sendbuf_size >= min_size) +return 0 ; + +fprintf (stderr,"%s: max socket send buffer is %d, need at least %zu.\n" , + mo->msgtag, sendbuf_size, min_size); +return MOUNT_SERVICE_FALLBACK_NEEDED ; +} + +static int mount_service_connect (struct mount_service *mo) +{ +struct sockaddr_un name = { + .sun_family =AF_UNIX , + }; +int sockfd; +ssize_t written; +int ret; + + written =snprintf (name.sun_path,sizeof (name.sun_path), +FUSE_SERVICE_SOCKET_DIR "/%s" , mo->subtype); +if (written >=sizeof (name.sun_path)) { +fprintf (stderr,"%s: filesystem type name `%s' is too long.\n" , + mo->msgtag, mo->subtype); +return -1 ; + } + + sockfd =socket (AF_UNIX ,SOCK_SEQPACKET |SOCK_CLOEXEC ,0 ); +if (sockfd <0 ) { +int error = errno; + +fprintf (stderr,"%s: opening %s service socket: %s\n" , + mo->msgtag, mo->subtype,strerror (error)); +return -1 ; + } + + ret =check_sendbuf_size (mo, sockfd); +if (ret) +return ret; + + ret =connect (sockfd, (const struct sockaddr *)&name,sizeof (name)); +if (ret && (errno ==ENOENT || errno ==ECONNREFUSED )) { +fprintf (stderr,"%s: no safe filesystem driver for %s available.\n" , + mo->msgtag, mo->subtype); +close (sockfd); +return MOUNT_SERVICE_FALLBACK_NEEDED ; + } +if (ret) { +int error = errno; + +fprintf (stderr,"%s: %s: %s\n" , + mo->msgtag, name.sun_path,strerror (error)); + goto out; + } + + ret =try_drop_passrights (mo, sockfd); +if (ret) + goto out; + + mo->sockfd = sockfd; +return 0 ; +out: +close (sockfd); +return -1 ; +} + +static int mount_service_send_hello (struct mount_service *mo) +{ +struct fuse_service_hello hello = { + .p.magic =htonl (FUSE_SERVICE_HELLO_CMD ), + .min_version =htons (FUSE_SERVICE_MIN_PROTO ), + .max_version =htons (FUSE_SERVICE_MAX_PROTO ), + }; +struct fuse_service_hello_reply reply = { }; +ssize_t size; + +if (getuid () ==0 ) + hello.flags |=htonl (FUSE_SERVICE_FLAG_ALLOW_OTHER ); + + size =__send_packet (mo, &hello,sizeof (hello)); +if (size <0 ) { +fprintf (stderr,"%s: send hello: %s\n" , + mo->msgtag,strerror (errno)); +return -1 ; + } + + size =__recv_packet (mo, &reply,sizeof (reply)); +if (size <0 ) { +fprintf (stderr,"%s: hello reply: %s\n" , + mo->msgtag,strerror (errno)); +return -1 ; + } +if (size !=sizeof (reply)) { +fprintf (stderr,"%s: wrong hello reply size %zd, expected %zu\n" , + mo->msgtag, size,sizeof (reply)); +return -1 ; + } + +if (ntohl (reply.p.magic) !=FUSE_SERVICE_HELLO_REPLY ) { +fprintf (stderr,"%s: %s service server did not reply to hello\n" , + mo->msgtag, mo->subtype); +return -1 ; + } + +if (ntohs (reply.version) <FUSE_SERVICE_MIN_PROTO || +ntohs (reply.version) >FUSE_SERVICE_MAX_PROTO ) { +fprintf (stderr,"%s: unsupported protocol version %u\n" , + mo->msgtag,ntohs (reply.version)); +return -1 ; + } + +if (reply.padding) { +fprintf (stderr,"%s: nonzero value in padding field\n" , + mo->msgtag); +return -1 ; + } + +return 0 ; +} + +static int mount_service_capture_arg (struct mount_service *mo, +struct fuse_service_memfd_argv *args, +const char *string,off_t *array_pos, +off_t *string_pos) +{ +const size_t string_len =strlen (string) +1 ; +struct fuse_service_memfd_arg arg = { + .pos =htonl (*string_pos), + .len =htonl (string_len), + }; +ssize_t written; + + written =pwrite (mo->argvfd, string, string_len, *string_pos); +if (written <0 ) { +fprintf (stderr,"%s: memfd argv write: %s\n" , + mo->msgtag,strerror (errno)); +return -1 ; + } +if (written < string_len) { +fprintf (stderr,"%s: memfd argv[%u] wrote %zd, expected %zu\n" , + mo->msgtag, args->argc, written, string_len); +return -1 ; + } + + written =pwrite (mo->argvfd, &arg,sizeof (arg), *array_pos); +if (written <0 ) { +fprintf (stderr,"%s: memfd arg write: %s\n" , + mo->msgtag,strerror (errno)); +return -1 ; + } +if (written <sizeof (arg)) { +fprintf (stderr,"%s: memfd arg[%u] wrote %zd, expected %zu\n" , + mo->msgtag, args->argc, written,sizeof (arg)); +return -1 ; + } + + args->argc++; + *string_pos += string_len; + *array_pos +=sizeof (arg); + +return 0 ; +} + +static int mount_service_capture_args (struct mount_service *mo,int argc, +char *argv[]) +{ +struct fuse_service_memfd_argv args = { + .magic =htonl (FUSE_SERVICE_ARGS_MAGIC ), + }; +off_t array_pos =sizeof (struct fuse_service_memfd_argv ); +off_t string_pos = array_pos + + (argc *sizeof (struct fuse_service_memfd_arg )); +ssize_t written; +int i; +int ret; + +if (argc <0 ) { +fprintf (stderr,"%s: argc cannot be negative\n" , + mo->msgtag); +return -1 ; + } + +/* +* Create the memfd in which we'll stash arguments, and set the write +* pointer for the names. +*/ + mo->argvfd =memfd_create ("fuse service argv" ,MFD_CLOEXEC ); +if (mo->argvfd <0 ) { +fprintf (stderr,"%s: argvfd create: %s\n" , + mo->msgtag,strerror (errno)); +return -1 ; + } + +/* +* Write the alleged subtype as if it were argv[0], then write the rest +* of the argv arguments. +*/ + ret =mount_service_capture_arg (mo, &args, mo->subtype, &array_pos, + &string_pos); +if (ret) +return ret; + +for (i =1 ; i < argc; i++) { +/* skip the -t(ype) argument */ +if (!strcmp (argv[i],"-t" ) && i +1 < argc) { + i++; +continue ; + } + + ret =mount_service_capture_arg (mo, &args, argv[i], + &array_pos, &string_pos); +if (ret) +return ret; + } + +/* Now write the header */ + args.argc =htonl (args.argc); + written =pwrite (mo->argvfd, &args,sizeof (args),0 ); +if (written <0 ) { +fprintf (stderr,"%s: memfd argv write: %s\n" , + mo->msgtag,strerror (errno)); +return -1 ; + } +if (written <sizeof (args)) { +fprintf (stderr,"%s: memfd argv wrote %zd, expected %zu\n" , + mo->msgtag, written,sizeof (args)); +return -1 ; + } + +return 0 ; +} + +static int mount_service_send_file (struct mount_service *mo, +const char *path,int fd) +{ +struct fuse_service_requested_file *req; +const size_t req_sz = +sizeof_fuse_service_requested_file (strlen (path)); +ssize_t written; +int ret =0 ; + + req =calloc (1 , req_sz); +if (!req) { +fprintf (stderr,"%s: alloc send file reply: %s\n" , + mo->msgtag,strerror (errno)); +return -1 ; + } + req->p.magic =htonl (FUSE_SERVICE_OPEN_REPLY ); + req->error =0 ; +strcpy (req->path, path); + + written =__send_fd (mo, req, req_sz, fd); +if (written <0 ) { +fprintf (stderr,"%s: send file reply: %s\n" , + mo->msgtag,strerror (errno)); + ret =-1 ; + goto out_req; + } +if (written < req_sz) { +fprintf (stderr,"%s: send file reply wrote %zd, expected %zu\n" , + mo->msgtag, written, req_sz); + ret =-1 ; + goto out_req; + } + +out_req: +free (req); +return ret; +} + +static int mount_service_send_file_error (struct mount_service *mo,int error, +const char *path) +{ +struct fuse_service_requested_file *req; +const size_t req_sz = +sizeof_fuse_service_requested_file (strlen (path)); +ssize_t written; +int ret =0 ; + + req =calloc (1 , req_sz); +if (!req) { +fprintf (stderr,"%s: alloc send file error: %s\n" , + mo->msgtag,strerror (errno)); +return -1 ; + } + req->p.magic =htonl (FUSE_SERVICE_OPEN_REPLY ); + req->error =htonl (error); +strcpy (req->path, path); + + written =__send_packet (mo, req, req_sz); +if (written <0 ) { +fprintf (stderr,"%s: send file error: %s\n" , + mo->msgtag,strerror (errno)); + ret =-1 ; + goto out_req; + } +if (written < req_sz) { +fprintf (stderr,"%s: send file error wrote %zd, expected %zu\n" , + mo->msgtag, written, req_sz); + ret =-1 ; + goto out_req; + } + +out_req: +free (req); +return ret; +} + +static int mount_service_send_required_files (struct mount_service *mo, +const char *fusedev) +{ +int ret; + + mo->fusedevfd =open (fusedev,O_RDWR |O_CLOEXEC ); +if (mo->fusedevfd <0 ) { +int error = errno; + +fprintf (stderr,"%s: %s: %s\n" , + mo->msgtag, fusedev,strerror (error)); +return -1 ; + } + + ret =mount_service_send_file (mo,FUSE_SERVICE_ARGV , mo->argvfd); +if (ret) + goto out_fusedevfd; + +close (mo->argvfd); + mo->argvfd =-1 ; + +return mount_service_send_file (mo,FUSE_SERVICE_FUSEDEV , + mo->fusedevfd); + +out_fusedevfd: +close (mo->fusedevfd); + mo->fusedevfd =-1 ; +return ret; +} + +static int mount_service_receive_command (struct mount_service *mo, +struct fuse_service_packet **commandp, +size_t *commandsz) +{ +struct fuse_service_packet *command; +ssize_t alleged_size, size; + + alleged_size =__recv_packet_size (mo); +if (alleged_size <0 ) { +fprintf (stderr,"%s: peek service command: %s\n" , + mo->msgtag,strerror (errno)); +return -1 ; + } +if (alleged_size ==0 ) { +/* fuse server probably exited early */ +fprintf (stderr,"%s: fuse server exited without saying goodbye!\n" , + mo->msgtag); +return -1 ; + } +if (alleged_size <sizeof (struct fuse_service_packet )) { +fprintf (stderr,"%s: wrong command packet size %zd, expected at least %zu\n" , + mo->msgtag, alleged_size, +sizeof (struct fuse_service_packet )); +return -1 ; + } +if (alleged_size >FUSE_SERVICE_MAX_CMD_SIZE ) { +fprintf (stderr,"%s: wrong command packet size %zd, expected less than %d\n" , + mo->msgtag, alleged_size,FUSE_SERVICE_MAX_CMD_SIZE ); +return -1 ; + } + + command =calloc (1 , alleged_size +1 ); +if (!command) { +fprintf (stderr,"%s: alloc service command: %s\n" , + mo->msgtag,strerror (errno)); +return -1 ; + } + + size =__recv_packet (mo, command, alleged_size); +if (size <0 ) { +fprintf (stderr,"%s: receive service command: %s\n" , + mo->msgtag,strerror (errno)); +free (command); +return -1 ; + } +if (size != alleged_size) { +fprintf (stderr,"%s: wrong service command size %zd, expected %zd\n" , + mo->msgtag, size, alleged_size); +free (command); +return -1 ; + } + + *commandp = command; + *commandsz = size; +return 0 ; +} + +static int mount_service_send_reply (struct mount_service *mo,int error) +{ +struct fuse_service_simple_reply reply = { + .p.magic =htonl (FUSE_SERVICE_SIMPLE_REPLY ), + .error =htonl (error), + }; +ssize_t size; + + size =__send_packet (mo, &reply,sizeof (reply)); +if (size <0 ) { +fprintf (stderr,"%s: send service reply: %s\n" , + mo->msgtag,strerror (errno)); +return -1 ; + } + +return 0 ; +} + +static int prepare_bdev (struct mount_service *mo, +struct fuse_service_open_command *oc,int fd) +{ +struct stat stbuf; +int ret; + + ret =fstat (fd, &stbuf); +if (ret) { +int error = errno; + +fprintf (stderr,"%s: %s: %s\n" , + mo->msgtag, oc->path,strerror (error)); +return -error; + } + +if (!S_ISBLK (stbuf.st_mode)) { +fprintf (stderr,"%s: %s: %s\n" , + mo->msgtag, oc->path,strerror (ENOTBLK )); +return -ENOTBLK ; + } + +if (oc->block_size) { +int block_size =ntohl (oc->block_size); + + ret =ioctl (fd,BLKBSZSET , &block_size); +if (ret) { +int error = errno; + +fprintf (stderr,"%s: %s: %s\n" , + mo->msgtag, oc->path,strerror (error)); +return -error; + } + } + +return 0 ; +} + +static int mount_service_open_path (struct mount_service *mo, +mode_t expected_fmt, +struct fuse_service_packet *p,size_t psz) +{ +struct fuse_service_open_command *oc = + container_of(p, struct fuse_service_open_command, p); +uint32_t request_flags; +int open_flags; +int ret; +int fd; + +if (psz <sizeof_fuse_service_open_command (1 )) { +fprintf (stderr,"%s: open command too small\n" , + mo->msgtag); +return mount_service_send_file_error (mo,EINVAL ,"?" ); + } + +if (!check_null_endbyte (p, psz)) { +fprintf (stderr,"%s: open command must be null terminated\n" , + mo->msgtag); +return mount_service_send_file_error (mo,EINVAL ,"?" ); + } + + request_flags =ntohl (oc->request_flags); +if (request_flags & ~FUSE_SERVICE_OPEN_FLAGS ) { +fprintf (stderr,"%s: open flags 0x%x not recognized\n" , + mo->msgtag, request_flags & ~FUSE_SERVICE_OPEN_FLAGS ); +return mount_service_send_file_error (mo,EINVAL , oc->path); + } + + open_flags =ntohl (oc->open_flags) |O_CLOEXEC ; + fd =open (oc->path, open_flags,ntohl (oc->create_mode)); +if (fd <0 ) { +int error = errno; + +/* +* Don't print a busy device error report because the +* filesystem might decide to retry. +*/ +if (error !=EBUSY && !(request_flags &FUSE_SERVICE_OPEN_QUIET )) +fprintf (stderr,"%s: %s: %s\n" , + mo->msgtag, oc->path,strerror (error)); +return mount_service_send_file_error (mo, error, oc->path); + } + +if (S_ISBLK (expected_fmt)) { + ret =prepare_bdev (mo, oc, fd); +if (ret <0 ) { +close (fd); +return mount_service_send_file_error (mo, -ret, + oc->path); + } + } + + ret =mount_service_send_file (mo, oc->path, fd); +close (fd); +return ret; +} + +static int mount_service_handle_open_cmd (struct mount_service *mo, +struct fuse_service_packet *p, +size_t psz) +{ +return mount_service_open_path (mo,0 , p, psz); +} + +static int mount_service_handle_open_bdev_cmd (struct mount_service *mo, +struct fuse_service_packet *p, +size_t psz) +{ +return mount_service_open_path (mo,S_IFBLK , p, psz); +} + +static inline const char *fsname (const struct mount_service *mo) +{ +return mo->fuseblk ?"fuseblk" :"fuse" ; +} + +static int mount_service_handle_fsopen_cmd (struct mount_service *mo, +const struct fuse_service_packet *p, +size_t psz) +{ +struct fuse_service_fsopen_command *oc = + container_of(p, struct fuse_service_fsopen_command, p); +uint32_t fsopen_flags; + +if (psz !=sizeof (struct fuse_service_fsopen_command )) { +fprintf (stderr,"%s: fsopen command wrong size %zu, expected %zu\n" , + mo->msgtag, psz,sizeof (*oc)); +return mount_service_send_reply (mo,EINVAL ); + } + +if (mo->fsopened) { +fprintf (stderr,"%s: fsopen command respecified\n" , + mo->msgtag); +return mount_service_send_reply (mo,EINVAL ); + } + + fsopen_flags =ntohl (oc->fsopen_flags); +if (fsopen_flags & ~FUSE_SERVICE_FSOPEN_FLAGS ) { +fprintf (stderr,"%s: unknown fsopen flags, 0x%x\n" , + mo->msgtag, fsopen_flags & ~FUSE_SERVICE_FSOPEN_FLAGS ); +return mount_service_send_reply (mo,EINVAL ); + } + +if (fsopen_flags &FUSE_SERVICE_FSOPEN_FUSEBLK ) { +if (getuid () !=0 ) { +fprintf (stderr,"%s: fuseblk requires root privilege\n" , + mo->msgtag); +return mount_service_send_reply (mo,EPERM ); + } + + mo->fuseblk = true; + } + mo->fsopened = true; + +return mount_service_send_reply (mo,0 ); +} + +static int mount_service_handle_source_cmd (struct mount_service *mo, +const struct fuse_service_packet *p, +size_t psz) +{ +struct fuse_service_string_command *oc = + container_of(p, struct fuse_service_string_command, p); + +if (psz <sizeof_fuse_service_string_command (1 )) { +fprintf (stderr,"%s: source command too small\n" , + mo->msgtag); +return mount_service_send_reply (mo,EINVAL ); + } + +if (!check_null_endbyte (p, psz)) { +fprintf (stderr,"%s: source command must be null terminated\n" , + mo->msgtag); +return mount_service_send_reply (mo,EINVAL ); + } + +if (mo->source) { +fprintf (stderr,"%s: source respecified!\n" , + mo->msgtag); +return mount_service_send_reply (mo,EINVAL ); + } + + mo->source =strdup (oc->value); +if (!mo->source) { +int error = errno; + +fprintf (stderr,"%s: alloc source string: %s\n" , + mo->msgtag,strerror (error)); +return mount_service_send_reply (mo, error); + } + +return mount_service_send_reply (mo,0 ); +} + +static int mount_service_handle_mntopts_cmd (struct mount_service *mo, +const struct fuse_service_packet *p, +size_t psz) +{ +struct fuse_service_string_command *oc = + container_of(p, struct fuse_service_string_command, p); + +if (psz <sizeof_fuse_service_string_command (1 )) { +fprintf (stderr,"%s: mount options command too small\n" , + mo->msgtag); +return mount_service_send_reply (mo,EINVAL ); + } + +if (!check_null_endbyte (p, psz)) { +fprintf (stderr,"%s: mount options command must be null terminated\n" , + mo->msgtag); +return mount_service_send_reply (mo,EINVAL ); + } + +if (mo->mntopts) { +fprintf (stderr,"%s: mount options respecified!\n" , + mo->msgtag); +return mount_service_send_reply (mo,EINVAL ); + } + + mo->mntopts =strdup (oc->value); +if (!mo->mntopts) { +int error = errno; + +fprintf (stderr,"%s: alloc mount options string: %s\n" , + mo->msgtag,strerror (error)); +return mount_service_send_reply (mo, error); + } + +return mount_service_send_reply (mo,0 ); +} + +static int attach_to_mountpoint (struct mount_service *mo,mode_t expected_fmt, +char *mntpt) +{ +struct stat stbuf; +char *res_mntpt; +int mountfd =-1 ; +int error; +int ret; + +/* +* Open the alleged mountpoint, make sure it's a dir or a file. +*/ + mountfd =open (mntpt,O_RDONLY |O_CLOEXEC ); +if (mountfd <0 ) { + error = errno; +fprintf (stderr,"%s: %s: %s\n" , mo->msgtag, mntpt, +strerror (error)); + goto out_error; + } + +/* +* Make sure we can access the mountpoint and that it's either a +* directory or a regular file. Linux can handle mounting atop special +* files, but we don't care to do such crazy things. +*/ + ret =fstat (mountfd, &stbuf); +if (ret) { + error = errno; +fprintf (stderr,"%s: %s: %s\n" , mo->msgtag, mntpt, +strerror (error)); + goto out_mountfd; + } + +if (!S_ISDIR (stbuf.st_mode) && !S_ISREG (stbuf.st_mode)) { + error =EACCES ; +fprintf (stderr,"%s: %s: Mount point must be directory or regular file.\n" , + mo->msgtag, mntpt); + goto out_mountfd; + } + +/* +* Resolve the (possibly relative) mountpoint path before chdir'ing +* onto it. +*/ + res_mntpt =fuse_mnt_resolve_path (mo->msgtag, mntpt); +if (!res_mntpt) { + error =EACCES ; + fprintf(stderr, "%s: %s: Could not resolve path to mount point.\n",Lines of this file are not shown. View the raw message.
The rest of this diff is not shown. View the raw message.
Part of the diff is not shown here. View the raw message.
PATCH 3/13 mount_service: create high level fuse helpers
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
Create a fuse_main wrapper for fuse services.
--- include/fuse.h | 34 +++++++++++++++ lib/fuse_versionscript | 1 lib/helper.c | 109 ++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 140 insertions(+), 4 deletions(-)
include/fuse.h +34 -0
@@ -1008,6 +1008,40 @@ static inline int fuse_main_fn(int argc, char *argv[],#define fuse_main (argc, argv, op, user_data) \ fuse_main_fn(argc, argv, op, user_data) +#if FUSE_MAKE_VERSION (3 ,19 ) <=FUSE_USE_VERSION +struct fuse_service ; +int fuse_service_main_real_versioned (struct fuse_service *service, +struct fuse_args *args, +const struct fuse_operations *op, +size_t op_size, +struct libfuse_version *version, +void *user_data); + +/** +* Same as fuse_service_main_fn, but takes its information from the mount +* service context and an fuse_args that has already had fuse_service_append_args +* applied to it. +*/ +static inline int fuse_service_main_fn (struct fuse_service *service, +struct fuse_args *args, +const struct fuse_operations *op, +void *user_data) +{ +struct libfuse_version version = { + .major =FUSE_MAJOR_VERSION , + .minor =FUSE_MINOR_VERSION , + .hotfix =FUSE_HOTFIX_VERSION , + .padding =FUSE_USE_VERSION , + }; + +return fuse_service_main_real_versioned (service, args, op, +sizeof (*(op)), &version, + user_data); +} +#define fuse_service_main (s, args, op, user_data) \ + fuse_service_main_fn(s, args, op, user_data) +#endif /* FUSE_USE_VERSION >= FUSE_MAKE_VERSION(3, 19) */ +/* ----------------------------------------------------------- * * More detailed API * * ----------------------------------------------------------- */
lib/fuse_versionscript +1 -0
@@ -236,6 +236,7 @@ FUSE_3.19 { fuse_service_exit; fuse_service_expect_mount_format; fuse_service_finish_file_requests; + fuse_service_main_real_versioned; fuse_service_parse_cmdline_opts; fuse_service_receive_file; fuse_service_release;
lib/helper.c +105 -4
127 lines not on this page · raw message
PATCH 4/13 mount_service: use the new mount api for the mount service
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
Use the new fsopen/fsmount system calls to mount the filesystem so that we get somewhat better diagnostics if something gets screwed up.
--- lib/fuse_i.h | 3 meson.build | 15 ++ util/mount_service.c | 332 +++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 346 insertions(+), 4 deletions(-)
lib/fuse_i.h +3 -0
@@ -215,6 +215,9 @@ struct fuse_chan *fuse_chan_get(struct fuse_chan *ch);*/ void fuse_chan_put (struct fuse_chan *ch); +/* Special return value for mount functions to indicate fallback to fusermount3 is needed */ +#define FUSE_MOUNT_FALLBACK_NEEDED (-2) +struct mount_opts *parse_mount_opts (struct fuse_args *args);void destroy_mount_opts (struct mount_opts *mo);void fuse_mount_version (void );
meson.build +15 -0
@@ -135,6 +135,21 @@ special_funcs = { int main(int argc, char *argv[]) { return SD_LISTEN_FDS_START; } + ''', + 'new_mount_api': ''' + #define _GNU_SOURCE + #include <sys/mount.h> + #include <linux/mount.h> + #include <unistd.h> + #include <fcntl.h> + + int main(void) { + int fsfd = fsopen("fuse", FSOPEN_CLOEXEC); + int res = fsconfig(fsfd, FSCONFIG_SET_STRING, "source", "test", 0); + int mntfd = fsmount(fsfd, FSMOUNT_CLOEXEC, 0); + res = move_mount(mntfd, "", AT_FDCWD, "/mnt", MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_T_EMPTY_PATH); + return 0; + } ''' }
util/mount_service.c +328 -4
425 lines not on this page · raw message
PATCH 5/13 mount_service: update mtab after a successful mount
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
Update "mtab" so that non-kernel mount options (e.g. "x-fubar=XXX") are recorded somewhere so that userspace utilities can pick that up. Note that this likely is not the venerable /etc/mtab, which has been a symlink to procfs for years. On a modern system, these non-kernel options end up /run/mount/utab.
But that's not a detail that libfuse has to worry about directly; it's really just calling mount -f(ake) to make the changes it wants. Old hats may remember the use of mount -f to update /etc/mtab after mounting the root filesystem.
--- include/fuse_service_priv.h | 1 lib/mount_common_i.h | 1 lib/fuse_service.c | 15 +++++++ lib/mount.c | 7 +++ util/mount_service.c | 95 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 119 insertions(+)
include/fuse_service_priv.h +1 -0
@@ -39,6 +39,7 @@ struct fuse_service_memfd_argv {#define FUSE_SERVICE_MOUNT_CMD 0x444f4954/* DOIT */ #define FUSE_SERVICE_UNMOUNT_CMD 0x554d4e54/* UMNT */ #define FUSE_SERVICE_BYE_CMD 0x42594545/* BYEE */ +#define FUSE_SERVICE_MTABOPTS_CMD 0x4d544142/* MTAB */ /* mount.service sends replies to the fuse server */ #define FUSE_SERVICE_OPEN_REPLY 0x46494c45/* FILE */
lib/mount_common_i.h +1 -0
@@ -15,6 +15,7 @@ struct mount_opts;char *fuse_mnt_build_source (const struct mount_opts *mo);char *fuse_mnt_build_type (const struct mount_opts *mo);char *fuse_mnt_kernel_opts (const struct mount_opts *mo); +char *fuse_mnt_mtab_opts (const struct mount_opts *mo);unsigned int fuse_mnt_flags (const struct mount_opts *mo);
lib/fuse_service.c +15 -0
@@ -964,6 +964,7 @@ int fuse_service_session_mount(struct fuse_service *sf, struct fuse_session *se,char *fstype =fuse_mnt_build_type (se->mo);char *source =fuse_mnt_build_source (se->mo);char *mntopts =fuse_mnt_kernel_opts (se->mo); +char *mtabopts =fuse_mnt_mtab_opts (se->mo);char path[32 ];int ret;int error =0 ;
@@ -1034,6 +1035,19 @@ int fuse_service_session_mount(struct fuse_service *sf, struct fuse_session *se, } } +if (mtabopts) { + ret =send_string (sf,FUSE_SERVICE_MTABOPTS_CMD , mtabopts, + &error); +if (ret) + goto out_strings; +if (error) { +fuse_log (FUSE_LOG_ERR ,"fuse: service fs mtab options: %s\n" , +strerror (error)); + ret = -error; + goto out_strings; + } + } + ret =send_mount (sf,fuse_mnt_flags (se->mo), &error);if (ret) goto out_strings;
@@ -1054,6 +1068,7 @@ int fuse_service_session_mount(struct fuse_service *sf, struct fuse_session *se, (void )chdir ("/" ); out_strings: +free (mtabopts);free (mntopts);free (source);free (fstype);
lib/mount.c +7 -0
@@ -758,6 +758,13 @@ char *fuse_mnt_kernel_opts(const struct mount_opts *mo)return NULL ; } +char *fuse_mnt_mtab_opts (const struct mount_opts *mo) +{ +if (mo->mtab_opts) +return strdup (mo->mtab_opts); +return NULL ; +} +unsigned int fuse_mnt_flags (const struct mount_opts *mo) {return mo->flags;
util/mount_service.c +95 -0
143 lines not on this page · raw message
PATCH 6/13 util: hoist the fuse.conf parsing and setuid mode enforcement code
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
Move all the code that parses fuse.conf into a separate file in util/ so that fuservicemount can read the same file, then add the security checks that occur when fusermount is trying to start up a filesystem but is not running as root. We'll want that for fusermount in a moment.
--- util/fuser_conf.h | 61 ++++++++ util/fuser_conf.c | 381 +++++++++++++++++++++++++++++++++++++++++++++++++++++ util/fusermount.c | 358 +------------------------------------------------- util/meson.build | 6 - 4 files changed, 453 insertions(+), 353 deletions(-) create mode 100644 util/fuser_conf.h create mode 100644 util/fuser_conf.c
util/fuser_conf.h new file +61 -0
@@ -0,0 +1,61 @@ +/* +* FUSE: Filesystem in Userspace +* Copyright (C) 2001-2007 Miklos Szeredi <[email protected]> +* +* This program can be distributed under the terms of the GNU LGPLv2. +* See the file LGPL2.txt. +*/ +#ifndef FUSER_CONF_H_ +#define FUSER_CONF_H_ + +#include <sys/vfs.h> +#include <sys/stat.h> + +extern int user_allow_other; +extern int mount_max; + +void unescape (char *buf); + +#ifdef GETMNTENT_NEEDS_UNESCAPING +#include <stdio.h> +#include <mntent.h> + +static inline struct mntent *GETMNTENT (FILE *stream) +{ +struct mntent *entp =getmntent (stream); +if (entp !=NULL ) { +unescape (entp->mnt_fsname); +unescape (entp->mnt_dir); +unescape (entp->mnt_type); +unescape (entp->mnt_opts); + } +return entp; +} +#else +#define GETMNTENT getmntent +#endif // GETMNTENT_NEEDS_UNESCAPING + +int count_fuse_fs (const char *progname); + +void read_conf (const char *progname); + +void drop_privs (void ); +void restore_privs (void ); + +int check_nonroot_mount_count (const char *progname); + +int check_nonroot_dir_access (const char *progname,const char *origmnt, +const char *mnt,const struct stat *stbuf); + +int check_nonroot_fstype (const char *progname,const struct statfs *fs_buf); + +struct mount_flags { +const char *opt; +unsigned long flag; +int on; +int safe; +}; + +extern const struct mount_flags mount_flags[]; + +#endif /* FUSER_CONF_H_ */
util/fuser_conf.c new file +381 -0
381 lines not on this page · raw message
util/fusermount.c +8 -350
428 lines not on this page · raw message
util/meson.build +3 -3
@@ -1,18 +1,18 @@ fuseconf_path = join_paths(get_option('prefix'), get_option('sysconfdir'), 'fuse.conf') -executable('fusermount3', ['fusermount.c', '../lib/mount_util.c', '../lib/util.c'], +executable('fusermount3', ['fusermount.c', '../lib/mount_util.c', '../lib/util.c', 'fuser_conf.c'], include_directories: include_dirs, install: true, install_dir: get_option('bindir'), c_args: '-DFUSE_CONF="@0@"'.format(fuseconf_path)) if private_cfg.get('HAVE_SERVICEMOUNT', false) - executable('fuservicemount3', ['mount_service.c', 'fuservicemount.c', '../lib/mount_util.c'], + executable('fuservicemount3', ['mount_service.c', 'fuservicemount.c', '../lib/mount_util.c', 'fuser_conf.c'], include_directories: include_dirs, link_with: [ libfuse ], install: true, install_dir: get_option('sbindir'), - c_args: '-DFUSE_USE_VERSION=319') + c_args: ['-DFUSE_USE_VERSION=319', '-DFUSE_CONF="@0@"'.format(fuseconf_path)]) endif executable('mount.fuse3', ['mount.fuse.c'],
Re: [PATCH 06/13] util: hoist the fuse.conf parsing and setuid mode enforcement code
Quoted 94 lines · Darrick J. Wong
From: Darrick J. Wong <[email protected]>
Move all the code that parses fuse.conf into a separate file in util/ so that fuservicemount can read the same file, then add the security checks that occur when fusermount is trying to start up a filesystem but is not running as root. We'll want that for fusermount in a moment.
Signed-off-by "Darrick J. Wong" <[email protected]>--- util/fuser_conf.h | 61 ++++++++ util/fuser_conf.c | 381 +++++++++++++++++++++++++++++++++++++++++++++++++++++ util/fusermount.c | 358 +------------------------------------------------- util/meson.build | 6 - 4 files changed, 453 insertions(+), 353 deletions(-) create mode 100644 util/fuser_conf.h create mode 100644 util/fuser_conf.cutil/fuser_conf.h new file +61
-0@@ -0,0 +1,61 @@ +/* +* FUSE: Filesystem in Userspace +* Copyright (C) 2001-2007 Miklos Szeredi <[email protected]> +* +* This program can be distributed under the terms of the GNU LGPLv2. +* See the file LGPL2.txt. +*/ +#ifndef FUSER_CONF_H_ +#define FUSER_CONF_H_ + +#include <sys/vfs.h> +#include <sys/stat.h> + +extern int user_allow_other; +extern int mount_max; + +void unescape (char *buf); + +#ifdef GETMNTENT_NEEDS_UNESCAPING +#include <stdio.h> +#include <mntent.h> + +static inline struct mntent *GETMNTENT (FILE *stream) +{ +struct mntent *entp =getmntent (stream); +if (entp !=NULL ) { +unescape (entp->mnt_fsname); +unescape (entp->mnt_dir); +unescape (entp->mnt_type); +unescape (entp->mnt_opts); + } +return entp; +} +#else +#define GETMNTENT getmntent +#endif // GETMNTENT_NEEDS_UNESCAPING + +int count_fuse_fs (const char *progname); + +void read_conf (const char *progname); + +void drop_privs (void ); +void restore_privs (void ); + +int check_nonroot_mount_count (const char *progname); + +int check_nonroot_dir_access (const char *progname,const char *origmnt, +const char *mnt,const struct stat *stbuf); + +int check_nonroot_fstype (const char *progname,const struct statfs *fs_buf); + +struct mount_flags { +const char *opt; +unsigned long flag; +int on; +int safe; +}; + +extern const struct mount_flags mount_flags[]; + +#endif /* FUSER_CONF_H_ */ util/fuser_conf.c new file +3
-0@@ -0,0 +1,381 @@ +/* +* FUSE: Filesystem in Userspace +* Copyright (C) 2001-2007 Miklos Szeredi <[email protected]>
util/fuser_conf.c line 13
Quoted 10 lines
util/fuser_conf.c new file +10
-0+* +* This program can be distributed under the terms of the GNU GPLv2. +* See the file GPL2.txt. +*/ +/* This program parses fuse.conf */ +#define _GNU_SOURCE +#include "fuse_config.h" +#include "mount_util.h" +#include "util.h" +#include "fuser_conf.h"
#include "fuse_mount_compat.h"
otherwise fails to compile
etc/fuse.conf"' -MD -MQ util/fusermount3.p/fuser_conf.c.o -MF util/fusermount3.p/fuser_conf.c.o.d -o util/fusermount3.p/fuser_conf.c.o -c ../util/fuser_conf.c../util/fuser_conf.c:357:13: error: use of undeclared identifier 'MS_RDONLY'
357 | {"rw", MS_RDONLY, 0, 1},
| ^../util/fuser_conf.c:358:13: error: use of undeclared identifier 'MS_RDONLY'
358 | {"ro", MS_RDONLY, 1, 1},
Quoted 837 lines
util/fuser_conf.c new file +368
-0+ +#include <string.h> +#include <stddef.h> +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <mntent.h> +#include <unistd.h> +#include <sys/fsuid.h> + +#if definedHAVE_LISTMOUNT +#include <linux/mount.h> +#include <syscall.h> +#include <stdint.h> +#endif + +int user_allow_other =0 ; +int mount_max =1000 ; +static uid_t oldfsuid; +static gid_t oldfsgid; + +// Older versions of musl libc don't unescape entries in /etc/mtab + +// unescapes octal sequences like \040 in-place +// That's ok, because unescaping can not extend the length of the string. +void unescape (char *buf) +{ +char *src = buf; +char *dest = buf; +while (1 ) { +char *next_src =strchrnul (src,'\\' ); +int offset = next_src - src; +memmove (dest, src, offset); + src = next_src; + dest += offset; + +if (*src =='\0' ) { + *dest = *src; +return ; + } + src++; + +if ('0' <= src[0 ] && src[0 ] <'2' && +'0' <= src[1 ] && src[1 ] <'8' && +'0' <= src[2 ] && src[2 ] <'8' ) { + *dest++ = (src[0 ] -'0' ) <<6 + | (src[1 ] -'0' ) <<3 + | (src[2 ] -'0' ) <<0 ; + src +=3 ; + }else if (src[0 ] =='\\' ) { + *dest++ ='\\' ; + src +=1 ; + }else { + *dest++ ='\\' ; + } + } +} + +#ifndef IGNORE_MTAB +static int count_fuse_fs_mtab (const char *progname) +{ +const struct mntent *entp; +int count =0 ; +const char *mtab = _PATH_MOUNTED; +FILE *fp =setmntent (mtab,"r" ); +if (fp ==NULL ) { +fprintf (stderr,"%s: failed to open %s: %s\n" , progname, mtab, +strerror (errno)); +return -1 ; + } +while ((entp =GETMNTENT (fp)) !=NULL ) { +if (strcmp (entp->mnt_type,"fuse" ) ==0 || +strncmp (entp->mnt_type,"fuse." ,5 ) ==0 ) + count ++; + } +endmntent (fp); +return count; +} + +#ifdef HAVE_LISTMOUNT +static int count_fuse_fs_ls_mnt (const char *progname) +{ +#define SMBUF_SIZE 1024 +#define MNT_ID_LEN 128 + +int fuse_count =0 ; +int n_mounts =0 ; +int ret =0 ; +uint64_t mnt_ids[MNT_ID_LEN ]; +unsigned char smbuf[SMBUF_SIZE ]; +struct mnt_id_req req = { + .size =sizeof (struct mnt_id_req ), + }; +struct statmount *sm; + +for (;;) { + req.mnt_id =LSMT_ROOT ; + + n_mounts =syscall (SYS_listmount, &req, &mnt_ids,MNT_ID_LEN ,0 ); +if (n_mounts ==-1 ) { +if (errno !=ENOSYS ) { +fprintf (stderr,"%s: failed to list mounts: %s\n" , progname, +strerror (errno)); + } +return -1 ; + } + +for (int i =0 ; i < n_mounts; i++) { + req.mnt_id = mnt_ids[i]; + req.param =STATMOUNT_FS_TYPE ; + ret =syscall (SYS_statmount, &req, &smbuf,SMBUF_SIZE ,0 ); +if (ret) { +if (errno ==ENOENT ) +continue ; + +fprintf (stderr,"%s: failed to stat mount %lld: %s\n" , progname, + req.mnt_id,strerror (errno)); +return -1 ; + } + + sm = (struct statmount *)smbuf; +if (sm->mask &STATMOUNT_FS_TYPE && +strcmp (&sm->str[sm->fs_type],"fuse" ) ==0 ) + fuse_count++; + } + +if (n_mounts <MNT_ID_LEN ) +break ; + req.param = mnt_ids[MNT_ID_LEN -1 ]; + } +return fuse_count; +} + +int count_fuse_fs (const char *progname) +{ +int count =count_fuse_fs_ls_mnt (progname); + +return count >=0 ? count :count_fuse_fs_mtab (progname); +} +#else +int count_fuse_fs (const char *progname) +{ +return count_fuse_fs_mtab (progname); +} +#endif /* HAVE_LISTMOUNT */ +#else +int count_fuse_fs (const char *progname) +{ +return 0 ; +} +#endif /* !IGNORE_MTAB */ + +static void strip_line (char *line) +{ +char *s =strchr (line,'#' ); +if (s !=NULL ) + s[0 ] ='\0' ; +for (s = line +strlen (line) -1 ; + s >= line &&isspace ((unsigned char ) *s); s--); + s[1 ] ='\0' ; +for (s = line;isspace ((unsigned char ) *s); s++); +if (s != line) +memmove (line, s,strlen (s)+1 ); +} + +static void parse_line (const char *line,int linenum,const char *progname) +{ +int tmp; +if (strcmp (line,"user_allow_other" ) ==0 ) + user_allow_other =1 ; +else if (sscanf (line,"mount_max = %i" , &tmp) ==1 ) + mount_max = tmp; +else if (line[0 ]) +fprintf (stderr, +"%s: unknown parameter in %s at line %i: '%s'\n" , + progname,FUSE_CONF , linenum, line); +} + +void read_conf (const char *progname) +{ +FILE *fp =fopen (FUSE_CONF ,"r" ); +if (fp !=NULL ) { +int linenum =1 ; +char line[256 ]; +int isnewline =1 ; +while (fgets (line,sizeof (line), fp) !=NULL ) { +if (isnewline) { +if (line[strlen (line)-1 ] =='\n' ) { +strip_line (line); +parse_line (line, linenum, progname); + }else { + isnewline =0 ; + } + }else if (line[strlen (line)-1 ] =='\n' ) { +fprintf (stderr,"%s: reading %s: line %i too long\n" , progname,FUSE_CONF , linenum); + + isnewline =1 ; + } +if (isnewline) + linenum ++; + } +if (!isnewline) { +fprintf (stderr,"%s: reading %s: missing newline at end of file\n" , progname,FUSE_CONF ); + + } +if (ferror (fp)) { +fprintf (stderr,"%s: reading %s: read failed\n" , progname,FUSE_CONF ); +exit (1 ); + } +fclose (fp); + }else if (errno !=ENOENT ) { +bool fatal = (errno !=EACCES && errno !=ELOOP && + errno !=ENAMETOOLONG && errno !=ENOTDIR && + errno !=EOVERFLOW ); +fprintf (stderr,"%s: failed to open %s: %s\n" , + progname,FUSE_CONF ,strerror (errno)); +if (fatal) +exit (1 ); + } +} + +void drop_privs (void ) +{ +if (getuid () !=0 ) { + oldfsuid =setfsuid (getuid ()); + oldfsgid =setfsgid (getgid ()); + } +} + +void restore_privs (void ) +{ +if (getuid () !=0 ) { +setfsuid (oldfsuid); +setfsgid (oldfsgid); + } +} + +int check_nonroot_mount_count (const char *progname) +{ +if (mount_max ==-1 ) +return 0 ; + +int mount_count =count_fuse_fs (progname); + +if (mount_count >= mount_max) { +fprintf (stderr, +"%s: too many FUSE filesystems mounted; mount_max=N can be set in %s\n" , + progname,FUSE_CONF ); +return -1 ; + } + +return 0 ; +} + +int check_nonroot_dir_access (const char *progname,const char *origmnt, +const char *mnt,const struct stat *stbuf) +{ +int res; + +if ((stbuf->st_mode &S_ISVTX ) && stbuf->st_uid !=getuid ()) { +fprintf (stderr,"%s: mountpoint %s not owned by user\n" , + progname, origmnt); +return -1 ; + } + + res =access (mnt,W_OK ); +if (res ==-1 ) { +fprintf (stderr,"%s: user has no write access to mountpoint %s\n" , + progname, origmnt); +return -1 ; + } + +return 0 ; +} + +int check_nonroot_fstype (const char *progname,const struct statfs *fs_buf) +{ +size_t i; + +/* Do not permit mounting over anything in procfs - it has a couple +* places to which we have "write access" without being supposed to be +* able to just put anything we want there. +* Luckily, without allow_other, we can't get other users to actually +* use any fake information we try to put there anyway. +* Use a whitelist to be safe. */ + +/* Define permitted filesystems for the mount target. This was +* originally the same list as used by the ecryptfs mount helper +* (https://bazaar.launchpad.net/~ecryptfs/ecryptfs/trunk/view/head:/src/utils/mount.ecryptfs_private.c#L225) +* but got expanded as we found more filesystems that needed to be +* overlaid. */ + typeof(fs_buf->f_type) f_type_whitelist[] = { +0x61756673 /* AUFS_SUPER_MAGIC */ , +0x00000187 /* AUTOFS_SUPER_MAGIC */ , +0xCA451A4E /* BCACHEFS_STATFS_MAGIC */ , +0x9123683E /* BTRFS_SUPER_MAGIC */ , +0x00C36400 /* CEPH_SUPER_MAGIC */ , +0xFF534D42 /* CIFS_MAGIC_NUMBER */ , +0x0000F15F /* ECRYPTFS_SUPER_MAGIC */ , +0X2011BAB0 /* EXFAT_SUPER_MAGIC */ , +0x0000EF53 /* EXT[234]_SUPER_MAGIC */ , +0xF2F52010 /* F2FS_SUPER_MAGIC */ , +0x65735546 /* FUSE_SUPER_MAGIC */ , +0x01161970 /* GFS2_MAGIC */ , +0x47504653 /* GPFS_SUPER_MAGIC */ , +0x0000482b /* HFSPLUS_SUPER_MAGIC */ , +0x000072B6 /* JFFS2_SUPER_MAGIC */ , +0x3153464A /* JFS_SUPER_MAGIC */ , +0x0BD00BD0 /* LL_SUPER_MAGIC */ , +0X00004D44 /* MSDOS_SUPER_MAGIC */ , +0x0000564C /* NCP_SUPER_MAGIC */ , +0x00006969 /* NFS_SUPER_MAGIC */ , +0x00003434 /* NILFS_SUPER_MAGIC */ , +0x5346544E /* NTFS_SB_MAGIC */ , +0x7366746E /* NTFS3_SUPER_MAGIC */ , +0x5346414f /* OPENAFS_SUPER_MAGIC */ , +0x794C7630 /* OVERLAYFS_SUPER_MAGIC */ , +0xAAD7AAEA /* PANFS_SUPER_MAGIC */ , +0x52654973 /* REISERFS_SUPER_MAGIC */ , +0xFE534D42 /* SMB2_SUPER_MAGIC */ , +0x73717368 /* SQUASHFS_MAGIC */ , +0x01021994 /* TMPFS_MAGIC */ , +0x24051905 /* UBIFS_SUPER_MAGIC */ , +0x18031977 /* WEKAFS_SUPER_MAGIC */ , +#if __SIZEOF_LONG__ > 4 +0x736675005346544e /* UFSD */ , +#endif +0x58465342 /* XFS_SB_MAGIC */ , +0x2FC12FC1 /* ZFS_SUPER_MAGIC */ , + 0x858458f6 /* RAMFS_MAGIC */, + }; +for (i =0 ; i <sizeof (f_type_whitelist)/sizeof (f_type_whitelist[0 ]); i++) { +if (f_type_whitelist[i] == fs_buf->f_type) +return 0 ; + } + +fprintf (stderr,"%s: mounting over filesystem type %#010lx is forbidden\n" , + progname, (unsigned long )fs_buf->f_type); +return -1 ; +} + +const struct mount_flags mount_flags[] = { + {"rw" ,MS_RDONLY ,0 ,1 }, + {"ro" ,MS_RDONLY ,1 ,1 }, + {"suid" ,MS_NOSUID ,0 ,0 }, + {"nosuid" ,MS_NOSUID ,1 ,1 }, + {"dev" ,MS_NODEV ,0 ,0 }, + {"nodev" ,MS_NODEV ,1 ,1 }, + {"exec" ,MS_NOEXEC ,0 ,1 }, + {"noexec" ,MS_NOEXEC ,1 ,1 }, + {"async" ,MS_SYNCHRONOUS ,0 ,1 }, + {"sync" ,MS_SYNCHRONOUS ,1 ,1 }, + {"atime" ,MS_NOATIME ,0 ,1 }, + {"noatime" ,MS_NOATIME ,1 ,1 }, + {"diratime" ,MS_NODIRATIME ,0 ,1 }, + {"nodiratime" ,MS_NODIRATIME ,1 ,1 }, + {"lazytime" ,MS_LAZYTIME ,1 ,1 }, + {"nolazytime" ,MS_LAZYTIME ,0 ,1 }, + {"relatime" ,MS_RELATIME ,1 ,1 }, + {"norelatime" ,MS_RELATIME ,0 ,1 }, + {"strictatime" ,MS_STRICTATIME ,1 ,1 }, + {"nostrictatime" ,MS_STRICTATIME ,0 ,1 }, + {"dirsync" ,MS_DIRSYNC ,1 ,1 }, + {"symfollow" ,MS_NOSYMFOLLOW ,0 ,1 }, + {"nosymfollow" ,MS_NOSYMFOLLOW ,1 ,1 }, + {NULL ,0 ,0 ,0 } +};util/fusermount.c +8
-350@@ -11,6 +11,7 @@#include "fuse_config.h" #include "mount_util.h" #include "util.h" +#include "fuser_conf.h" #include <stdio.h> #include <stdlib.h> @@ -50,63 +51,8 @@static const char *progname; -static int user_allow_other =0 ; -static int mount_max =1000 ; -static int auto_unmount =0 ; -#ifdef GETMNTENT_NEEDS_UNESCAPING -// Older versions of musl libc don't unescape entries in /etc/mtab - -// unescapes octal sequences like \040 in-place -// That's ok, because unescaping can not extend the length of the string. -static void unescape (char *buf) { -char *src = buf; -char *dest = buf; -while (1 ) { -char *next_src =strchrnul (src,'\\' ); -int offset = next_src - src; -memmove (dest, src, offset); - src = next_src; - dest += offset; - -if (*src =='\0' ) { - *dest = *src; -return ; - } - src++; - -if ('0' <= src[0 ] && src[0 ] <'2' && -'0' <= src[1 ] && src[1 ] <'8' && -'0' <= src[2 ] && src[2 ] <'8' ) { - *dest++ = (src[0 ] -'0' ) <<6 - | (src[1 ] -'0' ) <<3 - | (src[2 ] -'0' ) <<0 ; - src +=3 ; - }else if (src[0 ] =='\\' ) { - *dest++ ='\\' ; - src +=1 ; - }else { - *dest++ ='\\' ; - } - } -} - -static struct mntent *GETMNTENT (FILE *stream) -{ -struct mntent *entp =getmntent (stream); -if (entp !=NULL ) { -unescape (entp->mnt_fsname); -unescape (entp->mnt_dir); -unescape (entp->mnt_type); -unescape (entp->mnt_opts); - } -return entp; -} -#else -#define GETMNTENT getmntent -#endif // GETMNTENT_NEEDS_UNESCAPING -/* * Take a ',' separated option string and extract "x-" options */ @@ -188,25 +134,6 @@ static const char *get_user_name(void) } } -static uid_t oldfsuid; -static gid_t oldfsgid; - -static void drop_privs (void ) -{ -if (getuid () !=0 ) { - oldfsuid =setfsuid (getuid ()); - oldfsgid =setfsgid (getgid ()); - } -} - -static void restore_privs (void ) -{ -if (getuid () !=0 ) { -setfsuid (oldfsuid); -setfsgid (oldfsgid); - } -} -#ifndef IGNORE_MTAB /* * Make sure that /etc/mtab is checked and updated atomically @@ -568,100 +495,7 @@ static int unmount_fuse(const char *mnt, int quiet, int lazy) return res; } - -static int count_fuse_fs_mtab (void ) -{ -const struct mntent *entp; -int count =0 ; -const char *mtab = _PATH_MOUNTED; -FILE *fp =setmntent (mtab,"r" ); -if (fp ==NULL ) { -fprintf (stderr,"%s: failed to open %s: %s\n" , progname, mtab, -strerror (errno)); -return -1 ; - } -while ((entp =GETMNTENT (fp)) !=NULL ) { -if (strcmp (entp->mnt_type,"fuse" ) ==0 || -strncmp (entp->mnt_type,"fuse." ,5 ) ==0 ) - count ++; - } -endmntent (fp); -return count; -} - -#ifdef HAVE_LISTMOUNT -static int count_fuse_fs_ls_mnt (void ) -{ -#define SMBUF_SIZE 1024 -#define MNT_ID_LEN 128 - -int fuse_count =0 ; -int n_mounts =0 ; -int ret =0 ; -uint64_t mnt_ids[MNT_ID_LEN ]; -unsigned char smbuf[SMBUF_SIZE ]; -struct mnt_id_req req = { - .size =sizeof (struct mnt_id_req ), - }; -struct statmount *sm; - -for (;;) { - req.mnt_id =LSMT_ROOT ; - - n_mounts =syscall (SYS_listmount, &req, &mnt_ids,MNT_ID_LEN ,0 ); -if (n_mounts ==-1 ) { -if (errno !=ENOSYS ) { -fprintf (stderr,"%s: failed to list mounts: %s\n" , progname, -strerror (errno)); - } -return -1 ; - } - -for (int i =0 ; i < n_mounts; i++) { - req.mnt_id = mnt_ids[i]; - req.param =STATMOUNT_FS_TYPE ; - ret =syscall (SYS_statmount, &req, &smbuf,SMBUF_SIZE ,0 ); -if (ret) { -if (errno ==ENOENT ) -continue ; - -fprintf (stderr,"%s: failed to stat mount %lld: %s\n" , progname, - req.mnt_id,strerror (errno)); -return -1 ; - } - - sm = (struct statmount *)smbuf; -if (sm->mask &STATMOUNT_FS_TYPE && -strcmp (&sm->str[sm->fs_type],"fuse" ) ==0 ) - fuse_count++; - } - -if (n_mounts <MNT_ID_LEN ) -break ; - req.param = mnt_ids[MNT_ID_LEN -1 ]; - } -return fuse_count; -} - -static int count_fuse_fs (void ) -{ -int count =count_fuse_fs_ls_mnt (); - -return count >=0 ? count :count_fuse_fs_mtab (); -} -#else -static int count_fuse_fs (void ) -{ -return count_fuse_fs_mtab (); -} -#endif -#else /* IGNORE_MTAB */ -static int count_fuse_fs (void ) -{ -return 0 ; -} -static int add_mount (const char *source,const char *mnt,const char *type,const char *opts) {@@ -679,75 +513,6 @@ static int unmount_fuse(const char *mnt, int quiet, int lazy) }#endif /* IGNORE_MTAB */ -static void strip_line (char *line) -{ -char *s =strchr (line,'#' ); -if (s !=NULL ) - s[0 ] ='\0' ; -for (s = line +strlen (line) -1 ; - s >= line &&isspace ((unsigned char ) *s); s--); - s[1 ] ='\0' ; -for (s = line;isspace ((unsigned char ) *s); s++); -if (s != line) -memmove (line, s,strlen (s)+1 ); -} - -static void parse_line (const char *line,int linenum) -{ -int tmp; -if (strcmp (line,"user_allow_other" ) ==0 ) - user_allow_other =1 ; -else if (sscanf (line,"mount_max = %i" , &tmp) ==1 ) - mount_max = tmp; -else if (line[0 ]) -fprintf (stderr, -"%s: unknown parameter in %s at line %i: '%s'\n" , - progname,FUSE_CONF , linenum, line); -} - -static void read_conf (void ) -{ -FILE *fp =fopen (FUSE_CONF ,"r" ); -if (fp !=NULL ) { -int linenum =1 ; -char line[256 ]; -int isnewline =1 ; -while (fgets (line,sizeof (line), fp) !=NULL ) { -if (isnewline) { -if (line[strlen (line)-1 ] =='\n' ) { -strip_line (line); -parse_line (line, linenum); - }else { - isnewline =0 ; - } - }else if (line[strlen (line)-1 ] =='\n' ) { -fprintf (stderr,"%s: reading %s: line %i too long\n" , progname,FUSE_CONF , linenum); - - isnewline =1 ; - } -if (isnewline) - linenum ++; - } -if (!isnewline) { -fprintf (stderr,"%s: reading %s: missing newline at end of file\n" , progname,FUSE_CONF ); - - } -if (ferror (fp)) { -fprintf (stderr,"%s: reading %s: read failed\n" , progname,FUSE_CONF ); -exit (1 ); - } -fclose (fp); - }else if (errno !=ENOENT ) { -bool fatal = (errno !=EACCES && errno !=ELOOP && - errno !=ENAMETOOLONG && errno !=ENOTDIR && - errno !=EOVERFLOW ); -fprintf (stderr,"%s: failed to open %s: %s\n" , - progname,FUSE_CONF ,strerror (errno)); -if (fatal) -exit (1 ); - } -} -static int begins_with (const char *s,const char *beg) {if (strncmp (s, beg,strlen (beg)) ==0 )@@ -756,40 +521,6 @@ static int begins_with(const char *s, const char *beg)return 0 ; } -struct mount_flags { -const char *opt; -unsigned long flag; -int on; -int safe; -}; - -static struct mount_flags mount_flags[] = { - {"rw" ,MS_RDONLY ,0 ,1 }, - {"ro" ,MS_RDONLY ,1 ,1 }, - {"suid" ,MS_NOSUID ,0 ,0 }, - {"nosuid" ,MS_NOSUID ,1 ,1 }, - {"dev" ,MS_NODEV ,0 ,0 }, - {"nodev" ,MS_NODEV ,1 ,1 }, - {"exec" ,MS_NOEXEC ,0 ,1 }, - {"noexec" ,MS_NOEXEC ,1 ,1 }, - {"async" ,MS_SYNCHRONOUS ,0 ,1 }, - {"sync" ,MS_SYNCHRONOUS ,1 ,1 }, - {"atime" ,MS_NOATIME ,0 ,1 }, - {"noatime" ,MS_NOATIME ,1 ,1 }, - {"diratime" ,MS_NODIRATIME ,0 ,1 }, - {"nodiratime" ,MS_NODIRATIME ,1 ,1 }, - {"lazytime" ,MS_LAZYTIME ,1 ,1 }, - {"nolazytime" ,MS_LAZYTIME ,0 ,1 }, - {"relatime" ,MS_RELATIME ,1 ,1 }, - {"norelatime" ,MS_RELATIME ,0 ,1 }, - {"strictatime" ,MS_STRICTATIME ,1 ,1 }, - {"nostrictatime" ,MS_STRICTATIME ,0 ,1 }, - {"dirsync" ,MS_DIRSYNC ,1 ,1 }, - {"symfollow" ,MS_NOSYMFOLLOW ,0 ,1 }, - {"nosymfollow" ,MS_NOSYMFOLLOW ,1 ,1 }, - {NULL ,0 ,0 ,0 } -}; -static int find_mount_flag (const char *s,unsigned len,int *on,int *flag) {int i;@@ -1096,7 +827,6 @@ static int check_perm(const char **mntp, struct stat *stbuf, int *mountpoint_fd)const char *mnt = *mntp;const char *origmnt = mnt;struct statfs fs_buf; -size_t i; res =lstat (mnt, stbuf);if (res ==-1 ) {@@ -1126,18 +856,9 @@ static int check_perm(const char **mntp, struct stat *stbuf, int *mountpoint_fd)return -1 ; } -if ((stbuf->st_mode &S_ISVTX ) && stbuf->st_uid !=getuid ()) { -fprintf (stderr,"%s: mountpoint %s not owned by user\n" , - progname, origmnt); -return -1 ; - } - - res =access (mnt,W_OK ); -if (res ==-1 ) { -fprintf (stderr,"%s: user has no write access to mountpoint %s\n" , - progname, origmnt); -return -1 ; - } + res =check_nonroot_dir_access (progname, origmnt, mnt, stbuf); +if (res) +return res; }else if (S_ISREG (stbuf->st_mode)) {static char procfile[256 ]; *mountpoint_fd =open (mnt,O_WRONLY );@@ -1169,71 +890,13 @@ static int check_perm(const char **mntp, struct stat *stbuf, int *mountpoint_fd)return -1 ; } -/* Do not permit mounting over anything in procfs - it has a couple -* places to which we have "write access" without being supposed to be -* able to just put anything we want there. -* Luckily, without allow_other, we can't get other users to actually -* use any fake information we try to put there anyway. -* Use a whitelist to be safe. */ if (statfs (*mntp, &fs_buf)) {fprintf (stderr,"%s: failed to access mountpoint %s: %s\n" , progname, mnt,strerror (errno));return -1 ; } -/* Define permitted filesystems for the mount target. This was -* originally the same list as used by the ecryptfs mount helper -* (https://bazaar.launchpad.net/~ecryptfs/ecryptfs/trunk/view/head:/src/utils/mount.ecryptfs_private.c#L225) -* but got expanded as we found more filesystems that needed to be -* overlaid. */ - typeof(fs_buf.f_type) f_type_whitelist[] = { -0x61756673 /* AUFS_SUPER_MAGIC */ , -0x00000187 /* AUTOFS_SUPER_MAGIC */ , -0xCA451A4E /* BCACHEFS_STATFS_MAGIC */ , -0x9123683E /* BTRFS_SUPER_MAGIC */ , -0x00C36400 /* CEPH_SUPER_MAGIC */ , -0xFF534D42 /* CIFS_MAGIC_NUMBER */ , -0x0000F15F /* ECRYPTFS_SUPER_MAGIC */ , -0X2011BAB0 /* EXFAT_SUPER_MAGIC */ , -0x0000EF53 /* EXT[234]_SUPER_MAGIC */ , -0xF2F52010 /* F2FS_SUPER_MAGIC */ , -0x65735546 /* FUSE_SUPER_MAGIC */ , -0x01161970 /* GFS2_MAGIC */ , -0x47504653 /* GPFS_SUPER_MAGIC */ , -0x0000482b /* HFSPLUS_SUPER_MAGIC */ , -0x000072B6 /* JFFS2_SUPER_MAGIC */ , -0x3153464A /* JFS_SUPER_MAGIC */ , -0x0BD00BD0 /* LL_SUPER_MAGIC */ , -0X00004D44 /* MSDOS_SUPER_MAGIC */ , -0x0000564C /* NCP_SUPER_MAGIC */ , -0x00006969 /* NFS_SUPER_MAGIC */ , -0x00003434 /* NILFS_SUPER_MAGIC */ , -0x5346544E /* NTFS_SB_MAGIC */ , -0x7366746E /* NTFS3_SUPER_MAGIC */ , -0x5346414f /* OPENAFS_SUPER_MAGIC */ , -0x794C7630 /* OVERLAYFS_SUPER_MAGIC */ , -0xAAD7AAEA /* PANFS_SUPER_MAGIC */ , -0x52654973 /* REISERFS_SUPER_MAGIC */ , -0xFE534D42 /* SMB2_SUPER_MAGIC */ , -0x73717368 /* SQUASHFS_MAGIC */ , -0x01021994 /* TMPFS_MAGIC */ , -0x24051905 /* UBIFS_SUPER_MAGIC */ , -0x18031977 /* WEKAFS_SUPER_MAGIC */ , -#if __SIZEOF_LONG__ > 4 -0x736675005346544e /* UFSD */ , -#endif -0x58465342 /* XFS_SB_MAGIC */ , -0x2FC12FC1 /* ZFS_SUPER_MAGIC */ , - 0x858458f6 /* RAMFS_MAGIC */, - }; -for (i =0 ; i <sizeof (f_type_whitelist)/sizeof (f_type_whitelist[0 ]); i++) { -if (f_type_whitelist[i] == fs_buf.f_type) -return 0 ; - } - -fprintf (stderr,"%s: mounting over filesystem type %#010lx is forbidden\n" , - progname, (unsigned long )fs_buf.f_type); -return -1 ; +return check_nonroot_fstype (progname, &fs_buf); }static int open_fuse_device (const char *dev)@@ -1273,15 +936,10 @@ static int mount_fuse(const char *mnt, const char *opts, const char **type)return -1 ;drop_privs (); -read_conf (); +read_conf (progname); -if (getuid () !=0 && mount_max !=-1 ) { -int mount_count =count_fuse_fs (); -if (mount_count >= mount_max) { -fprintf (stderr,"%s: too many FUSE filesystems mounted; mount_max=N can be set in %s\n" , progname,FUSE_CONF ); - goto fail_close_fd; - } - } +if (getuid () !=0 &&check_nonroot_mount_count (progname) !=0 ) + goto fail_close_fd;// Extract any options starting with "x-" res=extract_x_options (opts, &do_mount_opts, &x_opts);util/meson.build +3
-3@@ -1,18 +1,18 @@ fuseconf_path = join_paths(get_option('prefix'), get_option('sysconfdir'), 'fuse.conf') -executable('fusermount3', ['fusermount.c', '../lib/mount_util.c', '../lib/util.c'], +executable('fusermount3', ['fusermount.c', '../lib/mount_util.c', '../lib/util.c', 'fuser_conf.c'], include_directories: include_dirs, install: true, install_dir: get_option('bindir'), c_args: '-DFUSE_CONF="@0@"'.format(fuseconf_path)) if private_cfg.get('HAVE_SERVICEMOUNT', false) - executable('fuservicemount3', ['mount_service.c', 'fuservicemount.c', '../lib/mount_util.c'], + executable('fuservicemount3', ['mount_service.c', 'fuservicemount.c', '../lib/mount_util.c', 'fuser_conf.c'], include_directories: include_dirs, link_with: [ libfuse ], install: true, install_dir: get_option('sbindir'), - c_args: '-DFUSE_USE_VERSION=319') + c_args: ['-DFUSE_USE_VERSION=319', '-DFUSE_CONF="@0@"'.format(fuseconf_path)]) endif executable('mount.fuse3', ['mount.fuse.c'],
Re: [PATCH 06/13] util: hoist the fuse.conf parsing and setuid mode enforcement code
Quoted 112 lines · Bernd Schubert
Quoted 94 lines · Darrick J. Wong
From: Darrick J. Wong <[email protected]>
Move all the code that parses fuse.conf into a separate file in util/ so that fuservicemount can read the same file, then add the security checks that occur when fusermount is trying to start up a filesystem but is not running as root. We'll want that for fusermount in a moment.
Signed-off-by "Darrick J. Wong" <[email protected]>--- util/fuser_conf.h | 61 ++++++++ util/fuser_conf.c | 381 +++++++++++++++++++++++++++++++++++++++++++++++++++++ util/fusermount.c | 358 +------------------------------------------------- util/meson.build | 6 - 4 files changed, 453 insertions(+), 353 deletions(-) create mode 100644 util/fuser_conf.h create mode 100644 util/fuser_conf.cutil/fuser_conf.h new file +61
-0@@ -0,0 +1,61 @@ +/* +* FUSE: Filesystem in Userspace +* Copyright (C) 2001-2007 Miklos Szeredi <[email protected]> +* +* This program can be distributed under the terms of the GNU LGPLv2. +* See the file LGPL2.txt. +*/ +#ifndef FUSER_CONF_H_ +#define FUSER_CONF_H_ + +#include <sys/vfs.h> +#include <sys/stat.h> + +extern int user_allow_other; +extern int mount_max; + +void unescape (char *buf); + +#ifdef GETMNTENT_NEEDS_UNESCAPING +#include <stdio.h> +#include <mntent.h> + +static inline struct mntent *GETMNTENT (FILE *stream) +{ +struct mntent *entp =getmntent (stream); +if (entp !=NULL ) { +unescape (entp->mnt_fsname); +unescape (entp->mnt_dir); +unescape (entp->mnt_type); +unescape (entp->mnt_opts); + } +return entp; +} +#else +#define GETMNTENT getmntent +#endif // GETMNTENT_NEEDS_UNESCAPING + +int count_fuse_fs (const char *progname); + +void read_conf (const char *progname); + +void drop_privs (void ); +void restore_privs (void ); + +int check_nonroot_mount_count (const char *progname); + +int check_nonroot_dir_access (const char *progname,const char *origmnt, +const char *mnt,const struct stat *stbuf); + +int check_nonroot_fstype (const char *progname,const struct statfs *fs_buf); + +struct mount_flags { +const char *opt; +unsigned long flag; +int on; +int safe; +}; + +extern const struct mount_flags mount_flags[]; + +#endif /* FUSER_CONF_H_ */ util/fuser_conf.c new file +3
-0@@ -0,0 +1,381 @@ +/* +* FUSE: Filesystem in Userspace +* Copyright (C) 2001-2007 Miklos Szeredi <[email protected]> util/fuser_conf.c line 13
Quoted 10 lines
util/fuser_conf.c new file +10
-0+* +* This program can be distributed under the terms of the GNU GPLv2. +* See the file GPL2.txt. +*/ +/* This program parses fuse.conf */ +#define _GNU_SOURCE +#include "fuse_config.h" +#include "mount_util.h" +#include "util.h" +#include "fuser_conf.h" #include "
fuse_mount_compat.h"otherwise fails to compile
Quoted 8 lines
etc/fuse.conf"' -MD -MQutil/fusermount3.p/fuser_conf.c.o-MF util/fusermount3.p/fuser_conf.c.o.d -outil/fusermount3.p/fuser_conf.c.o-c../util/fuser_conf.c../util/fuser_conf.c:357:13: error: use of undeclared identifier 'MS_RDONLY'357 | {"rw", MS_RDONLY, 0, 1}, | ^
../util/fuser_conf.c:358:13: error: use of undeclared identifier 'MS_RDONLY'
358 | {"ro",MS_RDONLY, 1, 1},
Fixed in my branch now too.
--D
Quoted 839 lines
Quoted 837 lines
util/fuser_conf.c new file +368
-0+ +#include <string.h> +#include <stddef.h> +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <mntent.h> +#include <unistd.h> +#include <sys/fsuid.h> + +#if definedHAVE_LISTMOUNT +#include <linux/mount.h> +#include <syscall.h> +#include <stdint.h> +#endif + +int user_allow_other =0 ; +int mount_max =1000 ; +static uid_t oldfsuid; +static gid_t oldfsgid; + +// Older versions of musl libc don't unescape entries in /etc/mtab + +// unescapes octal sequences like \040 in-place +// That's ok, because unescaping can not extend the length of the string. +void unescape (char *buf) +{ +char *src = buf; +char *dest = buf; +while (1 ) { +char *next_src =strchrnul (src,'\\' ); +int offset = next_src - src; +memmove (dest, src, offset); + src = next_src; + dest += offset; + +if (*src =='\0' ) { + *dest = *src; +return ; + } + src++; + +if ('0' <= src[0 ] && src[0 ] <'2' && +'0' <= src[1 ] && src[1 ] <'8' && +'0' <= src[2 ] && src[2 ] <'8' ) { + *dest++ = (src[0 ] -'0' ) <<6 + | (src[1 ] -'0' ) <<3 + | (src[2 ] -'0' ) <<0 ; + src +=3 ; + }else if (src[0 ] =='\\' ) { + *dest++ ='\\' ; + src +=1 ; + }else { + *dest++ ='\\' ; + } + } +} + +#ifndef IGNORE_MTAB +static int count_fuse_fs_mtab (const char *progname) +{ +const struct mntent *entp; +int count =0 ; +const char *mtab = _PATH_MOUNTED; +FILE *fp =setmntent (mtab,"r" ); +if (fp ==NULL ) { +fprintf (stderr,"%s: failed to open %s: %s\n" , progname, mtab, +strerror (errno)); +return -1 ; + } +while ((entp =GETMNTENT (fp)) !=NULL ) { +if (strcmp (entp->mnt_type,"fuse" ) ==0 || +strncmp (entp->mnt_type,"fuse." ,5 ) ==0 ) + count ++; + } +endmntent (fp); +return count; +} + +#ifdef HAVE_LISTMOUNT +static int count_fuse_fs_ls_mnt (const char *progname) +{ +#define SMBUF_SIZE 1024 +#define MNT_ID_LEN 128 + +int fuse_count =0 ; +int n_mounts =0 ; +int ret =0 ; +uint64_t mnt_ids[MNT_ID_LEN ]; +unsigned char smbuf[SMBUF_SIZE ]; +struct mnt_id_req req = { + .size =sizeof (struct mnt_id_req ), + }; +struct statmount *sm; + +for (;;) { + req.mnt_id =LSMT_ROOT ; + + n_mounts =syscall (SYS_listmount, &req, &mnt_ids,MNT_ID_LEN ,0 ); +if (n_mounts ==-1 ) { +if (errno !=ENOSYS ) { +fprintf (stderr,"%s: failed to list mounts: %s\n" , progname, +strerror (errno)); + } +return -1 ; + } + +for (int i =0 ; i < n_mounts; i++) { + req.mnt_id = mnt_ids[i]; + req.param =STATMOUNT_FS_TYPE ; + ret =syscall (SYS_statmount, &req, &smbuf,SMBUF_SIZE ,0 ); +if (ret) { +if (errno ==ENOENT ) +continue ; + +fprintf (stderr,"%s: failed to stat mount %lld: %s\n" , progname, + req.mnt_id,strerror (errno)); +return -1 ; + } + + sm = (struct statmount *)smbuf; +if (sm->mask &STATMOUNT_FS_TYPE && +strcmp (&sm->str[sm->fs_type],"fuse" ) ==0 ) + fuse_count++; + } + +if (n_mounts <MNT_ID_LEN ) +break ; + req.param = mnt_ids[MNT_ID_LEN -1 ]; + } +return fuse_count; +} + +int count_fuse_fs (const char *progname) +{ +int count =count_fuse_fs_ls_mnt (progname); + +return count >=0 ? count :count_fuse_fs_mtab (progname); +} +#else +int count_fuse_fs (const char *progname) +{ +return count_fuse_fs_mtab (progname); +} +#endif /* HAVE_LISTMOUNT */ +#else +int count_fuse_fs (const char *progname) +{ +return 0 ; +} +#endif /* !IGNORE_MTAB */ + +static void strip_line (char *line) +{ +char *s =strchr (line,'#' ); +if (s !=NULL ) + s[0 ] ='\0' ; +for (s = line +strlen (line) -1 ; + s >= line &&isspace ((unsigned char ) *s); s--); + s[1 ] ='\0' ; +for (s = line;isspace ((unsigned char ) *s); s++); +if (s != line) +memmove (line, s,strlen (s)+1 ); +} + +static void parse_line (const char *line,int linenum,const char *progname) +{ +int tmp; +if (strcmp (line,"user_allow_other" ) ==0 ) + user_allow_other =1 ; +else if (sscanf (line,"mount_max = %i" , &tmp) ==1 ) + mount_max = tmp; +else if (line[0 ]) +fprintf (stderr, +"%s: unknown parameter in %s at line %i: '%s'\n" , + progname,FUSE_CONF , linenum, line); +} + +void read_conf (const char *progname) +{ +FILE *fp =fopen (FUSE_CONF ,"r" ); +if (fp !=NULL ) { +int linenum =1 ; +char line[256 ]; +int isnewline =1 ; +while (fgets (line,sizeof (line), fp) !=NULL ) { +if (isnewline) { +if (line[strlen (line)-1 ] =='\n' ) { +strip_line (line); +parse_line (line, linenum, progname); + }else { + isnewline =0 ; + } + }else if (line[strlen (line)-1 ] =='\n' ) { +fprintf (stderr,"%s: reading %s: line %i too long\n" , progname,FUSE_CONF , linenum); + + isnewline =1 ; + } +if (isnewline) + linenum ++; + } +if (!isnewline) { +fprintf (stderr,"%s: reading %s: missing newline at end of file\n" , progname,FUSE_CONF ); + + } +if (ferror (fp)) { +fprintf (stderr,"%s: reading %s: read failed\n" , progname,FUSE_CONF ); +exit (1 ); + } +fclose (fp); + }else if (errno !=ENOENT ) { +bool fatal = (errno !=EACCES && errno !=ELOOP && + errno !=ENAMETOOLONG && errno !=ENOTDIR && + errno !=EOVERFLOW ); +fprintf (stderr,"%s: failed to open %s: %s\n" , + progname,FUSE_CONF ,strerror (errno)); +if (fatal) +exit (1 ); + } +} + +void drop_privs (void ) +{ +if (getuid () !=0 ) { + oldfsuid =setfsuid (getuid ()); + oldfsgid =setfsgid (getgid ()); + } +} + +void restore_privs (void ) +{ +if (getuid () !=0 ) { +setfsuid (oldfsuid); +setfsgid (oldfsgid); + } +} + +int check_nonroot_mount_count (const char *progname) +{ +if (mount_max ==-1 ) +return 0 ; + +int mount_count =count_fuse_fs (progname); + +if (mount_count >= mount_max) { +fprintf (stderr, +"%s: too many FUSE filesystems mounted; mount_max=N can be set in %s\n" , + progname,FUSE_CONF ); +return -1 ; + } + +return 0 ; +} + +int check_nonroot_dir_access (const char *progname,const char *origmnt, +const char *mnt,const struct stat *stbuf) +{ +int res; + +if ((stbuf->st_mode &S_ISVTX ) && stbuf->st_uid !=getuid ()) { +fprintf (stderr,"%s: mountpoint %s not owned by user\n" , + progname, origmnt); +return -1 ; + } + + res =access (mnt,W_OK ); +if (res ==-1 ) { +fprintf (stderr,"%s: user has no write access to mountpoint %s\n" , + progname, origmnt); +return -1 ; + } + +return 0 ; +} + +int check_nonroot_fstype (const char *progname,const struct statfs *fs_buf) +{ +size_t i; + +/* Do not permit mounting over anything in procfs - it has a couple +* places to which we have "write access" without being supposed to be +* able to just put anything we want there. +* Luckily, without allow_other, we can't get other users to actually +* use any fake information we try to put there anyway. +* Use a whitelist to be safe. */ + +/* Define permitted filesystems for the mount target. This was +* originally the same list as used by the ecryptfs mount helper +* (https://bazaar.launchpad.net/~ecryptfs/ecryptfs/trunk/view/head:/src/utils/mount.ecryptfs_private.c#L225) +* but got expanded as we found more filesystems that needed to be +* overlaid. */ + typeof(fs_buf->f_type) f_type_whitelist[] = { +0x61756673 /* AUFS_SUPER_MAGIC */ , +0x00000187 /* AUTOFS_SUPER_MAGIC */ , +0xCA451A4E /* BCACHEFS_STATFS_MAGIC */ , +0x9123683E /* BTRFS_SUPER_MAGIC */ , +0x00C36400 /* CEPH_SUPER_MAGIC */ , +0xFF534D42 /* CIFS_MAGIC_NUMBER */ , +0x0000F15F /* ECRYPTFS_SUPER_MAGIC */ , +0X2011BAB0 /* EXFAT_SUPER_MAGIC */ , +0x0000EF53 /* EXT[234]_SUPER_MAGIC */ , +0xF2F52010 /* F2FS_SUPER_MAGIC */ , +0x65735546 /* FUSE_SUPER_MAGIC */ , +0x01161970 /* GFS2_MAGIC */ , +0x47504653 /* GPFS_SUPER_MAGIC */ , +0x0000482b /* HFSPLUS_SUPER_MAGIC */ , +0x000072B6 /* JFFS2_SUPER_MAGIC */ , +0x3153464A /* JFS_SUPER_MAGIC */ , +0x0BD00BD0 /* LL_SUPER_MAGIC */ , +0X00004D44 /* MSDOS_SUPER_MAGIC */ , +0x0000564C /* NCP_SUPER_MAGIC */ , +0x00006969 /* NFS_SUPER_MAGIC */ , +0x00003434 /* NILFS_SUPER_MAGIC */ , +0x5346544E /* NTFS_SB_MAGIC */ , +0x7366746E /* NTFS3_SUPER_MAGIC */ , +0x5346414f /* OPENAFS_SUPER_MAGIC */ , +0x794C7630 /* OVERLAYFS_SUPER_MAGIC */ , +0xAAD7AAEA /* PANFS_SUPER_MAGIC */ , +0x52654973 /* REISERFS_SUPER_MAGIC */ , +0xFE534D42 /* SMB2_SUPER_MAGIC */ , +0x73717368 /* SQUASHFS_MAGIC */ , +0x01021994 /* TMPFS_MAGIC */ , +0x24051905 /* UBIFS_SUPER_MAGIC */ , +0x18031977 /* WEKAFS_SUPER_MAGIC */ , +#if __SIZEOF_LONG__ > 4 +0x736675005346544e /* UFSD */ , +#endif +0x58465342 /* XFS_SB_MAGIC */ , +0x2FC12FC1 /* ZFS_SUPER_MAGIC */ , + 0x858458f6 /* RAMFS_MAGIC */, + }; +for (i =0 ; i <sizeof (f_type_whitelist)/sizeof (f_type_whitelist[0 ]); i++) { +if (f_type_whitelist[i] == fs_buf->f_type) +return 0 ; + } + +fprintf (stderr,"%s: mounting over filesystem type %#010lx is forbidden\n" , + progname, (unsigned long )fs_buf->f_type); +return -1 ; +} + +const struct mount_flags mount_flags[] = { + {"rw" ,MS_RDONLY ,0 ,1 }, + {"ro" ,MS_RDONLY ,1 ,1 }, + {"suid" ,MS_NOSUID ,0 ,0 }, + {"nosuid" ,MS_NOSUID ,1 ,1 }, + {"dev" ,MS_NODEV ,0 ,0 }, + {"nodev" ,MS_NODEV ,1 ,1 }, + {"exec" ,MS_NOEXEC ,0 ,1 }, + {"noexec" ,MS_NOEXEC ,1 ,1 }, + {"async" ,MS_SYNCHRONOUS ,0 ,1 }, + {"sync" ,MS_SYNCHRONOUS ,1 ,1 }, + {"atime" ,MS_NOATIME ,0 ,1 }, + {"noatime" ,MS_NOATIME ,1 ,1 }, + {"diratime" ,MS_NODIRATIME ,0 ,1 }, + {"nodiratime" ,MS_NODIRATIME ,1 ,1 }, + {"lazytime" ,MS_LAZYTIME ,1 ,1 }, + {"nolazytime" ,MS_LAZYTIME ,0 ,1 }, + {"relatime" ,MS_RELATIME ,1 ,1 }, + {"norelatime" ,MS_RELATIME ,0 ,1 }, + {"strictatime" ,MS_STRICTATIME ,1 ,1 }, + {"nostrictatime" ,MS_STRICTATIME ,0 ,1 }, + {"dirsync" ,MS_DIRSYNC ,1 ,1 }, + {"symfollow" ,MS_NOSYMFOLLOW ,0 ,1 }, + {"nosymfollow" ,MS_NOSYMFOLLOW ,1 ,1 }, + {NULL ,0 ,0 ,0 } +};util/fusermount.c +8
-350@@ -11,6 +11,7 @@#include "fuse_config.h" #include "mount_util.h" #include "util.h" +#include "fuser_conf.h" #include <stdio.h> #include <stdlib.h> @@ -50,63 +51,8 @@static const char *progname; -static int user_allow_other =0 ; -static int mount_max =1000 ; -static int auto_unmount =0 ; -#ifdef GETMNTENT_NEEDS_UNESCAPING -// Older versions of musl libc don't unescape entries in /etc/mtab - -// unescapes octal sequences like \040 in-place -// That's ok, because unescaping can not extend the length of the string. -static void unescape (char *buf) { -char *src = buf; -char *dest = buf; -while (1 ) { -char *next_src =strchrnul (src,'\\' ); -int offset = next_src - src; -memmove (dest, src, offset); - src = next_src; - dest += offset; - -if (*src =='\0' ) { - *dest = *src; -return ; - } - src++; - -if ('0' <= src[0 ] && src[0 ] <'2' && -'0' <= src[1 ] && src[1 ] <'8' && -'0' <= src[2 ] && src[2 ] <'8' ) { - *dest++ = (src[0 ] -'0' ) <<6 - | (src[1 ] -'0' ) <<3 - | (src[2 ] -'0' ) <<0 ; - src +=3 ; - }else if (src[0 ] =='\\' ) { - *dest++ ='\\' ; - src +=1 ; - }else { - *dest++ ='\\' ; - } - } -} - -static struct mntent *GETMNTENT (FILE *stream) -{ -struct mntent *entp =getmntent (stream); -if (entp !=NULL ) { -unescape (entp->mnt_fsname); -unescape (entp->mnt_dir); -unescape (entp->mnt_type); -unescape (entp->mnt_opts); - } -return entp; -} -#else -#define GETMNTENT getmntent -#endif // GETMNTENT_NEEDS_UNESCAPING -/* * Take a ',' separated option string and extract "x-" options */ @@ -188,25 +134,6 @@ static const char *get_user_name(void) } } -static uid_t oldfsuid; -static gid_t oldfsgid; - -static void drop_privs (void ) -{ -if (getuid () !=0 ) { - oldfsuid =setfsuid (getuid ()); - oldfsgid =setfsgid (getgid ()); - } -} - -static void restore_privs (void ) -{ -if (getuid () !=0 ) { -setfsuid (oldfsuid); -setfsgid (oldfsgid); - } -} -#ifndef IGNORE_MTAB /* * Make sure that /etc/mtab is checked and updated atomically @@ -568,100 +495,7 @@ static int unmount_fuse(const char *mnt, int quiet, int lazy) return res; } - -static int count_fuse_fs_mtab (void ) -{ -const struct mntent *entp; -int count =0 ; -const char *mtab = _PATH_MOUNTED; -FILE *fp =setmntent (mtab,"r" ); -if (fp ==NULL ) { -fprintf (stderr,"%s: failed to open %s: %s\n" , progname, mtab, -strerror (errno)); -return -1 ; - } -while ((entp =GETMNTENT (fp)) !=NULL ) { -if (strcmp (entp->mnt_type,"fuse" ) ==0 || -strncmp (entp->mnt_type,"fuse." ,5 ) ==0 ) - count ++; - } -endmntent (fp); -return count; -} - -#ifdef HAVE_LISTMOUNT -static int count_fuse_fs_ls_mnt (void ) -{ -#define SMBUF_SIZE 1024 -#define MNT_ID_LEN 128 - -int fuse_count =0 ; -int n_mounts =0 ; -int ret =0 ; -uint64_t mnt_ids[MNT_ID_LEN ]; -unsigned char smbuf[SMBUF_SIZE ]; -struct mnt_id_req req = { - .size =sizeof (struct mnt_id_req ), - }; -struct statmount *sm; - -for (;;) { - req.mnt_id =LSMT_ROOT ; - - n_mounts =syscall (SYS_listmount, &req, &mnt_ids,MNT_ID_LEN ,0 ); -if (n_mounts ==-1 ) { -if (errno !=ENOSYS ) { -fprintf (stderr,"%s: failed to list mounts: %s\n" , progname, -strerror (errno)); - } -return -1 ; - } - -for (int i =0 ; i < n_mounts; i++) { - req.mnt_id = mnt_ids[i]; - req.param =STATMOUNT_FS_TYPE ; - ret =syscall (SYS_statmount, &req, &smbuf,SMBUF_SIZE ,0 ); -if (ret) { -if (errno ==ENOENT ) -continue ; - -fprintf (stderr,"%s: failed to stat mount %lld: %s\n" , progname, - req.mnt_id,strerror (errno)); -return -1 ; - } - - sm = (struct statmount *)smbuf; -if (sm->mask &STATMOUNT_FS_TYPE && -strcmp (&sm->str[sm->fs_type],"fuse" ) ==0 ) - fuse_count++; - } - -if (n_mounts <MNT_ID_LEN ) -break ; - req.param = mnt_ids[MNT_ID_LEN -1 ]; - } -return fuse_count; -} - -static int count_fuse_fs (void ) -{ -int count =count_fuse_fs_ls_mnt (); - -return count >=0 ? count :count_fuse_fs_mtab (); -} -#else -static int count_fuse_fs (void ) -{ -return count_fuse_fs_mtab (); -} -#endif -#else /* IGNORE_MTAB */ -static int count_fuse_fs (void ) -{ -return 0 ; -} -static int add_mount (const char *source,const char *mnt,const char *type,const char *opts) {@@ -679,75 +513,6 @@ static int unmount_fuse(const char *mnt, int quiet, int lazy) }#endif /* IGNORE_MTAB */ -static void strip_line (char *line) -{ -char *s =strchr (line,'#' ); -if (s !=NULL ) - s[0 ] ='\0' ; -for (s = line +strlen (line) -1 ; - s >= line &&isspace ((unsigned char ) *s); s--); - s[1 ] ='\0' ; -for (s = line;isspace ((unsigned char ) *s); s++); -if (s != line) -memmove (line, s,strlen (s)+1 ); -} - -static void parse_line (const char *line,int linenum) -{ -int tmp; -if (strcmp (line,"user_allow_other" ) ==0 ) - user_allow_other =1 ; -else if (sscanf (line,"mount_max = %i" , &tmp) ==1 ) - mount_max = tmp; -else if (line[0 ]) -fprintf (stderr, -"%s: unknown parameter in %s at line %i: '%s'\n" , - progname,FUSE_CONF , linenum, line); -} - -static void read_conf (void ) -{ -FILE *fp =fopen (FUSE_CONF ,"r" ); -if (fp !=NULL ) { -int linenum =1 ; -char line[256 ]; -int isnewline =1 ; -while (fgets (line,sizeof (line), fp) !=NULL ) { -if (isnewline) { -if (line[strlen (line)-1 ] =='\n' ) { -strip_line (line); -parse_line (line, linenum); - }else { - isnewline =0 ; - } - }else if (line[strlen (line)-1 ] =='\n' ) { -fprintf (stderr,"%s: reading %s: line %i too long\n" , progname,FUSE_CONF , linenum); - - isnewline =1 ; - } -if (isnewline) - linenum ++; - } -if (!isnewline) { -fprintf (stderr,"%s: reading %s: missing newline at end of file\n" , progname,FUSE_CONF ); - - } -if (ferror (fp)) { -fprintf (stderr,"%s: reading %s: read failed\n" , progname,FUSE_CONF ); -exit (1 ); - } -fclose (fp); - }else if (errno !=ENOENT ) { -bool fatal = (errno !=EACCES && errno !=ELOOP && - errno !=ENAMETOOLONG && errno !=ENOTDIR && - errno !=EOVERFLOW ); -fprintf (stderr,"%s: failed to open %s: %s\n" , - progname,FUSE_CONF ,strerror (errno)); -if (fatal) -exit (1 ); - } -} -static int begins_with (const char *s,const char *beg) {if (strncmp (s, beg,strlen (beg)) ==0 )@@ -756,40 +521,6 @@ static int begins_with(const char *s, const char *beg)return 0 ; } -struct mount_flags { -const char *opt; -unsigned long flag; -int on; -int safe; -}; - -static struct mount_flags mount_flags[] = { - {"rw" ,MS_RDONLY ,0 ,1 }, - {"ro" ,MS_RDONLY ,1 ,1 }, - {"suid" ,MS_NOSUID ,0 ,0 }, - {"nosuid" ,MS_NOSUID ,1 ,1 }, - {"dev" ,MS_NODEV ,0 ,0 }, - {"nodev" ,MS_NODEV ,1 ,1 }, - {"exec" ,MS_NOEXEC ,0 ,1 }, - {"noexec" ,MS_NOEXEC ,1 ,1 }, - {"async" ,MS_SYNCHRONOUS ,0 ,1 }, - {"sync" ,MS_SYNCHRONOUS ,1 ,1 }, - {"atime" ,MS_NOATIME ,0 ,1 }, - {"noatime" ,MS_NOATIME ,1 ,1 }, - {"diratime" ,MS_NODIRATIME ,0 ,1 }, - {"nodiratime" ,MS_NODIRATIME ,1 ,1 }, - {"lazytime" ,MS_LAZYTIME ,1 ,1 }, - {"nolazytime" ,MS_LAZYTIME ,0 ,1 }, - {"relatime" ,MS_RELATIME ,1 ,1 }, - {"norelatime" ,MS_RELATIME ,0 ,1 }, - {"strictatime" ,MS_STRICTATIME ,1 ,1 }, - {"nostrictatime" ,MS_STRICTATIME ,0 ,1 }, - {"dirsync" ,MS_DIRSYNC ,1 ,1 }, - {"symfollow" ,MS_NOSYMFOLLOW ,0 ,1 }, - {"nosymfollow" ,MS_NOSYMFOLLOW ,1 ,1 }, - {NULL ,0 ,0 ,0 } -}; -static int find_mount_flag (const char *s,unsigned len,int *on,int *flag) {int i;@@ -1096,7 +827,6 @@ static int check_perm(const char **mntp, struct stat *stbuf, int *mountpoint_fd)const char *mnt = *mntp;const char *origmnt = mnt;struct statfs fs_buf; -size_t i; res =lstat (mnt, stbuf);if (res ==-1 ) {@@ -1126,18 +856,9 @@ static int check_perm(const char **mntp, struct stat *stbuf, int *mountpoint_fd)return -1 ; } -if ((stbuf->st_mode &S_ISVTX ) && stbuf->st_uid !=getuid ()) { -fprintf (stderr,"%s: mountpoint %s not owned by user\n" , - progname, origmnt); -return -1 ; - } - - res =access (mnt,W_OK ); -if (res ==-1 ) { -fprintf (stderr,"%s: user has no write access to mountpoint %s\n" , - progname, origmnt); -return -1 ; - } + res =check_nonroot_dir_access (progname, origmnt, mnt, stbuf); +if (res) +return res; }else if (S_ISREG (stbuf->st_mode)) {static char procfile[256 ]; *mountpoint_fd =open (mnt,O_WRONLY );@@ -1169,71 +890,13 @@ static int check_perm(const char **mntp, struct stat *stbuf, int *mountpoint_fd)return -1 ; } -/* Do not permit mounting over anything in procfs - it has a couple -* places to which we have "write access" without being supposed to be -* able to just put anything we want there. -* Luckily, without allow_other, we can't get other users to actually -* use any fake information we try to put there anyway. -* Use a whitelist to be safe. */ if (statfs (*mntp, &fs_buf)) {fprintf (stderr,"%s: failed to access mountpoint %s: %s\n" , progname, mnt,strerror (errno));return -1 ; } -/* Define permitted filesystems for the mount target. This was -* originally the same list as used by the ecryptfs mount helper -* (https://bazaar.launchpad.net/~ecryptfs/ecryptfs/trunk/view/head:/src/utils/mount.ecryptfs_private.c#L225) -* but got expanded as we found more filesystems that needed to be -* overlaid. */ - typeof(fs_buf.f_type) f_type_whitelist[] = { -0x61756673 /* AUFS_SUPER_MAGIC */ , -0x00000187 /* AUTOFS_SUPER_MAGIC */ , -0xCA451A4E /* BCACHEFS_STATFS_MAGIC */ , -0x9123683E /* BTRFS_SUPER_MAGIC */ , -0x00C36400 /* CEPH_SUPER_MAGIC */ , -0xFF534D42 /* CIFS_MAGIC_NUMBER */ , -0x0000F15F /* ECRYPTFS_SUPER_MAGIC */ , -0X2011BAB0 /* EXFAT_SUPER_MAGIC */ , -0x0000EF53 /* EXT[234]_SUPER_MAGIC */ , -0xF2F52010 /* F2FS_SUPER_MAGIC */ , -0x65735546 /* FUSE_SUPER_MAGIC */ , -0x01161970 /* GFS2_MAGIC */ , -0x47504653 /* GPFS_SUPER_MAGIC */ , -0x0000482b /* HFSPLUS_SUPER_MAGIC */ , -0x000072B6 /* JFFS2_SUPER_MAGIC */ , -0x3153464A /* JFS_SUPER_MAGIC */ , -0x0BD00BD0 /* LL_SUPER_MAGIC */ , -0X00004D44 /* MSDOS_SUPER_MAGIC */ , -0x0000564C /* NCP_SUPER_MAGIC */ , -0x00006969 /* NFS_SUPER_MAGIC */ , -0x00003434 /* NILFS_SUPER_MAGIC */ , -0x5346544E /* NTFS_SB_MAGIC */ , -0x7366746E /* NTFS3_SUPER_MAGIC */ , -0x5346414f /* OPENAFS_SUPER_MAGIC */ , -0x794C7630 /* OVERLAYFS_SUPER_MAGIC */ , -0xAAD7AAEA /* PANFS_SUPER_MAGIC */ , -0x52654973 /* REISERFS_SUPER_MAGIC */ , -0xFE534D42 /* SMB2_SUPER_MAGIC */ , -0x73717368 /* SQUASHFS_MAGIC */ , -0x01021994 /* TMPFS_MAGIC */ , -0x24051905 /* UBIFS_SUPER_MAGIC */ , -0x18031977 /* WEKAFS_SUPER_MAGIC */ , -#if __SIZEOF_LONG__ > 4 -0x736675005346544e /* UFSD */ , -#endif -0x58465342 /* XFS_SB_MAGIC */ , -0x2FC12FC1 /* ZFS_SUPER_MAGIC */ , - 0x858458f6 /* RAMFS_MAGIC */, - }; -for (i =0 ; i <sizeof (f_type_whitelist)/sizeof (f_type_whitelist[0 ]); i++) { -if (f_type_whitelist[i] == fs_buf.f_type) -return 0 ; - } - -fprintf (stderr,"%s: mounting over filesystem type %#010lx is forbidden\n" , - progname, (unsigned long )fs_buf.f_type); -return -1 ; +return check_nonroot_fstype (progname, &fs_buf); }static int open_fuse_device (const char *dev)@@ -1273,15 +936,10 @@ static int mount_fuse(const char *mnt, const char *opts, const char **type)return -1 ;drop_privs (); -read_conf (); +read_conf (progname); -if (getuid () !=0 && mount_max !=-1 ) { -int mount_count =count_fuse_fs (); -if (mount_count >= mount_max) { -fprintf (stderr,"%s: too many FUSE filesystems mounted; mount_max=N can be set in %s\n" , progname,FUSE_CONF ); - goto fail_close_fd; - } - } +if (getuid () !=0 &&check_nonroot_mount_count (progname) !=0 ) + goto fail_close_fd;// Extract any options starting with "x-" res=extract_x_options (opts, &do_mount_opts, &x_opts);util/meson.build +3
-3@@ -1,18 +1,18 @@ fuseconf_path = join_paths(get_option('prefix'), get_option('sysconfdir'), 'fuse.conf') -executable('fusermount3', ['fusermount.c', '../lib/mount_util.c', '../lib/util.c'], +executable('fusermount3', ['fusermount.c', '../lib/mount_util.c', '../lib/util.c', 'fuser_conf.c'], include_directories: include_dirs, install: true, install_dir: get_option('bindir'), c_args: '-DFUSE_CONF="@0@"'.format(fuseconf_path)) if private_cfg.get('HAVE_SERVICEMOUNT', false) - executable('fuservicemount3', ['mount_service.c', 'fuservicemount.c', '../lib/mount_util.c'], + executable('fuservicemount3', ['mount_service.c', 'fuservicemount.c', '../lib/mount_util.c', 'fuser_conf.c'], include_directories: include_dirs, link_with: [ libfuse ], install: true, install_dir: get_option('sbindir'), - c_args: '-DFUSE_USE_VERSION=319') + c_args: ['-DFUSE_USE_VERSION=319', '-DFUSE_CONF="@0@"'.format(fuseconf_path)]) endif executable('mount.fuse3', ['mount.fuse.c'],
PATCH 7/13 util: fix checkpatch complaints in fuser_conf.[ch]
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
Fix the checkpatch complaints because we touched some code.
--- util/fuser_conf.h | 3 ++- util/fuser_conf.c | 51 +++++++++++++++++++++++++++++++++------------------ 2 files changed, 35 insertions(+), 19 deletions(-)
util/fuser_conf.h +2 -1
@@ -23,7 +23,8 @@ void unescape(char *buf);static inline struct mntent *GETMNTENT (FILE *stream) {struct mntent *entp =getmntent (stream); -if (entp !=NULL ) { + +if (entp !=NULL ) {unescape (entp->mnt_fsname);unescape (entp->mnt_dir);unescape (entp->mnt_type);
util/fuser_conf.c +33 -18
153 lines not on this page · raw message
PATCH 8/13 mount_service: enable unprivileged users in a similar manner as fusermount
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
Some Linux distributions allow unprivileged users to mount fuse filesystems through the use of the setuid fusermount helper program. It would be useful to provide similar functionality when mounting a filesystem that runs as a systemd service.
Therefore, read the fuse config file and implement the same checks as fusermount. The only new requirement is that the unprivileged user must be able to open the mountpoint for write access if it's a regular file; or have write access if it's a directory.
--- util/mount_service.c | 232 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 227 insertions(+), 5 deletions(-)
util/mount_service.c +227 -5
@@ -38,6 +38,7 @@#include "fuse_i.h" #include "fuse_service_priv.h" #include "mount_service.h" +#include "fuser_conf.h" struct mount_service {/* prefix for printing error messages */
@@ -313,8 +314,10 @@ static int mount_service_connect(struct mount_service *mo)if (ret)return ret; +drop_privs (); ret =connect (sockfd, (const struct sockaddr *)&name,sizeof (name));if (ret && (errno ==ENOENT || errno ==ECONNREFUSED )) { +restore_privs ();fprintf (stderr,"%s: no safe filesystem driver for %s available.\n" , mo->msgtag, mo->subtype);close (sockfd);
@@ -323,10 +326,12 @@ static int mount_service_connect(struct mount_service *mo)if (ret) {int error = errno; +restore_privs ();fprintf (stderr,"%s: %s: %s\n" , mo->msgtag, name.sun_path,strerror (error)); goto out; } +restore_privs (); ret =try_drop_passrights (mo, sockfd);if (ret)
@@ -349,7 +354,7 @@ static int mount_service_send_hello(struct mount_service *mo)struct fuse_service_hello_reply reply = { };ssize_t size; -if (getuid () ==0 ) +if (getuid () ==0 || user_allow_other) hello.flags |=htonl (FUSE_SERVICE_FLAG_ALLOW_OTHER ); size =__send_packet (mo, &hello,sizeof (hello));
@@ -586,14 +591,17 @@ static int mount_service_send_required_files(struct mount_service *mo, {int ret; +drop_privs (); mo->fusedevfd =open (fusedev,O_RDWR |O_CLOEXEC );if (mo->fusedevfd <0 ) {int error = errno; +restore_privs ();fprintf (stderr,"%s: %s: %s\n" , mo->msgtag, fusedev,strerror (error));return -1 ; } +restore_privs (); ret =mount_service_send_file (mo,FUSE_SERVICE_ARGV , mo->argvfd);if (ret)
@@ -710,14 +718,17 @@ static int prepare_bdev(struct mount_service *mo,if (oc->block_size) {int block_size =ntohl (oc->block_size); +drop_privs (); ret =ioctl (fd,BLKBSZSET , &block_size);if (ret) {int error = errno; +restore_privs ();fprintf (stderr,"%s: %s: %s\n" , mo->msgtag, oc->path,strerror (error));return -error; } +restore_privs (); }return 0 ;
@@ -754,6 +765,7 @@ static int mount_service_open_path(struct mount_service *mo, } open_flags =ntohl (oc->open_flags) |O_CLOEXEC ; +drop_privs (); fd =open (oc->path, open_flags,ntohl (oc->create_mode));if (fd <0 ) {int error = errno;
@@ -762,11 +774,13 @@ static int mount_service_open_path(struct mount_service *mo,* Don't print a busy device error report because the * filesystem might decide to retry. */ +restore_privs ();if (error !=EBUSY && !(request_flags &FUSE_SERVICE_OPEN_QUIET ))fprintf (stderr,"%s: %s: %s\n" , mo->msgtag, oc->path,strerror (error));return mount_service_send_file_error (mo, error, oc->path); } +restore_privs ();if (S_ISBLK (expected_fmt)) { ret =prepare_bdev (mo, oc, fd);
@@ -994,6 +1008,15 @@ static int mount_service_handle_mntopts_cmd(struct mount_service *mo, *equals =0 ; } +if (getuid () !=0 && !user_allow_other && + (!strcmp (tok,"allow_other" ) || + !strcmp (tok,"allow_root" ))) { +fprintf (stderr, +"%s: option %s only allowed if 'user_allow_other' is set in %s\n" , + mo->msgtag, tok,FUSE_CONF ); +return mount_service_send_reply (mo,EPERM ); + } +#ifdef HAVE_NEW_MOUNT_API if (mo->fsopenfd >=0 ) {int ret;
@@ -1077,19 +1100,64 @@ static int mount_service_handle_mtabopts_cmd(struct mount_service *mo,return mount_service_send_reply (mo,0 ); } +static int open_mountpoint (const char *mntpt,bool *require_dir) +{ +int ret; + + *require_dir = false; + +if (getuid () ==0 ) { +/* +* Open the alleged mountpoint. We're root, so we only bother +* checking for readability. +*/ +return open (mntpt,O_RDONLY |O_CLOEXEC ); + } + +/* +* Open the alleged mountpoint. For unprivileged callers, we only +* allow mounting on paths that the user can write to. +*/ + ret =open (mntpt,O_WRONLY |O_CLOEXEC ); +if (ret >=0 || errno !=EISDIR ) +return ret; + +/* +* However, we can't open directories with write access. Try again in +* readonly mode, but require the caller to verify that we actually got +* a directory. +*/ + *require_dir = true; + ret =open (mntpt,O_RDONLY |O_CLOEXEC ); +if (ret >=0 || (errno !=EACCES && errno !=EPERM )) +return ret; + +#ifdef O_PATH +/* +* If we can't open at all, let's try opening this directory with +* O_PATH. +*/ +return open (mntpt,O_PATH |O_CLOEXEC ); +#else +/* No idea what to do now */ + errno =EACCES ; +return -1 ; +#endif +} +static int attach_to_mountpoint (struct mount_service *mo,mode_t expected_fmt,char *mntpt) {struct stat stbuf;char *res_mntpt; +bool require_dir;int mountfd =-1 ;int error;int ret; -/* -* Open the alleged mountpoint, make sure it's a dir or a file. -*/ - mountfd =open (mntpt,O_RDONLY |O_CLOEXEC ); +drop_privs (); + + mountfd =open_mountpoint (mntpt, &require_dir);if (mountfd <0 ) { error = errno; fprintf(stderr, "%s: %s: %s\n", mo->msgtag, mntpt,
@@ -1117,6 +1185,13 @@ static int attach_to_mountpoint(struct mount_service *mo, mode_t expected_fmt, goto out_mountfd; } +if (require_dir && !S_ISDIR (stbuf.st_mode)) { + error =EACCES ; +fprintf (stderr,"%s: %s: Mount point must be directory.\n" , + mo->msgtag, mntpt); + goto out_mountfd; + } +/* * Resolve the (possibly relative) mountpoint path before chdir'ing * onto it.
@@ -1193,6 +1268,7 @@ static int attach_to_mountpoint(struct mount_service *mo, mode_t expected_fmt, mo->mountfd = mountfd; mo->resv_mountpoint = res_mntpt; +restore_privs ();return mount_service_send_reply (mo,0 ); out_res_mntpt:
@@ -1201,6 +1277,7 @@ static int attach_to_mountpoint(struct mount_service *mo, mode_t expected_fmt,close (mountfd); out_error:free (mntpt); +restore_privs ();return mount_service_send_reply (mo, error); }
@@ -1580,6 +1657,141 @@ static int mount_service_fsopen_mount(struct mount_service *mo,# define mount_service_fsopen_mount (...) (FUSE_MOUNT_FALLBACK_NEEDED)#endif +static int check_nonroot_file_access (struct mount_service *mo) +{ +struct stat sb1, sb2; +int fd; +int ret; + +/* +* If we already succeeded in opening the file with write access, then +* we're good. +*/ + ret =fcntl (mo->mountfd,F_GETFL ); +if (ret <0 ) { +int error = errno; + +fprintf (stderr,"%s: %s: %s\n" , mo->msgtag, mo->mountpoint, +strerror (error)); +return -1 ; + } + +if ((ret &O_ACCMODE ) !=O_RDONLY ) +return 0 ; + + ret =fstat (mo->mountfd, &sb1); +if (ret) { +int error = errno; + +fprintf (stderr,"%s: %s: %s\n" , + mo->msgtag, mo->mountpoint,strerror (error)); +return -1 ; + } + +/* Try to reopen the file with write access this time. */ + fd =open (mo->real_mountpoint,O_WRONLY |O_CLOEXEC ); +if (fd <0 ) { +int error = errno; + +fprintf (stderr,"%s: %s: %s\n" , + mo->msgtag, mo->mountpoint,strerror (error)); +return -1 ; + } + +/* Is this the same file? */ + ret =fstat (fd, &sb2); +if (ret) { +int error = errno; + +fprintf (stderr,"%s: %s: %s\n" , + mo->msgtag, mo->mountpoint,strerror (error)); + goto out_fd; + } + +if (sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino) { +fprintf (stderr,"%s: %s: Mount point moved during fuse startup.\n" , + mo->msgtag, mo->mountpoint); + ret =-1 ; + goto out_fd; + } + +/* +* We reopened the same file with write access, everything is ok. Swap +* the two file descriptors so that we retain our write access. +*/ + ret = mo->mountfd; + mo->mountfd = fd; + fd = ret; + ret =0 ; +out_fd: +close (fd); +return ret; +} + +static void adjust_nonroot_mount_flags (struct mount_service *mo, +struct fuse_service_mount_command *oc) +{ +const struct mount_flags *mf; +uint32_t ms_flags =ntohl (oc->ms_flags); + +/* only care that the unsafe flags are set to the value of @on */ +for (mf = mount_flags; mf->opt !=NULL ; mf++) { +if (mf->safe) +continue ; +if (!!(ms_flags & mf->flag) == !!mf->on) { + ms_flags = (ms_flags & ~mf->flag) | + (mf->on ?0 : mf->flag); + +fprintf (stderr,"%s: unsafe option %s ignored\n" , + mo->msgtag, mf->opt); + } + } + + oc->ms_flags =htonl (ms_flags); +} + +/* +* fuse.conf can limit the number of unprivileged fuse mounts. For +* unprivileged mounts (via setuid) we also require write access to the +* mountpoint, and we'll only accept certain underlying filesystems. +*/ +static int check_nonroot_access (struct mount_service *mo, +struct fuse_service_mount_command *oc, +const struct stat *stbuf) +{ +struct statfs fs_buf; +int ret; + + ret =check_nonroot_mount_count (mo->msgtag); +if (ret) +return -EUSERS ; + + ret =fstatfs (mo->mountfd, &fs_buf); +if (ret) { +int error = errno; + +fprintf (stderr,"%s: %s: %s\n" , + mo->msgtag, mo->mountpoint,strerror (error)); +return -error; + } + +adjust_nonroot_mount_flags (mo, oc); + +drop_privs (); +if (S_ISDIR (stbuf->st_mode)) + ret =check_nonroot_dir_access (mo->msgtag, + mo->mountpoint, + mo->real_mountpoint, + stbuf); +else + ret =check_nonroot_file_access (mo); +if (!ret) + ret =check_nonroot_fstype (mo->msgtag, &fs_buf); +restore_privs (); + +return ret ? -EPERM :0 ; +} +static int mount_service_handle_mount_cmd (struct mount_service *mo,struct fuse_service_packet *p,size_t psz)
@@ -1621,6 +1833,12 @@ static int mount_service_handle_mount_cmd(struct mount_service *mo,return mount_service_send_reply (mo, error); } +if (getuid () !=0 ) { + ret =check_nonroot_access (mo, oc, &stbuf); +if (ret) +return mount_service_send_reply (mo, -ret); + } +if (mo->fsopenfd >=0 ) { ret =mount_service_fsopen_mount (mo, oc, &stbuf);if (ret !=FUSE_MOUNT_FALLBACK_NEEDED )
@@ -1752,6 +1970,10 @@ int mount_service_main(int argc, char *argv[])else mo.msgtag ="mount.service" ; +drop_privs (); +read_conf (mo.msgtag); +restore_privs (); + ret =mount_service_init (&mo, argc, argv);if (ret)return EXIT_FAILURE ;
PATCH 9/13 mount.fuse3: integrate systemd service startup
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
Teach mount.fuse3 how to start fuse via systemd service, if present.
--- util/mount_service.h | 9 +++ doc/fuservicemount3.8 | 10 +++ meson.build | 3 + util/fuservicemount.c | 47 ++++++++++++++ util/meson.build | 14 +++- util/mount.fuse.c | 169 +++++++++++++++++++++++++++++++++++++++++++------ util/mount_service.c | 43 ++++++++++++ 7 files changed, 268 insertions(+), 27 deletions(-)
util/mount_service.h +9 -0
@@ -37,4 +37,13 @@ int mount_service_main(int argc, char *argv[]);*/ const char *mount_service_subtype (const char *fstype); +/** +* Discover if there is a fuse service socket for the given fuse filesystem type. +* The type must not contain a path separator. +* +* @param fstype the type of a fuse filesystem type (e.g. fuse.Y, fuseblk.Y, or Y) +* @return true if available, false if not +*/ +bool mount_service_present (const char *fstype); +#endif /* MOUNT_SERVICE_H_ */
doc/fuservicemount3.8 +9 -1
@@ -7,12 +7,20 @@ .SH SYNOPSIS .B mountpoint .BI -t " fstype" [ -.I options +.BI -o " options" ] + +.B fuservicemount3 +.BI -t " fstype" +.B --check + .SH DESCRIPTION Mount a filesystem using a FUSE server that runs as a socket service. These servers can be contained using the platform's service management framework. + +The second form checks if there is a FUSE service available for the given +filesystem type. .SH "AUTHORS" .LP The author of the fuse socket service code is Darrick J. Wong <[email protected]>.
meson.build +2 -1
@@ -83,7 +83,8 @@ private_cfg.set('FUSE_SERVICE_SOCKET_PERMS', service_socket_perms) # Test for presence of some functions test_funcs = [ 'fork', 'fstatat', 'openat', 'readlinkat', 'pipe2', 'splice', 'vmsplice', 'posix_fallocate', 'fdatasync', - 'utimensat', 'copy_file_range', 'fallocate', 'fspacectl' ] + 'utimensat', 'copy_file_range', 'fallocate', 'fspacectl', + 'faccessat' ] foreach func : test_funcs private_cfg.set('HAVE_' + func.to_upper(), cc.has_function(func, prefix: include_default, args: args_default))
util/fuservicemount.c +47 -0
57 lines not on this page · raw message
util/meson.build +10 -4
@@ -6,21 +6,27 @@ executable('fusermount3', ['fusermount.c', '../lib/mount_util.c', '../lib/util.c install_dir: get_option('bindir'), c_args: '-DFUSE_CONF="@0@"'.format(fuseconf_path)) +mount_service_sources = [] +mount_service_cflags = [] if private_cfg.get('HAVE_SERVICEMOUNT', false) - executable('fuservicemount3', ['mount_service.c', 'fuservicemount.c', '../lib/mount_util.c', 'fuser_conf.c'], + mount_service_sources += ['mount_service.c', '../lib/mount_util.c', 'fuser_conf.c'] + mount_service_cflags += ['-DFUSE_CONF="@0@"'.format(fuseconf_path)] + fuservicemount_path = join_paths(get_option('prefix'), get_option('sbindir')) + mount_service_cflags += ['-DFUSERVICEMOUNT_DIR="@0@"'.format(fuservicemount_path)] + executable('fuservicemount3', ['fuservicemount.c'] + mount_service_sources, include_directories: include_dirs, link_with: [ libfuse ], install: true, install_dir: get_option('sbindir'), - c_args: ['-DFUSE_USE_VERSION=319', '-DFUSE_CONF="@0@"'.format(fuseconf_path)]) + c_args: ['-DFUSE_USE_VERSION=319'] + mount_service_cflags) endif -executable('mount.fuse3', ['mount.fuse.c'], +executable('mount.fuse3', ['mount.fuse.c'] + mount_service_sources, include_directories: include_dirs, link_with: [ libfuse ], install: true, install_dir: get_option('sbindir'), - c_args: '-DFUSE_USE_VERSION=317') + c_args: ['-DFUSE_USE_VERSION=319'] + mount_service_cflags) udevrulesdir = get_option('udevrulesdir')
util/mount.fuse.c +148 -21
217 lines not on this page · raw message
util/mount_service.c +43 -0
46 lines not on this page · raw message
Re: [PATCH 09/13] mount.fuse3: integrate systemd service startup
Quoted 226 lines · Darrick J. Wong
From: Darrick J. Wong <[email protected]>
Teach mount.fuse3 how to start fuse via systemd service, if present.
Signed-off-by "Darrick J. Wong" <[email protected]>--- util/mount_service.h | 9 +++ doc/fuservicemount3.8 | 10 +++ meson.build | 3 + util/fuservicemount.c | 47 ++++++++++++++ util/meson.build | 14 +++- util/mount.fuse.c | 169 +++++++++++++++++++++++++++++++++++++++++++------ util/mount_service.c | 43 ++++++++++++ 7 files changed, 268 insertions(+), 27 deletions(-)util/mount_service.h +9
-0@@ -37,4 +37,13 @@ int mount_service_main(int argc, char *argv[]);*/ const char *mount_service_subtype (const char *fstype); +/** +* Discover if there is a fuse service socket for the given fuse filesystem type. +* The type must not contain a path separator. +* +* @param fstype the type of a fuse filesystem type (e.g. fuse.Y, fuseblk.Y, or Y) +* @return true if available, false if not +*/ +bool mount_service_present (const char *fstype); +#endif /* MOUNT_SERVICE_H_ */ doc/fuservicemount3.8 +9
-1@@ -7,12 +7,20 @@ .SH SYNOPSIS .B mountpoint .BI -t " fstype" [ -.I options +.BI -o " options" ] + +.B fuservicemount3 +.BI -t " fstype" +.B --check + .SH DESCRIPTION Mount a filesystem using a FUSE server that runs as a socket service. These servers can be contained using the platform's service management framework. + +The second form checks if there is a FUSE service available for the given +filesystem type. .SH "AUTHORS" .LP The author of the fuse socket service code is Darrick J. Wong <[email protected]>.meson.build +2
-1@@ -83,7 +83,8 @@ private_cfg.set('FUSE_SERVICE_SOCKET_PERMS', service_socket_perms) # Test for presence of some functions test_funcs = [ 'fork', 'fstatat', 'openat', 'readlinkat', 'pipe2', 'splice', 'vmsplice', 'posix_fallocate', 'fdatasync', - 'utimensat', 'copy_file_range', 'fallocate', 'fspacectl' ] + 'utimensat', 'copy_file_range', 'fallocate', 'fspacectl', + 'faccessat' ] foreach func : test_funcs private_cfg.set('HAVE_' + func.to_upper(), cc.has_function(func, prefix: include_default, args: args_default))util/fuservicemount.c +47
-0@@ -9,10 +9,57 @@* This program wraps the mounting of FUSE filesystems that run in systemd */ #define _GNU_SOURCE +#include <stdbool.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> #include "fuse_config.h" #include "mount_service.h" +static int check_service (const char *fstype) +{ +if (!fstype) { +fprintf (stderr, +"fuservicemount: expected fs type for --check\n" ); +return EXIT_FAILURE ; + } + +return mount_service_present (fstype) ?EXIT_SUCCESS :EXIT_FAILURE ; +} +int main (int argc,char *argv[]) { +char *fstype =NULL ; +bool check = false; +int i; + +/* +* If the user passes us exactly the args -t FSTYPE --check then +* we'll just check if there's a service for the FSTYPE fuse server. +* This doesn't tell us if the listening socket is actually connected +* to anything. +*/ +for (i =1 ; i < argc; i++) { +if (!strcmp (argv[i],"--check" )) { +if (check) { + check = false; +break ; + } + check = true; + }else if (!strcmp (argv[i],"-t" ) && i +1 < argc) { +if (fstype) { + check = false; +break ; + } + fstype = argv[i +1 ]; + i++; + }else { + check = false; +break ; + } + } +if (check) +return check_service (fstype); +return mount_service_main (argc, argv); }util/meson.build +10
-4@@ -6,21 +6,27 @@ executable('fusermount3', ['fusermount.c', '../lib/mount_util.c', '../lib/util.c install_dir: get_option('bindir'), c_args: '-DFUSE_CONF="@0@"'.format(fuseconf_path)) +mount_service_sources = [] +mount_service_cflags = [] if private_cfg.get('HAVE_SERVICEMOUNT', false) - executable('fuservicemount3', ['mount_service.c', 'fuservicemount.c', '../lib/mount_util.c', 'fuser_conf.c'], + mount_service_sources += ['mount_service.c', '../lib/mount_util.c', 'fuser_conf.c'] + mount_service_cflags += ['-DFUSE_CONF="@0@"'.format(fuseconf_path)] + fuservicemount_path = join_paths(get_option('prefix'), get_option('sbindir')) + mount_service_cflags += ['-DFUSERVICEMOUNT_DIR="@0@"'.format(fuservicemount_path)] + executable('fuservicemount3', ['fuservicemount.c'] + mount_service_sources, include_directories: include_dirs, link_with: [ libfuse ], install: true, install_dir: get_option('sbindir'), - c_args: ['-DFUSE_USE_VERSION=319', '-DFUSE_CONF="@0@"'.format(fuseconf_path)]) + c_args: ['-DFUSE_USE_VERSION=319'] + mount_service_cflags) endif -executable('mount.fuse3', ['mount.fuse.c'], +executable('mount.fuse3', ['mount.fuse.c'] + mount_service_sources, include_directories: include_dirs, link_with: [ libfuse ], install: true, install_dir: get_option('sbindir'), - c_args: '-DFUSE_USE_VERSION=317') + c_args: ['-DFUSE_USE_VERSION=319'] + mount_service_cflags) udevrulesdir = get_option('udevrulesdir')util/mount.fuse.c +25
-0@@ -6,6 +6,9 @@See the file GPL2.txt. */ +/* For environ */ +#define _GNU_SOURCE +#include "fuse_config.h" #include <stdio.h> @@ -17,6 +20,9 @@#include <fcntl.h> #include <pwd.h> #include <sys/wait.h> +#ifdef HAVE_SERVICEMOUNT +#include <spawn.h> +#endif #ifdef linux#include <sys/prctl.h> @@ -49,6 +55,9 @@#endif #include "fuse.h" +#ifdef HAVE_SERVICEMOUNT +# include "mount_service.h" +#endif static char *progname;@@ -233,6 +242,105 @@ static void drop_and_lock_capabilities(void) }#endif +#ifdef HAVE_SERVICEMOUNT +#define FUSERVICEMOUNT_PROG "fuservicemount3" + +static int mount_service_child (char **argv) +{ +const char *full_path =FUSERVICEMOUNT_DIR "/" FUSERVICEMOUNT_PROG ; +pid_t child_pid; +int child_status; +int ret; + +/* +* First try the install path, then a system install, just like we do +* for fusermount. See man 7 environ for the global environ pointer. +*/ + ret =posix_spawn (&child_pid, full_path,NULL ,NULL , + (char *const *)argv, environ);
util/mount.fuse.c static void drop_and_lock_capabilities(void) · line 270
Quoted 10 lines
util/mount.fuse.c +10
-0+if (ret) + ret =posix_spawnp (&child_pid,FUSERVICEMOUNT_PROG ,NULL ,NULL , + (char *const *)argv, environ); +if (ret) { +fprintf (stderr,"%s: could not start %s helper: %s\n" , + argv[0 ],FUSERVICEMOUNT_PROG ,strerror (ret)); +return MOUNT_SERVICE_FALLBACK_NEEDED ; + } + + ret =waitpid (child_pid, &child_status,0 );
Heh. I don't pass WNOHANG here, which means that waitpid could return EINTR due to a signal being sent to the mount.fuse process. It looks like glibc wraps that particular case, but the manpage says that a waitpid implementation can return EINTR so I'll change this to:
do {
ret = waitpid(...);
} while (ret < 0 && errno == EINTR);--D
Quoted 218 lines
util/mount.fuse.c +113
-21+if (ret <0 ) { +fprintf (stderr,"%s: could not wait for %s helper: %s\n" , + argv[0 ],FUSERVICEMOUNT_PROG ,strerror (errno)); +return MOUNT_SERVICE_FALLBACK_NEEDED ; + } + +if (WIFEXITED (child_status)) +return WEXITSTATUS (child_status); + +/* terminated due to signal or coredump */ +return EXIT_FAILURE ; +} + +static int try_service_main (char *argv0,char *fstype,char *source, +const char *mountpoint,char *options) +{ +char **argv; +char dash_o[] ="-o" ; +char dash_t[] ="-t" ; +char *mntpt; +int argc =5 ;/* argv[0], -t type, mountpoint, and trailing NULL */ +int i =0 ; +int ret; + +if (!mount_service_present (fstype)) +return MOUNT_SERVICE_FALLBACK_NEEDED ; + +/* This can be an empty string if "mount.fuse3 null# /tmp/a" */ +if (source && source[0 ] ==0 ) + source =NULL ; + + mntpt =strdup (mountpoint); +if (!mntpt) { +perror ("mountpoint allocation" ); +return -1 ; + } + +if (source) + argc++; +if (options) + argc +=2 ; + + argv =calloc (argc,sizeof (char *)); +if (!argv) { +perror ("argv allocation" ); +free (mntpt); +return -1 ; + } + + argv[i++] = argv0; +if (source) + argv[i++] = source; + argv[i++] = mntpt; + argv[i++] = dash_t; + argv[i++] = fstype; +if (options) { + argv[i++] = dash_o; + argv[i++] = options; + } + argv[i] =0 ; + +if (getuid () !=0 ) { + ret =mount_service_child (argv); + }else { +/* We're root, just do the mount directly. */ + ret =mount_service_main (argc -1 , argv); + } +free (argv); +free (mntpt); +return ret; +} +#endif +int main (int argc,char *argv[]) {char *type =NULL ;@@ -280,9 +388,7 @@ int main(int argc, char *argv[]) mountpoint = argv[2 ];for (i =3 ; i < argc; i++) { -if (strcmp (argv[i],"-v" ) ==0 ) { -continue ; - }else if (strcmp (argv[i],"-t" ) ==0 ) { +if (strcmp (argv[i],"-t" ) ==0 ) { i++;if (i == argc) {@@ -303,6 +409,30 @@ int main(int argc, char *argv[]) progname);exit (1 ); } + } + } + +if (!type) { +if (source) { + dup_source =xstrdup (source); + type = dup_source; + source =strchr (type,'#' ); +if (source) + *source++ ='\0' ; +if (!type[0 ]) { +fprintf (stderr,"%s: empty filesystem type\n" , + progname); +exit (1 ); + } + }else { +fprintf (stderr,"%s: empty source\n" , progname); +exit (1 ); + } + } + +for (i =3 ; i < argc; i++) { +if (strcmp (argv[i],"-v" ) ==0 ) { +continue ; }else if (strcmp (argv[i],"-o" ) ==0 ) {char *opts;const char *opt;@@ -366,24 +496,6 @@ int main(int argc, char *argv[])if (suid) options =add_option ("suid" , options); -if (!type) { -if (source) { - dup_source =xstrdup (source); - type = dup_source; - source =strchr (type,'#' ); -if (source) - *source++ ='\0' ; -if (!type[0 ]) { -fprintf (stderr,"%s: empty filesystem type\n" , - progname); -exit (1 ); - } - }else { -fprintf (stderr,"%s: empty source\n" , progname); -exit (1 ); - } - } -if (setuid_name && setuid_name[0 ]) {#ifdef linuxif (drop_privileges) {@@ -429,6 +541,21 @@ int main(int argc, char *argv[]) drop_and_lock_capabilities(); }#endif + +#ifdef HAVE_SERVICEMOUNT +/* +* Now that we know the desired filesystem type, see if we can find +* a socket service implementing that, if we haven't selected any weird +* options that would prevent that. +*/ +if (!pass_fuse_fd && !(setuid_name && setuid_name[0 ])) { +int ret =try_service_main (argv[0 ], type, source, mountpoint, + options); +if (ret !=MOUNT_SERVICE_FALLBACK_NEEDED ) +return ret; + } +#endif +add_arg (&command, type);if (source)add_arg (&command, source);util/mount_service.c +43
-0@@ -2066,3 +2066,46 @@ int mount_service_main(int argc, char *argv[])mount_service_destroy (&mo);return ret; } + +bool mount_service_present (const char *fstype) +{ +struct sockaddr_un name; +struct stat stbuf; +char path[PATH_MAX ]; +const char *subtype; +ssize_t written; +int ret; + + subtype =mount_service_subtype (fstype); +if (!subtype) +return false; + +/* +* The full path to the socket must fit within the AF_UNIX socket path +* buffer, which is much shorter than PATH_MAX. +*/ + written =snprintf (path,sizeof (path),FUSE_SERVICE_SOCKET_DIR "/%s" , + subtype); +if (written >=sizeof (name.sun_path) || written >=sizeof (path)) +return false; + + ret =stat (path, &stbuf); +if (ret) +return false; + +if (!S_ISSOCK (stbuf.st_mode)) +return false; + +#ifdef HAVE_FACCESSAT +/* +* Can we write to the service socket with the real uid? This accounts +* for setuid and ACLs on the socket. Note that we connect() to the +* socket having dropped setuid privileges. +*/ + ret =faccessat (AT_FDCWD , path,W_OK ,0 ); +#else +/* Can we write to the service socket with the real uid? */ + ret =access (path,W_OK ); +#endif +return ret ==0 ; +}
PATCH 10/13 mount_service: allow installation as a setuid program
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
Allow installation of the mount service helper as a setuid program so that regular users can access containerized filesystem drivers.
--- README.md | 3 +++ test/ci-build.sh | 14 ++++++++++++++ util/install_helper.sh | 6 ++++++ util/meson.build | 3 ++- 4 files changed, 25 insertions(+), 1 deletion(-)
README.md +3 -0
@@ -98,6 +98,9 @@ Security implications The *fusermount3* program is installed setuid root. This is done to allow normal users to mount their own filesystem implementations. +If built, the *fuservicemount3* program will also be installed setuid +root so that normal users can access containerized filesystem +implementations. To limit the harm that malicious users can do this way, *fusermount3* enforces the following limitations:
test/ci-build.sh +14 -0
@@ -60,11 +60,18 @@ non_sanitized_build() # libfuse will first try the install path and then system defaults sudo chmod 4755 ${PREFIX_DIR}/bin/fusermount3 + test -x "${PREFIX_DIR}/sbin/fuservicemount3" && \ + sudo chmod 4755 ${PREFIX_DIR}/sbin/fuservicemount3 # also needed for some of the tests sudo chown root:root util/fusermount3 sudo chmod 4755 util/fusermount3 + if [ -x util/fuservicemount3 ]; then + sudo chown root:root util/fuservicemount3 + sudo chmod 4755 util/fuservicemount3 + fi + ${TEST_CMD} popd rm -fr build-${CC}
@@ -101,11 +108,18 @@ sanitized_build() ninja sudo env PATH=$PATH ninja install sudo chmod 4755 ${PREFIX_DIR}/bin/fusermount3 + test -x "${PREFIX_DIR}/sbin/fuservicemount3" && \ + sudo chmod 4755 ${PREFIX_DIR}/sbin/fuservicemount3 # also needed for some of the tests sudo chown root:root util/fusermount3 sudo chmod 4755 util/fusermount3 + if [ -x util/fuservicemount3 ]; then + sudo chown root:root util/fuservicemount3 + sudo chmod 4755 util/fuservicemount3 + fi + # Test as root and regular user sudo env PATH=$PATH ${TEST_CMD} # Cleanup temporary files (since they are now owned by root)
util/install_helper.sh +6 -0
@@ -11,6 +11,7 @@ bindir="$2" udevrulesdir="$3" useroot="$4" initscriptdir="$5" +sbindir="$6" # Both sysconfdir and bindir are absolute paths (since they are joined # with --prefix in meson.build), but need to be interpreted relative
@@ -31,6 +32,11 @@ if $useroot; then chown root:root "${DESTDIR}${bindir}/fusermount3" chmod u+s "${DESTDIR}${bindir}/fusermount3" + if [ -e "${DESTDIR}${sbindir}/fuservicemount3" ]; then + chown root:root "${DESTDIR}${sbindir}/fuservicemount3" + chmod u+s "${DESTDIR}${sbindir}/fuservicemount3" + fi + if test ! -e "${DESTDIR}/dev/fuse"; then mkdir -p "${DESTDIR}/dev" mknod "${DESTDIR}/dev/fuse" -m 0666 c 10 229
util/meson.build +2 -1
@@ -46,4 +46,5 @@ meson.add_install_script('install_helper.sh', join_paths(get_option('prefix'), get_option('bindir')), udevrulesdir, '@0@'.format(get_option('useroot')), - get_option('initscriptdir')) + get_option('initscriptdir'), + join_paths(get_option('prefix'), get_option('sbindir')))
PATCH 11/13 example/service_ll: create a sample systemd service fuse server
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
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.
--- example/single_file.h | 153 +++++++++ lib/util.h | 35 ++ example/meson.build | 14 + example/service_ll.c | 312 ++++++++++++++++++ example/service_ll.socket.in | 15 + example/[email protected] | 102 ++++++ example/single_file.c | 733 ++++++++++++++++++++++++++++++++++++++++++ meson.build | 1 8 files changed, 1365 insertions(+) create mode 100644 example/single_file.h create mode 100644 example/service_ll.c create mode 100644 example/service_ll.socket.in create mode 100644 example/[email protected] create mode 100644 example/single_file.c
example/single_file.h new file +153 -0
@@ -0,0 +1,153 @@ +/* +* FUSE: Filesystem in Userspace +* Copyright (C) 2026 Oracle. +* +* This program can be distributed under the terms of the GNU GPLv2. +* See the file GPL2.txt. +*/ +#ifndef FUSE_SINGLE_FILE_H_ +#define FUSE_SINGLE_FILE_H_ + +static inline uint64_t round_up (uint64_t b,unsigned int align) +{ +unsigned int m; + +if (align ==0 ) +return b; + m = b % align; +if (m) + b += align - m; +return b; +} + +static inline uint64_t round_down (uint64_t b,unsigned int align) +{ +unsigned int m; + +if (align ==0 ) +return b; + m = b % align; +return b - m; +} + +static inline uint64_t howmany (uint64_t b,unsigned int align) +{ +unsigned int m; + +if (align ==0 ) +return b; + m = (b % align) ?1 :0 ; +return (b / align) + m; +} + +struct single_file { +int backing_fd; + +int64_t isize; +uint64_t blocks; + +mode_t mode; + +bool ro; +bool allow_dio; +bool sync; +bool require_bdev; + +unsigned int blocksize; + +struct timespec atime; +struct timespec mtime; +struct timespec ctime; + +pthread_mutex_t lock; +}; + +extern struct single_file single_file; + +static inline uint64_t b_to_fsbt (uint64_t off) +{ +return off / single_file.blocksize; +} + +static inline uint64_t b_to_fsb (uint64_t off) +{ +return (off + single_file.blocksize -1 ) / single_file.blocksize; +} + +static inline uint64_t fsb_to_b (uint64_t fsb) +{ +return fsb * single_file.blocksize; +} + +enum single_file_opt_keys { +SINGLE_FILE_RO =171717 ,/* how many options could we possibly have? */ +SINGLE_FILE_RW , +SINGLE_FILE_REQUIRE_BDEV , +SINGLE_FILE_DIO , +SINGLE_FILE_NODIO , +SINGLE_FILE_SYNC , +SINGLE_FILE_NOSYNC , +SINGLE_FILE_SIZE , +SINGLE_FILE_BLOCKSIZE , + +SINGLE_FILE_NR_KEYS , +}; + +#define SINGLE_FILE_OPT_KEYS \ + FUSE_OPT_KEY("ro", SINGLE_FILE_RO), \ + FUSE_OPT_KEY("rw", SINGLE_FILE_RW), \ + FUSE_OPT_KEY("require_bdev", SINGLE_FILE_REQUIRE_BDEV), \ + FUSE_OPT_KEY("dio", SINGLE_FILE_DIO), \ + FUSE_OPT_KEY("nodio", SINGLE_FILE_NODIO), \ + FUSE_OPT_KEY("sync", SINGLE_FILE_SYNC), \ + FUSE_OPT_KEY("nosync", SINGLE_FILE_NOSYNC), \ + FUSE_OPT_KEY("size=%s", SINGLE_FILE_SIZE), \ + FUSE_OPT_KEY("blocksize=%s", SINGLE_FILE_BLOCKSIZE) + +int single_file_opt_proc (void *data,const char *arg,int key, +struct fuse_args *outargs); + +unsigned long long parse_num_blocks (const char *arg,int log_block_size); + +struct fuse_service ; +int single_file_service_open (struct fuse_service *sf,const char *path); + +void single_file_check_read (off_t pos,size_t *count); +int single_file_check_write (off_t pos,size_t *count); + +int single_file_configure (const char *device,const char *filename); +int single_file_configure_simple (const char *filename); +void single_file_close (void ); + +ssize_t single_file_pwrite (const char *buf,size_t count,off_t pos); +ssize_t single_file_pread (char *buf,size_t count,off_t pos); + +/* low-level fuse operation handlers */ + +bool is_single_file_child (fuse_ino_t parent,const char *name); +bool is_single_file_ino (fuse_ino_t ino); + +void single_file_ll_readdir (fuse_req_t req,fuse_ino_t ino,size_t size, +off_t off,struct fuse_file_info *fi); + +void single_file_ll_statfs (fuse_req_t req,fuse_ino_t ino); + +void single_file_ll_statx (fuse_req_t req,fuse_ino_t ino,int flags,int mask, +struct fuse_file_info *fi); + +void single_file_ll_getattr (fuse_req_t req,fuse_ino_t ino, +struct fuse_file_info *fi); +void single_file_ll_setattr (fuse_req_t req,fuse_ino_t ino,struct stat *attr, +int to_set,struct fuse_file_info *fi); + +void single_file_ll_lookup (fuse_req_t req,fuse_ino_t parent,const char *name); +void single_file_ll_open (fuse_req_t req,fuse_ino_t ino, +struct fuse_file_info *fi); + +void single_file_ll_fsync (fuse_req_t req,fuse_ino_t ino,int datasync, +struct fuse_file_info *fi); + +int reply_buf_limited (fuse_req_t req,const char *buf,size_t bufsize, +off_t off,size_t maxsize); + +#endif /* FUSE_SINGLE_FILE_H_ */
lib/util.h +35 -0
@@ -4,6 +4,9 @@#include <stdint.h> #include <stdbool.h> +#define max (x, y) ((x) > (y) ? (x) : (y)) +#define min (x, y) ((x) < (y) ? (x) : (y)) +#define ROUND_UP (val, round_to) (((val) + (round_to - 1)) & ~(round_to - 1))#define likely (x) __builtin_expect(!!(x), 1)
@@ -46,4 +49,36 @@ static inline uint64_t fuse_higher_32_bits(uint64_t nr)#define fallthrough do {} while (0)#endif +static inline uint64_t round_up (uint64_t b,unsigned int align) +{ +unsigned int m; + +if (align ==0 ) +return b; + m = b % align; +if (m) + b += align - m; +return b; +} + +static inline uint64_t round_down (uint64_t b,unsigned int align) +{ +unsigned int m; + +if (align ==0 ) +return b; + m = b % align; +return b - m; +} + +static inline uint64_t howmany (uint64_t b,unsigned int align) +{ +unsigned int m; + +if (align ==0 ) +return b; + m = (b % align) ?1 :0 ; +return (b / align) + m; +} +#endif /* FUSE_UTIL_H_ */
example/meson.build +14 -0
@@ -12,6 +12,15 @@ if not platform.endswith('bsd') and platform != 'dragonfly' examples += [ 'null' ] endif +single_file_examples = [ ] + +if platform.endswith('linux') + single_file_examples += [ 'service_ll' ] + configure_file(input: 'service_ll.socket.in', + output: 'service_ll.socket', + configuration: private_cfg) +endif + threaded_examples = [ 'notify_inval_inode', 'invalidate_path', 'notify_store_retrieve',
@@ -25,6 +34,11 @@ foreach ex : examples install: false) endforeach +foreach ex : single_file_examples + executable(ex, [ex + '.c', 'single_file.c'], + dependencies: [ libfuse_dep ], + install: false) +endforeach foreach ex : threaded_examples executable(ex, ex + '.c',
example/service_ll.c new file +312 -0
312 lines not on this page · raw message
example/service_ll.socket.in new file +15 -0
@@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2026 Oracle. All Rights Reserved. +# Author: Darrick J. Wong <[email protected]> +[Unit] +Description=Socket for service_ll Service + +[Socket] +ListenSequentialPacket=@FUSE_SERVICE_SOCKET_DIR_RAW@/service_ll +Accept=yes +SocketMode=@FUSE_SERVICE_SOCKET_PERMS@ +RemoveOnStop=yes + +[Install] +WantedBy=sockets.target
example/[email protected] new file +102 -0
102 lines not on this page · raw message
example/single_file.c new file +733 -0
733 lines not on this page · raw message
meson.build +1 -0
@@ -77,6 +77,7 @@ endif if service_socket_perms == '' service_socket_perms = '0220' endif +private_cfg.set('FUSE_SERVICE_SOCKET_DIR_RAW', service_socket_dir) private_cfg.set_quoted('FUSE_SERVICE_SOCKET_DIR', service_socket_dir) private_cfg.set('FUSE_SERVICE_SOCKET_PERMS', service_socket_perms)
Re: [PATCH 11/13] example/service_ll: create a sample systemd service fuse server
Quoted 30 lines · Darrick J. Wong
From: Darrick J. Wong <[email protected]>
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.
Signed-off-by "Darrick J. Wong" <[email protected]>--- example/single_file.h | 153 +++++++++ lib/util.h | 35 ++ example/meson.build | 14 + example/service_ll.c | 312 ++++++++++++++++++ example/service_ll.socket.in | 15 + example/[email protected] | 102 ++++++ example/single_file.c | 733 ++++++++++++++++++++++++++++++++++++++++++ meson.build | 1 8 files changed, 1365 insertions(+) create mode 100644 example/single_file.h create mode 100644 example/service_ll.c create mode 100644 example/service_ll.socket.in create mode 100644 example/[email protected] create mode 100644 example/single_file.cexample/single_file.h new file +0
-0
example/single_file.h line 9
Quoted 9 lines
example/single_file.h new file +9
-0@@ -0,0 +1,153 @@ +/* +* FUSE: Filesystem in Userspace +* Copyright (C) 2026 Oracle. +* +* This program can be distributed under the terms of the GNU GPLv2. +* See the file GPL2.txt. +*/ +#ifndef FUSE_SINGLE_FILE_H_ +#define FUSE_SINGLE_FILE_H_
From the name it is kind of impossible to figure out what it is for - requires to go through the code. Could you add something like
This is not a standalone file system, but a helper for other file systems to expose a single file backed by a physical file or block device.
For both the .h and .c file? Please feel free to make it more verbose.
Thanks,
Bernd
Re: [PATCH 11/13] example/service_ll: create a sample systemd service fuse server
Quoted 46 lines · Bernd Schubert
Quoted 30 lines · Darrick J. Wong
From: Darrick J. Wong <[email protected]>
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.
Signed-off-by "Darrick J. Wong" <[email protected]>--- example/single_file.h | 153 +++++++++ lib/util.h | 35 ++ example/meson.build | 14 + example/service_ll.c | 312 ++++++++++++++++++ example/service_ll.socket.in | 15 + example/[email protected] | 102 ++++++ example/single_file.c | 733 ++++++++++++++++++++++++++++++++++++++++++ meson.build | 1 8 files changed, 1365 insertions(+) create mode 100644 example/single_file.h create mode 100644 example/service_ll.c create mode 100644 example/service_ll.socket.in create mode 100644 example/[email protected] create mode 100644 example/single_file.cexample/single_file.h new file +0
-0example/single_file.h line 9
Quoted 9 lines
example/single_file.h new file +9
-0@@ -0,0 +1,153 @@ +/* +* FUSE: Filesystem in Userspace +* Copyright (C) 2026 Oracle. +* +* This program can be distributed under the terms of the GNU GPLv2. +* See the file GPL2.txt. +*/ +#ifndef FUSE_SINGLE_FILE_H_ +#define FUSE_SINGLE_FILE_H_ From the name it is kind of impossible to figure out what it is for - requires to go through the code. Could you add something like
Quoted 4 lines
This is not a standalone file system, but a helper for other file systems to expose a single file backed by a physical file or block device.
For both the .h and .c file? Please feel free to make it more verbose.
Ok, I have written:
"This file is shared library code for example fuse servers that want to expose a single regular file that wraps another file in a manner that goes beyond simple passthrough. It is not itself a fuse server."
How does that sound?
--D
PATCH 12/13 example/service: create a sample systemd service for a high-level fuse server
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
Create a simple high-level fuse server that can be run as a systemd service.
--- example/single_file.h | 38 ++++++ example/meson.build | 6 + example/service_hl.c | 224 +++++++++++++++++++++++++++++++++++ example/service_hl.socket.in | 15 ++ example/[email protected] | 102 ++++++++++++++++ example/service_ll.c | 1 example/single_file.c | 271 +++++++++++++++++++++++++++++++++++++++--- 7 files changed, 640 insertions(+), 17 deletions(-) create mode 100644 example/service_hl.c create mode 100644 example/service_hl.socket.in create mode 100644 example/[email protected]
example/single_file.h +38 -0
@@ -124,6 +124,7 @@ ssize_t single_file_pread(char *buf, size_t count, off_t pos);/* low-level fuse operation handlers */ +#ifdef USE_SINGLE_FILE_LL_API bool is_single_file_child (fuse_ino_t parent,const char *name);bool is_single_file_ino (fuse_ino_t ino);
@@ -149,5 +150,42 @@ void single_file_ll_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,int reply_buf_limited (fuse_req_t req,const char *buf,size_t bufsize,off_t off,size_t maxsize); +#endif + +/* high-level fuse operation handlers */ + +#ifdef USE_SINGLE_FILE_HL_API +bool is_single_open_file_path (const struct fuse_file_info *fi,const char *name); + +int single_file_hl_readdir (const char *path,void *buf,fuse_fill_dir_t filler, +off_t offset,struct fuse_file_info *fi, +enum fuse_readdir_flags flags); + +int single_file_hl_statfs (const char *path,struct statvfs *buf); + +int single_file_hl_statx (const char *path,int statx_flags,int statx_mask, +struct statx *stx,struct fuse_file_info *fi); + +int single_file_hl_getattr (const char *path,struct stat *stbuf, +struct fuse_file_info *fi); +int single_file_hl_chmod (const char *path,mode_t mode, +struct fuse_file_info *fi); +int single_file_hl_utimens (const char *path,const struct timespec ctv[2 ], +struct fuse_file_info *fi); +int single_file_hl_chown (const char *path,uid_t owner,gid_t group, +struct fuse_file_info *fi); +int single_file_hl_truncate (const char *path,off_t len, +struct fuse_file_info *fi); + +int single_file_hl_opendir (const char *path,struct fuse_file_info *fi); +int single_file_hl_open (const char *path,struct fuse_file_info *fi); + +int single_file_hl_fsync (const char *path,int datasync, +struct fuse_file_info *fi); +#endif + +#if !defined(USE_SINGLE_FILE_LL_API ) && !defined(USE_SINGLE_FILE_HL_API ) +# warning USE_SINGLE_FILE_[HL]L_API not defined! +#endif #endif /* FUSE_SINGLE_FILE_H_ */
example/meson.build +6 -0
@@ -19,6 +19,12 @@ if platform.endswith('linux') configure_file(input: 'service_ll.socket.in', output: 'service_ll.socket', configuration: private_cfg) + + single_file_examples += [ 'service_hl' ] + configure_file(input: 'service_hl.socket.in', + output: 'service_hl.socket', + configuration: private_cfg) + endif threaded_examples = [ 'notify_inval_inode',
example/service_hl.c new file +224 -0
224 lines not on this page · raw message
example/service_hl.socket.in new file +15 -0
@@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2026 Oracle. All Rights Reserved. +# Author: Darrick J. Wong <[email protected]> +[Unit] +Description=Socket for service_hl Service + +[Socket] +ListenSequentialPacket=@FUSE_SERVICE_SOCKET_DIR_RAW@/service_hl +Accept=yes +SocketMode=@FUSE_SERVICE_SOCKET_PERMS@ +RemoveOnStop=yes + +[Install] +WantedBy=sockets.target
example/[email protected] new file +102 -0
102 lines not on this page · raw message
example/service_ll.c +1 -0
@@ -51,6 +51,7 @@#include <unistd.h> #include <assert.h> #include <pthread.h> +#define USE_SINGLE_FILE_LL_API #include "single_file.h" struct service_ll {
example/single_file.c +254 -17
343 lines not on this page · raw message
Re: [PATCH 12/13] example/service: create a sample systemd service for a high-level fuse server
Quoted 555 lines · Darrick J. Wong
From: Darrick J. Wong <[email protected]>
Create a simple high-level fuse server that can be run as a systemd service.
Signed-off-by "Darrick J. Wong" <[email protected]>--- example/single_file.h | 38 ++++++ example/meson.build | 6 + example/service_hl.c | 224 +++++++++++++++++++++++++++++++++++ example/service_hl.socket.in | 15 ++ example/[email protected] | 102 ++++++++++++++++ example/service_ll.c | 1 example/single_file.c | 271 +++++++++++++++++++++++++++++++++++++++--- 7 files changed, 640 insertions(+), 17 deletions(-) create mode 100644 example/service_hl.c create mode 100644 example/service_hl.socket.in create mode 100644 example/[email protected]example/single_file.h +38
-0@@ -124,6 +124,7 @@ ssize_t single_file_pread(char *buf, size_t count, off_t pos);/* low-level fuse operation handlers */ +#ifdef USE_SINGLE_FILE_LL_API bool is_single_file_child (fuse_ino_t parent,const char *name);bool is_single_file_ino (fuse_ino_t ino);@@ -149,5 +150,42 @@ void single_file_ll_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,int reply_buf_limited (fuse_req_t req,const char *buf,size_t bufsize,off_t off,size_t maxsize); +#endif + +/* high-level fuse operation handlers */ + +#ifdef USE_SINGLE_FILE_HL_API +bool is_single_open_file_path (const struct fuse_file_info *fi,const char *name); + +int single_file_hl_readdir (const char *path,void *buf,fuse_fill_dir_t filler, +off_t offset,struct fuse_file_info *fi, +enum fuse_readdir_flags flags); + +int single_file_hl_statfs (const char *path,struct statvfs *buf); + +int single_file_hl_statx (const char *path,int statx_flags,int statx_mask, +struct statx *stx,struct fuse_file_info *fi); + +int single_file_hl_getattr (const char *path,struct stat *stbuf, +struct fuse_file_info *fi); +int single_file_hl_chmod (const char *path,mode_t mode, +struct fuse_file_info *fi); +int single_file_hl_utimens (const char *path,const struct timespec ctv[2 ], +struct fuse_file_info *fi); +int single_file_hl_chown (const char *path,uid_t owner,gid_t group, +struct fuse_file_info *fi); +int single_file_hl_truncate (const char *path,off_t len, +struct fuse_file_info *fi); + +int single_file_hl_opendir (const char *path,struct fuse_file_info *fi); +int single_file_hl_open (const char *path,struct fuse_file_info *fi); + +int single_file_hl_fsync (const char *path,int datasync, +struct fuse_file_info *fi); +#endif + +#if !defined(USE_SINGLE_FILE_LL_API ) && !defined(USE_SINGLE_FILE_HL_API ) +# warning USE_SINGLE_FILE_[HL]L_API not defined! +#endif #endif /* FUSE_SINGLE_FILE_H_ */ example/meson.build +6
-0@@ -19,6 +19,12 @@ if platform.endswith('linux') configure_file(input: 'service_ll.socket.in', output: 'service_ll.socket', configuration: private_cfg) + + single_file_examples += [ 'service_hl' ] + configure_file(input: 'service_hl.socket.in', + output: 'service_hl.socket', + configuration: private_cfg) + endif threaded_examples = [ 'notify_inval_inode',example/service_hl.c new file +224
-0@@ -0,0 +1,224 @@ +/* +* FUSE: Filesystem in Userspace +* Copyright (C) 2026 Oracle. +* +* This program can be distributed under the terms of the GNU GPLv2. +* See the file GPL2.txt. +*/ + +/** @file +* +* minimal example filesystem using high-level API and systemd service api +* +* Compile with: +* +* gcc -Wall single_file.c service_hl.c `pkg-config fuse3 --cflags --libs` -o service_hl +* +* Note: If the pkg-config command fails due to the absence of the fuse3.pc +* file, you should configure the path to the fuse3.pc file in the +* PKG_CONFIG_PATH variable. +* +* Change the ExecStart line in [email protected]: +* +* ExecStart=/path/to/service_hl +* +* to point to the actual path of the service_hl binary. +* +* Finally, install the [email protected] and service_hl.socket files to the +* systemd service directory, usually /run/systemd/system. +* +* ## Source code ## +* \include service_hl.c +* \include service_hl.socket +* \include [email protected] +* \include single_file.c +* \include single_file.h +*/ + +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 19) + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include <fuse.h> +#include <fuse_service.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <assert.h> +#include <pthread.h> +#include <stddef.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <linux/fs.h> +#include <linux/stat.h> +#define USE_SINGLE_FILE_HL_API +#include "single_file.h" + +struct service_hl { +char *device; +struct fuse_service *service; + +/* really booleans */ +int debug; +}; + +static struct service_hl hl = { }; + +static void *service_hl_init (struct fuse_conn_info *conn, +struct fuse_config *cfg) +{ + (void ) conn; + cfg->kernel_cache =1 ; + +return NULL ; +} + +static int service_hl_read (const char *path,char *buf,size_t count, +off_t pos,struct fuse_file_info *fi) +{ +if (!is_single_open_file_path (fi, path)) +return -EIO ; + +if (hl.debug) +fprintf (stderr,"%s: pos 0x%llx count 0x%llx\n" , + __func__, + (unsigned long long )pos, + (unsigned long long )count); + +if (!single_file.allow_dio && fi->direct_io) +return -ENOSYS ; + +single_file_check_read (pos, &count); + +if (!count) +return 0 ; + +return single_file_pread (buf, count, pos); +} + +static int service_hl_write (const char *path,const char *buf,size_t count, +off_t pos,struct fuse_file_info *fi) +{ +int ret; + +if (!is_single_open_file_path (fi, path)) +return -EIO ; + +if (hl.debug) +fprintf (stderr,"%s: pos 0x%llx count 0x%llx\n" , + __func__, + (unsigned long long )pos, + (unsigned long long )count); + +if (!single_file.allow_dio && fi->direct_io) +return -ENOSYS ; + + ret =single_file_check_write (pos, &count); +if (ret <0 ) +return ret; + +if (!count) +return 0 ; + +return single_file_pwrite (buf, count, pos); +} + +static const struct fuse_operations service_hl_oper = { + .getattr = single_file_hl_getattr, + .readdir = single_file_hl_readdir, + .open = single_file_hl_open, + .opendir = single_file_hl_opendir, + .statfs = single_file_hl_statfs, + .chmod = single_file_hl_chmod, + .utimens = single_file_hl_utimens, + .fsync = single_file_hl_fsync, + .chown = single_file_hl_chown, + .truncate = single_file_hl_truncate, + .statx = single_file_hl_statx, + + .init = service_hl_init, + .read = service_hl_read, + .write = service_hl_write, +}; + +#define SERVICE_HL_OPT (t, p, v) { t, offsetof(struct service_hl, p), v } + +static struct fuse_opt service_hl_opts[] = { +SERVICE_HL_OPT ("debug" , debug,1 ), +SINGLE_FILE_OPT_KEYS , +FUSE_OPT_END +}; + +static int service_hl_opt_proc (void *data,const char *arg,int key, +struct fuse_args *outargs) +{ +int ret =single_file_opt_proc (data, arg, key, outargs); + +if (ret <1 ) +return ret; + +switch (key) { +case FUSE_OPT_KEY_NONOPT : +if (!hl.device) { + hl.device =strdup (arg); +return 0 ; + } +return 1 ; +default : +break ; + } + +return 1 ; +} + +int main (int argc,char *argv[]) +{ +struct fuse_args args =FUSE_ARGS_INIT (argc, argv); +int ret =1 ; + +if (fuse_service_accept (&hl.service)) + goto err_args; + +if (!fuse_service_accepted (hl.service)) + goto err_args; + +if (fuse_service_append_args (hl.service, &args)) + goto err_service; + +if (fuse_opt_parse (&args, &hl, service_hl_opts, service_hl_opt_proc)) + goto err_service; + +if (!hl.device) { +printf ("usage: %s [options] <device> <mountpoint>\n" , argv[0 ]); +printf (" %s --help\n" , argv[0 ]); + goto err_service; + } + +if (single_file_service_open (hl.service, hl.device)) + goto err_service; + +if (fuse_service_finish_file_requests (hl.service)) + goto err_singlefile; + +if (single_file_configure (hl.device,NULL )) + goto err_singlefile; + +fuse_service_expect_mount_format (hl.service,S_IFDIR ); + + ret =fuse_service_main (hl.service, &args, &service_hl_oper,NULL ); + +err_singlefile: +single_file_close (); +err_service: +free (hl.device); +fuse_service_send_goodbye (hl.service, ret); +fuse_service_destroy (&hl.service); +err_args: +fuse_opt_free_args (&args); +return fuse_service_exit (ret); +}example/service_hl.socket.in new file +15
-0@@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2026 Oracle. All Rights Reserved. +# Author: Darrick J. Wong <[email protected]> +[Unit] +Description=Socket for service_hl Service + +[Socket] +ListenSequentialPacket=@FUSE_SERVICE_SOCKET_DIR_RAW@/service_hl +Accept=yes +SocketMode=@FUSE_SERVICE_SOCKET_PERMS@ +RemoveOnStop=yes + +[Install] +WantedBy=sockets.targetexample/[email protected] new file +102
-0@@ -0,0 +1,102 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2026 Oracle. All Rights Reserved. +# Author: Darrick J. Wong <[email protected]> +[Unit] +Description=service_hl Sample Fuse Service + +# Don't leave failed units behind, systemd does not clean them up! +CollectMode=inactive-or-failed + +[Service] +Type=exec +ExecStart=/path/to/service_hl + +# Try to capture core dumps +LimitCORE=infinity + +SyslogIdentifier=%N + +# No realtime CPU scheduling +RestrictRealtime=true + +# Don't let us see anything in the regular system, and don't run as root +DynamicUser=true +ProtectSystem=strict +ProtectHome=true +PrivateTmp=true +PrivateDevices=true +PrivateUsers=true + +# No network access +PrivateNetwork=true +ProtectHostname=true +RestrictAddressFamilies=none +IPAddressDeny=any + +# Don't let the program mess with the kernel configuration at all +ProtectKernelLogs=true +ProtectKernelModules=true +ProtectKernelTunables=true +ProtectControlGroups=true +ProtectProc=invisible +RestrictNamespaces=true +RestrictFileSystems= + +# Hide everything in /proc, even /proc/mounts +ProcSubset=pid + +# Only allow the default personality Linux +LockPersonality=true + +# No writable memory pages +MemoryDenyWriteExecute=true + +# Don't let our mounts leak out to the host +PrivateMounts=true + +# Restrict system calls to the native arch and only enough to get things going +SystemCallArchitectures=native +SystemCallFilter=@system-service +SystemCallFilter=~@privileged +SystemCallFilter=~@resources + +SystemCallFilter=~@clock +SystemCallFilter=~@cpu-emulation +SystemCallFilter=~@debug +SystemCallFilter=~@module +SystemCallFilter=~@reboot +SystemCallFilter=~@swap + +SystemCallFilter=~@mount + +# libfuse io_uring wants to pin cores and memory +SystemCallFilter=mbind +SystemCallFilter=sched_setaffinity + +# Leave a breadcrumb if we get whacked by the system call filter +SystemCallErrorNumber=EL3RST + +# Log to the kernel dmesg, just like an in-kernel filesystem driver +StandardOutput=append:/dev/ttyprintk +StandardError=append:/dev/ttyprintk + +# Run with no capabilities at all +CapabilityBoundingSet= +AmbientCapabilities= +NoNewPrivileges=true + +# We don't create files +UMask=7777 + +# No access to hardware /dev files at all +ProtectClock=true +DevicePolicy=closed + +# Don't mess with set[ug]id anything. +RestrictSUIDSGID=true + +# Don't let OOM kills of processes in this containment group kill the whole +# service, because we don't want filesystem drivers to go down. +OOMPolicy=continue +OOMScoreAdjust=-1000example/service_ll.c +1
-0@@ -51,6 +51,7 @@#include <unistd.h> #include <assert.h> #include <pthread.h> +#define USE_SINGLE_FILE_LL_API #include "single_file.h" struct service_ll {example/single_file.c +58
-0@@ -23,7 +23,10 @@#define FUSE_USE_VERSION (FUSE_MAKE_VERSION(3, 19))#include "fuse_lowlevel.h" +#include "fuse.h" #include "fuse_service.h" +#define USE_SINGLE_FILE_LL_API +#define USE_SINGLE_FILE_HL_API #include "single_file.h" #define min (x, y) ((x) < (y) ? (x) : (y))@@ -56,6 +59,23 @@ struct single_file single_file = { .lock = PTHREAD_MUTEX_INITIALIZER, }; +static fuse_ino_t single_file_path_to_ino (const char *path) +{ +if (strcmp (path,"/" ) ==0 ) +return FUSE_ROOT_ID ; +if (strcmp (path +1 , single_file_name) ==0 ) +return SINGLE_FILE_INO ; +return 0 ; +} + +static fuse_ino_t single_open_file_path_to_ino (const struct fuse_file_info *fi, +const char *path) +{ +if (fi) +return fi->fh; +return single_file_path_to_ino (path); +} +static void dirbuf_add (fuse_req_t req,struct dirbuf *b,const char *name,fuse_ino_t ino) {@@ -91,6 +111,13 @@ bool is_single_file_ino(fuse_ino_t ino)return ino ==SINGLE_FILE_INO ; } +bool is_single_open_file_path (const struct fuse_file_info *fi,const char *name) +{ +if (fi) +return is_single_file_ino (fi->fh); +return name[0 ] =='/' &&strcmp (name +1 , single_file_name) ==0 ; +} +void single_file_ll_readdir (fuse_req_t req,fuse_ino_t ino,size_t size,off_t off,struct fuse_file_info *fi) {@@ -117,6 +144,37 @@ void single_file_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,free (b.p); } +int single_file_hl_readdir (const char *path,void *buf,fuse_fill_dir_t filler, +off_t offset,struct fuse_file_info *fi, +enum fuse_readdir_flags flags) +{ +struct stat stbuf; +fuse_ino_t ino =single_open_file_path_to_ino (fi, path); + +memset (&stbuf,0 ,sizeof (stbuf)); + + (void ) offset; + (void ) flags; + +switch (ino) { +case FUSE_ROOT_ID : +break ; +case SINGLE_FILE_INO : +return -ENOTDIR ; +default : +return -ENOENT ; + } + + stbuf.st_ino =FUSE_ROOT_ID ; +filler (buf,"." , &stbuf,0 ,FUSE_FILL_DIR_DEFAULTS ); +filler (buf,".." , &stbuf,0 ,FUSE_FILL_DIR_DEFAULTS ); + + stbuf.st_ino =SINGLE_FILE_INO ; +filler (buf, single_file_name, &stbuf,0 ,FUSE_FILL_DIR_DEFAULTS ); + +return 0 ; +} +static bool sf_stat (fuse_ino_t ino,struct single_file_stat *llstat) {struct fuse_entry_param *entry = &llstat->entry;
example/single_file.c void single_file_ll_statx(fuse_req_t req, fuse_ino_t ino, int flags, int mask, · line 299
Quoted 7 lines
example/single_file.c +4
-0@@ -235,40 +293,74 @@ void single_file_ll_statx(fuse_req_t req, fuse_ino_t ino, int flags, int mask,else fuse_reply_statx (req,0 , &stx,0.0 ); } + +int single_file_hl_statx (const char *path,int statx_flags,int statx_mask, +struct statx *stx,struct fuse_file_info *fi) +{
bschubert2@imesrv6 build>ninja
[55/102] Compiling C object example/service_ll.p/single_file.c.o ../example/single_file.c:297:48: warning: unused parameter 'statx_flags' [-Wunused-parameter]
297 | int single_file_hl_statx(const char *path, int statx_flags, intstatx_mask,
| ^
1 warning generated.
[56/102] Compiling C object example/service_hl.p/single_file.c.o ../example/single_file.c:297:48: warning: unused parameter 'statx_flags' [-Wunused-parameter]
297 | int single_file_hl_statx(const char *path, int statx_flags, intstatx_mask,
| ^
1 warning generated.
[102/102] Linking target example/passthrough_hp
And CI tests run with -Werror - fails here.
Quoted 259 lines
example/single_file.c +192
-17+fuse_ino_t ino =single_open_file_path_to_ino (fi, path); +bool filled; + +if (!ino) +return -ENOENT ; + +pthread_mutex_lock (&single_file.lock); + filled =sf_statx (ino, statx_mask, stx); +pthread_mutex_unlock (&single_file.lock); + +return filled ?0 : -ENOENT ; +}#else void single_file_ll_statx (fuse_req_t req,fuse_ino_t ino,int flags,int mask,struct fuse_file_info *fi) {fuse_reply_err (req,ENOSYS ); } + +int single_file_hl_statx (const char *path,int statx_flags,int statx_mask, +struct statx *stx,struct fuse_file_info *fi) +{ +return -ENOSYS ; +}#endif /* STATX_BASIC_STATS */ +static void single_file_statfs (struct statvfs *buf) +{ +pthread_mutex_lock (&single_file.lock); + buf->f_bsize = single_file.blocksize; + buf->f_frsize =0 ; + + buf->f_blocks = single_file.blocks; + buf->f_bfree =0 ; + buf->f_bavail =0 ; + buf->f_files =1 ; + buf->f_ffree =0 ; + buf->f_favail =0 ; + buf->f_fsid =0x50C00L ; + buf->f_flag =0 ; +if (single_file.ro) + buf->f_flag |=ST_RDONLY ; + buf->f_namemax =255 ; +pthread_mutex_unlock (&single_file.lock); +} +void single_file_ll_statfs (fuse_req_t req,fuse_ino_t ino) {struct statvfs buf; (void )ino; -pthread_mutex_lock (&single_file.lock); - buf.f_bsize = single_file.blocksize; - buf.f_frsize =0 ; - - buf.f_blocks = single_file.blocks; - buf.f_bfree =0 ; - buf.f_bavail =0 ; - buf.f_files =1 ; - buf.f_ffree =0 ; - buf.f_favail =0 ; - buf.f_fsid =0x50C00L ; - buf.f_flag =0 ; -if (single_file.ro) - buf.f_flag |=ST_RDONLY ; - buf.f_namemax =255 ; -pthread_mutex_unlock (&single_file.lock); - +single_file_statfs (&buf);fuse_reply_statfs (req, &buf); } +int single_file_hl_statfs (const char *path,struct statvfs *buf) +{ + (void )path; + +single_file_statfs (buf); +return 0 ; +} +void single_file_ll_getattr (fuse_req_t req,fuse_ino_t ino,struct fuse_file_info *fi) {@@ -288,6 +380,28 @@ void single_file_ll_getattr(fuse_req_t req, fuse_ino_t ino, llstat.entry.attr_timeout); } +int single_file_hl_getattr (const char *path,struct stat *stbuf, +struct fuse_file_info *fi) +{ +struct single_file_stat llstat; +fuse_ino_t ino =single_open_file_path_to_ino (fi, path); +bool filled; + +if (!ino) +return -ENOENT ; + +memset (&llstat,0 ,sizeof (llstat)); +pthread_mutex_lock (&single_file.lock); + filled =sf_stat (ino, &llstat); +pthread_mutex_unlock (&single_file.lock); + +if (!filled) +return -ENOENT ; + +memcpy (stbuf, &llstat.entry.attr,sizeof (*stbuf)); +return 0 ; +} +static void get_now (struct timespec *now) {#ifdef CLOCK_REALTIME @@ -342,6 +456,81 @@ void single_file_ll_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,fuse_reply_err (req,EPERM ); } +int single_file_hl_chmod (const char *path,mode_t mode, +struct fuse_file_info *fi) +{ +fuse_ino_t ino =single_open_file_path_to_ino (fi, path); + +if (!ino) +return -ENOENT ; +if (ino !=SINGLE_FILE_INO ) +return -EPERM ; +if (single_file.ro) +return -EPERM ; + +pthread_mutex_lock (&single_file.lock); + single_file.mode = (single_file.mode &S_IFMT ) | (mode & ~S_IFMT ); +pthread_mutex_unlock (&single_file.lock); + +return 0 ; +} + +static void set_time (const struct timespec *ctv,struct timespec *tv) +{ +switch (ctv->tv_nsec) { +case UTIME_OMIT : +return ; +case UTIME_NOW : +get_now (tv); +break ; +default : +memcpy (tv, ctv,sizeof (*tv)); +break ; + } +} + +int single_file_hl_utimens (const char *path,const struct timespec ctv[2 ], +struct fuse_file_info *fi) +{ +fuse_ino_t ino =single_open_file_path_to_ino (fi, path); + +if (!ino) +return -ENOENT ; +if (ino !=SINGLE_FILE_INO ) +return -EPERM ; +if (single_file.ro) +return -EPERM ; + +pthread_mutex_lock (&single_file.lock); +set_time (&ctv[0 ], &single_file.atime); +set_time (&ctv[1 ], &single_file.mtime); +get_now (&single_file.ctime); +pthread_mutex_unlock (&single_file.lock); + +return 0 ; +} + +int single_file_hl_chown (const char *path,uid_t owner,gid_t group, +struct fuse_file_info *fi) +{ + (void )path; + (void )owner; + (void )group; + (void )fi; + +return -EPERM ; +} + +int single_file_hl_truncate (const char *path,off_t len, +struct fuse_file_info *fi) +{ + (void )path; + (void )len; + (void )fi; + +return -EPERM ; +} +void single_file_ll_lookup (fuse_req_t req,fuse_ino_t parent,const char *name) {struct single_file_stat llstat;@@ -373,6 +562,34 @@ void single_file_ll_open(fuse_req_t req, fuse_ino_t ino,fuse_reply_open (req, fi); } +int single_file_hl_opendir (const char *path,struct fuse_file_info *fi) +{ +fuse_ino_t ino =single_file_path_to_ino (path); + +if (!ino) +return -ENOENT ; +if (ino ==SINGLE_FILE_INO ) +return -ENOTDIR ; + + fi->fh = ino; +return 0 ; +} + +int single_file_hl_open (const char *path,struct fuse_file_info *fi) +{ +fuse_ino_t ino =single_file_path_to_ino (path); + +if (!ino) +return -ENOENT ; +if (ino !=SINGLE_FILE_INO ) +return -EISDIR ; +if (single_file.ro && (fi->flags &O_ACCMODE ) !=O_RDONLY ) +return -EROFS ; + + fi->fh = ino; +return 0 ; +} +void single_file_ll_fsync (fuse_req_t req,fuse_ino_t ino,int datasync,struct fuse_file_info *fi) {@@ -390,6 +607,26 @@ void single_file_ll_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,fuse_reply_err (req, ret); } +int single_file_hl_fsync (const char *path,int datasync, +struct fuse_file_info *fi) +{ +fuse_ino_t ino =single_open_file_path_to_ino (fi, path); + + (void )datasync; + +if (!ino) +return -ENOENT ; + +if (ino ==SINGLE_FILE_INO ) { +int ret =fsync (single_file.backing_fd); + +if (ret) +return -errno; + } + +return 0 ; +} +unsigned long long parse_num_blocks (const char *arg,int log_block_size) {char *p;
Re: [PATCH 12/13] example/service: create a sample systemd service for a high-level fuse server
Quoted 583 lines · Bernd Schubert
Quoted 555 lines · Darrick J. Wong
From: Darrick J. Wong <[email protected]>
Create a simple high-level fuse server that can be run as a systemd service.
Signed-off-by "Darrick J. Wong" <[email protected]>--- example/single_file.h | 38 ++++++ example/meson.build | 6 + example/service_hl.c | 224 +++++++++++++++++++++++++++++++++++ example/service_hl.socket.in | 15 ++ example/[email protected] | 102 ++++++++++++++++ example/service_ll.c | 1 example/single_file.c | 271 +++++++++++++++++++++++++++++++++++++++--- 7 files changed, 640 insertions(+), 17 deletions(-) create mode 100644 example/service_hl.c create mode 100644 example/service_hl.socket.in create mode 100644 example/[email protected]example/single_file.h +38
-0@@ -124,6 +124,7 @@ ssize_t single_file_pread(char *buf, size_t count, off_t pos);/* low-level fuse operation handlers */ +#ifdef USE_SINGLE_FILE_LL_API bool is_single_file_child (fuse_ino_t parent,const char *name);bool is_single_file_ino (fuse_ino_t ino);@@ -149,5 +150,42 @@ void single_file_ll_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,int reply_buf_limited (fuse_req_t req,const char *buf,size_t bufsize,off_t off,size_t maxsize); +#endif + +/* high-level fuse operation handlers */ + +#ifdef USE_SINGLE_FILE_HL_API +bool is_single_open_file_path (const struct fuse_file_info *fi,const char *name); + +int single_file_hl_readdir (const char *path,void *buf,fuse_fill_dir_t filler, +off_t offset,struct fuse_file_info *fi, +enum fuse_readdir_flags flags); + +int single_file_hl_statfs (const char *path,struct statvfs *buf); + +int single_file_hl_statx (const char *path,int statx_flags,int statx_mask, +struct statx *stx,struct fuse_file_info *fi); + +int single_file_hl_getattr (const char *path,struct stat *stbuf, +struct fuse_file_info *fi); +int single_file_hl_chmod (const char *path,mode_t mode, +struct fuse_file_info *fi); +int single_file_hl_utimens (const char *path,const struct timespec ctv[2 ], +struct fuse_file_info *fi); +int single_file_hl_chown (const char *path,uid_t owner,gid_t group, +struct fuse_file_info *fi); +int single_file_hl_truncate (const char *path,off_t len, +struct fuse_file_info *fi); + +int single_file_hl_opendir (const char *path,struct fuse_file_info *fi); +int single_file_hl_open (const char *path,struct fuse_file_info *fi); + +int single_file_hl_fsync (const char *path,int datasync, +struct fuse_file_info *fi); +#endif + +#if !defined(USE_SINGLE_FILE_LL_API ) && !defined(USE_SINGLE_FILE_HL_API ) +# warning USE_SINGLE_FILE_[HL]L_API not defined! +#endif #endif /* FUSE_SINGLE_FILE_H_ */ example/meson.build +6
-0@@ -19,6 +19,12 @@ if platform.endswith('linux') configure_file(input: 'service_ll.socket.in', output: 'service_ll.socket', configuration: private_cfg) + + single_file_examples += [ 'service_hl' ] + configure_file(input: 'service_hl.socket.in', + output: 'service_hl.socket', + configuration: private_cfg) + endif threaded_examples = [ 'notify_inval_inode',example/service_hl.c new file +224
-0@@ -0,0 +1,224 @@ +/* +* FUSE: Filesystem in Userspace +* Copyright (C) 2026 Oracle. +* +* This program can be distributed under the terms of the GNU GPLv2. +* See the file GPL2.txt. +*/ + +/** @file +* +* minimal example filesystem using high-level API and systemd service api +* +* Compile with: +* +* gcc -Wall single_file.c service_hl.c `pkg-config fuse3 --cflags --libs` -o service_hl +* +* Note: If the pkg-config command fails due to the absence of the fuse3.pc +* file, you should configure the path to the fuse3.pc file in the +* PKG_CONFIG_PATH variable. +* +* Change the ExecStart line in [email protected]: +* +* ExecStart=/path/to/service_hl +* +* to point to the actual path of the service_hl binary. +* +* Finally, install the [email protected] and service_hl.socket files to the +* systemd service directory, usually /run/systemd/system. +* +* ## Source code ## +* \include service_hl.c +* \include service_hl.socket +* \include [email protected] +* \include single_file.c +* \include single_file.h +*/ + +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 19) + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include <fuse.h> +#include <fuse_service.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <assert.h> +#include <pthread.h> +#include <stddef.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <linux/fs.h> +#include <linux/stat.h> +#define USE_SINGLE_FILE_HL_API +#include "single_file.h" + +struct service_hl { +char *device; +struct fuse_service *service; + +/* really booleans */ +int debug; +}; + +static struct service_hl hl = { }; + +static void *service_hl_init (struct fuse_conn_info *conn, +struct fuse_config *cfg) +{ + (void ) conn; + cfg->kernel_cache =1 ; + +return NULL ; +} + +static int service_hl_read (const char *path,char *buf,size_t count, +off_t pos,struct fuse_file_info *fi) +{ +if (!is_single_open_file_path (fi, path)) +return -EIO ; + +if (hl.debug) +fprintf (stderr,"%s: pos 0x%llx count 0x%llx\n" , + __func__, + (unsigned long long )pos, + (unsigned long long )count); + +if (!single_file.allow_dio && fi->direct_io) +return -ENOSYS ; + +single_file_check_read (pos, &count); + +if (!count) +return 0 ; + +return single_file_pread (buf, count, pos); +} + +static int service_hl_write (const char *path,const char *buf,size_t count, +off_t pos,struct fuse_file_info *fi) +{ +int ret; + +if (!is_single_open_file_path (fi, path)) +return -EIO ; + +if (hl.debug) +fprintf (stderr,"%s: pos 0x%llx count 0x%llx\n" , + __func__, + (unsigned long long )pos, + (unsigned long long )count); + +if (!single_file.allow_dio && fi->direct_io) +return -ENOSYS ; + + ret =single_file_check_write (pos, &count); +if (ret <0 ) +return ret; + +if (!count) +return 0 ; + +return single_file_pwrite (buf, count, pos); +} + +static const struct fuse_operations service_hl_oper = { + .getattr = single_file_hl_getattr, + .readdir = single_file_hl_readdir, + .open = single_file_hl_open, + .opendir = single_file_hl_opendir, + .statfs = single_file_hl_statfs, + .chmod = single_file_hl_chmod, + .utimens = single_file_hl_utimens, + .fsync = single_file_hl_fsync, + .chown = single_file_hl_chown, + .truncate = single_file_hl_truncate, + .statx = single_file_hl_statx, + + .init = service_hl_init, + .read = service_hl_read, + .write = service_hl_write, +}; + +#define SERVICE_HL_OPT (t, p, v) { t, offsetof(struct service_hl, p), v } + +static struct fuse_opt service_hl_opts[] = { +SERVICE_HL_OPT ("debug" , debug,1 ), +SINGLE_FILE_OPT_KEYS , +FUSE_OPT_END +}; + +static int service_hl_opt_proc (void *data,const char *arg,int key, +struct fuse_args *outargs) +{ +int ret =single_file_opt_proc (data, arg, key, outargs); + +if (ret <1 ) +return ret; + +switch (key) { +case FUSE_OPT_KEY_NONOPT : +if (!hl.device) { + hl.device =strdup (arg); +return 0 ; + } +return 1 ; +default : +break ; + } + +return 1 ; +} + +int main (int argc,char *argv[]) +{ +struct fuse_args args =FUSE_ARGS_INIT (argc, argv); +int ret =1 ; + +if (fuse_service_accept (&hl.service)) + goto err_args; + +if (!fuse_service_accepted (hl.service)) + goto err_args; + +if (fuse_service_append_args (hl.service, &args)) + goto err_service; + +if (fuse_opt_parse (&args, &hl, service_hl_opts, service_hl_opt_proc)) + goto err_service; + +if (!hl.device) { +printf ("usage: %s [options] <device> <mountpoint>\n" , argv[0 ]); +printf (" %s --help\n" , argv[0 ]); + goto err_service; + } + +if (single_file_service_open (hl.service, hl.device)) + goto err_service; + +if (fuse_service_finish_file_requests (hl.service)) + goto err_singlefile; + +if (single_file_configure (hl.device,NULL )) + goto err_singlefile; + +fuse_service_expect_mount_format (hl.service,S_IFDIR ); + + ret =fuse_service_main (hl.service, &args, &service_hl_oper,NULL ); + +err_singlefile: +single_file_close (); +err_service: +free (hl.device); +fuse_service_send_goodbye (hl.service, ret); +fuse_service_destroy (&hl.service); +err_args: +fuse_opt_free_args (&args); +return fuse_service_exit (ret); +}example/service_hl.socket.in new file +15
-0@@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2026 Oracle. All Rights Reserved. +# Author: Darrick J. Wong <[email protected]> +[Unit] +Description=Socket for service_hl Service + +[Socket] +ListenSequentialPacket=@FUSE_SERVICE_SOCKET_DIR_RAW@/service_hl +Accept=yes +SocketMode=@FUSE_SERVICE_SOCKET_PERMS@ +RemoveOnStop=yes + +[Install] +WantedBy=sockets.targetexample/[email protected] new file +102
-0@@ -0,0 +1,102 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2026 Oracle. All Rights Reserved. +# Author: Darrick J. Wong <[email protected]> +[Unit] +Description=service_hl Sample Fuse Service + +# Don't leave failed units behind, systemd does not clean them up! +CollectMode=inactive-or-failed + +[Service] +Type=exec +ExecStart=/path/to/service_hl + +# Try to capture core dumps +LimitCORE=infinity + +SyslogIdentifier=%N + +# No realtime CPU scheduling +RestrictRealtime=true + +# Don't let us see anything in the regular system, and don't run as root +DynamicUser=true +ProtectSystem=strict +ProtectHome=true +PrivateTmp=true +PrivateDevices=true +PrivateUsers=true + +# No network access +PrivateNetwork=true +ProtectHostname=true +RestrictAddressFamilies=none +IPAddressDeny=any + +# Don't let the program mess with the kernel configuration at all +ProtectKernelLogs=true +ProtectKernelModules=true +ProtectKernelTunables=true +ProtectControlGroups=true +ProtectProc=invisible +RestrictNamespaces=true +RestrictFileSystems= + +# Hide everything in /proc, even /proc/mounts +ProcSubset=pid + +# Only allow the default personality Linux +LockPersonality=true + +# No writable memory pages +MemoryDenyWriteExecute=true + +# Don't let our mounts leak out to the host +PrivateMounts=true + +# Restrict system calls to the native arch and only enough to get things going +SystemCallArchitectures=native +SystemCallFilter=@system-service +SystemCallFilter=~@privileged +SystemCallFilter=~@resources + +SystemCallFilter=~@clock +SystemCallFilter=~@cpu-emulation +SystemCallFilter=~@debug +SystemCallFilter=~@module +SystemCallFilter=~@reboot +SystemCallFilter=~@swap + +SystemCallFilter=~@mount + +# libfuse io_uring wants to pin cores and memory +SystemCallFilter=mbind +SystemCallFilter=sched_setaffinity + +# Leave a breadcrumb if we get whacked by the system call filter +SystemCallErrorNumber=EL3RST + +# Log to the kernel dmesg, just like an in-kernel filesystem driver +StandardOutput=append:/dev/ttyprintk +StandardError=append:/dev/ttyprintk + +# Run with no capabilities at all +CapabilityBoundingSet= +AmbientCapabilities= +NoNewPrivileges=true + +# We don't create files +UMask=7777 + +# No access to hardware /dev files at all +ProtectClock=true +DevicePolicy=closed + +# Don't mess with set[ug]id anything. +RestrictSUIDSGID=true + +# Don't let OOM kills of processes in this containment group kill the whole +# service, because we don't want filesystem drivers to go down. +OOMPolicy=continue +OOMScoreAdjust=-1000example/service_ll.c +1
-0@@ -51,6 +51,7 @@#include <unistd.h> #include <assert.h> #include <pthread.h> +#define USE_SINGLE_FILE_LL_API #include "single_file.h" struct service_ll {example/single_file.c +58
-0@@ -23,7 +23,10 @@#define FUSE_USE_VERSION (FUSE_MAKE_VERSION(3, 19))#include "fuse_lowlevel.h" +#include "fuse.h" #include "fuse_service.h" +#define USE_SINGLE_FILE_LL_API +#define USE_SINGLE_FILE_HL_API #include "single_file.h" #define min (x, y) ((x) < (y) ? (x) : (y))@@ -56,6 +59,23 @@ struct single_file single_file = { .lock = PTHREAD_MUTEX_INITIALIZER, }; +static fuse_ino_t single_file_path_to_ino (const char *path) +{ +if (strcmp (path,"/" ) ==0 ) +return FUSE_ROOT_ID ; +if (strcmp (path +1 , single_file_name) ==0 ) +return SINGLE_FILE_INO ; +return 0 ; +} + +static fuse_ino_t single_open_file_path_to_ino (const struct fuse_file_info *fi, +const char *path) +{ +if (fi) +return fi->fh; +return single_file_path_to_ino (path); +} +static void dirbuf_add (fuse_req_t req,struct dirbuf *b,const char *name,fuse_ino_t ino) {@@ -91,6 +111,13 @@ bool is_single_file_ino(fuse_ino_t ino)return ino ==SINGLE_FILE_INO ; } +bool is_single_open_file_path (const struct fuse_file_info *fi,const char *name) +{ +if (fi) +return is_single_file_ino (fi->fh); +return name[0 ] =='/' &&strcmp (name +1 , single_file_name) ==0 ; +} +void single_file_ll_readdir (fuse_req_t req,fuse_ino_t ino,size_t size,off_t off,struct fuse_file_info *fi) {@@ -117,6 +144,37 @@ void single_file_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,free (b.p); } +int single_file_hl_readdir (const char *path,void *buf,fuse_fill_dir_t filler, +off_t offset,struct fuse_file_info *fi, +enum fuse_readdir_flags flags) +{ +struct stat stbuf; +fuse_ino_t ino =single_open_file_path_to_ino (fi, path); + +memset (&stbuf,0 ,sizeof (stbuf)); + + (void ) offset; + (void ) flags; + +switch (ino) { +case FUSE_ROOT_ID : +break ; +case SINGLE_FILE_INO : +return -ENOTDIR ; +default : +return -ENOENT ; + } + + stbuf.st_ino =FUSE_ROOT_ID ; +filler (buf,"." , &stbuf,0 ,FUSE_FILL_DIR_DEFAULTS ); +filler (buf,".." , &stbuf,0 ,FUSE_FILL_DIR_DEFAULTS ); + + stbuf.st_ino =SINGLE_FILE_INO ; +filler (buf, single_file_name, &stbuf,0 ,FUSE_FILL_DIR_DEFAULTS ); + +return 0 ; +} +static bool sf_stat (fuse_ino_t ino,struct single_file_stat *llstat) {struct fuse_entry_param *entry = &llstat->entry;example/single_file.c void single_file_ll_statx(fuse_req_t req, fuse_ino_t ino, int flags, int mask, · line 299
Quoted 7 lines
example/single_file.c +4
-0@@ -235,40 +293,74 @@ void single_file_ll_statx(fuse_req_t req, fuse_ino_t ino, int flags, int mask,else fuse_reply_statx (req,0 , &stx,0.0 ); } + +int single_file_hl_statx (const char *path,int statx_flags,int statx_mask, +struct statx *stx,struct fuse_file_info *fi) +{bschubert2@imesrv6 build>ninja
[55/102] Compiling C objectexample/service_ll.p/single_file.c.o../example/single_file.c:297:48: warning: unused parameter 'statx_flags' [-Wunused-parameter]
297 | int single_file_hl_statx(const char *path, intstatx_flags, intstatx_mask,
| ^
1 warning generated.
[56/102] Compiling C objectexample/service_hl.p/single_file.c.o../example/single_file.c:297:48: warning: unused parameter 'statx_flags' [-Wunused-parameter]
297 | int single_file_hl_statx(const char *path, intstatx_flags, intstatx_mask,
| ^
1 warning generated.
[102/102] Linking target example/passthrough_hp
Quoted 1 line
And CI tests run with -Werror - fails here.
Ah, I forgot to (void)statx_flags here. How about I make it do something useful instead:
if ((statx_flags & AT_STATX_FORCE_SYNC) && is_single_file_ino(ino) &&
single_file.backing_fd >= 0) {
int ret = fsync(single_file.backing_fd); if (ret)
return -errno;}
(both ll and hl versions)
Oh, bah, when I copied extra warnings CFLAGS from xfsprogs I neglected to invert -Wno-unused-parameter, that's why we keep having these exchanges. Sorry about that. :(
--D
Quoted 261 lines
Quoted 259 lines
example/single_file.c +192
-17+fuse_ino_t ino =single_open_file_path_to_ino (fi, path); +bool filled; + +if (!ino) +return -ENOENT ; + +pthread_mutex_lock (&single_file.lock); + filled =sf_statx (ino, statx_mask, stx); +pthread_mutex_unlock (&single_file.lock); + +return filled ?0 : -ENOENT ; +}#else void single_file_ll_statx (fuse_req_t req,fuse_ino_t ino,int flags,int mask,struct fuse_file_info *fi) {fuse_reply_err (req,ENOSYS ); } + +int single_file_hl_statx (const char *path,int statx_flags,int statx_mask, +struct statx *stx,struct fuse_file_info *fi) +{ +return -ENOSYS ; +}#endif /* STATX_BASIC_STATS */ +static void single_file_statfs (struct statvfs *buf) +{ +pthread_mutex_lock (&single_file.lock); + buf->f_bsize = single_file.blocksize; + buf->f_frsize =0 ; + + buf->f_blocks = single_file.blocks; + buf->f_bfree =0 ; + buf->f_bavail =0 ; + buf->f_files =1 ; + buf->f_ffree =0 ; + buf->f_favail =0 ; + buf->f_fsid =0x50C00L ; + buf->f_flag =0 ; +if (single_file.ro) + buf->f_flag |=ST_RDONLY ; + buf->f_namemax =255 ; +pthread_mutex_unlock (&single_file.lock); +} +void single_file_ll_statfs (fuse_req_t req,fuse_ino_t ino) {struct statvfs buf; (void )ino; -pthread_mutex_lock (&single_file.lock); - buf.f_bsize = single_file.blocksize; - buf.f_frsize =0 ; - - buf.f_blocks = single_file.blocks; - buf.f_bfree =0 ; - buf.f_bavail =0 ; - buf.f_files =1 ; - buf.f_ffree =0 ; - buf.f_favail =0 ; - buf.f_fsid =0x50C00L ; - buf.f_flag =0 ; -if (single_file.ro) - buf.f_flag |=ST_RDONLY ; - buf.f_namemax =255 ; -pthread_mutex_unlock (&single_file.lock); - +single_file_statfs (&buf);fuse_reply_statfs (req, &buf); } +int single_file_hl_statfs (const char *path,struct statvfs *buf) +{ + (void )path; + +single_file_statfs (buf); +return 0 ; +} +void single_file_ll_getattr (fuse_req_t req,fuse_ino_t ino,struct fuse_file_info *fi) {@@ -288,6 +380,28 @@ void single_file_ll_getattr(fuse_req_t req, fuse_ino_t ino, llstat.entry.attr_timeout); } +int single_file_hl_getattr (const char *path,struct stat *stbuf, +struct fuse_file_info *fi) +{ +struct single_file_stat llstat; +fuse_ino_t ino =single_open_file_path_to_ino (fi, path); +bool filled; + +if (!ino) +return -ENOENT ; + +memset (&llstat,0 ,sizeof (llstat)); +pthread_mutex_lock (&single_file.lock); + filled =sf_stat (ino, &llstat); +pthread_mutex_unlock (&single_file.lock); + +if (!filled) +return -ENOENT ; + +memcpy (stbuf, &llstat.entry.attr,sizeof (*stbuf)); +return 0 ; +} +static void get_now (struct timespec *now) {#ifdef CLOCK_REALTIME @@ -342,6 +456,81 @@ void single_file_ll_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,fuse_reply_err (req,EPERM ); } +int single_file_hl_chmod (const char *path,mode_t mode, +struct fuse_file_info *fi) +{ +fuse_ino_t ino =single_open_file_path_to_ino (fi, path); + +if (!ino) +return -ENOENT ; +if (ino !=SINGLE_FILE_INO ) +return -EPERM ; +if (single_file.ro) +return -EPERM ; + +pthread_mutex_lock (&single_file.lock); + single_file.mode = (single_file.mode &S_IFMT ) | (mode & ~S_IFMT ); +pthread_mutex_unlock (&single_file.lock); + +return 0 ; +} + +static void set_time (const struct timespec *ctv,struct timespec *tv) +{ +switch (ctv->tv_nsec) { +case UTIME_OMIT : +return ; +case UTIME_NOW : +get_now (tv); +break ; +default : +memcpy (tv, ctv,sizeof (*tv)); +break ; + } +} + +int single_file_hl_utimens (const char *path,const struct timespec ctv[2 ], +struct fuse_file_info *fi) +{ +fuse_ino_t ino =single_open_file_path_to_ino (fi, path); + +if (!ino) +return -ENOENT ; +if (ino !=SINGLE_FILE_INO ) +return -EPERM ; +if (single_file.ro) +return -EPERM ; + +pthread_mutex_lock (&single_file.lock); +set_time (&ctv[0 ], &single_file.atime); +set_time (&ctv[1 ], &single_file.mtime); +get_now (&single_file.ctime); +pthread_mutex_unlock (&single_file.lock); + +return 0 ; +} + +int single_file_hl_chown (const char *path,uid_t owner,gid_t group, +struct fuse_file_info *fi) +{ + (void )path; + (void )owner; + (void )group; + (void )fi; + +return -EPERM ; +} + +int single_file_hl_truncate (const char *path,off_t len, +struct fuse_file_info *fi) +{ + (void )path; + (void )len; + (void )fi; + +return -EPERM ; +} +void single_file_ll_lookup (fuse_req_t req,fuse_ino_t parent,const char *name) {struct single_file_stat llstat;@@ -373,6 +562,34 @@ void single_file_ll_open(fuse_req_t req, fuse_ino_t ino,fuse_reply_open (req, fi); } +int single_file_hl_opendir (const char *path,struct fuse_file_info *fi) +{ +fuse_ino_t ino =single_file_path_to_ino (path); + +if (!ino) +return -ENOENT ; +if (ino ==SINGLE_FILE_INO ) +return -ENOTDIR ; + + fi->fh = ino; +return 0 ; +} + +int single_file_hl_open (const char *path,struct fuse_file_info *fi) +{ +fuse_ino_t ino =single_file_path_to_ino (path); + +if (!ino) +return -ENOENT ; +if (ino !=SINGLE_FILE_INO ) +return -EISDIR ; +if (single_file.ro && (fi->flags &O_ACCMODE ) !=O_RDONLY ) +return -EROFS ; + + fi->fh = ino; +return 0 ; +} +void single_file_ll_fsync (fuse_req_t req,fuse_ino_t ino,int datasync,struct fuse_file_info *fi) {@@ -390,6 +607,26 @@ void single_file_ll_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,fuse_reply_err (req, ret); } +int single_file_hl_fsync (const char *path,int datasync, +struct fuse_file_info *fi) +{ +fuse_ino_t ino =single_open_file_path_to_ino (fi, path); + + (void )datasync; + +if (!ino) +return -ENOENT ; + +if (ino ==SINGLE_FILE_INO ) { +int ret =fsync (single_file.backing_fd); + +if (ret) +return -errno; + } + +return 0 ; +} +unsigned long long parse_num_blocks (const char *arg,int log_block_size) {char *p;
Re: [PATCH 12/13] example/service: create a sample systemd service for a high-level fuse server
In reply to Darrick J. WongQuoted 99 lines · Darrick J. Wong
From: Darrick J. Wong <[email protected]>
Create a simple high-level fuse server that can be run as a systemd service.
Signed-off-by "Darrick J. Wong" <[email protected]>--- example/single_file.h | 38 ++++++ example/meson.build | 6 + example/service_hl.c | 224 +++++++++++++++++++++++++++++++++++ example/service_hl.socket.in | 15 ++ example/[email protected] | 102 ++++++++++++++++ example/service_ll.c | 1 example/single_file.c | 271 +++++++++++++++++++++++++++++++++++++++--- 7 files changed, 640 insertions(+), 17 deletions(-) create mode 100644 example/service_hl.c create mode 100644 example/service_hl.socket.in create mode 100644 example/[email protected]example/single_file.h +38
-0@@ -124,6 +124,7 @@ ssize_t single_file_pread(char *buf, size_t count, off_t pos);/* low-level fuse operation handlers */ +#ifdef USE_SINGLE_FILE_LL_API bool is_single_file_child (fuse_ino_t parent,const char *name);bool is_single_file_ino (fuse_ino_t ino);@@ -149,5 +150,42 @@ void single_file_ll_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,int reply_buf_limited (fuse_req_t req,const char *buf,size_t bufsize,off_t off,size_t maxsize); +#endif + +/* high-level fuse operation handlers */ + +#ifdef USE_SINGLE_FILE_HL_API +bool is_single_open_file_path (const struct fuse_file_info *fi,const char *name); + +int single_file_hl_readdir (const char *path,void *buf,fuse_fill_dir_t filler, +off_t offset,struct fuse_file_info *fi, +enum fuse_readdir_flags flags); + +int single_file_hl_statfs (const char *path,struct statvfs *buf); + +int single_file_hl_statx (const char *path,int statx_flags,int statx_mask, +struct statx *stx,struct fuse_file_info *fi); + +int single_file_hl_getattr (const char *path,struct stat *stbuf, +struct fuse_file_info *fi); +int single_file_hl_chmod (const char *path,mode_t mode, +struct fuse_file_info *fi); +int single_file_hl_utimens (const char *path,const struct timespec ctv[2 ], +struct fuse_file_info *fi); +int single_file_hl_chown (const char *path,uid_t owner,gid_t group, +struct fuse_file_info *fi); +int single_file_hl_truncate (const char *path,off_t len, +struct fuse_file_info *fi); + +int single_file_hl_opendir (const char *path,struct fuse_file_info *fi); +int single_file_hl_open (const char *path,struct fuse_file_info *fi); + +int single_file_hl_fsync (const char *path,int datasync, +struct fuse_file_info *fi); +#endif + +#if !defined(USE_SINGLE_FILE_LL_API ) && !defined(USE_SINGLE_FILE_HL_API ) +# warning USE_SINGLE_FILE_[HL]L_API not defined! +#endif #endif /* FUSE_SINGLE_FILE_H_ */ example/meson.build +6
-0@@ -19,6 +19,12 @@ if platform.endswith('linux') configure_file(input: 'service_ll.socket.in', output: 'service_ll.socket', configuration: private_cfg) + + single_file_examples += [ 'service_hl' ] + configure_file(input: 'service_hl.socket.in', + output: 'service_hl.socket', + configuration: private_cfg) + endif threaded_examples = [ 'notify_inval_inode',example/service_hl.c new file +1
-0@@ -0,0 +1,224 @@ +/*
example/service_hl.c line 11
Quoted 10 lines
example/service_hl.c new file +10
-0+* FUSE: Filesystem in Userspace +* Copyright (C) 2026 Oracle. +* +* This program can be distributed under the terms of the GNU GPLv2. +* See the file GPL2.txt. +*/ + +/** @file +* +* minimal example filesystem using high-level API and systemd service api
Could you add a bit more context?
- Shows how to build a high level FUSE filesystems that can be managed by systemd
- Enables on-demand filesystem mounting via socket activation
- Allows running FUSE servers with minimal privileges (unlike traditional FUSE which needs mount permissions)
Thanks,
Bernd
Re: [PATCH 12/13] example/service: create a sample systemd service for a high-level fuse server
Quoted 115 lines · Bernd Schubert
Quoted 99 lines · Darrick J. Wong
From: Darrick J. Wong <[email protected]>
Create a simple high-level fuse server that can be run as a systemd service.
Signed-off-by "Darrick J. Wong" <[email protected]>--- example/single_file.h | 38 ++++++ example/meson.build | 6 + example/service_hl.c | 224 +++++++++++++++++++++++++++++++++++ example/service_hl.socket.in | 15 ++ example/[email protected] | 102 ++++++++++++++++ example/service_ll.c | 1 example/single_file.c | 271 +++++++++++++++++++++++++++++++++++++++--- 7 files changed, 640 insertions(+), 17 deletions(-) create mode 100644 example/service_hl.c create mode 100644 example/service_hl.socket.in create mode 100644 example/[email protected]example/single_file.h +38
-0@@ -124,6 +124,7 @@ ssize_t single_file_pread(char *buf, size_t count, off_t pos);/* low-level fuse operation handlers */ +#ifdef USE_SINGLE_FILE_LL_API bool is_single_file_child (fuse_ino_t parent,const char *name);bool is_single_file_ino (fuse_ino_t ino);@@ -149,5 +150,42 @@ void single_file_ll_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,int reply_buf_limited (fuse_req_t req,const char *buf,size_t bufsize,off_t off,size_t maxsize); +#endif + +/* high-level fuse operation handlers */ + +#ifdef USE_SINGLE_FILE_HL_API +bool is_single_open_file_path (const struct fuse_file_info *fi,const char *name); + +int single_file_hl_readdir (const char *path,void *buf,fuse_fill_dir_t filler, +off_t offset,struct fuse_file_info *fi, +enum fuse_readdir_flags flags); + +int single_file_hl_statfs (const char *path,struct statvfs *buf); + +int single_file_hl_statx (const char *path,int statx_flags,int statx_mask, +struct statx *stx,struct fuse_file_info *fi); + +int single_file_hl_getattr (const char *path,struct stat *stbuf, +struct fuse_file_info *fi); +int single_file_hl_chmod (const char *path,mode_t mode, +struct fuse_file_info *fi); +int single_file_hl_utimens (const char *path,const struct timespec ctv[2 ], +struct fuse_file_info *fi); +int single_file_hl_chown (const char *path,uid_t owner,gid_t group, +struct fuse_file_info *fi); +int single_file_hl_truncate (const char *path,off_t len, +struct fuse_file_info *fi); + +int single_file_hl_opendir (const char *path,struct fuse_file_info *fi); +int single_file_hl_open (const char *path,struct fuse_file_info *fi); + +int single_file_hl_fsync (const char *path,int datasync, +struct fuse_file_info *fi); +#endif + +#if !defined(USE_SINGLE_FILE_LL_API ) && !defined(USE_SINGLE_FILE_HL_API ) +# warning USE_SINGLE_FILE_[HL]L_API not defined! +#endif #endif /* FUSE_SINGLE_FILE_H_ */ example/meson.build +6
-0@@ -19,6 +19,12 @@ if platform.endswith('linux') configure_file(input: 'service_ll.socket.in', output: 'service_ll.socket', configuration: private_cfg) + + single_file_examples += [ 'service_hl' ] + configure_file(input: 'service_hl.socket.in', + output: 'service_hl.socket', + configuration: private_cfg) + endif threaded_examples = [ 'notify_inval_inode',example/service_hl.c new file +1
-0@@ -0,0 +1,224 @@ +/* example/service_hl.c line 11
Quoted 10 lines
example/service_hl.c new file +10
-0+* FUSE: Filesystem in Userspace +* Copyright (C) 2026 Oracle. +* +* This program can be distributed under the terms of the GNU GPLv2. +* See the file GPL2.txt. +*/ + +/** @file +* +* minimal example filesystem using high-level API and systemd service api Could you add a bit more context?
Quoted 5 lines
- Shows how to build a high level FUSE filesystems that can be managed by systemd
- Enables on-demand filesystem mounting via socket activation
- Allows running FUSE servers with minimal privileges (unlike traditional FUSE which needs mount permissions)
I will do that for both service_[hl]l.c files. Currently I have for the lowlevel example:
* Minimal example filesystem using low-level API and systemd service API. *
* - Shows how to build a low level FUSE filesystem server that can be managed
* by systemd
* - Enables on-demand filesystem mounting via socket activation
* - Demonstrates requesting resources from the mount-caller's environment
* - Allows running FUSE servers with minimal privileges; isolated mount,
* network, and pid namespaces; and a separate uid/gid (unlike traditional
* FUSE which needs mount permissions and runs in the caller's environment)
*
* Compile with:
*
* gcc -Wall single_file.c service_ll.c pkg-config fuse3 --cflags --libs -o service_ll
*
* Note: If the pkg-config command fails due to the absence of the fuse3.pc
* file, you should configure the path to the fuse3.pc file in the
* PKG_CONFIG_PATH variable.
*
* Change the ExecStart line in [email protected]:
*
* ExecStart=/path/to/service_ll
*
* to point to the actual path of the service_ll binary.
*
* Finally, install the [email protected] and service_ll.socket files to the
* systemd service directory, usually /run/systemd/system. Run these commands
* to activate:
*
* systemctl daemon-reload
* systemctl start service_ll.socket
*
* Then mount with:
*
* mount -t fuse.service_ll /dev/sda /mnt
and similar for service_hl.c.
--D
Quoted 4 lines
Thanks,
Bernd
PATCH 13/13 nullfs: support fuse systemd service mode
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
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.
--- example/meson.build | 6 +++ example/null.c | 51 +++++++++++++++++++++++- example/null.socket.in | 15 +++++++ example/[email protected] | 102 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 171 insertions(+), 3 deletions(-) create mode 100644 example/null.socket.in create mode 100644 example/[email protected]
example/meson.build +6 -0
@@ -10,6 +10,12 @@ if not platform.endswith('bsd') and platform != 'dragonfly' # support mounting files, This is enforced in vfs_domount_first() # with the v_type != VDIR check. examples += [ 'null' ] + + if platform.endswith('linux') + configure_file(input: 'null.socket.in', + output: 'null.socket', + configuration: private_cfg) + endif endif single_file_examples = [ ]
example/null.c +48 -3
87 lines not on this page · raw message
example/null.socket.in new file +15 -0
@@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2026 Oracle. All Rights Reserved. +# Author: Darrick J. Wong <[email protected]> +[Unit] +Description=Socket for null Service + +[Socket] +ListenSequentialPacket=@FUSE_SERVICE_SOCKET_DIR_RAW@/null +Accept=yes +SocketMode=@FUSE_SERVICE_SOCKET_PERMS@ +RemoveOnStop=yes + +[Install] +WantedBy=sockets.target
example/[email protected] new file +102 -0
102 lines not on this page · raw message
Re: [PATCHSET v5] libfuse: run fuse servers as a contained service
In reply to Darrick J. WongQuoted 32 lines · Darrick J. Wong
Hi all,
This patchset defines the necessary communication protocols and library code so that users can mount fuse servers that run in unprivileged systemd service containers. That in turn allows unprivileged untrusted mounts, because the worst that can happen is that a malicious image crashes the fuse server and the mount dies, instead of corrupting the kernel's memory.
v5: Refactor socket IO into helpers, tighten the security checks in
mount_service.c, always set nosuid/nodev for unprivileged mounts, useposix_spawnpin mount.fuse, restructure sample programs and hl library code to avoid the need for unmounting during startup
v4.1: fix various cppcheck/codecheck complaints
v4: fix a large number of security problems that only matter when the
mount helper is being run as a setuid program; fix protocol byteswapping problems; add CLOEXEC to all files being traded back and forth; add an umount command; and strengthen mount socket protocol checks.
v3: refactor the sample code to reduce duplication; fix all thecheckpatch complaints; examples actually build standalone; fuservicemount handles utab now; cleaned up meson feature detection; handle MS_ flags that don't translate to MOUNT_ATTR_*v2: cleaned up error code handling and logging; add some example fuse
service; fuservicemount3 can now be a setuid program to allow unprivileged userspace to fire up a contained filesystem driver. This could be opening Pandora's box...
v1: detach from fuse-iomap seriesIf you're going to start using this code, I strongly recommend pulling from my git trees, which are linked below.
Quoted 7 lines
With a bit of luck, this should all go splendidly. Comments and questions are, as always, welcome.
--D
kernel git tree:
https://git.kernel.org/cgit/linux/kernel/git/djwong/xfs-linux.git/log/?h=fuse-service-container
Hi Darrick,
going to look for your previous pull request - kernel tree doesn't help me for libfuse ;)
Bernd
Quoted 82 lines
---
Commits in this patchset:
* Refactor mount code / move common functions tomount_util.c
*mount_service: add systemd socket service mounting helper
*mount_service: create high level fuse helpers
*mount_service: use the new mount api for the mount service
*mount_service: update mtab after a successful mount
* util: hoist the fuse.conf parsing and setuid mode enforcement code
* util: fix checkpatch complaints in fuser_conf.[ch]
*mount_service: enable unprivileged users in a similar manner as fusermount
* mount.fuse3: integrate systemd service startup
*mount_service: allow installation as a setuid program
* example/service_ll: create a sample systemd service fuse server
* example/service: create a sample systemd service for a high-level fuse server
* nullfs: support fuse systemd service mode--- example/single_file.h | 191 ++ include/fuse.h | 34 include/fuse_service.h | 243 +++ include/fuse_service_priv.h | 161 ++ lib/fuse_i.h | 3 lib/mount_common_i.h | 22 lib/mount_util.h | 8 lib/util.h | 35 util/fuser_conf.h | 62 + util/mount_service.h | 49 + .github/workflows/install-ubuntu-dependencies.sh | 4 README.md | 3 doc/fuservicemount3.8 | 32 doc/meson.build | 3 example/meson.build | 26 example/null.c | 51 + example/null.socket.in | 15 example/[email protected] | 102 + example/service_hl.c | 224 ++ example/service_hl.socket.in | 15 example/[email protected] | 102 + example/service_ll.c | 313 +++ example/service_ll.socket.in | 15 example/[email protected] | 102 + example/single_file.c | 970 ++++++++++ include/meson.build | 4 lib/fuse_service.c | 1220 +++++++++++++ lib/fuse_service_stub.c | 106 + lib/fuse_versionscript | 18 lib/helper.c | 160 ++ lib/meson.build | 17 lib/mount.c | 72 + lib/mount_util.c | 9 meson.build | 53 + meson_options.txt | 9 test/ci-build.sh | 14 util/fuser_conf.c | 396 ++++ util/fusermount.c | 363 ---- util/fuservicemount.c | 65 + util/install_helper.sh | 6 util/meson.build | 24 util/mount.fuse.c | 169 ++ util/mount_service.c | 2111 ++++++++++++++++++++++ 43 files changed, 7197 insertions(+), 404 deletions(-) create mode 100644 example/single_file.h create mode 100644 include/fuse_service.h create mode 100644 include/fuse_service_priv.h create mode 100644 lib/mount_common_i.h create mode 100644 util/fuser_conf.h create mode 100644 util/mount_service.h create mode 100644 doc/fuservicemount3.8 create mode 100644 example/null.socket.in create mode 100644 example/[email protected] create mode 100644 example/service_hl.c create mode 100644 example/service_hl.socket.in create mode 100644 example/[email protected] create mode 100644 example/service_ll.c create mode 100644 example/service_ll.socket.in create mode 100644 example/[email protected] create mode 100644 example/single_file.c create mode 100644 lib/fuse_service.c create mode 100644 lib/fuse_service_stub.c create mode 100644 util/fuser_conf.c create mode 100644 util/fuservicemount.c create mode 100644 util/mount_service.c
Re: [PATCHSET v5] libfuse: run fuse servers as a contained service
Quoted 40 lines · Bernd Schubert
Quoted 37 lines · Darrick J. Wong
Hi all,
This patchset defines the necessary communication protocols and library code so that users can mount fuse servers that run in unprivileged systemd service containers. That in turn allows unprivileged untrusted mounts, because the worst that can happen is that a malicious image crashes the fuse server and the mount dies, instead of corrupting the kernel's memory.
v5: Refactor socket IO into helpers, tighten the security checks in
mount_service.c, always set nosuid/nodev for unprivileged mounts, useposix_spawnpin mount.fuse, restructure sample programs and hl library code to avoid the need for unmounting during startup
v4.1: fix various cppcheck/codecheck complaints
v4: fix a large number of security problems that only matter when the
mount helper is being run as a setuid program; fix protocol byteswapping problems; add CLOEXEC to all files being traded back and forth; add an umount command; and strengthen mount socket protocol checks.
v3: refactor the sample code to reduce duplication; fix all thecheckpatch complaints; examples actually build standalone; fuservicemount handles utab now; cleaned up meson feature detection; handle MS_ flags that don't translate to MOUNT_ATTR_*v2: cleaned up error code handling and logging; add some example fuse
service; fuservicemount3 can now be a setuid program to allow unprivileged userspace to fire up a contained filesystem driver. This could be opening Pandora's box...
v1: detach from fuse-iomap seriesIf you're going to start using this code, I strongly recommend pulling from my git trees, which are linked below.
With a bit of luck, this should all go splendidly. Comments and questions are, as always, welcome.
--D
Quoted 7 lines
Quoted 2 lines
kernel git tree:
https://git.kernel.org/cgit/linux/kernel/git/djwong/xfs-linux.git/log/?h=fuse-service-containerHi Darrick,
going to look for your previous pull request - kernel tree doesn't help me for libfuse ;)
Urrk, that wasn't helpful of me. :(
The following changes since commit ff7aa456d426d89eb19661da7b4c171153bac516:
update kernel FUSE io_uring doc URL (2026-04-20 10:34:32 +0200)
are available in the Git repository at:
https://git.kernel.org/pub/scm/linux/kernel/git/djwong/libfuse.git tags/fuse-service-container_2026-04-22
for you to fetch changes up to 4f47bd86cd84bd511afdeb59fc18994915eb13fa:
nullfs: support fuse systemd service mode (2026-04-22 16:08:25 -0700)
(Sorry for the slow reply, I'm at LinuxFest this weekend. Hopefully you could construct the path to the 22 April version from the previous PR.)
--D
Quoted 85 lines
Bernd
Quoted 82 lines
---
Commits in this patchset:
* Refactor mount code / move common functions tomount_util.c
*mount_service: add systemd socket service mounting helper
*mount_service: create high level fuse helpers
*mount_service: use the new mount api for the mount service
*mount_service: update mtab after a successful mount
* util: hoist the fuse.conf parsing and setuid mode enforcement code
* util: fix checkpatch complaints in fuser_conf.[ch]
*mount_service: enable unprivileged users in a similar manner as fusermount
* mount.fuse3: integrate systemd service startup
*mount_service: allow installation as a setuid program
* example/service_ll: create a sample systemd service fuse server
* example/service: create a sample systemd service for a high-level fuse server
* nullfs: support fuse systemd service mode--- example/single_file.h | 191 ++ include/fuse.h | 34 include/fuse_service.h | 243 +++ include/fuse_service_priv.h | 161 ++ lib/fuse_i.h | 3 lib/mount_common_i.h | 22 lib/mount_util.h | 8 lib/util.h | 35 util/fuser_conf.h | 62 + util/mount_service.h | 49 + .github/workflows/install-ubuntu-dependencies.sh | 4 README.md | 3 doc/fuservicemount3.8 | 32 doc/meson.build | 3 example/meson.build | 26 example/null.c | 51 + example/null.socket.in | 15 example/[email protected] | 102 + example/service_hl.c | 224 ++ example/service_hl.socket.in | 15 example/[email protected] | 102 + example/service_ll.c | 313 +++ example/service_ll.socket.in | 15 example/[email protected] | 102 + example/single_file.c | 970 ++++++++++ include/meson.build | 4 lib/fuse_service.c | 1220 +++++++++++++ lib/fuse_service_stub.c | 106 + lib/fuse_versionscript | 18 lib/helper.c | 160 ++ lib/meson.build | 17 lib/mount.c | 72 + lib/mount_util.c | 9 meson.build | 53 + meson_options.txt | 9 test/ci-build.sh | 14 util/fuser_conf.c | 396 ++++ util/fusermount.c | 363 ---- util/fuservicemount.c | 65 + util/install_helper.sh | 6 util/meson.build | 24 util/mount.fuse.c | 169 ++ util/mount_service.c | 2111 ++++++++++++++++++++++ 43 files changed, 7197 insertions(+), 404 deletions(-) create mode 100644 example/single_file.h create mode 100644 include/fuse_service.h create mode 100644 include/fuse_service_priv.h create mode 100644 lib/mount_common_i.h create mode 100644 util/fuser_conf.h create mode 100644 util/mount_service.h create mode 100644 doc/fuservicemount3.8 create mode 100644 example/null.socket.in create mode 100644 example/[email protected] create mode 100644 example/service_hl.c create mode 100644 example/service_hl.socket.in create mode 100644 example/[email protected] create mode 100644 example/service_ll.c create mode 100644 example/service_ll.socket.in create mode 100644 example/[email protected] create mode 100644 example/single_file.c create mode 100644 lib/fuse_service.c create mode 100644 lib/fuse_service_stub.c create mode 100644 util/fuser_conf.c create mode 100644 util/fuservicemount.c create mode 100644 util/mount_service.c
Re: [PATCHSET v5] libfuse: run fuse servers as a contained service
Quoted 57 lines · Darrick J. Wong
Quoted 40 lines · Bernd Schubert
Quoted 37 lines · Darrick J. Wong
Hi all,
This patchset defines the necessary communication protocols and library code so that users can mount fuse servers that run in unprivileged systemd service containers. That in turn allows unprivileged untrusted mounts, because the worst that can happen is that a malicious image crashes the fuse server and the mount dies, instead of corrupting the kernel's memory.
v5: Refactor socket IO into helpers, tighten the security checks in
mount_service.c, always set nosuid/nodev for unprivileged mounts, useposix_spawnpin mount.fuse, restructure sample programs and hl library code to avoid the need for unmounting during startup
v4.1: fix various cppcheck/codecheck complaints
v4: fix a large number of security problems that only matter when the
mount helper is being run as a setuid program; fix protocol byteswapping problems; add CLOEXEC to all files being traded back and forth; add an umount command; and strengthen mount socket protocol checks.
v3: refactor the sample code to reduce duplication; fix all thecheckpatch complaints; examples actually build standalone; fuservicemount handles utab now; cleaned up meson feature detection; handle MS_ flags that don't translate to MOUNT_ATTR_*v2: cleaned up error code handling and logging; add some example fuse
service; fuservicemount3 can now be a setuid program to allow unprivileged userspace to fire up a contained filesystem driver. This could be opening Pandora's box...
v1: detach from fuse-iomap seriesIf you're going to start using this code, I strongly recommend pulling from my git trees, which are linked below.
With a bit of luck, this should all go splendidly. Comments and questions are, as always, welcome.
--D
Quoted 7 lines
Quoted 2 lines
kernel git tree:
https://git.kernel.org/cgit/linux/kernel/git/djwong/xfs-linux.git/log/?h=fuse-service-containerHi Darrick,
going to look for your previous pull request - kernel tree doesn't help me for libfuse ;)
Urrk, that wasn't helpful of me. :(
The following changes since commit
ff7aa456d426d89eb19661da7b4c171153bac516:update kernel FUSE
io_uringdoc URL (2026-04-20 10:34:32 +0200)are available in the Git repository at:
Quoted 8 lines
https://git.kernel.org/pub/scm/linux/kernel/git/djwong/libfuse.git tags/fuse-service-container_2026-04-22
for you to fetch changes up to
4f47bd86cd84bd511afdeb59fc18994915eb13fa:nullfs: support fuse systemd service mode (2026-04-22 16:08:25 -0700)
(Sorry for the slow reply, I'm at LinuxFest this weekend. Hopefully you could construct the path to the 22 April version from the previous PR.)
No worries at all. Enjoy LinuxFest! I had taken the "fuse-service-container" branch and HEAD points to the tag. Got distracted by another issue anyway. Right now I'm trying to look at the test failures from your branch, but github seems to be very slow.
Thanks,
Bernd
Re: [PATCHSET v5] libfuse: run fuse servers as a contained service
Quoted 66 lines · Bernd Schubert
Quoted 63 lines · Darrick J. Wong
Quoted 40 lines · Bernd Schubert
Quoted 37 lines · Darrick J. Wong
Hi all,
This patchset defines the necessary communication protocols and library code so that users can mount fuse servers that run in unprivileged systemd service containers. That in turn allows unprivileged untrusted mounts, because the worst that can happen is that a malicious image crashes the fuse server and the mount dies, instead of corrupting the kernel's memory.
v5: Refactor socket IO into helpers, tighten the security checks in
mount_service.c, always set nosuid/nodev for unprivileged mounts, useposix_spawnpin mount.fuse, restructure sample programs and hl library code to avoid the need for unmounting during startup
v4.1: fix various cppcheck/codecheck complaints
v4: fix a large number of security problems that only matter when the
mount helper is being run as a setuid program; fix protocol byteswapping problems; add CLOEXEC to all files being traded back and forth; add an umount command; and strengthen mount socket protocol checks.
v3: refactor the sample code to reduce duplication; fix all thecheckpatch complaints; examples actually build standalone; fuservicemount handles utab now; cleaned up meson feature detection; handle MS_ flags that don't translate to MOUNT_ATTR_*v2: cleaned up error code handling and logging; add some example fuse
service; fuservicemount3 can now be a setuid program to allow unprivileged userspace to fire up a contained filesystem driver. This could be opening Pandora's box...
v1: detach from fuse-iomap seriesIf you're going to start using this code, I strongly recommend pulling from my git trees, which are linked below.
With a bit of luck, this should all go splendidly. Comments and questions are, as always, welcome.
--D
Quoted 7 lines
Quoted 2 lines
kernel git tree:
https://git.kernel.org/cgit/linux/kernel/git/djwong/xfs-linux.git/log/?h=fuse-service-containerHi Darrick,
going to look for your previous pull request - kernel tree doesn't help me for libfuse ;)
Urrk, that wasn't helpful of me. :(
The following changes since commit
ff7aa456d426d89eb19661da7b4c171153bac516:update kernel FUSE
io_uringdoc URL (2026-04-20 10:34:32 +0200)are available in the Git repository at:
https://git.kernel.org/pub/scm/linux/kernel/git/djwong/libfuse.git tags/fuse-service-container_2026-04-22
for you to fetch changes up to
4f47bd86cd84bd511afdeb59fc18994915eb13fa:nullfs: support fuse systemd service mode (2026-04-22 16:08:25 -0700)
Quoted 9 lines
Quoted 2 lines
(Sorry for the slow reply, I'm at LinuxFest this weekend. Hopefully you could construct the path to the 22 April version from the previous PR.)
No worries at all. Enjoy LinuxFest! I had taken the "fuse-service-container" branch and HEAD points to the tag. Got distracted by another issue anyway. Right now I'm trying to look at the test failures from your branch, but github seems to be very slow.
Something must have went wrong with the branch
[4/92] Compiling C object util/fusermount3.p/fuser_conf.c.o
FAILED: util/fusermount3.p/fuser_conf.c.o
clang -Iutil/fusermount3.p -Iutil -I../util -Iinclude -I../include -Ilib -I../lib -I. -I.. -fdiagnostics-color=always -fsanitize=address,undefined -fno-omit-frame-pointer -Wall -Winvalid-pch -Wextra -std=gnu11 -O2 -g -D_REENTRANT -DHAVE_LIBFUSE_PRIVATE_CONFIG_H -Wno-sign-compare -D_FILE_OFFSET_BITS=64 -Wstrict-prototypes -Wmissing-declarations -Wwrite-strings -fno-strict-aliasing '-DFUSE_CONF="/usr/local/etc/fuse.conf"' -MD -MQ util/fusermount3.p/fuser_conf.c.o -MF util/fusermount3.p/fuser_conf.c.o.d -o util/fusermount3.p/fuser_conf.c.o -c ../util/fuser_conf.c../util/fuser_conf.c:372:13: error: use of undeclared identifier 'MS_RDONLY'
372 | {"rw", MS_RDONLY, 0, 1},
| ^
../util/fuser_conf.c:373:13: error: use of undeclared identifier 'MS_RDONLY'
373 | {"ro", MS_RDONLY, 1, 1},
| ^
../util/fuser_conf.c:374:14: error: use of undeclared identifier 'MS_NOSUID'
374 | {"suid", MS_NOSUID, 0, 0},
| ^Going to fix that now.
PATCH 1/2 libext2fs: fix some missed fsync calls
In reply to Darrick J. WongHi all,
Fix a few places (like device closing) where we really ought to tell the block device to flush whatever's dirty to disk, even if we've failed to flush all our cached buffers out to disk.
If you're going to start using this code, I strongly recommend pulling from my git trees, which are linked below.
Comments and questions are, as always, welcome.
e2fsprogs git tree:
https://git.kernel.org/cgit/linux/kernel/git/djwong/e2fsprogs.git/log/?h=libext2fs-flushing-fixes
---
Commits in this patchset:
* libext2fs: always fsync the device when flushing the cache
* libext2fs: always fsync the device when closing the unix IO manager
* libext2fs: only fsync the unix fd if we wrote to the device
--- lib/ext2fs/unix_io.c | 83 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 69 insertions(+), 14 deletions(-)
PATCH 1/3 libext2fs: always fsync the device when flushing the cache
From: Darrick J. Wong <[email protected]>
When we're flushing the unix IO manager's buffer cache, we should always fsync the block device, because something could have written to the block device -- either the buffer cache itself, or a direct write. Regardless, the callers all want all dirtied regions to be persisted to stable media.
--- lib/ext2fs/unix_io.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
lib/ext2fs/unix_io.c +2 -1
@@ -1531,7 +1531,8 @@ static errcode_t unix_flush(io_channel channel) retval =flush_cached_blocks (channel, data,0 );#endif #ifdef HAVE_FSYNC -if (!retval &&fsync (data->dev) !=0 ) +/* always fsync the device, even if flushing our own cache failed */ +if (fsync (data->dev) !=0 && !retval)return errno;#endif return retval;
PATCH 2/3 libext2fs: always fsync the device when closing the unix IO manager
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
unix_close is the last chance that libext2fs has to report write failures to users. Although it's likely that ext2fs_close already called ext2fs_flush and told the IO manager to flush, we could do one more sync before we close the file descriptor. Also don't override the fsync's errno with the close's errno.
--- lib/ext2fs/unix_io.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-)
lib/ext2fs/unix_io.c +7 -1
@@ -1213,10 +1213,16 @@ static errcode_t unix_close(io_channel channel)#ifndef NO_IO_CACHE retval =flush_cached_blocks (channel, data,0 );#endif +#ifdef HAVE_FSYNC +/* always fsync the device, even if flushing our own cache failed */ +if (fsync (data->dev) !=0 && !retval) + retval = errno; +#endif unix_funlock (channel); -if (channel->manager != unixfd_io_manager &&close (data->dev) <0 ) +if (channel->manager != unixfd_io_manager &&close (data->dev) <0 && + !retval) retval = errno;free_cache (data);free (data->cache);
PATCH 3/3 libext2fs: only fsync the unix fd if we wrote to the device
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
As an optimization, only fsync the block device fd if we tried to write to the io channel.
--- lib/ext2fs/unix_io.c | 86 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 67 insertions(+), 19 deletions(-)
lib/ext2fs/unix_io.c +67 -19
@@ -132,10 +132,13 @@ struct unix_cache {#define WRITE_DIRECT_SIZE 4/* Must be smaller than CACHE_SIZE */ #define READ_DIRECT_SIZE 4/* Should be smaller than CACHE_SIZE */ +#define UNIX_STATE_DIRTY (1U << 0)/* device needs fsyncing */ +struct unix_private_data {int magic;int dev;int flags; +unsigned int state;/* UNIX_STATE_* */ int align;int access_time;int unix_flock_flags;
@@ -1198,10 +1201,65 @@ static errcode_t unix_open(const char *name, int flags,return unix_open_channel (name, fd, flags, channel, unix_io_manager); } +#ifdef HAVE_FSYNC +static void mark_dirty (io_channel channel) +{ +struct unix_private_data *data = + (struct unix_private_data *) channel->private_data; + +mutex_lock (data,CACHE_MTX ); + data->state |=UNIX_STATE_DIRTY ; +mutex_unlock (data,CACHE_MTX ); +} + +static errcode_t maybe_fsync (io_channel channel,int force_fsync) +{ +struct unix_private_data *data = + (struct unix_private_data *) channel->private_data; +int need_fsync; +errcode_t retval =0 ; + +#ifndef NO_IO_CACHE + retval =flush_cached_blocks (channel, data,0 ); +#endif + +mutex_lock (data,CACHE_MTX ); + need_fsync = force_fsync || (data->state &UNIX_STATE_DIRTY ); + data->state &= ~UNIX_STATE_DIRTY ; +mutex_unlock (data,CACHE_MTX ); + +if (need_fsync &&fsync (data->dev) !=0 ) { +if (!retval) + retval = errno; + } +if (retval) { +/* redirty because writeback failed */ +mark_dirty (channel); +return retval; + } + +return 0 ; +} +#else +# define mark_dirty (...) ((void)0) + +static errcode_t maybe_fsync (io_channel channel,int force_fsync) +{ +struct unix_private_data *data = + (struct unix_private_data *) channel->private_data; +errcode_t retval =0 ; + +#ifndef NO_IO_CACHE + retval =flush_cached_blocks (channel, data,0 ); +#endif +return retval; +} +#endif +static errcode_t unix_close (io_channel channel) {struct unix_private_data *data; -errcode_t retval =0 ; +errcode_t retval;EXT2_CHECK_MAGIC (channel,EXT2_ET_MAGIC_IO_CHANNEL ); data = (struct unix_private_data *) channel->private_data;
@@ -1210,14 +1268,7 @@ static errcode_t unix_close(io_channel channel)if (--channel->refcount >0 )return 0 ; -#ifndef NO_IO_CACHE - retval =flush_cached_blocks (channel, data,0 ); -#endif -#ifdef HAVE_FSYNC -/* always fsync the device, even if flushing our own cache failed */ -if (fsync (data->dev) !=0 && !retval) - retval = errno; -#endif + retval =maybe_fsync (channel,1 );unix_funlock (channel);
@@ -1388,6 +1439,8 @@ static errcode_t unix_write_blk64(io_channel channel, unsigned long long block, data = (struct unix_private_data *) channel->private_data;EXT2_CHECK_MAGIC (data,EXT2_ET_MAGIC_UNIX_IO_CHANNEL ); +mark_dirty (channel); +#ifdef NO_IO_CACHE return raw_write_blk (channel, data, block, count, buf,0 );#else
@@ -1512,6 +1565,8 @@ static errcode_t unix_write_byte(io_channel channel, unsigned long offset,if (lseek (data->dev, offset + data->offset,SEEK_SET ) <0 )return errno; +mark_dirty (channel); + actual =write (data->dev, buf, size);if (actual <0 )return errno;
@@ -1527,21 +1582,12 @@ static errcode_t unix_write_byte(io_channel channel, unsigned long offset,static errcode_t unix_flush (io_channel channel) {struct unix_private_data *data; -errcode_t retval =0 ;EXT2_CHECK_MAGIC (channel,EXT2_ET_MAGIC_IO_CHANNEL ); data = (struct unix_private_data *) channel->private_data;EXT2_CHECK_MAGIC (data,EXT2_ET_MAGIC_UNIX_IO_CHANNEL ); -#ifndef NO_IO_CACHE - retval =flush_cached_blocks (channel, data,0 ); -#endif -#ifdef HAVE_FSYNC -/* always fsync the device, even if flushing our own cache failed */ -if (fsync (data->dev) !=0 && !retval) -return errno; -#endif -return retval; +return maybe_fsync (channel,0 ); } static errcode_t unix_set_option(io_channel channel, const char *option,
@@ -1653,6 +1699,7 @@ static errcode_t unix_discard(io_channel channel, unsigned long long block, }return errno; } +mark_dirty (channel);return 0 ; unimplemented:return EXT2_ET_UNIMPLEMENTED ;
@@ -1734,6 +1781,7 @@ static errcode_t unix_zeroout(io_channel channel, unsigned long long block, }return errno; } +mark_dirty (channel);return 0 ; unimplemented:return EXT2_ET_UNIMPLEMENTED ;
PATCH v5 2/2 fuse4fs: run servers as a contained service
In reply to Darrick J. WongHi all,
This series packages the newly created fuse4fs server into a systemd socket service. This service can be used by the "mount.service" helper in libfuse to implement untrusted unprivileged mounts.
If you're going to start using this code, I strongly recommend pulling from my git trees, which are linked below.
Comments and questions are, as always, welcome.
e2fsprogs git tree:
https://git.kernel.org/cgit/linux/kernel/git/djwong/e2fsprogs.git/log/?h=fuse4fs-service-container
---
Commits in this patchset:
* libext2fs: make it possible to extract the fd from an IO manager
* libext2fs: fix checking for valid fds in mmp.c
* unix_io: allow passing /dev/fd/XXX paths to the unixfd IO manager
* libext2fs: fix MMP code to work with unixfd IO manager
* libext2fs: bump libfuse API version to 3.19
* fuse4fs: hoist some code out of fuse4fs_main
* fuse4fs: enable safe service mode
* fuse4fs: set proc title when in fuse service mode
* fuse4fs: make MMP work correctly in safe service mode
* debian: update packaging for fuse4fs service
--- lib/ext2fs/ext2_io.h | 4 lib/ext2fs/ext2fs.h | 1 lib/ext2fs/ext2fsP.h | 4 MCONFIG.in | 2 configure | 303 +++++++++++++++++++++++++++ configure.ac | 131 +++++++++++- debian/e2fsprogs.install | 7 + debian/fuse4fs.install | 3 debian/libext2fs2t64.symbols | 1 debian/rules | 3 fuse4fs/Makefile.in | 42 +++- fuse4fs/fuse4fs.c | 466 +++++++++++++++++++++++++++++++++++------- fuse4fs/fuse4fs.socket.in | 17 ++ fuse4fs/[email protected] | 102 +++++++++ lib/config.h.in | 12 + lib/ext2fs/io_manager.c | 8 + lib/ext2fs/mmp.c | 101 +++++++++ lib/ext2fs/openfs.c | 1 lib/ext2fs/unix_io.c | 50 ++++- util/subst.conf.in | 3 20 files changed, 1164 insertions(+), 97 deletions(-) mode change 100644 => 100755 debian/fuse4fs.install create mode 100644 fuse4fs/fuse4fs.socket.in create mode 100644 fuse4fs/[email protected]
PATCH 1/10 libext2fs: make it possible to extract the fd from an IO manager
From: Darrick J. Wong <[email protected]>
Make it so that we can extract the fd from an open IO manager. This will be used in subsequent patches to register the open block device with the fuse iomap kernel driver.
--- lib/ext2fs/ext2_io.h | 4 +++- debian/libext2fs2t64.symbols | 1 + lib/ext2fs/io_manager.c | 8 ++++++++ lib/ext2fs/unix_io.c | 20 ++++++++++++++++++++ 4 files changed, 32 insertions(+), 1 deletion(-)
lib/ext2fs/ext2_io.h +3 -1
@@ -103,7 +103,8 @@ struct struct_io_manager {errcode_t (*zeroout)(io_channel channel,unsigned long long block,unsigned long long count);errcode_t (*flock)(io_channel channel,unsigned int flock_flags); -long reserved[13 ]; +errcode_t (*get_fd)(io_channel channel,int *fd); +long reserved[12 ]; };#define IO_FLAG_RW 0x0001
@@ -155,6 +156,7 @@ extern errcode_t io_channel_cache_readahead(io_channel io, unsigned long long count);extern errcode_t io_channel_flock (io_channel io,unsigned int flock_flags);extern errcode_t io_channel_funlock (io_channel io); +extern errcode_t io_channel_get_fd (io_channel io,int *fd);#ifdef _WIN32/* windows_io.c */
debian/libext2fs2t64.symbols +1 -0
@@ -701,6 +701,7 @@ libext2fs.so.2 libext2fs2t64 #MINVER# io_channel_discard@Base 1.42 io_channel_flock@Base 1.47.99 io_channel_funlock@Base 1.47.99 + io_channel_get_fd@Base 1.47.99 io_channel_read_blk64@Base 1.41.1 io_channel_set_options@Base 1.37 io_channel_write_blk64@Base 1.41.1
lib/ext2fs/io_manager.c +8 -0
@@ -166,3 +166,11 @@ errcode_t io_channel_funlock(io_channel io)return io->manager->flock (io,0 ); } + +errcode_t io_channel_get_fd (io_channel io,int *fd) +{ +if (!io->manager->get_fd) +return EXT2_ET_OP_NOT_SUPPORTED ; + +return io->manager->get_fd (io, fd); +}
lib/ext2fs/unix_io.c +20 -0
@@ -1786,6 +1786,24 @@ static errcode_t unix_zeroout(io_channel channel, unsigned long long block, unimplemented:return EXT2_ET_UNIMPLEMENTED ; } + +static errcode_t unix_get_fd (io_channel channel,int *fd) +{ +struct unix_private_data *data; + +EXT2_CHECK_MAGIC (channel,EXT2_ET_MAGIC_IO_CHANNEL ); + data = (struct unix_private_data *) channel->private_data; +EXT2_CHECK_MAGIC (data,EXT2_ET_MAGIC_UNIX_IO_CHANNEL ); + +if (data->offset) { + *fd =-1 ; +return EINVAL ; + } + + *fd = data->dev; +return 0 ; +} +#if __GNUC_PREREQ (4 ,6 )#pragma GCC diagnostic pop#endif
@@ -1808,6 +1826,7 @@ static struct struct_io_manager struct_unix_manager = { .cache_readahead = unix_cache_readahead, .zeroout = unix_zeroout, .flock = unix_flock, + .get_fd = unix_get_fd, };io_manager unix_io_manager = &struct_unix_manager;
@@ -1830,6 +1849,7 @@ static struct struct_io_manager struct_unixfd_manager = { .cache_readahead = unix_cache_readahead, .zeroout = unix_zeroout, .flock = unix_flock, + .get_fd = unix_get_fd, };io_manager unixfd_io_manager = &struct_unixfd_manager;
PATCH 2/10 libext2fs: fix checking for valid fds in mmp.c
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
File descriptors are non-negative numbers, which means that 0 is a valid fd. Fix the code to be consistent with Unix behaviors.
--- lib/ext2fs/mmp.c | 6 +++--- lib/ext2fs/openfs.c | 1 + 2 files changed, 4 insertions(+), 3 deletions(-)
lib/ext2fs/mmp.c +3 -3
@@ -59,11 +59,11 @@ errcode_t ext2fs_mmp_read(ext2_filsys fs, blk64_t mmp_blk, void *buf)return EXT2_ET_MMP_BAD_BLOCK ;/* ext2fs_open() reserves fd0,1,2 to avoid stdio collision, so checking -* mmp_fd <= 0 is OK to validate that the fd is valid. This opens its +* mmp_fd < 0 is OK to validate that the fd is valid. This opens its * own fd to read the MMP block to ensure that it is using O_DIRECT, * regardless of how the io_manager is doing reads, to avoid caching of * the MMP block by the io_manager or the VM. It needs to be fresh. */ -if (fs->mmp_fd <=0 ) { +if (fs->mmp_fd <0 ) {struct stat st;int flags =O_RDONLY |O_DIRECT ;
@@ -427,7 +427,7 @@ errcode_t ext2fs_mmp_stop(ext2_filsys fs) retval =ext2fs_mmp_write (fs, fs->super->s_mmp_block, fs->mmp_cmp); mmp_error: -if (fs->mmp_fd >0 ) { +if (fs->mmp_fd >=0 ) {close (fs->mmp_fd); fs->mmp_fd =-1 ; }
lib/ext2fs/openfs.c +1 -0
@@ -148,6 +148,7 @@ errcode_t ext2fs_open2(const char *name, const char *io_options,/* don't overwrite sb backups unless flag is explicitly cleared */ fs->flags |=EXT2_FLAG_MASTER_SB_ONLY ; fs->umask =022 ; + fs->mmp_fd =-1 ; time_env =ext2fs_safe_getenv ("SOURCE_DATE_EPOCH" );if (time_env) {
PATCH 3/10 unix_io: allow passing /dev/fd/XXX paths to the unixfd IO manager
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
Commit 4ccf9e4fe165cf created a "unixfd" IO manager that allows someone to choose the unixfd IO manager and then mount a filesystem from an existing file descriptor by passing a string with the fd number as the "device" name to ext2fs_open().
That was an unfortunate choice of naming, however, because that could be mistaken for a relative path to a file whose name is an integer number. Let's improve this by allowing callers to pass /dev/fd/XX as the filesystem device name. The upcoming fuse4fs service patches will employ this method to open a filesystem on a block device fd passed into the secure container from a mount helper.
--- lib/ext2fs/unix_io.c | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-)
lib/ext2fs/unix_io.c +29 -1
@@ -67,6 +67,7 @@#ifdef HAVE_SYS_FILE_H #include <sys/file.h> #endif +#include <limits.h> #if defined(__linux__) && defined(_IO) && !defined(BLKROGET )#define BLKROGET _IO(0x12, 94)/* Get read-only status (0 = read_write). */
@@ -1148,13 +1149,40 @@ static errcode_t unix_open_channel(const char *name, int fd,return retval; } +#define DEV_FD_PATH "/dev/fd/" +#define DEV_FD_PATHLEN (sizeof(DEV_FD_PATH) - 1) + +static int possible_unixfd_pathname (const char *path) +{ +return strncmp (DEV_FD_PATH , path,DEV_FD_PATHLEN ) ==0 ; +} +static errcode_t unixfd_open (const char *str_fd,int flags,io_channel *channel) {int fd;int fd_flags; - fd =atoi (str_fd); +/* +* The caller should provide a path in the form "/dev/fd/XX", +* but the shorthand form "XX" is allowed for legacy reasons. +*/ +if (possible_unixfd_pathname (str_fd)) { +char *endptr; +long maybe_fd; + + errno =0 ; + maybe_fd =strtol (str_fd +DEV_FD_PATHLEN , &endptr,10 ); +if (errno) +return errno; +if (*endptr !=0 ) +return EINVAL ; +if (maybe_fd <0 || maybe_fd >INT_MAX ) +return EINVAL ; + fd = maybe_fd; + }else { + fd =atoi (str_fd); + }#if defined(HAVE_FCNTL ) fd_flags =fcntl (fd,F_GETFL );if (fd_flags ==-1 )
PATCH 4/10 libext2fs: fix MMP code to work with unixfd IO manager
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
The MMP code wants to be able to read and write the MMP block directly to storage so that the pagecache does not get in the way. This is critical for correct operation of MMP, because it is guarding against two cluster nodes trying to change the filesystem at the same time.
Unfortunately there's no convenient way to tell an IO manager to perform a particular IO in directio mode, so the MMP code open()s the filesystem source device a second time so that it can set O_DIRECT and maintain its own file position independently of the IO channel. This is a gross layering violation.
For unprivileged containerized fuse4fs, we're going to have a privileged mount helper pass us the fd to the block device, so we'll be using the unixfd IO manager. The enhanced security posture provided by the service definition file (minimal /dev) means that we cannot reopen the source device. In this case, MMP can only duplicate the fd and use the IO channel carefully.
Fix this (sort of) by detecting the unixfd IO manager and duplicating the open fd if it's in use. This adds a requirement that the unixfd originally be opened in O_DIRECT mode if the filesystem is on a block device, but that's the best we can do here.
--- lib/ext2fs/ext2fs.h | 1 + lib/ext2fs/ext2fsP.h | 4 ++ lib/ext2fs/mmp.c | 95 +++++++++++++++++++++++++++++++++++++++++++++++++- lib/ext2fs/unix_io.c | 2 + 4 files changed, 100 insertions(+), 2 deletions(-)
lib/ext2fs/ext2fs.h +1 -0
@@ -225,6 +225,7 @@ typedef struct ext2_file *ext2_file_t;* Internal flags for use by the ext2fs library only */ #define EXT2_FLAG2_USE_FAKE_TIME 0x000000001 +#define EXT2_FLAG2_MMP_USE_IOCHANNEL 0x000000002/* * Special flag in the ext2 inode i_flag field that means that this is
lib/ext2fs/ext2fsP.h +4 -0
@@ -218,3 +218,7 @@ errcode_t ext2fs_remove_exit_fn(ext2_exit_fn fn, void *data); (sizeof(array) / sizeof(array[0]))#define EXT2FS_BUILD_BUG_ON (cond) ((void)sizeof(char[1 - 2*!!(cond)])) + +#ifndef _WIN32 +int possible_unixfd_pathname (const char *path); +#endif
lib/ext2fs/mmp.c +94 -1
134 lines not on this page · raw message
lib/ext2fs/unix_io.c +1 -1
@@ -1152,7 +1152,7 @@ static errcode_t unix_open_channel(const char *name, int fd,#define DEV_FD_PATH "/dev/fd/"#define DEV_FD_PATHLEN (sizeof(DEV_FD_PATH) - 1) -static int possible_unixfd_pathname (const char *path) +int possible_unixfd_pathname (const char *path) {return strncmp (DEV_FD_PATH , path,DEV_FD_PATHLEN ) ==0 ; }
PATCH 5/10 libext2fs: bump libfuse API version to 3.19
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
The fuse service container API is only available in 3.19, so we need to bump FUSE_USE_VERSION up from 3.14 to 3.19.
--- configure | 8 ++++---- configure.ac | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-)
configure +4 -4
@@ -14598,14 +14598,14 @@ fi if test -n "$FUSE_LIB" then - FUSE_USE_VERSION=314 + FUSE_USE_VERSION=319 CFLAGS="$fuse3_CFLAGS $CFLAGS" FUSE_LIB="$fuse3_LIBS" for ac_header in pthread.h fuse.h do : as_ac_Header=`printf "%s\n" "ac_cv_header_$ac_header" | sed "$as_sed_sh"` ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "#define _FILE_OFFSET_BITS 64 -#define FUSE_USE_VERSION 314 +#define FUSE_USE_VERSION 319 " if eval test \"x\$"$as_ac_Header"\" = x"yes" then :
@@ -14640,7 +14640,7 @@ printf %s "checking for lowlevel interface in libfuse... " >&6; } #define _GNU_SOURCE #define _FILE_OFFSET_BITS 64 - #define FUSE_USE_VERSION 314 + #define FUSE_USE_VERSION 319 #include <fuse_lowlevel.h> int
@@ -14818,7 +14818,7 @@ printf %s "checking for cache_readdir support in libfuse... " >&6; } #define _GNU_SOURCE #define _FILE_OFFSET_BITS 64 - #define FUSE_USE_VERSION 314 + #define FUSE_USE_VERSION 319 #include <fuse.h> int
configure.ac +5 -5
@@ -1383,17 +1383,17 @@ AC_SUBST(FUSE_LIB) dnl dnl Set FUSE_USE_VERSION, which is how fuse servers build against a particular -dnl libfuse ABI. Currently we link against the libfuse 3.14 ABI (hence 314) +dnl libfuse ABI. Currently we link against the libfuse 3.19 ABI (hence 319) dnl if test -n "$FUSE_LIB" then - FUSE_USE_VERSION=314 + FUSE_USE_VERSION=319 CFLAGS="$fuse3_CFLAGS $CFLAGS" FUSE_LIB="$fuse3_LIBS" AC_CHECK_HEADERS([pthread.h fuse.h], [], [AC_MSG_FAILURE([Cannot build against fuse3 headers])], [#define _FILE_OFFSET_BITS 64 -#define FUSE_USE_VERSION 314]) +#define FUSE_USE_VERSION 319]) fi if test -n "$FUSE_USE_VERSION" then
@@ -1412,7 +1412,7 @@ then [ AC_LANG_PROGRAM([[ #define _GNU_SOURCE #define _FILE_OFFSET_BITS 64 - #define FUSE_USE_VERSION 314 + #define FUSE_USE_VERSION 319 #include <fuse_lowlevel.h> ]], [[ struct fuse_lowlevel_ops fs_ops = { };
@@ -1512,7 +1512,7 @@ then [ AC_LANG_PROGRAM([[ #define _GNU_SOURCE #define _FILE_OFFSET_BITS 64 - #define FUSE_USE_VERSION 314 + #define FUSE_USE_VERSION 319 #include <fuse.h> ]], [[ struct fuse_file_info fs_ops = {
PATCH 6/10 fuse4fs: hoist some code out of fuse4fs_main
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
In the next patch, we're going to create a separate fuse4fs_main function when we're running in service mode. Hoist into separate helpers the code that will be shared between the two functions.
--- fuse4fs/fuse4fs.c | 95 +++++++++++++++++++++++++++-------------------------- 1 file changed, 49 insertions(+), 46 deletions(-)
fuse4fs/fuse4fs.c +49 -46
@@ -6016,47 +6016,64 @@ static void fuse4fs_com_err_proc(const char *whoami, errcode_t code,fflush (stderr); } -static int fuse4fs_main (struct fuse_args *args,struct fuse4fs *ff) +static int fuse4fs_create_session (struct fuse4fs *ff,struct fuse_args *args, +struct fuse_cmdline_opts *opts) { -struct fuse_cmdline_opts opts; -struct fuse_session *se; -struct fuse_loop_config *loop_config =NULL ; -int ret; - -if (fuse_parse_cmdline (args, &opts) !=0 ) { - ret =1 ; - goto out; - } -if (ff->debug) - opts.debug = true; + opts->debug = true; -if (opts.show_help) { +if (opts->show_help) {fuse_cmdline_help (); - ret =0 ; - goto out_free_opts; +return 0 ; } -if (opts.show_version) { +if (opts->show_version) {printf ("FUSE library version %s\n" ,fuse_pkgversion ()); - ret =0 ; - goto out_free_opts; +return 0 ; } -if (!opts.mountpoint) { +if (!opts->mountpoint) {fprintf (stderr,"error: no mountpoint specified\n" ); - ret =2 ; - goto out_free_opts; +return 2 ; } - se =fuse_session_new (args, &fs_ops,sizeof (fs_ops), ff); -if (se ==NULL ) { - ret =3 ; - goto out_free_opts; + ff->fuse =fuse_session_new (args, &fs_ops,sizeof (fs_ops), ff); +return ff->fuse ?0 :3 ; +} + +static int fuse4fs_event_loop (struct fuse4fs *ff, +struct fuse_loop_config *loop_config, +const struct fuse_cmdline_opts *opts) +{ +/* +* Since there's a Big Kernel Lock around all the libext2fs code, we +* only need to start four threads -- one to decode a request, another +* to do the filesystem work, a third to transmit the reply, and a +* fourth to handle fuse notifications. +*/ +fuse_loop_cfg_set_clone_fd (loop_config, opts->clone_fd); +fuse_loop_cfg_set_idle_threads (loop_config, opts->max_idle_threads); +fuse_loop_cfg_set_max_threads (loop_config,4 ); + +return fuse_session_loop_mt (ff->fuse, loop_config) ==0 ?0 :8 ; +} + +static int fuse4fs_main (struct fuse_args *args,struct fuse4fs *ff) +{ +struct fuse_cmdline_opts opts; +struct fuse_loop_config *loop_config =NULL ; +int ret; + +if (fuse_parse_cmdline (args, &opts) !=0 ) { + ret =1 ; + goto out; } - ff->fuse = se; -if (fuse_session_mount (se, opts.mountpoint) !=0 ) { + ret =fuse4fs_create_session (ff, args, &opts); +if (ret || !ff->fuse) + goto out_free_opts; + +if (fuse_session_mount (ff->fuse, opts.mountpoint) !=0 ) { ret =4 ; goto out_destroy_session; }
@@ -6076,7 +6093,7 @@ static int fuse4fs_main(struct fuse_args *args, struct fuse4fs *ff)close (ff->logfd); ff->logfd =-1 ; -if (fuse_set_signal_handlers (se) !=0 ) { +if (fuse_set_signal_handlers (ff->fuse) !=0 ) { ret =6 ; goto out_unmount; }
@@ -6087,30 +6104,16 @@ static int fuse4fs_main(struct fuse_args *args, struct fuse4fs *ff) goto out_remove_signal_handlers; } -/* -* Since there's a Big Kernel Lock around all the libext2fs code, we -* only need to start four threads -- one to decode a request, another -* to do the filesystem work, a third to transmit the reply, and a -* fourth to handle fuse notifications. -*/ -fuse_loop_cfg_set_clone_fd (loop_config, opts.clone_fd); -fuse_loop_cfg_set_idle_threads (loop_config, opts.max_idle_threads); -fuse_loop_cfg_set_max_threads (loop_config,4 ); + ret =fuse4fs_event_loop (ff, loop_config, &opts); -if (fuse_session_loop_mt (se, loop_config) !=0 ) { - ret =8 ; - goto out_loopcfg; - } - -out_loopcfg:fuse_loop_cfg_destroy (loop_config); out_remove_signal_handlers: -fuse_remove_signal_handlers (se); +fuse_remove_signal_handlers (ff->fuse); out_unmount: -fuse_session_unmount (se); +fuse_session_unmount (ff->fuse); out_destroy_session: +fuse_session_destroy (ff->fuse); ff->fuse =NULL ; -fuse_session_destroy (se); out_free_opts:free (opts.mountpoint); out:
PATCH 7/10 fuse4fs: enable safe service mode
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
Make it possible to run fuse4fs as a safe systemd service, wherein the fuse server only has access to the fds that we pass in.
--- MCONFIG.in | 2 configure | 186 +++++++++++++++++++++++++++++++++ configure.ac | 108 +++++++++++++++++++ fuse4fs/Makefile.in | 40 ++++++- fuse4fs/fuse4fs.c | 241 +++++++++++++++++++++++++++++++++++++++++-- fuse4fs/fuse4fs.socket.in | 17 +++ fuse4fs/[email protected] | 102 ++++++++++++++++++ lib/config.h.in | 6 + util/subst.conf.in | 3 + 9 files changed, 690 insertions(+), 15 deletions(-) create mode 100644 fuse4fs/fuse4fs.socket.in create mode 100644 fuse4fs/[email protected]
MCONFIG.in +2 -0
@@ -42,6 +42,8 @@ HAVE_CROND = @have_crond@ CROND_DIR = @crond_dir@ HAVE_SYSTEMD = @have_systemd@ SYSTEMD_SYSTEM_UNIT_DIR = @systemd_system_unit_dir@ +HAVE_FUSE_SERVICE = @have_fuse_service@ +HAVE_FUSE4FS_SERVICE = @have_fuse4fs_service@ @SET_MAKE@
configure +185 -1
228 lines not on this page · raw message
configure.ac +107 -1
126 lines not on this page · raw message
fuse4fs/Makefile.in +36 -4
@@ -17,6 +17,13 @@ UMANPAGES= @FUSE4FS_CMT@UPROGS+=fuse4fs @FUSE4FS_CMT@UMANPAGES+=fuse4fs.1 +ifeq ($(HAVE_FUSE4FS_SERVICE),yes) +SERVICE_FILES += fuse4fs.socket [email protected] +INSTALLDIRS_TGT += installdirs-systemd +INSTALL_TGT += install-systemd +UNINSTALL_TGT += uninstall-systemd +endif + FUSE4FS_OBJS= fuse4fs.o journal.o recovery.o revoke.o PROFILED_FUSE4FS_OJBS= profiled/fuse4fs.o profiled/journal.o \
@@ -54,7 +61,7 @@ DEPEND_CFLAGS = -I$(top_srcdir)/e2fsck @PROFILE_CMT@ $(Q) $(CC) $(ALL_CFLAGS) -g -pg -o profiled/$*.o -c $< all:: profiled $(SPROGS) $(UPROGS) $(USPROGS) $(SMANPAGES) $(UMANPAGES) \ - $(FMANPAGES) $(LPROGS) + $(FMANPAGES) $(LPROGS) $(SERVICE_FILES) all-static::
@@ -71,6 +78,14 @@ fuse4fs: $(FUSE4FS_OBJS) $(DEPLIBS) $(DEPLIBBLKID) $(DEPLIBUUID) \ $(LIBFUSE) $(LIBBLKID) $(LIBUUID) $(LIBEXT2FS) $(LIBINTL) \ $(CLOCK_GETTIME_LIB) $(SYSLIBS) $(LIBS_E2P) +%.socket: %.socket.in $(DEP_SUBSTITUTE) + $(E) " SUBST $@" + $(Q) $(SUBSTITUTE_UPTIME) $< $@ + +%.service: %.service.in $(DEP_SUBSTITUTE) + $(E) " SUBST $@" + $(Q) $(SUBSTITUTE_UPTIME) $< $@ + journal.o: $(srcdir)/../debugfs/journal.c $(E) " CC $<" $(Q) $(CC) -c $(JOURNAL_CFLAGS) -I$(srcdir) \
@@ -93,11 +108,15 @@ fuse4fs.1: $(DEP_SUBSTITUTE) $(srcdir)/fuse4fs.1.in $(E) " SUBST $@" $(Q) $(SUBSTITUTE_UPTIME) $(srcdir)/fuse4fs.1.in fuse4fs.1 -installdirs: +installdirs: $(INSTALLDIRS_TGT) $(E) " MKDIR_P $(bindir) $(man1dir)" $(Q) $(MKDIR_P) $(DESTDIR)$(bindir) $(DESTDIR)$(man1dir) -install: all $(UMANPAGES) installdirs +installdirs-systemd: + $(E) " MKDIR_P $(SYSTEMD_SYSTEM_UNIT_DIR)" + $(Q) $(MKDIR_P) $(DESTDIR)$(SYSTEMD_SYSTEM_UNIT_DIR) + +install: all $(UMANPAGES) installdirs $(INSTALL_TGT) $(Q) for i in $(UPROGS); do \ $(ES) " INSTALL $(bindir)/$$i"; \ $(INSTALL_PROGRAM) $$i $(DESTDIR)$(bindir)/$$i; \
@@ -110,13 +129,19 @@ install: all $(UMANPAGES) installdirs $(INSTALL_DATA) $$i $(DESTDIR)$(man1dir)/$$i; \ done +install-systemd: $(SERVICE_FILES) installdirs-systemd + $(Q) for i in $(SERVICE_FILES); do \ + $(ES) " INSTALL_DATA $(SYSTEMD_SYSTEM_UNIT_DIR)/$$i"; \ + $(INSTALL_DATA) $$i $(DESTDIR)$(SYSTEMD_SYSTEM_UNIT_DIR)/$$i; \ + done + install-strip: install $(Q) for i in $(UPROGS); do \ $(E) " STRIP $(bindir)/$$i"; \ $(STRIP) $(DESTDIR)$(bindir)/$$i; \ done -uninstall: +uninstall: $(UNINSTALL_TGT) for i in $(UPROGS); do \ $(RM) -f $(DESTDIR)$(bindir)/$$i; \ done
@@ -124,9 +149,16 @@ uninstall: $(RM) -f $(DESTDIR)$(man1dir)/$$i; \ done +uninstall-systemd: + for i in $(SERVICE_FILES); do \ + $(RM) -f $(DESTDIR)$(SYSTEMD_SYSTEM_UNIT_DIR)/$$i; \ + done + clean:: $(RM) -f $(UPROGS) $(UMANPAGES) profile.h \ fuse4fs.profiled \ + $(SERVICE_FILES) \ + fuse4fs.socket \ profiled/*.o \#* *.s *.o *.a *~ core gmon.out mostlyclean: clean
fuse4fs/fuse4fs.c +232 -9
343 lines not on this page · raw message
fuse4fs/fuse4fs.socket.in new file +17 -0
@@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2025-2026 Oracle. All Rights Reserved. +# Author: Darrick J. Wong <[email protected]> +[Unit] +Description=Socket for ext4 Service + +[Socket] +ListenSequentialPacket=@fuse_service_socket_dir@/ext2 +ListenSequentialPacket=@fuse_service_socket_dir@/ext3 +ListenSequentialPacket=@fuse_service_socket_dir@/ext4 +Accept=yes +SocketMode=@fuse_service_socket_perms@ +RemoveOnStop=yes + +[Install] +WantedBy=sockets.target
fuse4fs/[email protected] new file +102 -0
102 lines not on this page · raw message
lib/config.h.in +6 -0
@@ -139,6 +139,9 @@ /* Define to 1 if you have the 'ftruncate64' function. */ #undef HAVE_FTRUNCATE64 +/* Define to 1 if fuse4fs should be built with fuse service support */ +#undef HAVE_FUSE4FS_SERVICE + /* Define to 1 if fuse supports cache_readdir */ #undef HAVE_FUSE_CACHE_READDIR
@@ -148,6 +151,9 @@ /* Define to 1 if fuse supports lowlevel API */ #undef HAVE_FUSE_LOWLEVEL +/* Define to 1 if fuse supports service */ +#undef HAVE_FUSE_SERVICE + /* Define to 1 if you have the 'futimes' function. */ #undef HAVE_FUTIMES
util/subst.conf.in +3 -0
@@ -24,3 +24,6 @@ root_bindir @root_bindir@ libdir @libdir@ $exec_prefix @exec_prefix@ pkglibexecdir @libexecdir@/e2fsprogs +bindir @bindir@ +fuse_service_socket_dir @fuse_service_socket_dir@ +fuse_service_socket_perms @fuse_service_socket_perms@
PATCH 8/10 fuse4fs: set proc title when in fuse service mode
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
When in fuse service mode, set the process title so that we can identify fuse servers by mount arguments. When the service ends, amend the title again to say that we're cleaning up. This is done to make ps aux a bit more communicative as to what is going on.
--- configure | 109 +++++++++++++++++++++++++++++++++++++++++++++++++++ configure.ac | 13 ++++++ fuse4fs/Makefile.in | 2 - fuse4fs/fuse4fs.c | 47 ++++++++++++++++++++++ lib/config.h.in | 6 +++ 5 files changed, 176 insertions(+), 1 deletion(-)
configure +109 -0
@@ -696,6 +696,7 @@ gcc_ranlib gcc_ar UNI_DIFF_OPTS SEM_INIT_LIB +LIBBSD_LIB FUSE4FS_CMT FUSE2FS_CMT fuse_service_socket_perms
@@ -15014,6 +15015,114 @@ printf "%s\n" "#define HAVE_FUSE_CACHE_READDIR 1" >>confdefs.h fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for setproctitle in -lbsd" >&5 +printf %s "checking for setproctitle in -lbsd... " >&6; } +if test ${ac_cv_lib_bsd_setproctitle+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS +LIBS="-lbsd $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char setproctitle (void); +int +main (void) +{ +return setproctitle (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + ac_cv_lib_bsd_setproctitle=yes +else case e in #( + e) ac_cv_lib_bsd_setproctitle=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS ;; +esac +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_bsd_setproctitle" >&5 +printf "%s\n" "$ac_cv_lib_bsd_setproctitle" >&6; } +if test "x$ac_cv_lib_bsd_setproctitle" = xyes +then : + LIBBSD_LIB=-lbsd +fi + +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for setproctitle_init in -lbsd" >&5 +printf %s "checking for setproctitle_init in -lbsd... " >&6; } +if test ${ac_cv_lib_bsd_setproctitle_init+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS +LIBS="-lbsd $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char setproctitle_init (void); +int +main (void) +{ +return setproctitle_init (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + ac_cv_lib_bsd_setproctitle_init=yes +else case e in #( + e) ac_cv_lib_bsd_setproctitle_init=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS ;; +esac +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_bsd_setproctitle_init" >&5 +printf "%s\n" "$ac_cv_lib_bsd_setproctitle_init" >&6; } +if test "x$ac_cv_lib_bsd_setproctitle_init" = xyes +then : + LIBBSD_LIB=-lbsd +fi + + +if test "$ac_cv_lib_bsd_setproctitle" = yes ; then + +printf "%s\n" "#define HAVE_SETPROCTITLE 1" >>confdefs.h + +fi +if test "$ac_cv_lib_bsd_setproctitle_init" = yes ; then + +printf "%s\n" "#define HAVE_SETPROCTITLE_INIT 1" >>confdefs.h + +fi + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for PR_SET_IO_FLUSHER" >&5 printf %s "checking for PR_SET_IO_FLUSHER... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext
configure.ac +13 -0
@@ -1619,6 +1619,19 @@ then [Define to 1 if fuse supports cache_readdir]) fi +dnl +dnl see if setproctitle exists +dnl +AC_CHECK_LIB(bsd, setproctitle, [LIBBSD_LIB=-lbsd]) +AC_CHECK_LIB(bsd, setproctitle_init, [LIBBSD_LIB=-lbsd]) +AC_SUBST(LIBBSD_LIB) +if test "$ac_cv_lib_bsd_setproctitle" = yes ; then + AC_DEFINE(HAVE_SETPROCTITLE, 1, [Define to 1 if setproctitle present in libbsd]) +fi +if test "$ac_cv_lib_bsd_setproctitle_init" = yes ; then + AC_DEFINE(HAVE_SETPROCTITLE_INIT, 1, [Define to 1 if setproctitle_init present in libbsd]) +fi + dnl dnl see if PR_SET_IO_FLUSHER exists dnl
fuse4fs/Makefile.in +1 -1
@@ -76,7 +76,7 @@ fuse4fs: $(FUSE4FS_OBJS) $(DEPLIBS) $(DEPLIBBLKID) $(DEPLIBUUID) \ $(E) " LD $@" $(Q) $(CC) $(ALL_LDFLAGS) -o fuse4fs $(FUSE4FS_OBJS) $(LIBS) \ $(LIBFUSE) $(LIBBLKID) $(LIBUUID) $(LIBEXT2FS) $(LIBINTL) \ - $(CLOCK_GETTIME_LIB) $(SYSLIBS) $(LIBS_E2P) + $(CLOCK_GETTIME_LIB) $(SYSLIBS) $(LIBS_E2P) @LIBBSD_LIB@ %.socket: %.socket.in $(DEP_SUBSTITUTE) $(E) " SUBST $@"
fuse4fs/fuse4fs.c +47 -0
77 lines not on this page · raw message
lib/config.h.in +6 -0
@@ -376,6 +376,12 @@ /* Define to 1 if you have the 'setmntent' function. */ #undef HAVE_SETMNTENT +/* Define to 1 if setproctitle present in libbsd */ +#undef HAVE_SETPROCTITLE + +/* Define to 1 if setproctitle_init present in libbsd */ +#undef HAVE_SETPROCTITLE_INIT + /* Define to 1 if you have the 'setresgid' function. */ #undef HAVE_SETRESGID
PATCH 9/10 fuse4fs: make MMP work correctly in safe service mode
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
Normally, the libext2fs MMP code open()s a complete separate file descriptor to read and write the MMP block so that it can have its own private open file with its own access mode and file position. However, if the unixfd IO manager is in use, it will reuse the io channel, which means that MMP and the unixfd share the same open file and hence the access mode and file position.
MMP requires directio access to block devices so that changes are immediately visible on other nodes. Therefore, we need the IO channel (and thus the filesystem) to be running in directio mode if MMP is in use.
To make this work correctly with the sole unixfd IO manager user (fuse4fs in unprivileged service mode), we must set O_DIRECT on the bdev fd and mount the filesystem in directio mode.
--- fuse4fs/fuse4fs.c | 51 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 3 deletions(-)
fuse4fs/fuse4fs.c +48 -3
@@ -1371,12 +1371,57 @@ static int fuse4fs_service_get_config(struct fuse4fs *ff) }static errcode_t fuse4fs_service_openfs (struct fuse4fs *ff,char *options, -int flags) +int *flags) { +struct stat statbuf;char path[64 ]; +errcode_t retval; +int ret; + ret =fstat (ff->bdev_fd, &statbuf); +if (ret) +return errno; + +/* +* Open the filesystem with SKIP_MMP so that we can find out if the +* filesystem actually has MMP. +*/ snprintf (path,sizeof (path),"/dev/fd/%d" , ff->bdev_fd); -return ext2fs_open2 (path, options, flags,0 ,0 , unixfd_io_manager, + retval =ext2fs_open2 (path, options, *flags |EXT2_FLAG_SKIP_MMP ,0 ,0 , + unixfd_io_manager, &ff->fs); +if (retval) +return retval; + +/* +* If the fs doesn't have MMP then we're good to go. Otherwise close +* the filesystem so that we can reopen it with MMP enabled. +*/ +if (!ext2fs_has_feature_mmp (ff->fs->super)) +return 0 ; + + retval =ext2fs_close_free (&ff->fs); +if (retval) +return retval; + +/* +* If the filesystem is not on a regular file, MMP will share the same +* fd as the unixfd IO channel. We need to set O_DIRECT on the bdev_fd +* and open the filesystem in directio mode. +*/ +if (!S_ISREG (statbuf.st_mode)) { +int fflags =fcntl (ff->bdev_fd,F_GETFL ); + +if (!(fflags &O_DIRECT )) { + ret =fcntl (ff->bdev_fd,F_SETFL , fflags |O_DIRECT ); +if (ret) +return EXT2_ET_MMP_OPEN_DIRECT ; + } + + ff->directio =1 ; + *flags |=EXT2_FLAG_DIRECT_IO ; + } + +return ext2fs_open2 (path, options, *flags,0 ,0 , unixfd_io_manager, &ff->fs); }#else
@@ -1516,7 +1561,7 @@ static errcode_t fuse4fs_open(struct fuse4fs *ff) deadline =init_deadline (FUSE4FS_OPEN_TIMEOUT ); do { if (fuse4fs_is_service(ff)) - err = fuse4fs_service_openfs(ff, options, flags); + err = fuse4fs_service_openfs(ff, options, &flags); else err = ext2fs_open2(ff->device, options, flags, 0, 0, unix_io_manager, &ff->fs);
PATCH 10/10 debian: update packaging for fuse4fs service
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
Update the Debian packaging code so that we can create fuse4fs service containers.
--- debian/e2fsprogs.install | 7 ++++++- debian/fuse4fs.install | 3 +++ debian/rules | 3 +++ 3 files changed, 12 insertions(+), 1 deletion(-) mode change 100644 => 100755 debian/fuse4fs.install
debian/e2fsprogs.install +6 -1
@@ -50,4 +50,9 @@ usr/share/man/man8/resize2fs.8 usr/share/man/man8/tune2fs.8 etc [linux-any] ${deb_udevudevdir}/rules.d -[linux-any] ${deb_systemdsystemunitdir} +[linux-any] ${deb_systemdsystemunitdir}/[email protected] +[linux-any] ${deb_systemdsystemunitdir}/[email protected] +[linux-any] ${deb_systemdsystemunitdir}/e2scrub_all.service +[linux-any] ${deb_systemdsystemunitdir}/e2scrub_all.timer +[linux-any] ${deb_systemdsystemunitdir}/[email protected] +[linux-any] ${deb_systemdsystemunitdir}/e2scrub_reap.service
debian/fuse4fs.install +3 -0
@@ -1,2 +1,5 @@ +#!/usr/bin/dh-exec usr/bin/fuse4fs usr/share/man/man1/fuse4fs.1 +[linux-any] ${deb_systemdsystemunitdir}/fuse4fs.socket +[linux-any] ${deb_systemdsystemunitdir}/[email protected]
debian/rules +3 -0
@@ -173,6 +173,9 @@ override_dh_installinfo: ifneq ($(DEB_HOST_ARCH_OS), hurd) override_dh_installsystemd: dh_installsystemd -p e2fsprogs --no-restart-after-upgrade --no-stop-on-upgrade e2scrub_all.timer e2scrub_reap.service +ifeq ($(SKIP_FUSE4FS),) + dh_installsystemd -p fuse4fs fuse4fs.socket +endif endif override_dh_makeshlibs:
RFC PATCH 1/4 fusefatfs: enable fuse systemd service mode
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
Enable use of fusefatfs as a contained systemd service.
--- CMakeLists.txt | 18 +++++++ config.h.in | 2 + diskio.c | 3 + fusefatfs.c | 124 ++++++++++++++++++++++++++++++++++++++++++++++--- fusefatfs.socket.in | 17 +++++++ [email protected] | 102 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 259 insertions(+), 7 deletions(-) create mode 100644 fusefatfs.socket.in create mode 100644 [email protected]
CMakeLists.txt +18 -0
@@ -14,6 +14,24 @@ pkg_check_modules(FUSE fuse3) if(NOT FUSE_FOUND) pkg_check_modules(FUSE REQUIRED fuse) endif() +pkg_get_variable(FUSE3_SERVICE_SOCKET_DIR fuse3 service_socket_dir) +pkg_get_variable(FUSE3_SERVICE_SOCKET_PERMS fuse3 service_socket_perms) +pkg_check_modules(SYSTEMD systemd) +if (SYSTEMD_FOUND) + pkg_get_variable(SYSTEMD_SYSTEM_UNIT_DIR systemd systemd_system_unit_dir) +endif() +IF ( (NOT "${FUSE3_SERVICE_SOCKET_DIR}" STREQUAL "") AND (NOT "${SYSTEMD_SYSTEM_UNIT_DIR}" STREQUAL "") ) + message(STATUS "Found libfuse3 service socket dir: ${FUSE3_SERVICE_SOCKET_DIR}") + message(STATUS "Found libfuse3 service socket perms: ${FUSE3_SERVICE_SOCKET_PERMS}") + set(DEFINE_HAVE_FUSE_SERVICE "#define HAVE_FUSE_SERVICE") + configure_file(fusefatfs.socket.in ${CMAKE_BINARY_DIR}/fusefatfs.socket @ONLY) + configure_file([email protected] ${CMAKE_BINARY_DIR}/[email protected] @ONLY) + message(STATUS "Found systemd system unit dir: ${SYSTEMD_SYSTEM_UNIT_DIR}") + install(FILES ${CMAKE_BINARY_DIR}/fusefatfs.socket + DESTINATION ${CMAKE_INSTALL_PREFIX}${SYSTEMD_SYSTEM_UNIT_DIR}) + install(FILES ${CMAKE_BINARY_DIR}/[email protected] + DESTINATION ${CMAKE_INSTALL_PREFIX}${SYSTEMD_SYSTEM_UNIT_DIR}) +endif() string(REGEX REPLACE "\\..*" "" FUSE_VERSION ${FUSE_VERSION}) set(CMAKE_REQUIRED_DEFINITIONS -D_FILE_OFFSET_BITS=64)
config.h.in +2 -0
@@ -4,5 +4,7 @@ #define PROGNAME "@CMAKE_PROJECT_NAME@" #define VERSION "@CMAKE_PROJECT_VERSION@" +@DEFINE_HAVE_FUSE_SERVICE@ + #endif
diskio.c +3 -0
@@ -41,6 +41,9 @@ DSTATUS disk_initialize (struct fftab *drv =fftab_get (pdrv);if (!drv)return STA_NOINIT ; +if (drv->fd >=0 ) +return RES_OK ; +if (drv->flags &FFFF_RDONLY ) drv->fd =open (drv->path,O_RDONLY );else
fusefatfs.c +117 -7
177 lines not on this page · raw message
fusefatfs.socket.in new file +17 -0
@@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2026 Oracle. All Rights Reserved. +# Author: Darrick J. Wong <[email protected]> +[Unit] +Description=Socket for fusefatfs Service + +[Socket] +ListenSequentialPacket=@FUSE3_SERVICE_SOCKET_DIR@/vfat +ListenSequentialPacket=@FUSE3_SERVICE_SOCKET_DIR@/msdos +ListenSequentialPacket=@FUSE3_SERVICE_SOCKET_DIR@/fat +Accept=yes +SocketMode=@FUSE3_SERVICE_SOCKET_PERMS@ +RemoveOnStop=yes + +[Install] +WantedBy=sockets.target
[email protected] new file +102 -0
102 lines not on this page · raw message
RFC PATCH 2/4 exfat: enable fuse systemd service mode
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
Enable use of exfat as a contained systemd service.
--- libexfat/exfat.h | 3 + configure.ac | 150 ++++++++++++++++++++++++++++++++++++++++++++++++ fuse/Makefile.am | 16 +++++ fuse/exfat.socket.in | 15 +++++ fuse/[email protected] | 102 +++++++++++++++++++++++++++++++++ fuse/main.c | 131 ++++++++++++++++++++++++++++++++++++++++-- libexfat/io.c | 21 ++++++- libexfat/mount.c | 19 +++++- 8 files changed, 447 insertions(+), 10 deletions(-) create mode 100644 fuse/exfat.socket.in create mode 100644 fuse/[email protected]
libexfat/exfat.h +3 -0
@@ -147,6 +147,7 @@ void exfat_warn(const char* format, ...) PRINTF; void exfat_debug(const char* format, ...) PRINTF;struct exfat_dev *exfat_open (const char * spec,enum exfat_mode mode); +struct exfat_dev *exfat_fdopen (int fd,const char * spec,enum exfat_mode mode);int exfat_close (struct exfat_dev * dev);int exfat_fsync (struct exfat_dev * dev);enum exfat_mode exfat_get_mode (const struct exfat_dev * dev);
@@ -225,6 +226,8 @@ int exfat_set_label(struct exfat* ef, const char* label);int exfat_soil_super_block (const struct exfat * ef);int exfat_mount (struct exfat * ef,const char * spec,const char * options); +int exfat_mount_from (struct exfat * ef,int fd,const char * spec, +const char * options);void exfat_unmount (struct exfat * ef); time_t exfat_exfat2unix(le16_t date, le16_t time, uint8_t centisec,
configure.ac +149 -1
162 lines not on this page · raw message
fuse/Makefile.am +16 -0
@@ -27,6 +27,22 @@ mount_exfat_fuse_CPPFLAGS = -imacros $(top_srcdir)/libexfat/config.h mount_exfat_fuse_CFLAGS = $(FUSE2_CFLAGS) $(FUSE3_CFLAGS) $(UBLIO_CFLAGS) mount_exfat_fuse_LDADD = $(top_srcdir)/libexfat/libexfat.a $(FUSE2_LIBS) $(FUSE3_LIBS) $(UBLIO_LIBS) +if HAVE_EXFAT_FUSE_SERVICE +bin_SCRIPTS = exfat.socket [email protected] +endif + +exfat.socket: exfat.socket.in + sed \ + -e "s|@FUSE3_SERVICE_SOCKET_DIR@|$(fuse_service_socket_dir)|g" \ + -e "s|@FUSE3_SERVICE_SOCKET_PERMS@|$(fuse_service_socket_perms)|g" \ + < $< > $@ + [email protected]: [email protected] + sed -e "s|@SBINDIR@|$(sbindir)|g" \ + < $< > $@ + +EXTRA_PROGRAMS=$(SERVICE_FILES) + install-exec-hook: ln -sf $(sbin_PROGRAMS) $(DESTDIR)$(sbindir)/mount.exfat
fuse/exfat.socket.in new file +15 -0
@@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2026 Oracle. All Rights Reserved. +# Author: Darrick J. Wong <[email protected]> +[Unit] +Description=Socket for exfat Service + +[Socket] +ListenSequentialPacket=@FUSE3_SERVICE_SOCKET_DIR@/exfat +Accept=yes +SocketMode=@FUSE3_SERVICE_SOCKET_PERMS@ +RemoveOnStop=yes + +[Install] +WantedBy=sockets.target
fuse/[email protected] new file +102 -0
102 lines not on this page · raw message
fuse/main.c +125 -6
183 lines not on this page · raw message
libexfat/io.c +20 -1
@@ -86,7 +86,8 @@ static int open_rw(const char* spec)return fd; } -struct exfat_dev *exfat_open (const char * spec,enum exfat_mode mode) +static struct exfat_dev *__exfat_open (int fd,const char * spec, +enum exfat_mode mode) {struct exfat_dev * dev;struct stat stbuf;
@@ -119,6 +120,10 @@ struct exfat_dev* exfat_open(const char* spec, enum exfat_mode mode)return NULL ; } + dev->fd = fd; +if (dev->fd >=0 ) + goto opened; +switch (mode) {case EXFAT_MODE_RO :
@@ -162,6 +167,7 @@ struct exfat_dev* exfat_open(const char* spec, enum exfat_mode mode)return NULL ; } +opened:if (fstat (dev->fd, &stbuf) !=0 ) {close (dev->fd);
@@ -284,6 +290,19 @@ struct exfat_dev* exfat_open(const char* spec, enum exfat_mode mode)return dev; } +struct exfat_dev *exfat_open (const char * spec,enum exfat_mode mode) +{ +return __exfat_open (-1 , spec, mode); +} + +struct exfat_dev *exfat_fdopen (int fd,const char * spec,enum exfat_mode mode) +{ +if (fd <0 ) +return NULL ; + +return __exfat_open (fd, spec, mode); +} +int exfat_close (struct exfat_dev * dev) {int rc =0 ;
libexfat/mount.c +17 -2
@@ -180,7 +180,8 @@ static void exfat_free(struct exfat* ef) ef->sb =NULL ; } -int exfat_mount (struct exfat * ef,const char * spec,const char * options) +static int __exfat_mount (struct exfat * ef,int fd,const char * spec, +const char * options) {int rc;enum exfat_mode mode;
@@ -196,7 +197,10 @@ int exfat_mount(struct exfat* ef, const char* spec, const char* options) mode =EXFAT_MODE_ANY ; else mode =EXFAT_MODE_RW ; - ef->dev =exfat_open (spec, mode); +if (fd >=0 ) + ef->dev =exfat_fdopen (fd, spec, mode); +else + ef->dev =exfat_open (spec, mode);if (ef->dev ==NULL )return -ENODEV ;if (exfat_get_mode (ef->dev) ==EXFAT_MODE_RO )
@@ -340,6 +344,17 @@ int exfat_mount(struct exfat* ef, const char* spec, const char* options)return -EIO ; } +int exfat_mount_from (struct exfat * ef,int fd,const char * spec, +const char * options) +{ +return __exfat_mount (ef, fd, spec, options); +} + +int exfat_mount (struct exfat * ef,const char * spec,const char * options) +{ +return __exfat_mount (ef,-1 , spec, options); +} +static void finalize_super_block (struct exfat * ef) {if (ef->ro)