public inbox for linux-fsdevel@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCHSET v4] libfuse: run fuse servers as a contained service
@ 2026-04-09 22:20 Darrick J. Wong
  2026-04-09 22:20 ` [PATCH 01/13] Refactor mount code / move common functions to mount_util.c Darrick J. Wong
                   ` (12 more replies)
  0 siblings, 13 replies; 24+ messages in thread
From: Darrick J. Wong @ 2026-04-09 22:20 UTC (permalink / raw)
  To: djwong, bschubert; +Cc: miklos, neal, linux-fsdevel, bernd, joannelkoong

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.

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/inetd 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 the same 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                            |  184 ++
 include/fuse.h                                   |   34 
 include/fuse_service.h                           |  243 +++
 include/fuse_service_priv.h                      |  134 ++
 lib/fuse_i.h                                     |    3 
 lib/mount_common_i.h                             |   22 
 lib/mount_util.h                                 |    8 
 lib/util.h                                       |   35 
 util/fuser_conf.h                                |   53 +
 util/mount_service.h                             |   47 +
 .github/workflows/install-ubuntu-dependencies.sh |   12 
 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/null@.service                            |  102 +
 example/service_hl.c                             |  237 +++
 example/service_hl.socket.in                     |   15 
 example/service_hl@.service                      |  102 +
 example/service_ll.c                             |  309 ++++
 example/service_ll.socket.in                     |   15 
 example/service_ll@.service                      |  102 +
 example/single_file.c                            |  856 ++++++++++
 include/meson.build                              |    4 
 lib/fuse_service.c                               | 1114 +++++++++++++
 lib/fuse_service_stub.c                          |  106 +
 lib/fuse_versionscript                           |   18 
 lib/helper.c                                     |  176 ++
 lib/meson.build                                  |   17 
 lib/mount.c                                      |   72 +
 lib/mount_util.c                                 |    9 
 meson.build                                      |   53 +
 meson_options.txt                                |    9 
 test/ci-build.sh                                 |    7 
 util/fuser_conf.c                                |  369 ++++
 util/fusermount.c                                |  329 ----
 util/fuservicemount.c                            |   66 +
 util/install_helper.sh                           |    6 
 util/meson.build                                 |   24 
 util/mount.fuse.c                                |  135 +-
 util/mount_service.c                             | 1866 ++++++++++++++++++++++
 43 files changed, 6650 insertions(+), 373 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/null@.service
 create mode 100644 example/service_hl.c
 create mode 100644 example/service_hl.socket.in
 create mode 100644 example/service_hl@.service
 create mode 100644 example/service_ll.c
 create mode 100644 example/service_ll.socket.in
 create mode 100644 example/service_ll@.service
 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

The range diff from the 27 March posting is as follows:

 1:  7772a73135e655 !  1:  cc4c087e2ab537 Refactor mount code / move common functions to mount_util.c
    @@ lib/mount.c
      
      #ifndef MS_DIRSYNC
      #define MS_DIRSYNC 128
      #endif
      
      enum {
    -@@ lib/mount.c: static int fuse_mount_fusermount(const char *mountpoint, struct mount_opts *mo,
    +@@ lib/mount.c: static int fuse_mount_fusermount(const char *mountpoint, const struct mount_opts
      #endif
      
      static int fuse_mount_sys(const char *mnt, struct mount_opts *mo,
      			  const char *mnt_opts)
      {
      	char tmp[128];
    @@ lib/mount.c: int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo)
     +
     +	return type;
     +}
     
      ## lib/mount_util.c ##
     @@ lib/mount_util.c: int fuse_mnt_parse_fuse_fd(const char *mountpoint)
    - 	    len == strlen(mountpoint)) {
    - 		return fd;
    + 		}
    + 		return (int)fd;
      	}
      
      	return -1;
      }
     +
     +#define FUSE_KERN_DEVICE_ENV	"FUSE_KERN_DEVICE"
 2:  2a5e7f1feed3e8 !  2:  110783e2c1ba7d mount_service: add systemd/inetd socket service mounting helper
    @@ include/fuse_service.h (new)
     + * 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
     +
    @@ include/fuse_service.h (new)
     + * @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
    @@ include/fuse_service.h (new)
     + */
     +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
    @@ include/fuse_service_priv.h (new)
     +#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 */
     +
    @@ include/fuse_service_priv.h (new)
     +
     +static inline size_t sizeof_fuse_service_requested_file(size_t pathlen)
     +{
     +	return sizeof(struct fuse_service_requested_file) + pathlen + 1;
     +}
     +
    -+#define FUSE_SERVICE_OPEN_FLAGS		(0)
    ++#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;
    @@ include/fuse_service_priv.h (new)
     +struct fuse_service_mount_command {
     +	struct fuse_service_packet p;
     +	uint32_t ms_flags;
     +	uint16_t expected_fmt;
     +};
     +
    ++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"
     +
    @@ util/mount_service.h (new)
     + * 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
    ++ * @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
    @@ util/mount_service.h (new)
     + * @return fuse subtype
     + */
     +const char *mount_service_subtype(const char *fstype);
     +
     +#endif /* MOUNT_SERVICE_H_ */
     
    - ## .github/workflows/abicheck.yml ##
    -@@ .github/workflows/abicheck.yml: jobs:
    + ## .github/workflows/install-ubuntu-dependencies.sh ##
    +@@ .github/workflows/install-ubuntu-dependencies.sh: install_full() {
    +         pkg-config:i386 \
    +         liburing-dev \
    +         libnuma-dev \
    +         meson \
    +         ninja-build \
    +         python3 \
    +-        python3-pip
    ++        python3-pip \
    ++        libsystemd-dev \
    ++        systemd-dev
      
    -     steps:
    -       - name: Install dependencies (Ubuntu)
    -         if: runner.os == 'Linux'
    -         run: |
    -           sudo apt-get update
    --          sudo apt-get -y install abigail-tools clang gcc liburing-dev libnuma-dev
    -+          sudo apt-get -y install abigail-tools clang gcc liburing-dev libnuma-dev libsystemd-dev systemd-dev
    +     echo "Installing Python test dependencies..."
    +     pip install -r requirements.txt
    + }
      
    -       - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
    -         with:
    -           path: current
    + install_codechecker() {
    +@@ .github/workflows/install-ubuntu-dependencies.sh: install_abicheck() {
    +         gcc \
    +         liburing-dev \
    +         libnuma-dev \
    +         meson \
    +         ninja-build \
    +         python3 \
    +-        python3-pip
    ++        python3-pip \
    ++        libsystemd-dev \
    ++        systemd-dev
    + }
      
    -       - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
    -
    - ## .github/workflows/abicheck_prev_release.yml ##
    -@@ .github/workflows/abicheck_prev_release.yml: jobs:
    + install_codeql() {
    +     echo "Installing CodeQL dependencies..."
    +     sudo apt-get update
    +     sudo apt-get install -y \
    +         meson \
    +         ninja-build \
    +         python3-pytest \
    +         liburing-dev \
    +-        libnuma-dev
    ++        libnuma-dev \
    ++        libsystemd-dev \
    ++        systemd-dev
    + }
      
    -     steps:
    -       - name: Install dependencies (Ubuntu)
    -         if: runner.os == 'Linux'
    -         run: |
    -           sudo apt-get update
    --          sudo apt-get -y install abigail-tools clang gcc liburing-dev libnuma-dev
    -+          sudo apt-get -y install abigail-tools clang gcc liburing-dev libnuma-dev libsystemd-dev systemd-dev
    - 
    -       - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
    -         with:
    -           path: current
    -           fetch-depth: 0  # Fetch all history and tags
    - 
    -
    - ## .github/workflows/pr-ci.yml ##
    -@@ .github/workflows/pr-ci.yml: jobs:
    -         run: |
    -           sudo dpkg --add-architecture i386
    -           sudo apt-get update
    -           sudo apt-get install -y clang doxygen gcc gcc-10 gcc-9 valgrind \
    -             gcc-multilib g++-multilib libc6-dev-i386 \
    -             libpcap0.8-dev:i386 libudev-dev:i386 pkg-config:i386 \
    --            liburing-dev libnuma-dev
    -+            liburing-dev libnuma-dev libsystemd-dev systemd-dev
    -       - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
    -       - uses: actions/setup-python@v6
    -         with:
    -           python-version: '3.12'
    -       - run: pip install -r requirements.txt
    -       - run: test/ci-build.sh
    + install_cppcheck() {
    +     echo "Installing cppcheck..."
    +     sudo apt-get update
    +     sudo apt-get install -y cppcheck
     
      ## doc/fuservicemount3.8 (new) ##
     @@
     +.TH fuservicemount3 "8"
     +.SH NAME
     +fuservicemount3 \- mount a FUSE filesystem that runs as a system socket service
    @@ lib/fuse_service.c (new)
     +#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"
    @@ lib/fuse_service.c (new)
     +	};
     +	struct cmsghdr *cmsg;
     +	ssize_t size;
     +
     +	memset(&cmsgu, 0, sizeof(cmsgu));
     +
    -+	size = recvmsg(sockfd, &msg, MSG_TRUNC);
    ++	size = recvmsg(sockfd, &msg, MSG_TRUNC | MSG_CMSG_CLOEXEC);
     +	if (size < 0) {
     +		int error = errno;
     +
     +		fuse_log(FUSE_LOG_ERR, "fuse: service file reply: %s\n",
     +			 strerror(error));
     +		return -error;
    @@ lib/fuse_service.c (new)
     +	}
     +
     +	ret = __recv_fd(sockfd, req, req_sz, &fd);
     +	if (ret)
     +		goto out_req;
     +
    -+	if (req->p.magic != ntohl(FUSE_SERVICE_OPEN_REPLY)) {
    ++	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",
    @@ lib/fuse_service.c (new)
     +int fuse_service_receive_file(struct fuse_service *sf, const char *path,
     +			      int *fdp)
     +{
     +	return recv_requested_file(sf->sockfd, path, fdp);
     +}
     +
    -+#define FUSE_SERVICE_REQUEST_FILE_FLAGS	(0)
    ++#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)
    @@ lib/fuse_service.c (new)
     +	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, iov.iov_len);
     +	if (!cmd) {
     +		int error = errno;
     +
     +		fuse_log(FUSE_LOG_ERR, "fuse: alloc service file request: %s\n",
     +			 strerror(error));
    @@ lib/fuse_service.c (new)
     +	shutdown(sf->sockfd, SHUT_RDWR);
     +	close(sf->sockfd);
     +	sf->sockfd = -1;
     +	return 0;
     +}
     +
    -+static int find_socket_fd(void)
    ++static int count_listen_fds(void)
     +{
    -+	struct stat statbuf;
    -+	struct sockaddr_un urk;
     +	char *listen_fds;
    -+	socklen_t urklen = sizeof(urk);
    -+	int nr_fds;
    -+	int ret;
    ++	char *listen_pid;
    ++	char *p;
    ++	long l;
     +
     +	/*
    -+	 * No environment variable means we're not running as a system socket
    ++	 * 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");
    -+	if (!listen_fds)
    -+		return -ENOENT;
    ++	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 find_socket_fd(int nr_fds)
    ++{
    ++	struct stat statbuf;
    ++	struct sockaddr_un urk;
    ++	socklen_t urklen = sizeof(urk);
    ++	int ret;
     +
    -+	nr_fds = atoi(listen_fds);
     +	if (nr_fds != 1) {
     +		fuse_log(FUSE_LOG_ERR, "fuse: can only handle 1 service socket, got %d.\n",
     +			 nr_fds);
     +		return -E2BIG;
     +	}
     +
    @@ lib/fuse_service.c (new)
     +	if (size != sizeof(hello)) {
     +		fuse_log(FUSE_LOG_ERR, "fuse: wrong service hello size %zd, expected %zd\n",
     +			 size, sizeof(hello));
     +		return -EBADMSG;
     +	}
     +
    -+	if (hello.p.magic != ntohl(FUSE_SERVICE_HELLO_CMD)) {
    ++	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",
    @@ lib/fuse_service.c (new)
     +	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;
     +	}
    -+
    -+	/* Find the socket that connects us to mount.service */
    -+	ret = find_socket_fd();
    -+	if (ret == -ENOENT) {
    -+		/*
    -+		 * No socket found, so we're not running as a service.  Don't
    -+		 * fail the request, but leave *sfp set to NULL.
    -+		 */
    -+		ret = 0;
    -+		goto out_sf;
    -+	}
    -+	if (ret < 0)
    -+		goto out_sf;
    -+	sf->sockfd = ret;
    ++	sf->sockfd = sockfd;
     +
     +	ret = negotiate_hello(sf);
     +	if (ret)
     +		goto out_sf;
     +
     +	/* Receive the two critical sockets */
    @@ lib/fuse_service.c (new)
     +	ret = recv_requested_file(sf->sockfd, 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->argvfd));
    -+		ret = sf->argvfd;
    ++			 strerror(-sf->fusedevfd));
    ++		ret = sf->fusedevfd;
     +		goto out_argvfd;
     +	}
     +
     +	sf->owns_fusedevfd = true;
     +	*sfp = sf;
     +	return 0;
    @@ lib/fuse_service.c (new)
     +		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 = ntohl(memfd_args.magic);
    -+	memfd_args.argc = ntohl(memfd_args.argc);
    ++	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) {
    @@ lib/fuse_service.c (new)
     +			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 = ntohl(memfd_arg.pos);
    -+		memfd_arg.len = ntohl(memfd_arg.len);
    ++		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;
    @@ lib/fuse_service.c (new)
     +		int error = errno;
     +
     +		fuse_log(FUSE_LOG_ERR, "fuse: alloc service string send: %s\n",
     +			 strerror(error));
     +		return -error;
     +	}
    -+	cmd->p.magic = ntohl(command);
    ++	cmd->p.magic = htonl(command);
     +	strcpy(cmd->value, value);
     +	iov.iov_base = cmd;
     +
     +	size = sendmsg(sf->sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
     +	if (size < 0) {
     +		int error = errno;
    @@ lib/fuse_service.c (new)
     +	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),
    ++	};
    ++	struct iovec iov = {
    ++		.iov_base = &c,
    ++		.iov_len = sizeof(c),
    ++	};
    ++	struct msghdr msg = {
    ++		.msg_iov = &iov,
    ++		.msg_iovlen = 1,
    ++	};
    ++	ssize_t size;
    ++
    ++	/* already gone? */
    ++	if (sf->sockfd < 0)
    ++		return 0;
    ++
    ++	size = sendmsg(sf->sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
    ++	if (size < 0) {
    ++		int error = errno;
    ++
    ++		fuse_log(FUSE_LOG_ERR, "fuse: send service unmount: %s\n",
    ++			 strerror(error));
    ++		return -error;
    ++	}
    ++
    ++	iov.iov_base = &reply;
    ++	iov.iov_len = sizeof(reply);
    ++	size = recvmsg(sf->sockfd, &msg, MSG_TRUNC);
    ++	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;
    @@ lib/fuse_service_stub.c (new)
     +	return -EOPNOTSUPP;
     +}
     +
     +int fuse_service_accept(struct fuse_service **sfp)
     +{
     +	*sfp = NULL;
    -+	return -EOPNOTSUPP;
    ++	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_mode(struct fuse_service *sf,
    @@ lib/fuse_service_stub.c (new)
     +			       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)
     +{
    @@ lib/fuse_versionscript: FUSE_3.18 {
     +		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:
      # indent-tabs-mode: t
      # End:
     
    @@ lib/helper.c
      #define FUSE_HELPER_OPT(t, p) \
      	{ t, offsetof(struct fuse_cmdline_opts, p), 1 }
      
      static const struct fuse_opt fuse_helper_opts[] = {
      	FUSE_HELPER_OPT("-h",		show_help),
      	FUSE_HELPER_OPT("--help",	show_help),
    -@@ lib/helper.c: static int fuse_helper_opt_proc(void *data, const char *arg, int key,
    - 	default:
    - 		/* Pass through unknown options */
    - 		return 1;
    - 	}
    +@@ lib/helper.c: int fuse_parse_cmdline_312(struct fuse_args *args,
    + 		if (add_default_subtype(args->argv[0], args) == -1)
    + 			return -1;
    + 
    + 	return 0;
      }
      
     +#ifdef HAVE_SERVICEMOUNT
     +static int fuse_helper_opt_proc_service(void *data, const char *arg, int key,
     +					struct fuse_args *outargs)
     +{
    @@ lib/helper.c: static int fuse_helper_opt_proc(void *data, const char *arg, int k
     +		return -1;
     +	default:
     +		/* Pass through unknown options */
     +		return 1;
     +	}
     +}
    -+#endif
     +
    - /* Under FreeBSD, there is no subtype option so this
    -    function actually sets the fsname */
    - static int add_default_subtype(const char *progname, struct fuse_args *args)
    - {
    - 	int res;
    - 	char *subtype_opt;
    -@@ lib/helper.c: int fuse_parse_cmdline_312(struct fuse_args *args,
    - 		if (add_default_subtype(args->argv[0], args) == -1)
    - 			return -1;
    - 
    - 	return 0;
    - }
    - 
    -+#ifdef HAVE_SERVICEMOUNT
     +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 */
    @@ lib/meson.build: libfuse = library('fuse3',
                                    + '/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',
    @@ meson.build: args_default = [ '-D_GNU_SOURCE' ]
      # Feature detection, only available at libfuse compilation time,
      # but not for application linking to libfuse.
      #
      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',
                     'splice', 'vmsplice', 'posix_fallocate', 'fdatasync',
                     'utimensat', 'copy_file_range', 'fallocate', 'fspacectl' ]
      foreach func : test_funcs
    @@ meson.build: if get_option('enable-io-uring') and liburing.found() and libnuma.f
                    dependencies: [liburing])
            private_cfg.set('HAVE_URING', true)
         endif
      endif
      
     +# Check for systemd support
    -+systemd_system_unit_dir = get_option('systemdsystemunitdir')
    ++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
    @@ meson_options.txt: option('disable-libc-symbol-version', type : 'boolean', value
      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('systemdsystemunitdir', type : 'string', value : '',
    ++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) ##
     @@
     +/*
     + * FUSE: Filesystem in Userspace
    @@ util/meson.build: fuseconf_path = join_paths(get_option('prefix'), get_option('s
     +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=317')
    ++             c_args: '-DFUSE_USE_VERSION=319')
     +endif
     +
      executable('mount.fuse3', ['mount.fuse.c'],
                 include_directories: include_dirs,
                 link_with: [ libfuse ],
                 install: true,
    @@ util/mount_service.c (new)
     + *
     + * 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 <string.h>
    -+#include <stdio.h>
    -+#include <stdlib.h>
    -+#include <unistd.h>
    -+#include <sys/socket.h>
    -+#include <sys/un.h>
    -+#include <errno.h>
    -+#include <fcntl.h>
     +#include <sys/mount.h>
    -+#include <stdbool.h>
    -+#include <limits.h>
     +#include <sys/stat.h>
    -+#include <arpa/inet.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"
    @@ util/mount_service.c (new)
     +	char *source;
     +
     +	/* target argument (aka mountpoint) to mount() */
     +	char *mountpoint;
     +
     +	/* mountpoint that we pass to mount() */
    -+	const char *real_mountpoint;
    ++	char *real_mountpoint;
    ++
    ++	/* resolved path to mountpoint that we use for mtab updates */
    ++	char *resv_mountpoint;
     +
     +	/* mount options */
     +	char *mntopts;
     +
     +	/* socket fd */
     +	int sockfd;
    @@ util/mount_service.c (new)
     +	/* /dev/fuse device */
     +	int fusedevfd;
     +
     +	/* memfd for cli arguments */
     +	int argvfd;
     +
    -+	/* O_PATH fd for mount point */
    ++	/* fd for mount point */
     +	int mountfd;
    ++
    ++	/* did we actually mount successfully? */
    ++	bool mounted;
     +};
     +
    -+/* Filter out the subtype of the filesystem (e.g. fuse.Y -> Y) */
    ++/* Filter out the subtype of the filesystem (e.g. fuse.Y[.Z] -> Y[.Z]) */
     +const char *mount_service_subtype(const char *fstype)
     +{
    -+	char *period = strrchr(fstype, '.');
    -+
    -+	if (period)
    -+		return period + 1;
    -+
    ++	if (!strncmp(fstype, "fuse.", 5))
    ++		return fstype + 5;
    ++	if (!strncmp(fstype, "fuseblk.", 8))
    ++		return fstype + 8;
     +	return fstype;
     +}
     +
     +static int mount_service_init(struct mount_service *mo, int argc, char *argv[])
     +{
     +	char *fstype = NULL;
    @@ util/mount_service.c (new)
     +	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)) {
    ++	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, 0);
    ++	sockfd = socket(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0);
     +	if (sockfd < 0) {
     +		fprintf(stderr, "%s: opening %s service socket: %s\n",
     +			mo->msgtag, mo->subtype, strerror(errno));
     +		return -1;
     +	}
     +
     +	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) {
    -+		if (errno == ENOENT)
    -+			fprintf(stderr, "%s: no safe filesystem driver for %s available.\n",
    -+				mo->msgtag, mo->subtype);
    -+		else
    -+			fprintf(stderr, "%s: %s: %s\n",
    -+				mo->msgtag, name.sun_path, strerror(errno));
    ++		fprintf(stderr, "%s: %s: %s\n",
    ++			mo->msgtag, name.sun_path, strerror(errno));
     +		goto out;
     +	}
     +
     +	ret = try_drop_passrights(mo, sockfd);
     +	if (ret)
     +		goto out;
    @@ util/mount_service.c (new)
     +	if (size != sizeof(reply)) {
     +		fprintf(stderr, "%s: wrong hello reply size %zd, expected %zd\n",
     +			mo->msgtag, size, sizeof(reply));
     +		return -1;
     +	}
     +
    -+	if (reply.p.magic != ntohl(FUSE_SERVICE_HELLO_REPLY)) {
    ++	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 ||
    @@ util/mount_service.c (new)
     +	}
     +
     +	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)
    ++					 struct fuse_service_packet **commandp,
    ++					 size_t *commandsz)
     +{
     +	struct iovec iov = {
     +	};
     +	struct msghdr msg = {
     +		.msg_iov = &iov,
     +		.msg_iovlen = 1,
    @@ util/mount_service.c (new)
     +	}
     +	if (size < sizeof(struct fuse_service_packet)) {
     +		fprintf(stderr, "%s: wrong command packet size %zd, expected at least %zd\n",
     +			mo->msgtag, size, sizeof(struct fuse_service_packet));
     +		return -1;
     +	}
    ++	if (size > 32768) {
    ++		fprintf(stderr, "%s: wrong command packet size %zd, expected less than %d\n",
    ++			mo->msgtag, size, 32768);
    ++		return -1;
    ++	}
     +
     +	command = calloc(1, size + 1);
     +	if (!command) {
     +		fprintf(stderr, "%s: alloc service command: %s\n",
     +			mo->msgtag, strerror(errno));
     +		return -1;
    @@ util/mount_service.c (new)
     +			size, iov.iov_len);
     +		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 = {
    @@ util/mount_service.c (new)
     +}
     +
     +static int prepare_bdev(struct mount_service *mo,
     +			struct fuse_service_open_command *oc, int fd)
     +{
     +	struct stat statbuf;
    -+	int block_size;
     +	int ret;
     +
     +	ret = fstat(fd, &statbuf);
     +	if (ret) {
     +		int error = errno;
     +
    @@ util/mount_service.c (new)
     +	if (!S_ISBLK(statbuf.st_mode)) {
     +		fprintf(stderr, "%s: %s: %s\n",
     +			mo->msgtag, oc->path, strerror(ENOTBLK));
     +		return -ENOTBLK;
     +	}
     +
    -+	if (!oc->block_size)
    -+		return 0;
    -+	block_size = ntohl(oc->block_size);
    ++	if (oc->block_size) {
    ++		int block_size = ntohl(oc->block_size);
     +
    -+	ret = ioctl(fd, BLKBSZSET, &block_size);
    -+	if (ret) {
    -+		int error = errno;
    ++		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;
    ++			fprintf(stderr, "%s: %s: %s\n",
    ++				mo->msgtag, oc->path, strerror(error));
    ++			return -error;
    ++		}
     +	}
     +
     +	return 0;
     +}
     +
    -+static int mount_service_handle_open_path(struct mount_service *mo,
    -+					  mode_t expected_fmt,
    -+					  struct fuse_service_packet *p)
    ++static inline bool check_null_endbyte(const void *p, size_t psz)
    ++{
    ++	return *((const char *)p + psz - 1) == 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 = ntohl(oc->request_flags);
    ++	uint32_t request_flags;
     +	int ret;
     +	int fd;
     +
    -+	if (request_flags & ~FUSE_SERVICE_OPEN_FLAGS)
    ++	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);
    ++	}
     +
     +	fd = open(oc->path, ntohl(oc->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)
    ++		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)) {
    @@ util/mount_service.c (new)
     +	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)
    ++					 struct fuse_service_packet *p,
    ++					 size_t psz)
     +{
    -+	return mount_service_handle_open_path(mo, 0, p);
    ++	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)
    ++					      struct fuse_service_packet *p,
    ++					      size_t psz)
     +{
    -+	return mount_service_handle_open_path(mo, S_IFBLK, p);
    ++	return mount_service_open_path(mo, S_IFBLK, p, psz);
     +}
     +
     +static int mount_service_handle_fsopen_cmd(struct mount_service *mo,
    -+					   const struct fuse_service_packet *p)
    ++					   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: fsopen command too small\n",
    ++			mo->msgtag);
    ++		return mount_service_send_reply(mo, EINVAL);
    ++	}
    ++
    ++	if (!check_null_endbyte(p, psz)) {
    ++		fprintf(stderr, "%s: fsopen command must be null terminated\n",
    ++			mo->msgtag);
    ++		return mount_service_send_reply(mo, EINVAL);
    ++	}
    ++
     +	if (mo->fstype) {
     +		fprintf(stderr, "%s: fstype respecified!\n",
     +			mo->msgtag);
     +		return mount_service_send_reply(mo, EINVAL);
     +	}
     +
    @@ util/mount_service.c (new)
     +	}
     +
     +	return mount_service_send_reply(mo, 0);
     +}
     +
     +static int mount_service_handle_source_cmd(struct mount_service *mo,
    -+					   const struct fuse_service_packet *p)
    ++					   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);
     +	}
     +
    @@ util/mount_service.c (new)
     +	}
     +
     +	return mount_service_send_reply(mo, 0);
     +}
     +
     +static int mount_service_handle_mntopts_cmd(struct mount_service *mo,
    -+					    const struct fuse_service_packet *p)
    ++					    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);
     +	}
     +
    @@ util/mount_service.c (new)
     +		return mount_service_send_reply(mo, error);
     +	}
     +
     +	return mount_service_send_reply(mo, 0);
     +}
     +
    ++static int attach_to_mountpoint(struct mount_service *mo, char *mntpt)
    ++{
    ++	struct stat statbuf;
    ++	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;
    ++	}
    ++
    ++	ret = fstat(mountfd, &statbuf);
    ++	if (ret) {
    ++		error = errno;
    ++		fprintf(stderr, "%s: %s: %s\n", mo->msgtag, mntpt,
    ++			strerror(error));
    ++		goto out_mountfd;
    ++	}
    ++
    ++	if (!S_ISDIR(statbuf.st_mode) && !S_ISREG(statbuf.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",
    ++			mo->msgtag, mntpt);
    ++		goto out_mountfd;
    ++	}
    ++
    ++	switch (statbuf.st_mode & S_IFMT) {
    ++	case S_IFREG:
    ++		/*
    ++		 * This is a regular file, so we point mount() at the open file
    ++		 * descriptor.
    ++		 */
    ++		asprintf(&mo->real_mountpoint, "/dev/fd/%d", mountfd);
    ++		break;
    ++	case S_IFDIR:
    ++		/*
    ++		 * Pin the mount so it can't go anywhere.  This only works for
    ++		 * directories, which is fortunately the common case.
    ++		 */
    ++		ret = fchdir(mountfd);
    ++		if (ret) {
    ++			error = errno;
    ++			fprintf(stderr, "%s: %s: %s\n", mo->msgtag, mntpt,
    ++				strerror(error));
    ++			goto out_res_mntpt;
    ++		}
    ++
    ++		/*
    ++		 * Now that we're sitting on the mountpoint directory, we can
    ++		 * pass "." to mount() and avoid races with directory tree
    ++		 * mutations.
    ++		 */
    ++		mo->real_mountpoint = strdup(".");
    ++		break;
    ++	default:
    ++		/* Should never get here */
    ++		error = EINVAL;
    ++		goto out_res_mntpt;
    ++	}
    ++	if (!mo->real_mountpoint) {
    ++		error = ENOMEM;
    ++		fprintf(stderr, "%s: %s: %s\n", mo->msgtag, mntpt,
    ++			strerror(error));
    ++		goto out_res_mntpt;
    ++	}
    ++
    ++	mo->mountpoint = mntpt;
    ++	mo->mountfd = mountfd;
    ++	mo->resv_mountpoint = res_mntpt;
    ++
    ++	return mount_service_send_reply(mo, 0);
    ++
    ++out_res_mntpt:
    ++	free(res_mntpt);
    ++out_mountfd:
    ++	close(mountfd);
    ++out_error:
    ++	free(mntpt);
    ++	return mount_service_send_reply(mo, error);
    ++}
    ++
     +static int mount_service_handle_mountpoint_cmd(struct mount_service *mo,
    -+					       const struct fuse_service_packet *p)
    ++					       const struct fuse_service_packet *p,
    ++					       size_t psz)
     +{
     +	struct fuse_service_string_command *oc =
     +			container_of(p, struct fuse_service_string_command, p);
    -+	int ret;
    ++	char *mntpt;
    ++
    ++	if (psz < sizeof_fuse_service_string_command(1)) {
    ++		fprintf(stderr, "%s: mount point command too small\n",
    ++			mo->msgtag);
    ++		return mount_service_send_reply(mo, EINVAL);
    ++	}
    ++
    ++	if (!check_null_endbyte(p, psz)) {
    ++		fprintf(stderr, "%s: mount point command must be null terminated\n",
    ++			mo->msgtag);
    ++		return mount_service_send_reply(mo, EINVAL);
    ++	}
     +
     +	if (mo->mountpoint) {
     +		fprintf(stderr, "%s: mount point respecified!\n",
     +			mo->msgtag);
     +		return mount_service_send_reply(mo, EINVAL);
     +	}
     +
    -+#ifdef O_PATH
    -+	mo->mountfd = open(oc->value, O_PATH);
    -+	if (mo->mountfd < 0) {
    -+		int error = errno;
    -+
    -+		fprintf(stderr, "%s: %s: %s\n",
    -+			mo->msgtag, oc->value, strerror(error));
    -+		return mount_service_send_reply(mo, error);
    -+	}
    -+#else
    -+	mo->mountfd = -1;
    -+#endif
    -+
    -+	mo->mountpoint = strdup(oc->value);
    -+	if (!mo->mountpoint) {
    ++	mntpt = strdup(oc->value);
    ++	if (!mntpt) {
     +		int error = errno;
     +
     +		fprintf(stderr, "%s: alloc mount point string: %s\n",
     +			mo->msgtag, strerror(error));
     +		return mount_service_send_reply(mo, error);
     +	}
    -+	mo->real_mountpoint = mo->mountpoint;
     +
    -+	ret = chdir(oc->value);
    -+	if (ret) {
    -+		if (errno != ENOTDIR) {
    -+			int error = errno;
    -+
    -+			fprintf(stderr, "%s: %s: %s\n",
    -+				mo->msgtag, oc->value, strerror(error));
    -+			return mount_service_send_reply(mo, error);
    -+		}
    -+
    -+		/* not a directory */
    -+	} else {
    -+		/*
    -+		 * Now that we're sitting on the mountpoint directory, we can
    -+		 * pass "." to mount() and avoid races with directory tree
    -+		 * mutations.
    -+		 */
    -+		mo->real_mountpoint = ".";
    -+	}
    -+
    -+	return mount_service_send_reply(mo, 0);
    ++	return attach_to_mountpoint(mo, mntpt);
     +}
     +
     +static inline int format_libfuse_mntopts(char *buf, size_t bufsz,
     +					 const struct mount_service *mo,
     +					 const struct stat *statbuf)
     +{
    @@ util/mount_service.c (new)
     +
     +static int mount_service_regular_mount(struct mount_service *mo,
     +				       struct fuse_service_mount_command *oc,
     +				       struct stat *stbuf)
     +{
     +	char *realmopts;
    -+	const char *mntpt;
    -+	char buf[32];
     +	int ret;
     +
     +	/* Compute the amount of buffer space needed for the mount options */
     +	ret = format_libfuse_mntopts(NULL, 0, mo, stbuf);
     +	if (ret < 0) {
     +		int error = errno;
    @@ util/mount_service.c (new)
     +		free(realmopts);
     +		fprintf(stderr, "%s: mount options formatting: %s\n",
     +			mo->msgtag, strerror(error));
     +		return mount_service_send_reply(mo, error);
     +	}
     +
    -+	if (mo->mountfd >= 0) {
    -+		snprintf(buf, sizeof(buf), "/dev/fd/%d", mo->mountfd);
    -+		mntpt = buf;
    -+	} else {
    -+		mntpt = mo->real_mountpoint;
    -+	}
    -+	ret = mount(mo->source, mntpt, mo->fstype, ntohl(oc->ms_flags),
    -+		    realmopts);
    ++	ret = mount(mo->source, mo->real_mountpoint, mo->fstype,
    ++		    ntohl(oc->ms_flags), realmopts);
     +	free(realmopts);
     +	if (ret) {
     +		int error = errno;
     +
     +		fprintf(stderr, "%s: mount: %s\n",
     +			mo->msgtag, strerror(error));
     +		return mount_service_send_reply(mo, error);
     +	}
     +
    ++	mo->mounted = true;
     +	return mount_service_send_reply(mo, 0);
     +}
     +
     +static int mount_service_handle_mount_cmd(struct mount_service *mo,
    -+					  struct fuse_service_packet *p)
    ++					  struct fuse_service_packet *p,
    ++					  size_t psz)
     +{
     +	struct stat stbuf;
     +	struct fuse_service_mount_command *oc =
     +			container_of(p, struct fuse_service_mount_command, p);
     +	int ret;
     +
    ++	if (psz != sizeof(struct fuse_service_mount_command)) {
    ++		fprintf(stderr, "%s: mount command wrong size\n",
    ++			mo->msgtag);
    ++		return mount_service_send_reply(mo, EINVAL);
    ++	}
    ++
     +	if (!mo->fstype) {
     +		fprintf(stderr, "%s: missing mount type parameter\n",
     +			mo->msgtag);
     +		return mount_service_send_reply(mo, EINVAL);
     +	}
     +
    @@ util/mount_service.c (new)
     +
     +	/*
     +	 * 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.
     +	 */
    -+	if (mo->mountfd >= 0)
    -+		ret = fstat(mo->mountfd, &stbuf);
    -+	else
    -+		ret = stat(mo->mountpoint, &stbuf);
    ++	ret = fstat(mo->mountfd, &stbuf);
     +	if (ret < 0) {
     +		int error = errno;
     +
     +		fprintf(stderr, "%s: %s: %s\n",
     +			mo->msgtag, mo->mountpoint, strerror(error));
     +		return mount_service_send_reply(mo, error);
     +	}
     +
    -+	if (!S_ISDIR(stbuf.st_mode) && !S_ISREG(stbuf.st_mode)) {
    -+		fprintf(stderr, "%s: %s: Must be a regular file or directory\n",
    -+			mo->msgtag, mo->mountpoint);
    -+		return mount_service_send_reply(mo, EACCES);
    -+	}
    -+
     +	/* Make sure the mountpoint type matches what the caller wanted */
     +	switch (ntohs(oc->expected_fmt)) {
     +	case S_IFDIR:
     +		if (!S_ISDIR(stbuf.st_mode)) {
     +			fprintf(stderr, "%s: %s: %s\n",
     +				mo->msgtag, mo->mountpoint, strerror(ENOTDIR));
    @@ util/mount_service.c (new)
     +		}
     +		break;
     +	case 0:
     +		/* don't care */
     +		break;
     +	default:
    -+		fprintf(stderr, "%s: %s: Weird expected format 0%o\n",
    ++		fprintf(stderr, "%s: %s: weird expected format 0%o\n",
     +			mo->msgtag, mo->mountpoint, ntohs(oc->expected_fmt));
     +		return mount_service_send_reply(mo, EINVAL);
     +	}
     +
     +	return mount_service_regular_mount(mo, oc, &stbuf);
     +}
     +
    ++static int mount_service_handle_unmount_cmd(struct mount_service *mo,
    ++					    struct fuse_service_packet *p,
    ++					    size_t psz)
    ++{
    ++	int ret;
    ++
    ++	(void)p;
    ++
    ++	if (psz != sizeof(struct fuse_service_unmount_command)) {
    ++		fprintf(stderr, "%s: unmount command wrong size\n",
    ++			mo->msgtag);
    ++		return mount_service_send_reply(mo, EINVAL);
    ++	}
    ++
    ++	if (!mo->mounted) {
    ++		fprintf(stderr, "%s: will not umount before successful mount!\n",
    ++			mo->msgtag);
    ++		return mount_service_send_reply(mo, EINVAL);
    ++	}
    ++
    ++	ret = chdir("/");
    ++	if (ret) {
    ++		int error = errno;
    ++
    ++		fprintf(stderr, "%s: fuse server failed chdir: %s\n",
    ++			mo->msgtag, strerror(error));
    ++		return mount_service_send_reply(mo, error);
    ++	}
    ++
    ++	close(mo->mountfd);
    ++	mo->mountfd = -1;
    ++
    ++	/*
    ++	 * Try to unmount the resolved mountpoint, and hope that we're not the
    ++	 * victim of a race.
    ++	 */
    ++	ret = umount2(mo->resv_mountpoint, MNT_DETACH);
    ++	if (ret) {
    ++		int error = errno;
    ++
    ++		fprintf(stderr, "%s: fuse server failed unmount: %s\n",
    ++			mo->msgtag, strerror(error));
    ++		return mount_service_send_reply(mo, error);
    ++	}
    ++
    ++	mo->mounted = false;
    ++	return mount_service_send_reply(mo, 0);
    ++}
    ++
     +static int mount_service_handle_bye_cmd(struct mount_service *mo,
    -+					struct fuse_service_packet *p)
    ++					struct fuse_service_packet *p,
    ++					size_t psz)
     +{
     +	struct fuse_service_bye_command *bc =
     +			container_of(p, struct fuse_service_bye_command, p);
    -+	int ret = ntohl(bc->exitcode);
    ++	int ret;
     +
    ++	if (psz != sizeof(struct fuse_service_bye_command)) {
    ++		fprintf(stderr, "%s: bye command wrong size\n",
    ++			mo->msgtag);
    ++		return mount_service_send_reply(mo, EINVAL);
    ++	}
    ++
    ++	ret = ntohl(bc->exitcode);
     +	if (ret)
     +		fprintf(stderr, "%s: fuse server failed mount, check dmesg/logs for details.\n",
     +			mo->msgtag);
     +
     +	return ret;
     +}
    @@ util/mount_service.c (new)
     +	close(mo->argvfd);
     +	shutdown(mo->sockfd, SHUT_RDWR);
     +	close(mo->sockfd);
     +
     +	free(mo->source);
     +	free(mo->mountpoint);
    ++	free(mo->real_mountpoint);
    ++	free(mo->resv_mountpoint);
     +	free(mo->mntopts);
     +	free(mo->fstype);
     +
     +	memset(mo, 0, sizeof(*mo));
     +	mo->sockfd = -1;
     +	mo->argvfd = -1;
    @@ util/mount_service.c (new)
     +
     +	ret = mount_service_init(&mo, argc, argv);
     +	if (ret)
     +		return EXIT_FAILURE;
     +
     +	ret = mount_service_connect(&mo);
    ++	if (ret == MOUNT_SERVICE_FALLBACK_NEEDED)
    ++		goto out;
     +	if (ret) {
     +		ret = EXIT_FAILURE;
     +		goto out;
     +	}
     +
     +	ret = mount_service_send_hello(&mo);
    @@ util/mount_service.c (new)
     +		ret = EXIT_FAILURE;
     +		goto out;
     +	}
     +
     +	while (running) {
     +		struct fuse_service_packet *p = NULL;
    ++		size_t sz;
     +
    -+		ret = mount_service_receive_command(&mo, &p);
    ++		ret = mount_service_receive_command(&mo, &p, &sz);
     +		if (ret) {
     +			ret = EXIT_FAILURE;
     +			goto out;
     +		}
     +
     +		switch (ntohl(p->magic)) {
     +		case FUSE_SERVICE_OPEN_CMD:
    -+			ret = mount_service_handle_open_cmd(&mo, p);
    ++			ret = mount_service_handle_open_cmd(&mo, p, sz);
     +			break;
     +		case FUSE_SERVICE_OPEN_BDEV_CMD:
    -+			ret = mount_service_handle_open_bdev_cmd(&mo, p);
    ++			ret = mount_service_handle_open_bdev_cmd(&mo, p, sz);
     +			break;
     +		case FUSE_SERVICE_FSOPEN_CMD:
    -+			ret = mount_service_handle_fsopen_cmd(&mo, p);
    ++			ret = mount_service_handle_fsopen_cmd(&mo, p, sz);
     +			break;
     +		case FUSE_SERVICE_SOURCE_CMD:
    -+			ret = mount_service_handle_source_cmd(&mo, p);
    ++			ret = mount_service_handle_source_cmd(&mo, p, sz);
     +			break;
     +		case FUSE_SERVICE_MNTOPTS_CMD:
    -+			ret = mount_service_handle_mntopts_cmd(&mo, p);
    ++			ret = mount_service_handle_mntopts_cmd(&mo, p, sz);
     +			break;
     +		case FUSE_SERVICE_MNTPT_CMD:
    -+			ret = mount_service_handle_mountpoint_cmd(&mo, p);
    ++			ret = mount_service_handle_mountpoint_cmd(&mo, p, sz);
     +			break;
     +		case FUSE_SERVICE_MOUNT_CMD:
    -+			ret = mount_service_handle_mount_cmd(&mo, p);
    ++			ret = mount_service_handle_mount_cmd(&mo, p, sz);
    ++			break;
    ++		case FUSE_SERVICE_UNMOUNT_CMD:
    ++			ret = mount_service_handle_unmount_cmd(&mo, p, sz);
     +			break;
     +		case FUSE_SERVICE_BYE_CMD:
    -+			ret = mount_service_handle_bye_cmd(&mo, p);
    ++			ret = mount_service_handle_bye_cmd(&mo, p, sz);
     +			free(p);
     +			goto out;
     +		default:
     +			fprintf(stderr, "%s: unrecognized packet 0x%x\n",
     +				mo.msgtag, ntohl(p->magic));
     +			ret = EXIT_FAILURE;
 3:  b15a7e75fc2067 <  -:  -------------- mount_service: create high level fuse helpers
 -:  -------------- >  3:  31c157f517bb31 mount_service: create high level fuse helpers
 4:  dfadaa6315ce03 !  4:  8b1af8ccf4b0bf mount_service: use the new mount api for the mount service
    @@ Commit message
     
         Use the new fsopen/fsmount system calls to mount the filesystem so that
         we get somewhat better diagnostics if something gets screwed up.
     
         Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
     
    + ## lib/fuse_i.h ##
    +@@ lib/fuse_i.h: struct fuse_chan *fuse_chan_get(struct fuse_chan *ch);
    +  * Drop counted reference to a channel
    +  *
    +  * @param ch the channel
    +  */
    + 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);
    + unsigned int get_max_read(const struct mount_opts *o);
    + void fuse_kern_unmount(const char *mountpoint, int fd);
    + int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo);
    +
      ## meson.build ##
     @@ meson.build: special_funcs = {
          'systemd_headers': '''
      	#include <systemd/sd-daemon.h>
      
      	int main(int argc, char *argv[]) {
    @@ meson.build: special_funcs = {
     +       #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);
    ++           res = move_mount(mntfd, "", AT_FDCWD, "/mnt", MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_T_EMPTY_PATH);
     +           return 0;
     +       }
          '''
      }
      
      foreach name, code : special_funcs
          private_cfg.set('HAVE_' + name.to_upper(),
              cc.links(code, args: args_default,
     
      ## util/mount_service.c ##
     @@ util/mount_service.c: struct mount_service {
    - 
      	/* memfd for cli arguments */
      	int argvfd;
      
    - 	/* O_PATH fd for mount point */
    + 	/* fd for mount point */
      	int mountfd;
    -+
    + 
     +	/* fd for fsopen */
     +	int fsopenfd;
    ++
    + 	/* did we actually mount successfully? */
    + 	bool mounted;
      };
      
    - /* Filter out the subtype of the filesystem (e.g. fuse.Y -> Y) */
    + /* Filter out the subtype of the filesystem (e.g. fuse.Y[.Z] -> Y[.Z]) */
      const char *mount_service_subtype(const char *fstype)
    - {
    - 	char *period = strrchr(fstype, '.');
     @@ util/mount_service.c: static int mount_service_init(struct mount_service *mo, int argc, char *argv[])
      	int i;
      
      	mo->sockfd = -1;
      	mo->argvfd = -1;
      	mo->fusedevfd = -1;
    @@ util/mount_service.c: static int mount_service_init(struct mount_service *mo, in
      
      	for (i = 0; i < argc; i++) {
      		if (!strcmp(argv[i], "-t") && i + 1 < argc) {
      			fstype = argv[i + 1];
      			break;
      		}
    +@@ util/mount_service.c: 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);
    + }
    + 
    ++#ifdef HAVE_NEW_MOUNT_API
    ++static void try_fsopen(struct mount_service *mo,
    ++		       struct fuse_service_string_command *oc)
    ++{
    ++	/*
    ++	 * As of Linux 7.0 you can pass subtypes to fsopen, but the manpage for
    ++	 * fsopen only says that you can pass any value of the second column of
    ++	 * /proc/filesystems into fsopen.
    ++	 */
    ++	if (!strncmp(oc->value, "fuse.", 5))
    ++		*(oc->value + 4) = 0;
    ++	else if (!strncmp(oc->value, "fuseblk.", 8))
    ++		*(oc->value + 7) = 0;
    ++
    ++	mo->fsopenfd = fsopen(oc->value, FSOPEN_CLOEXEC);
    ++}
    ++#else
    ++# define try_fsopen(...)	((void)0)
    ++#endif
    ++
    + static int mount_service_handle_fsopen_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);
     @@ util/mount_service.c: static int mount_service_handle_fsopen_cmd(struct mount_service *mo,
      
      		fprintf(stderr, "%s: alloc fstype string: %s\n",
      			mo->msgtag, strerror(error));
      		return mount_service_send_reply(mo, error);
      	}
      
    -+#ifdef HAVE_NEW_MOUNT_API
    -+	/* If this fails we fall back on mount() */
    -+	mo->fsopenfd = fsopen(oc->value, FSOPEN_CLOEXEC);
    -+#endif
    -+
    ++	/* If this fails we fall back on mount(); oc->value is mutated */
    ++	try_fsopen(mo, oc);
      	return mount_service_send_reply(mo, 0);
      }
      
     +#ifdef HAVE_NEW_MOUNT_API
     +/* callers must preserve errno */
     +static void emit_fsconfig_messages(const struct mount_service *mo)
     +{
     +	uint8_t buf[BUFSIZ];
     +	ssize_t sz;
     +
    -+	while ((sz = read(mo->fsopenfd, buf, sizeof(buf) - 1)) != -1) {
    -+		if (sz <= 0)
    -+			continue;
    ++	while ((sz = read(mo->fsopenfd, buf, sizeof(buf) - 1)) >= 1) {
     +		if (buf[sz - 1] == '\n')
     +			buf[--sz] = '\0';
     +		else
     +			buf[sz] = '\0';
     +
     +		if (!*buf)
    @@ util/mount_service.c: static int mount_service_handle_fsopen_cmd(struct mount_se
     +		}
     +	}
     +}
     +#endif
     +
      static int mount_service_handle_source_cmd(struct mount_service *mo,
    - 					   const struct fuse_service_packet *p)
    + 					   const struct fuse_service_packet *p,
    + 					   size_t psz)
      {
      	struct fuse_service_string_command *oc =
      			container_of(p, struct fuse_service_string_command, p);
    - 
     @@ util/mount_service.c: static int mount_service_handle_source_cmd(struct mount_service *mo,
      
      		fprintf(stderr, "%s: alloc source string: %s\n",
      			mo->msgtag, strerror(error));
      		return mount_service_send_reply(mo, error);
      	}
    @@ util/mount_service.c: static int mount_service_handle_source_cmd(struct mount_se
     +#endif
     +
      	return mount_service_send_reply(mo, 0);
      }
      
      static int mount_service_handle_mntopts_cmd(struct mount_service *mo,
    - 					    const struct fuse_service_packet *p)
    + 					    const struct fuse_service_packet *p,
    + 					    size_t psz)
      {
      	struct fuse_service_string_command *oc =
      			container_of(p, struct fuse_service_string_command, p);
     +	char *tokstr = oc->value;
     +	char *tok, *savetok;
      
    - 	if (mo->mntopts) {
    - 		fprintf(stderr, "%s: mount options respecified!\n",
    + 	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);
      	}
     @@ util/mount_service.c: static int mount_service_handle_mntopts_cmd(struct mount_service *mo,
      
      		fprintf(stderr, "%s: alloc mount options string: %s\n",
    @@ util/mount_service.c: static int mount_service_handle_mntopts_cmd(struct mount_s
     +		tokstr = NULL;
     +	}
     +
      	return mount_service_send_reply(mo, 0);
      }
      
    - static int mount_service_handle_mountpoint_cmd(struct mount_service *mo,
    - 					       const struct fuse_service_packet *p)
    + static int attach_to_mountpoint(struct mount_service *mo, char *mntpt)
      {
    + 	struct stat statbuf;
     @@ util/mount_service.c: static int mount_service_regular_mount(struct mount_service *mo,
    - 		return mount_service_send_reply(mo, error);
      	}
      
    + 	mo->mounted = true;
      	return mount_service_send_reply(mo, 0);
      }
      
     +#ifdef HAVE_NEW_MOUNT_API
     +struct ms_to_mount_map {
     +	unsigned long ms_flag;
    @@ util/mount_service.c: static int mount_service_regular_mount(struct mount_servic
     +
     +	/*
     +	 * We can't translate all the supplied MS_ flags into MOUNT_ATTR_ flags
     +	 * or string flags!  Return a magic code so the caller will fall back
     +	 * to regular mount(2).
     +	 */
    -+	return ms_flags ? -2 : 0;
    ++	if (ms_flags)
    ++		return FUSE_MOUNT_FALLBACK_NEEDED;
    ++
    ++	return 0;
     +}
     +
     +static int mount_service_fsopen_mount(struct mount_service *mo,
     +				      struct fuse_service_mount_command *oc,
     +				      struct stat *stbuf)
     +{
    @@ util/mount_service.c: static int mount_service_regular_mount(struct mount_servic
     +	int error;
     +	int ret;
     +
     +	get_mount_attr_flags(oc, &attr_flags, &ms_flags);
     +
     +	ret = set_ms_flags(mo, ms_flags);
    -+	if (ret == -2)
    ++	if (ret == FUSE_MOUNT_FALLBACK_NEEDED)
     +		return ret;
     +	if (ret) {
     +		error = errno;
     +		goto fail_mount;
     +	}
     +
    @@ util/mount_service.c: static int mount_service_regular_mount(struct mount_servic
     +	dot = strchr(mo->fstype, '.');
     +	if (dot) {
     +		ret = fsconfig(mo->fsopenfd, FSCONFIG_SET_STRING, "subtype",
     +			       dot + 1, 0);
     +		if (ret) {
     +			error = errno;
    ++
    ++			/* The subtype option came after fsopen */
    ++			if (error == EINVAL)
    ++				return FUSE_MOUNT_FALLBACK_NEEDED;
    ++
     +			fprintf(stderr, "%s: set subtype option: %s\n",
     +				mo->msgtag, strerror(error));
     +			goto fail_fsconfig;
     +		}
     +	}
     +
    @@ util/mount_service.c: static int mount_service_regular_mount(struct mount_servic
     +		error = errno;
     +		fprintf(stderr, "%s: move_mount: %s\n",
     +			mo->msgtag, strerror(error));
     +		goto fail_mount;
     +	}
     +
    ++	mo->mounted = true;
     +	return mount_service_send_reply(mo, 0);
     +
     +fail_fsconfig:
     +	emit_fsconfig_messages(mo);
     +fail_mount:
     +	return mount_service_send_reply(mo, error);
     +}
     +#else
    -+# define mount_service_fsopen_mount(...)	(-2)
    ++# define mount_service_fsopen_mount(...)	(FUSE_MOUNT_FALLBACK_NEEDED)
     +#endif
     +
      static int mount_service_handle_mount_cmd(struct mount_service *mo,
    - 					  struct fuse_service_packet *p)
    + 					  struct fuse_service_packet *p,
    + 					  size_t psz)
      {
      	struct stat stbuf;
      	struct fuse_service_mount_command *oc =
    - 			container_of(p, struct fuse_service_mount_command, p);
     @@ util/mount_service.c: static int mount_service_handle_mount_cmd(struct mount_service *mo,
      	default:
    - 		fprintf(stderr, "%s: %s: Weird expected format 0%o\n",
    + 		fprintf(stderr, "%s: %s: weird expected format 0%o\n",
      			mo->msgtag, mo->mountpoint, ntohs(oc->expected_fmt));
      		return mount_service_send_reply(mo, EINVAL);
      	}
      
     +	if (mo->fsopenfd >= 0) {
     +		ret = mount_service_fsopen_mount(mo, oc, &stbuf);
    -+		if (ret != -2)
    ++		if (ret != FUSE_MOUNT_FALLBACK_NEEDED)
     +			return ret;
     +	}
     +
      	return mount_service_regular_mount(mo, oc, &stbuf);
      }
      
    - static int mount_service_handle_bye_cmd(struct mount_service *mo,
    - 					struct fuse_service_packet *p)
    - {
    + static int mount_service_handle_unmount_cmd(struct mount_service *mo,
    + 					    struct fuse_service_packet *p,
    + 					    size_t psz)
     @@ util/mount_service.c: static int mount_service_handle_bye_cmd(struct mount_service *mo,
      
      static void mount_service_destroy(struct mount_service *mo)
      {
      	close(mo->mountfd);
      	close(mo->fusedevfd);
    @@ util/mount_service.c: static int mount_service_handle_bye_cmd(struct mount_servi
     +	close(mo->fsopenfd);
      	shutdown(mo->sockfd, SHUT_RDWR);
      	close(mo->sockfd);
      
      	free(mo->source);
      	free(mo->mountpoint);
    - 	free(mo->mntopts);
    + 	free(mo->real_mountpoint);
     @@ util/mount_service.c: static void mount_service_destroy(struct mount_service *mo)
      
      	memset(mo, 0, sizeof(*mo));
      	mo->sockfd = -1;
      	mo->argvfd = -1;
      	mo->fusedevfd = -1;
 5:  14e93185778efa !  5:  3459dcf34974c7 mount_service: update mtab after a successful mount
    @@ Commit message
         the root filesystem.
     
         Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
     
      ## include/fuse_service_priv.h ##
     @@ include/fuse_service_priv.h: struct fuse_service_memfd_argv {
    - #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 */
     +#define FUSE_SERVICE_MTABOPTS_CMD	0x4d544142	/* MTAB */
      
      /* mount.service sends replies to the fuse server */
      #define FUSE_SERVICE_OPEN_REPLY		0x46494c45	/* FILE */
      #define FUSE_SERVICE_SIMPLE_REPLY	0x5245504c	/* REPL */
    @@ lib/mount.c: char *fuse_mnt_kernel_opts(const struct mount_opts *mo)
      {
      	return mo->flags;
      }
     
      ## util/mount_service.c ##
     @@ util/mount_service.c: struct mount_service {
    - 	/* mountpoint that we pass to mount() */
    - 	const char *real_mountpoint;
    + 	/* resolved path to mountpoint that we use for mtab updates */
    + 	char *resv_mountpoint;
      
      	/* mount options */
      	char *mntopts;
      
     +	/* mtab options */
     +	char *mtabopts;
    @@ util/mount_service.c: static int mount_service_handle_mntopts_cmd(struct mount_s
      	}
      
      	return mount_service_send_reply(mo, 0);
      }
      
     +static int mount_service_handle_mtabopts_cmd(struct mount_service *mo,
    -+					     const struct fuse_service_packet *p)
    ++					     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: mtab options command too small\n",
    ++			mo->msgtag);
    ++		return mount_service_send_reply(mo, EINVAL);
    ++	}
    ++
    ++	if (!check_null_endbyte(p, psz)) {
    ++		fprintf(stderr, "%s: mtab options command must be null terminated\n",
    ++			mo->msgtag);
    ++		return mount_service_send_reply(mo, EINVAL);
    ++	}
    ++
     +	if (mo->mtabopts) {
     +		fprintf(stderr, "%s: mtab options respecified!\n",
     +			mo->msgtag);
     +		return mount_service_send_reply(mo, EINVAL);
     +	}
     +
    @@ util/mount_service.c: static int mount_service_handle_mntopts_cmd(struct mount_s
     +		return mount_service_send_reply(mo, error);
     +	}
     +
     +	return mount_service_send_reply(mo, 0);
     +}
     +
    - static int mount_service_handle_mountpoint_cmd(struct mount_service *mo,
    - 					       const struct fuse_service_packet *p)
    + static int attach_to_mountpoint(struct mount_service *mo, char *mntpt)
      {
    - 	struct fuse_service_string_command *oc =
    - 			container_of(p, struct fuse_service_string_command, p);
    - 	int ret;
    + 	struct stat statbuf;
    + 	char *res_mntpt;
    + 	int mountfd = -1;
    + 	int error;
     @@ util/mount_service.c: static int mount_service_regular_mount(struct mount_service *mo,
      
      		fprintf(stderr, "%s: mount: %s\n",
      			mo->msgtag, strerror(error));
      		return mount_service_send_reply(mo, error);
      	}
      
     +	/*
     +	 * The mount succeeded, so we send a positive reply even if the mtab
     +	 * update fails.
     +	 */
     +	if (mo->mtabopts)
    -+		fuse_mnt_add_mount(mo->msgtag, mo->source, mo->mountpoint,
    ++		fuse_mnt_add_mount(mo->msgtag, mo->source, mo->resv_mountpoint,
     +				   mo->fstype, mo->mtabopts);
     +
    + 	mo->mounted = true;
      	return mount_service_send_reply(mo, 0);
      }
      
      #ifdef HAVE_NEW_MOUNT_API
      struct ms_to_mount_map {
    - 	unsigned long ms_flag;
     @@ util/mount_service.c: static int mount_service_fsopen_mount(struct mount_service *mo,
      		error = errno;
      		fprintf(stderr, "%s: move_mount: %s\n",
      			mo->msgtag, strerror(error));
      		goto fail_mount;
      	}
      
     +	/*
     +	 * The mount succeeded, so we send a positive reply even if the mtab
     +	 * update fails.
     +	 */
     +	if (mo->mtabopts)
    -+		fuse_mnt_add_mount(mo->msgtag, mo->source, mo->mountpoint,
    ++		fuse_mnt_add_mount(mo->msgtag, mo->source, mo->resv_mountpoint,
     +				   mo->fstype, mo->mtabopts);
     +
    + 	mo->mounted = true;
      	return mount_service_send_reply(mo, 0);
      
      fail_fsconfig:
      	emit_fsconfig_messages(mo);
      fail_mount:
    - 	return mount_service_send_reply(mo, error);
    +@@ util/mount_service.c: static int mount_service_handle_unmount_cmd(struct mount_service *mo,
    + 
    + 		fprintf(stderr, "%s: fuse server failed unmount: %s\n",
    + 			mo->msgtag, strerror(error));
    + 		return mount_service_send_reply(mo, error);
    + 	}
    + 
    ++	/*
    ++	 * The unmount succeeded, so we send a positive reply even if the mtab
    ++	 * update fails.
    ++	 */
    ++	if (mo->mtabopts)
    ++		fuse_mnt_remove_mount(mo->msgtag, mo->resv_mountpoint);
    ++
    + 	mo->mounted = false;
    + 	return mount_service_send_reply(mo, 0);
    + }
    + 
    + static int mount_service_handle_bye_cmd(struct mount_service *mo,
    + 					struct fuse_service_packet *p,
     @@ util/mount_service.c: static void mount_service_destroy(struct mount_service *mo)
    - 	close(mo->fsopenfd);
    - 	shutdown(mo->sockfd, SHUT_RDWR);
      	close(mo->sockfd);
      
      	free(mo->source);
      	free(mo->mountpoint);
    + 	free(mo->real_mountpoint);
    + 	free(mo->resv_mountpoint);
     +	free(mo->mtabopts);
      	free(mo->mntopts);
      	free(mo->fstype);
      
      	memset(mo, 0, sizeof(*mo));
      	mo->sockfd = -1;
      	mo->argvfd = -1;
     @@ util/mount_service.c: int mount_service_main(int argc, char *argv[])
      		case FUSE_SERVICE_MNTOPTS_CMD:
    - 			ret = mount_service_handle_mntopts_cmd(&mo, p);
    + 			ret = mount_service_handle_mntopts_cmd(&mo, p, sz);
      			break;
      		case FUSE_SERVICE_MNTPT_CMD:
    - 			ret = mount_service_handle_mountpoint_cmd(&mo, p);
    + 			ret = mount_service_handle_mountpoint_cmd(&mo, p, sz);
      			break;
     +		case FUSE_SERVICE_MTABOPTS_CMD:
    -+			ret = mount_service_handle_mtabopts_cmd(&mo, p);
    ++			ret = mount_service_handle_mtabopts_cmd(&mo, p, sz);
     +			break;
      		case FUSE_SERVICE_MOUNT_CMD:
    - 			ret = mount_service_handle_mount_cmd(&mo, p);
    + 			ret = mount_service_handle_mount_cmd(&mo, p, sz);
    + 			break;
    + 		case FUSE_SERVICE_UNMOUNT_CMD:
    + 			ret = mount_service_handle_unmount_cmd(&mo, p, sz);
      			break;
    - 		case FUSE_SERVICE_BYE_CMD:
    - 			ret = mount_service_handle_bye_cmd(&mo, p);
    - 			free(p);
 6:  4834a22f31fca6 !  6:  3c8e031fd672ee util: hoist the fuse.conf parsing code
    @@
      ## Metadata ##
     Author: Darrick J. Wong <djwong@kernel.org>
     
      ## Commit message ##
    -    util: hoist the fuse.conf parsing code
    +    util: hoist the fuse.conf parsing and setuid mode enforcement code
     
         Move all the code that parses fuse.conf into a separate file in util/ so
    -    that fuservicemount can read the same file.  We'll add the limit
    -    enforcement in separate patches.
    +    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" <djwong@kernel.org>
     
      ## util/fuser_conf.h (new) ##
     @@
     +/*
    @@ util/fuser_conf.h (new)
     + * 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);
    @@ util/fuser_conf.h (new)
     +#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);
    ++
     +#endif /* FUSER_CONF_H_ */
     
      ## util/fuser_conf.c (new) ##
     @@
     +/*
     + * FUSE: Filesystem in Userspace
    @@ util/fuser_conf.c (new)
     +#include <ctype.h>
     +#include <stdio.h>
     +#include <stdlib.h>
     +#include <errno.h>
     +#include <mntent.h>
     +#include <unistd.h>
    ++#include <sys/fsuid.h>
     +
     +#if defined HAVE_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)
    @@ util/fuser_conf.c (new)
     +	}
     +}
     +
     +#ifndef IGNORE_MTAB
     +static int count_fuse_fs_mtab(const char *progname)
     +{
    -+	struct mntent *entp;
    ++	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));
    @@ util/fuser_conf.c (new)
     +int count_fuse_fs(const char *progname)
     +{
     +	return count_fuse_fs_mtab(progname);
     +}
     +#endif /* HAVE_LISTMOUNT */
     +#else
    -+static int count_fuse_fs(const char *progname)
    ++int count_fuse_fs(const char *progname)
     +{
     +	return 0;
     +}
    -+#endif /* IGNORE_MTAB */
    ++#endif /* !IGNORE_MTAB */
     +
     +static void strip_line(char *line)
     +{
     +	char *s = strchr(line, '#');
     +	if (s != NULL)
     +		s[0] = '\0';
    @@ util/fuser_conf.c (new)
     +	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 *progname, char *line, int linenum)
    ++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;
    @@ util/fuser_conf.c (new)
     +		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(progname, line, linenum);
    ++					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);
     +
    @@ util/fuser_conf.c (new)
     +			      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;
     +}
     
      ## util/fusermount.c ##
     @@
      /* This program does the mounting and unmounting of FUSE filesystems */
      
    @@ util/fusermount.c
      /*
       * Take a ',' separated option string and extract "x-" options
       */
      static int extract_x_options(const char *original, char **non_x_opts,
      			     char **x_opts)
      {
    +@@ util/fusermount.c: static const char *get_user_name(void)
    + 	else {
    + 		fprintf(stderr, "%s: could not determine username\n", progname);
    + 		return NULL;
    + 	}
    + }
    + 
    +-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
    +  */
    + static int lock_umount(void)
    + {
     @@ util/fusermount.c: static int unmount_fuse(const char *mnt, int quiet, int lazy)
    + 
      	res = unmount_fuse_locked(mnt, quiet, lazy);
      	unlock_umount(mtablock);
      
      	return res;
      }
    - 
    +-
     -static int count_fuse_fs_mtab(void)
     -{
    --	struct mntent *entp;
    +-	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));
    @@ util/fusermount.c: static int unmount_fuse(const char *mnt, int quiet, int lazy)
     -	s[1] = '\0';
     -	for (s = line; isspace((unsigned char) *s); s++);
     -	if (s != line)
     -		memmove(line, s, strlen(s)+1);
     -}
     -
    --static void parse_line(char *line, int linenum)
    +-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;
    @@ util/fusermount.c: static int unmount_fuse(const char *mnt, int quiet, int lazy)
      static int begins_with(const char *s, const char *beg)
      {
      	if (strncmp(s, beg, strlen(beg)) == 0)
      		return 1;
      	else
      		return 0;
    +@@ util/fusermount.c: static int do_mount(const char *mnt, const char **typep, mode_t rootmode,
    + static int check_perm(const char **mntp, struct stat *stbuf, int *mountpoint_fd)
    + {
    + 	int res;
    + 	const char *mnt = *mntp;
    + 	const char *origmnt = mnt;
    + 	struct statfs fs_buf;
    +-	size_t i;
    + 
    + 	res = lstat(mnt, stbuf);
    + 	if (res == -1) {
    + 		fprintf(stderr, "%s: failed to access mountpoint %s: %s\n",
    + 			progname, mnt, strerror(errno));
    + 		return -1;
    +@@ util/fusermount.c: static int check_perm(const char **mntp, struct stat *stbuf, int *mountpoint_fd)
    + 			fprintf(stderr,
    + 				"%s: failed to access mountpoint %s: %s\n",
    + 				progname, origmnt, strerror(errno));
    + 			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);
    + 		if (*mountpoint_fd == -1) {
    + 			fprintf(stderr, "%s: failed to open %s: %s\n",
    + 				progname, mnt, strerror(errno));
    +@@ util/fusermount.c: static int check_perm(const char **mntp, struct stat *stbuf, int *mountpoint_fd)
    + 		fprintf(stderr,
    + 			"%s: mountpoint %s is not a directory or a regular file\n",
    + 			progname, mnt);
    + 		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)
    + {
    + 	int fd;
    + 
     @@ util/fusermount.c: static int mount_fuse(const char *mnt, const char *opts, const char **type)
      
      	fd = open_fuse_device(dev);
      	if (fd == -1)
      		return -1;
      
      	drop_privs();
     -	read_conf();
     +	read_conf(progname);
      
    - 	if (getuid() != 0 && mount_max != -1) {
    +-	if (getuid() != 0 && mount_max != -1) {
     -		int mount_count = count_fuse_fs();
    -+		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);
    - 			goto fail_close_fd;
    - 		}
    - 	}
    +-		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);
    + 	if (res)
    + 		goto fail_close_fd;
      
     
      ## util/meson.build ##
     @@
      fuseconf_path = join_paths(get_option('prefix'), get_option('sysconfdir'), 'fuse.conf')
      
    @@ util/meson.build
     -  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=317')
    -+             c_args: ['-DFUSE_USE_VERSION=317', '-DFUSE_CONF="@0@"'.format(fuseconf_path)])
    +-             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'],
                 include_directories: include_dirs,
                 link_with: [ libfuse ],
                 install: true,
 7:  26e4b15e5741f8 !  7:  2405d43153ba4b util: fix checkpatch complaints in fuser_conf.[ch]
    @@ Commit message
     
         Fix the checkpatch complaints because we touched some code.
     
         Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
     
      ## util/fuser_conf.h ##
    -@@ util/fuser_conf.h: extern int mount_max;
    - void unescape(char *buf);
    +@@ util/fuser_conf.h: void unescape(char *buf);
    + #include <stdio.h>
    + #include <mntent.h>
      
    - #ifdef GETMNTENT_NEEDS_UNESCAPING
      static inline struct mntent *GETMNTENT(FILE *stream)
      {
      	struct mntent *entp = getmntent(stream);
     -	if(entp != NULL) {
     +
     +	if (entp != NULL) {
    @@ util/fuser_conf.c
      #include <stdint.h>
      #endif
      
     -int user_allow_other = 0;
     +int user_allow_other;
      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)
    @@ util/fuser_conf.c
      			src += 1;
      		} else {
      			*dest++ = '\\';
     @@ util/fuser_conf.c: void unescape(char *buf)
      static int count_fuse_fs_mtab(const char *progname)
      {
    - 	struct mntent *entp;
    + 	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,
    @@ util/fuser_conf.c: void unescape(char *buf)
      	}
      	endmntent(fp);
      	return count;
      }
      
      #ifdef HAVE_LISTMOUNT
    -@@ util/fuser_conf.c: static int count_fuse_fs(const char *progname)
    +@@ util/fuser_conf.c: int count_fuse_fs(const char *progname)
      }
    - #endif /* IGNORE_MTAB */
    + #endif /* !IGNORE_MTAB */
      
      static void strip_line(char *line)
      {
      	char *s = strchr(line, '#');
     +
      	if (s != NULL)
    @@ util/fuser_conf.c: static int count_fuse_fs(const char *progname)
     +	for (s = line; isspace((unsigned char) *s); s++)
     +		; /* empty */
      	if (s != line)
      		memmove(line, s, strlen(s)+1);
      }
      
    - static void parse_line(const char *progname, char *line, int linenum)
    + 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)
    @@ util/fuser_conf.c: static int count_fuse_fs(const char *progname)
      		int isnewline = 1;
     +
      		while (fgets(line, sizeof(line), fp) != NULL) {
      			if (isnewline) {
      				if (line[strlen(line)-1] == '\n') {
      					strip_line(line);
    - 					parse_line(progname, line, linenum);
    + 					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);
     +			} else if (line[strlen(line)-1] == '\n') {
    @@ util/fuser_conf.c: static int count_fuse_fs(const char *progname)
      
      		}
      		if (ferror(fp)) {
      			fprintf(stderr, "%s: reading %s: read failed\n", progname, FUSE_CONF);
      			exit(1);
      		}
    +@@ util/fuser_conf.c: int check_nonroot_dir_access(const char *progname, const char *origmnt,
    + 		return -1;
    + 	}
    + 
    + 	return 0;
    + }
    + 
    ++#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[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. */
    ++	 * 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. */
    ++	 * 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 */,
    +@@ util/fuser_conf.c: int check_nonroot_fstype(const char *progname, const struct statfs *fs_buf)
    + 		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++) {
    ++	for (i = 0; i < ARRAY_SIZE(f_type_whitelist); 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);
 8:  3f41f8303f02f9 <  -:  -------------- mount_service: read fuse.conf to enable allow_other for unprivileged mounts
 9:  c29554d5ecbe44 <  -:  -------------- util: hoist the other non-root user limits
10:  0b498783afacf2 <  -:  -------------- util: fix more checkpatch complaints in fuser_conf.[ch]
11:  3e3739ed798956 <  -:  -------------- mount_service: use over the other non-root user checks
 -:  -------------- >  8:  d2537b6030b04d mount_service: enable unprivileged users in the same manner as fusermount
12:  72a53f466688ae !  9:  b1a887475585ed mount.fuse3: integrate systemd service startup
    @@ doc/fuservicemount3.8: .SH NAME
      .LP
      The author of the fuse socket service code is Darrick J. Wong <djwong@kernel.org>.
      Debian GNU/Linux distribution.
      .SH SEE ALSO
      .BR fusermount3 (1)
     
    + ## meson.build ##
    +@@ meson.build: 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',
    +                '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))
    + endforeach
    + 
    + # Special case checks that need custom code
    +
      ## util/fuservicemount.c ##
     @@
       * 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
    @@ util/meson.build: fuseconf_path = join_paths(get_option('prefix'), get_option('s
     +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=317', '-DFUSE_CONF="@0@"'.format(fuseconf_path)])
    -+             c_args: ['-DFUSE_USE_VERSION=317'] + mount_service_cflags)
    +-             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=317'] + mount_service_cflags)
    ++           c_args: ['-DFUSE_USE_VERSION=319'] + mount_service_cflags)
      
      
      udevrulesdir = get_option('udevrulesdir')
      if udevrulesdir == ''
        udev = dependency('udev', required: false)
        if udev.found()
    @@ util/mount.fuse.c
      
      static char *progname;
      
      static char *xstrdup(const char *s)
      {
      	char *t = strdup(s);
    +@@ util/mount.fuse.c: static void drop_and_lock_capabilities(void)
    + 			progname, strerror(errno));
    + 		exit(1);
    + 	}
    + }
    + #endif
    + 
    ++#ifdef HAVE_SERVICEMOUNT
    ++#define FUSERVICEMOUNT_PROG	"fuservicemount3"
    ++
    ++static int try_service_main(char *argv0, char *type, char *source,
    ++			    const char *mountpoint, char *options)
    ++{
    ++	const char *full_path = FUSERVICEMOUNT_DIR "/" FUSERVICEMOUNT_PROG;
    ++	char **argv;
    ++	char dash_o[] = "-o";
    ++	char dash_t[] = "-t";
    ++	char *mntpt = strdup(mountpoint);
    ++	int argc = 5; /* argv[0], -t type, mountpoint, and trailing NULL */
    ++	int i = 0;
    ++	int ret;
    ++
    ++	if (!mount_service_present(type))
    ++		return MOUNT_SERVICE_FALLBACK_NEEDED;
    ++
    ++	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++] = type;
    ++	if (options) {
    ++		argv[i++] = dash_o;
    ++		argv[i++] = options;
    ++	}
    ++	argv[i] = 0;
    ++
    ++	if (getuid() != 0) {
    ++		/*
    ++		 * First try the install path, then a system install, just like
    ++		 * we do for fusermount.
    ++		 */
    ++		ret = execvp(full_path, argv);
    ++		if (ret)
    ++			ret = execvp(FUSERVICEMOUNT_PROG, argv);
    ++		if (ret) {
    ++			fprintf(stderr, "%s: could not start %s helper: %s\n",
    ++				argv[0], FUSERVICEMOUNT_PROG,
    ++				strerror(errno));
    ++			ret = MOUNT_SERVICE_FALLBACK_NEEDED;
    ++		}
    ++	} 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;
    + 	char *source;
    + 	char *dup_source = NULL;
    + 	const char *mountpoint;
     @@ util/mount.fuse.c: int main(int argc, char *argv[])
      	if (!source[0])
      		source = NULL;
      
      	mountpoint = argv[2];
      
    @@ util/mount.fuse.c: int main(int argc, char *argv[])
     +		} else {
     +			fprintf(stderr, "%s: empty source\n", progname);
     +			exit(1);
     +		}
     +	}
     +
    -+#ifdef HAVE_SERVICEMOUNT
    -+	/*
    -+	 * Now that we know the desired filesystem type, see if we can find
    -+	 * a socket service implementing that.
    -+	 */
    -+	if (mount_service_present(type))
    -+		return mount_service_main(argc, argv);
    -+#endif
    -+
     +	for (i = 3; i < argc; i++) {
     +		if (strcmp(argv[i], "-v") == 0) {
     +			continue;
      		} else	if (strcmp(argv[i], "-o") == 0) {
      			char *opts;
    - 			char *opt;
    + 			const char *opt;
      			i++;
      			if (i == argc)
      				break;
     @@ util/mount.fuse.c: int main(int argc, char *argv[])
      
      	if (dev)
    @@ util/mount.fuse.c: int main(int argc, char *argv[])
      	if (setuid_name && setuid_name[0]) {
      #ifdef linux
      		if (drop_privileges) {
      			/*
      			 * Make securebits more permissive before calling
      			 * setuid(). Specifically, if SECBIT_KEEP_CAPS and
    +@@ util/mount.fuse.c: int main(int argc, char *argv[])
    + 
    + #ifdef linux
    + 	if (drop_privileges) {
    + 		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);
    + 	add_arg(&command, mountpoint);
    + 	if (options) {
    + 		add_arg(&command, "-o");
     
      ## util/mount_service.c ##
     @@ util/mount_service.c: int mount_service_main(int argc, char *argv[])
      
      	ret = EXIT_SUCCESS;
      out:
    @@ util/mount_service.c: int mount_service_main(int argc, char *argv[])
     +	if (ret)
     +		return false;
     +
     +	if (!S_ISSOCK(stbuf.st_mode))
     +		return false;
     +
    -+	ret = access(path, R_OK | W_OK);
    ++#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;
     +}
13:  e7903cbaf2b638 = 10:  9fe14e80a4d315 mount_service: allow installation as a setuid program
14:  442656a22e8045 ! 11:  2a66e8073f338e example/service_ll: create a sample systemd service fuse server
    @@ example/single_file.h (new)
     +	return (b / align) + m;
     +}
     +
     +struct single_file {
     +	int backing_fd;
     +
    -+	uint64_t isize;
    ++	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;
     +
    @@ example/single_file.h (new)
     +	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,
    @@ example/single_file.h (new)
     +	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)
    @@ example/single_file.h (new)
     +
     +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);
     +
     +/* low-level fuse operation handlers */
     +
    @@ example/single_file.h (new)
     +
     +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 *fp);
    ++			  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_ */
     
    @@ example/service_ll.c (new)
     +	(void)userdata;
     +
     +	conn->time_gran = 1;
     +}
     +
     +static void service_ll_read(fuse_req_t req, fuse_ino_t ino, size_t count,
    -+			    off_t pos, struct fuse_file_info *fp)
    ++			    off_t pos, struct fuse_file_info *fi)
     +{
     +	void *buf = NULL;
     +	ssize_t got;
     +	int ret;
     +
     +	if (!is_single_file_ino(ino)) {
    @@ example/service_ll.c (new)
     +	if (ll.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 && fp->direct_io) {
    ++	if (!single_file.allow_dio && fi->direct_io) {
     +		ret = ENOSYS;
     +		goto out_reply;
     +	}
     +
    ++	single_file_check_read(pos, &count);
    ++
    ++	if (!count) {
    ++		fuse_reply_buf(req, buf, 0);
    ++		return;
    ++	}
    ++
     +	buf = malloc(count);
     +	if (!buf) {
     +		ret = ENOMEM;
     +		goto out_reply;
     +	}
     +
     +	got = pread(single_file.backing_fd, buf, count, pos);
     +	if (got < 0) {
    -+		ret = -errno;
    ++		ret = errno;
     +		goto out_reply;
     +	}
     +
     +	fuse_reply_buf(req, buf, got);
     +	goto out_buf;
     +
    @@ example/service_ll.c (new)
     +out_buf:
     +	free(buf);
     +}
     +
     +static void service_ll_write(fuse_req_t req, fuse_ino_t ino, const char *buf,
     +			     size_t count, off_t pos,
    -+			     struct fuse_file_info *fp)
    ++			     struct fuse_file_info *fi)
     +{
     +	ssize_t got;
     +	int ret;
     +
     +	if (!is_single_file_ino(ino)) {
     +		ret = EIO;
    @@ example/service_ll.c (new)
     +	if (ll.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 && fp->direct_io) {
    ++	if (!single_file.allow_dio && fi->direct_io) {
     +		ret = ENOSYS;
     +		goto out_reply;
     +	}
     +
    -+	if (pos >= single_file.isize) {
    -+		ret = EFBIG;
    ++	ret = -single_file_check_write(pos, &count);
    ++	if (ret)
     +		goto out_reply;
    -+	}
    -+
    -+	if (pos >= single_file.isize - count)
    -+		count = single_file.isize - pos;
     +
     +	got = pwrite(single_file.backing_fd, buf, count, pos);
     +	if (got < 0) {
    -+		ret = -errno;
    ++		ret = errno;
     +		goto out_reply;
     +	}
     +
     +	if (single_file.sync) {
     +		ret = fsync(single_file.backing_fd);
     +		if (ret < 0) {
    -+			ret = -errno;
    ++			ret = errno;
     +			goto out_reply;
     +		}
     +	}
     +
     +	fuse_reply_write(req, got);
     +	return;
    @@ example/service_ll.c (new)
     +	FUSE_OPT_END
     +};
     +
     +static int service_ll_opt_proc(void *data, const char *arg, int key,
     +				 struct fuse_args *outargs)
     +{
    -+	if (single_file_opt_proc(data, arg, key, outargs) == 0)
    -+		return 0;
    ++	int ret = single_file_opt_proc(data, arg, key, outargs);
    ++
    ++	if (ret < 1)
    ++		return ret;
     +
     +	switch (key) {
     +	case FUSE_OPT_KEY_NONOPT:
     +		if (!ll.device) {
     +			ll.device = strdup(arg);
     +			return 0;
    @@ example/service_ll.socket.in (new)
     +[Unit]
     +Description=Socket for service_ll Service
     +
     +[Socket]
     +ListenSequentialPacket=@FUSE_SERVICE_SOCKET_DIR_RAW@/service_ll
     +Accept=yes
    -+SocketMode=0220
    ++SocketMode=@FUSE_SERVICE_SOCKET_PERMS@
     +RemoveOnStop=yes
     +
     +[Install]
     +WantedBy=sockets.target
     
      ## example/service_ll@.service (new) ##
    @@ example/single_file.c (new)
     +#include <errno.h>
     +#include <stdio.h>
     +#include <string.h>
     +#include <unistd.h>
     +#include <sys/ioctl.h>
     +#include <sys/stat.h>
    ++#ifdef __linux__
     +#include <linux/fs.h>
     +#include <linux/stat.h>
    ++#endif
     +
     +#define FUSE_USE_VERSION (FUSE_MAKE_VERSION(3, 19))
     +
     +#include "fuse_lowlevel.h"
     +#include "fuse_service.h"
     +#include "single_file.h"
    @@ example/single_file.c (new)
     +	return ino == SINGLE_FILE_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)
     +{
    ++	struct dirbuf b;
    ++
     +	(void) fi;
     +
    -+	if (ino != FUSE_ROOT_ID)
    ++	switch (ino) {
    ++	case FUSE_ROOT_ID:
    ++		break;
    ++	case SINGLE_FILE_INO:
     +		fuse_reply_err(req, ENOTDIR);
    -+	else {
    -+		struct dirbuf b;
    -+
    -+		memset(&b, 0, sizeof(b));
    -+		dirbuf_add(req, &b, ".", FUSE_ROOT_ID);
    -+		dirbuf_add(req, &b, "..", FUSE_ROOT_ID);
    -+		dirbuf_add(req, &b, single_file_name, SINGLE_FILE_INO);
    -+		reply_buf_limited(req, b.p, b.size, off, size);
    -+		free(b.p);
    ++		return;
    ++	default:
    ++		fuse_reply_err(req, ENOENT);
    ++		return;
     +	}
    ++
    ++	memset(&b, 0, sizeof(b));
    ++	dirbuf_add(req, &b, ".", FUSE_ROOT_ID);
    ++	dirbuf_add(req, &b, "..", FUSE_ROOT_ID);
    ++	dirbuf_add(req, &b, single_file_name, SINGLE_FILE_INO);
    ++	reply_buf_limited(req, b.p, b.size, off, size);
    ++	free(b.p);
     +}
     +
     +static bool sf_stat(fuse_ino_t ino, struct single_file_stat *llstat)
     +{
     +	struct fuse_entry_param *entry = &llstat->entry;
     +	struct stat *stbuf = &entry->attr;
    @@ example/single_file.c (new)
     +		fuse_reply_err(req, ENOENT);
     +	else
     +		fuse_reply_attr(req, &llstat.entry.attr,
     +				llstat.entry.attr_timeout);
     +}
     +
    ++static void get_now(struct timespec *now)
    ++{
    ++#ifdef CLOCK_REALTIME
    ++	if (!clock_gettime(CLOCK_REALTIME, now))
    ++		return;
    ++#endif
    ++
    ++	now->tv_sec = time(NULL);
    ++	now->tv_nsec = 0;
    ++}
    ++
     +void single_file_ll_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
     +			    int to_set, struct fuse_file_info *fi)
     +{
    ++	struct timespec now;
    ++
    ++	if (ino != SINGLE_FILE_INO)
    ++		goto deny;
    ++	if (to_set & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID |
    ++		      FUSE_SET_ATTR_SIZE))
    ++		goto deny;
    ++
    ++	get_now(&now);
    ++
     +	pthread_mutex_lock(&single_file.lock);
     +	if (to_set & FUSE_SET_ATTR_MODE)
    -+		single_file.mode = attr->st_mode;
    -+	if (to_set & FUSE_SET_ATTR_ATIME)
    -+		single_file.atime = attr->st_atim;
    -+	if (to_set & FUSE_SET_ATTR_MTIME)
    -+		single_file.mtime = attr->st_mtim;
    ++		single_file.mode = (single_file.mode & S_IFMT) |
    ++				   (attr->st_mode & ~S_IFMT);
    ++	if (to_set & FUSE_SET_ATTR_ATIME) {
    ++		if (to_set & FUSE_SET_ATTR_ATIME_NOW)
    ++			single_file.atime = now;
    ++		else
    ++			single_file.atime = attr->st_atim;
    ++	}
    ++	if (to_set & FUSE_SET_ATTR_MTIME) {
    ++		if (to_set & FUSE_SET_ATTR_MTIME_NOW)
    ++			single_file.mtime = now;
    ++		else
    ++			single_file.mtime = attr->st_mtim;
    ++	}
     +	pthread_mutex_unlock(&single_file.lock);
     +
     +	single_file_ll_getattr(req, ino, fi);
    ++	return;
    ++deny:
    ++	fuse_reply_err(req, EPERM);
     +}
     +
     +void single_file_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
     +{
     +	struct single_file_stat llstat;
     +	bool filled;
    @@ example/single_file.c (new)
     +		fuse_reply_err(req, EACCES);
     +	else
     +		fuse_reply_open(req, fi);
     +}
     +
     +void single_file_ll_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
    -+			  struct fuse_file_info *fp)
    ++			  struct fuse_file_info *fi)
     +{
     +	int ret = 0;
     +
     +	(void)datasync;
    -+	(void)fp;
    ++	(void)fi;
     +
     +	if (ino == SINGLE_FILE_INO) {
     +		ret = fsync(single_file.backing_fd);
     +		if (ret)
    -+			ret = -errno;
    ++			ret = errno;
     +	}
     +
     +	fuse_reply_err(req, ret);
     +}
     +
     +unsigned long long parse_num_blocks(const char *arg, int log_block_size)
    @@ example/single_file.c (new)
     +	}
     +	return num;
     +}
     +
     +static int single_file_set_blocksize(const char *arg)
     +{
    -+	single_file.blocksize = parse_num_blocks(arg, -1);
    -+	if (single_file.blocksize < 512 || single_file.blocksize > INT32_MAX ||
    -+	    (single_file.blocksize & (single_file.blocksize - 1)) != 0) {
    ++	unsigned long long l = parse_num_blocks(arg, -1);
    ++
    ++	if (l < 512 || l > INT32_MAX || (l & (l - 1)) != 0) {
     +		fprintf(stderr, "%s: block size must be power of two between 512 and 2G.\n",
     +			arg);
     +		return -1;
     +	}
     +
     +	/* do not pass through to libfuse */
    ++	single_file.blocksize = l;
     +	return 0;
     +}
     +
     +static int single_file_set_size(const char *arg)
     +{
    -+	single_file.isize = parse_num_blocks(arg, -1);
    -+	if (single_file.isize < 1 || (single_file.isize & 511) != 0) {
    ++	unsigned long long l = parse_num_blocks(arg, -1);
    ++
    ++	if (l < 1 || (l & 511) != 0 || l > INT64_MAX) {
     +		fprintf(stderr, "%s: size must be multiple of 512 and larger than zero.\n",
    -+			arg + 5);
    ++			arg);
     +		return -1;
     +	}
     +
     +	/* do not pass through to libfuse */
    ++	single_file.isize = l;
     +	return 0;
     +}
     +
     +int single_file_opt_proc(void *data, const char *arg, int key,
     +			 struct fuse_args *outargs)
     +{
     +	(void)data;
     +	(void)outargs;
     +
     +	switch (key) {
     +	case SINGLE_FILE_RO:
    ++		/* pass through to libfuse */
     +		single_file.ro = true;
    -+		return 0;
    ++		return 1;
     +	case SINGLE_FILE_RW:
    ++		/* pass through to libfuse */
     +		single_file.ro = false;
    ++		return 1;
    ++	case SINGLE_FILE_REQUIRE_BDEV:
    ++		single_file.require_bdev = true;
     +		return 0;
     +	case SINGLE_FILE_DIO:
     +		single_file.allow_dio = true;
     +		return 0;
     +	case SINGLE_FILE_NODIO:
     +		single_file.allow_dio = false;
    @@ example/single_file.c (new)
     +{
     +	int open_flags = single_file.ro ? O_RDONLY : O_RDWR;
     +	int fd;
     +	int ret;
     +
     +again:
    -+	if (single_file.blocksize)
    ++	if (single_file.require_bdev)
     +		ret = fuse_service_request_blockdev(sf, path,
     +						    open_flags | O_EXCL, 0, 0,
     +						    single_file.blocksize);
     +	else
     +		ret = fuse_service_request_file(sf, path, open_flags | O_EXCL,
     +						0, 0);
     +	if (ret)
     +		return ret;
     +
    ++	if (!single_file.ro && open_flags == O_RDONLY)
    ++		single_file.ro = true;
    ++
     +	ret = fuse_service_receive_file(sf, path, &fd);
     +	if (ret)
     +		return ret;
     +
     +	/* downgrade from rw to ro if necessary */
     +	if ((fd == -EPERM || fd == -EACCES) && open_flags == O_RDWR) {
    @@ example/single_file.c (new)
     +	}
     +
     +	single_file.backing_fd = fd;
     +	return 0;
     +}
     +
    ++int single_file_check_write(off_t pos, size_t *count)
    ++{
    ++	if (pos >= single_file.isize)
    ++		return -EFBIG;
    ++
    ++	if (*count > single_file.isize)
    ++		*count = single_file.isize;
    ++	if (pos >= single_file.isize - *count)
    ++		*count = single_file.isize - pos;
    ++
    ++	return 0;
    ++}
    ++
    ++void single_file_check_read(off_t pos, size_t *count)
    ++{
    ++	int ret = single_file_check_write(pos, count);
    ++
    ++	if (ret)
    ++		*count = 0;
    ++}
    ++
     +int single_file_configure(const char *device, const char *filename)
     +{
     +	struct stat statbuf;
     +	unsigned long long backing_size;
    ++	unsigned int proposed_blocksize;
     +	int lbasize;
     +	int ret;
     +
    -+	if (!single_file.blocksize)
    -+		single_file.blocksize = sysconf(_SC_PAGESIZE);
    -+
     +	ret = fstat(single_file.backing_fd, &statbuf);
     +	if (ret) {
     +		perror(device);
     +		return -1;
     +	}
     +	lbasize = statbuf.st_blksize;
    @@ example/single_file.c (new)
     +
     +	if (lbasize == 0) {
     +		fprintf(stderr, "%s: blocksize zero?\n", device);
     +		return -1;
     +	}
     +
    -+	if (lbasize > single_file.blocksize) {
    ++	proposed_blocksize = single_file.blocksize ? single_file.blocksize :
    ++						     sysconf(_SC_PAGESIZE);
    ++	if (lbasize > proposed_blocksize) {
     +		fprintf(stderr, "%s: lba size %u smaller than blocksize %u\n",
    -+			device, lbasize, single_file.blocksize);
    ++			device, lbasize, proposed_blocksize);
     +		return -1;
     +	}
     +
    -+	if (single_file.isize % single_file.blocksize > 0) {
    ++	if (single_file.isize % proposed_blocksize > 0) {
     +		fprintf(stderr, "%s: size parameter %llu not congruent with blocksize %u\n",
     +			device, (unsigned long long)single_file.isize,
    -+			single_file.blocksize);
    ++			proposed_blocksize);
     +		return -1;
     +	}
     +
     +	if (single_file.isize > backing_size) {
     +		fprintf(stderr, "%s: file size %llu smaller than size param %llu\n",
     +			device, backing_size,
     +			(unsigned long long)single_file.isize);
     +		return -1;
     +	}
     +
    ++	if (!single_file.blocksize)
    ++		single_file.blocksize = proposed_blocksize;
     +	if (!single_file.isize)
     +		single_file.isize = backing_size;
    ++
     +	single_file.isize = round_down(single_file.isize, single_file.blocksize);
     +	single_file.blocks = single_file.isize / single_file.blocksize;
     +
     +	return single_file_configure_simple(filename);
     +}
     +
    @@ example/single_file.c (new)
     +	if (single_file_name_set)
     +		free((void *)single_file_name);
     +	single_file_name_set = false;
     +}
     
      ## meson.build ##
    -@@ meson.build: 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')
    +@@ meson.build: 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('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)
      
      # Test for presence of some functions
      test_funcs = [ 'fork', 'fstatat', 'openat', 'readlinkat', 'pipe2',
                     'splice', 'vmsplice', 'posix_fallocate', 'fdatasync',
    -                'utimensat', 'copy_file_range', 'fallocate', 'fspacectl' ]
    -@@ meson.build: special_funcs = {
    -            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);
    -            return 0;
    -        }
    -+    ''',
    -+    'linux_bdev_ioctls': '''
    -+       #define _GNU_SOURCE
    -+       #include <stddef.h>
    -+       #include <sys/ioctl.h>
    -+       #include <sys/stat.h>
    -+       #include <linux/fs.h>
    -+       #include <linux/stat.h>
    -+
    -+       int main(void) {
    -+           ioctl(-1, BLKSSZGET, 0);
    -+           ioctl(-1, BLKGETSIZE64, 0);
    -+           return 0;
    -+       }
    -     '''
    - }
    - 
    - foreach name, code : special_funcs
    -     private_cfg.set('HAVE_' + name.to_upper(),
    -         cc.links(code, args: args_default,
15:  778207a250dba7 ! 12:  e29aaa099e1192 example/service: create a sample systemd service for a high-level fuse server
    @@ Commit message
         Create a simple high-level fuse server that can be run as a systemd
         service.
     
         Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
     
      ## example/single_file.h ##
    -@@ example/single_file.h: int single_file_service_open(struct fuse_service *sf, const char *path);
    +@@ example/single_file.h: 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);
      
      /* low-level fuse operation handlers */
      
    @@ example/single_file.h: int single_file_service_open(struct fuse_service *sf, con
      void single_file_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
      				off_t off, struct fuse_file_info *fi);
      
     @@ example/single_file.h: void single_file_ll_open(fuse_req_t req, fuse_ino_t ino,
      
      void single_file_ll_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
    - 			  struct fuse_file_info *fp);
    + 			  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
     +
     +/* high-level fuse operation handlers */
     +
     +#ifdef USE_SINGLE_FILE_HL_API
    -+bool is_single_file_path(const char *name);
    ++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_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 *fp);
    ++			 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 *fp);
    ++			 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
      
    @@ example/service_hl.c (new)
     +	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 *fp)
    ++			   off_t pos, struct fuse_file_info *fi)
     +{
     +	ssize_t got;
     +
    -+	(void)fp;
    -+
    -+	if (!is_single_file_path(path))
    ++	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;
    ++
     +	got = pread(single_file.backing_fd, buf, count, pos);
     +	if (got < 0)
     +		return -errno;
     +
     +	return got;
     +}
     +
     +static int service_hl_write(const char *path, const char *buf, size_t count,
    -+			    off_t pos, struct fuse_file_info *fp)
    ++			    off_t pos, struct fuse_file_info *fi)
     +{
     +	ssize_t got;
    ++	int ret;
     +
    -+	(void)fp;
    -+
    -+	if (!is_single_file_path(path))
    ++	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 (pos >= single_file.isize)
    -+		return -EFBIG;
    ++	if (!single_file.allow_dio && fi->direct_io)
    ++		return -ENOSYS;
     +
    -+	if (pos >= single_file.isize - count)
    -+		count = single_file.isize - pos;
    ++	ret = single_file_check_write(pos, &count);
    ++	if (ret < 0)
    ++		return ret;
     +
     +	got = pwrite(single_file.backing_fd, buf, count, pos);
     +	if (got < 0)
     +		return -errno;
     +
    ++	if (single_file.sync) {
    ++		ret = fsync(single_file.backing_fd);
    ++		if (ret < 0)
    ++			return -errno;
    ++	}
    ++
     +	return got;
     +}
     +
     +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,
     +
     +	.init		= service_hl_init,
     +	.read		= service_hl_read,
     +	.write		= service_hl_write,
     +};
     +
    @@ example/service_hl.c (new)
     +	FUSE_OPT_END
     +};
     +
     +static int service_hl_opt_proc(void *data, const char *arg, int key,
     +			       struct fuse_args *outargs)
     +{
    -+	(void)data;
    -+	(void)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;
    @@ example/service_hl.c (new)
     +
     +	if (single_file_configure(hl.device, NULL))
     +		goto err_singlefile;
     +
     +	fuse_service_expect_mount_mode(hl.service, S_IFDIR);
     +
    -+	ret = fuse_service_main(hl.service, args.argc, args.argv,
    -+				&service_hl_oper, NULL);
    ++	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);
    @@ example/service_hl.socket.in (new)
     +[Unit]
     +Description=Socket for service_hl Service
     +
     +[Socket]
     +ListenSequentialPacket=@FUSE_SERVICE_SOCKET_DIR_RAW@/service_hl
     +Accept=yes
    -+SocketMode=0220
    ++SocketMode=@FUSE_SERVICE_SOCKET_PERMS@
     +RemoveOnStop=yes
     +
     +[Install]
     +WantedBy=sockets.target
     
      ## example/service_hl@.service (new) ##
    @@ example/service_ll.c
      	struct fuse_session *se;
      	char *device;
      	struct fuse_service *service;
     
      ## example/single_file.c ##
     @@
    - #include <linux/fs.h>
      #include <linux/stat.h>
    + #endif
      
      #define FUSE_USE_VERSION (FUSE_MAKE_VERSION(3, 19))
      
      #include "fuse_lowlevel.h"
     +#include "fuse.h"
      #include "fuse_service.h"
    @@ example/single_file.c: struct single_file single_file = {
     +	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)
      {
      	struct stat stbuf;
      	size_t oldsize = b->size;
    @@ example/single_file.c: bool is_single_file_child(fuse_ino_t parent, const char *
      
      bool is_single_file_ino(fuse_ino_t ino)
      {
      	return ino == SINGLE_FILE_INO;
      }
      
    -+bool is_single_file_path(const char *name)
    ++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)
      {
    + 	struct dirbuf b;
    + 
      	(void) fi;
    - 
    - 	if (ino != FUSE_ROOT_ID)
     @@ example/single_file.c: void single_file_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
    - 		dirbuf_add(req, &b, single_file_name, SINGLE_FILE_INO);
    - 		reply_buf_limited(req, b.p, b.size, off, size);
    - 		free(b.p);
    - 	}
    + 	dirbuf_add(req, &b, "..", FUSE_ROOT_ID);
    + 	dirbuf_add(req, &b, single_file_name, SINGLE_FILE_INO);
    + 	reply_buf_limited(req, b.p, b.size, off, 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_file_path_to_ino(path);
    ++	fuse_ino_t ino = single_open_file_path_to_ino(fi, path);
     +
     +	memset(&stbuf, 0, sizeof(stbuf));
     +
     +	(void) offset;
    -+	(void) fi;
     +	(void) flags;
     +
    -+	if (!ino)
    -+		return -ENOENT;
    -+	if (ino != SINGLE_FILE_INO)
    ++	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;
    @@ example/single_file.c: void single_file_ll_getattr(fuse_req_t req, fuse_ino_t in
      }
      
     +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_file_path_to_ino(path);
    ++	fuse_ino_t ino = single_open_file_path_to_ino(fi, path);
     +	bool filled;
     +
    -+	(void) fi;
    -+
     +	if (!ino)
     +		return -ENOENT;
     +
     +	memset(&llstat, 0, sizeof(llstat));
     +	pthread_mutex_lock(&single_file.lock);
     +	filled = sf_stat(ino, &llstat);
    @@ example/single_file.c: void single_file_ll_getattr(fuse_req_t req, fuse_ino_t in
     +		return -ENOENT;
     +
     +	memcpy(stbuf, &llstat.entry.attr, sizeof(*stbuf));
     +	return 0;
     +}
     +
    - void single_file_ll_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
    - 			    int to_set, struct fuse_file_info *fi)
    + static void get_now(struct timespec *now)
      {
    - 	pthread_mutex_lock(&single_file.lock);
    - 	if (to_set & FUSE_SET_ATTR_MODE)
    - 		single_file.mode = attr->st_mode;
    + #ifdef CLOCK_REALTIME
    + 	if (!clock_gettime(CLOCK_REALTIME, now))
    + 		return;
    + #endif
     @@ example/single_file.c: void single_file_ll_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
    - 		single_file.mtime = attr->st_mtim;
    - 	pthread_mutex_unlock(&single_file.lock);
    - 
      	single_file_ll_getattr(req, ino, fi);
    + 	return;
    + deny:
    + 	fuse_reply_err(req, EPERM);
      }
      
     +int single_file_hl_chmod(const char *path, mode_t mode,
    -+			 struct fuse_file_info *fp)
    ++			 struct fuse_file_info *fi)
     +{
    -+	fuse_ino_t ino = single_file_path_to_ino(path);
    -+
    -+	(void)fp;
    ++	fuse_ino_t ino = single_open_file_path_to_ino(fi, path);
     +
     +	if (!ino)
     +		return -ENOENT;
     +	if (ino != SINGLE_FILE_INO)
    -+		return 0;
    ++		return -EPERM;
     +
     +	pthread_mutex_lock(&single_file.lock);
    -+	single_file.mode = (single_file.mode & ~0xFFF) | (mode & 0xFFF);
    ++	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_file_path_to_ino(path);
    -+
    -+	(void)fi;
    ++	fuse_ino_t ino = single_open_file_path_to_ino(fi, path);
     +
     +	if (!ino)
     +		return -ENOENT;
     +	if (ino != SINGLE_FILE_INO)
    -+		return 0;
    ++		return -EPERM;
     +
     +	pthread_mutex_lock(&single_file.lock);
    -+	single_file.atime = ctv[0];
    -+	single_file.mtime = ctv[1];
    ++	set_time(&ctv[0], &single_file.atime);
    ++	set_time(&ctv[1], &single_file.mtime);
     +	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;
      	bool filled;
      
    @@ example/single_file.c: void single_file_ll_open(fuse_req_t req, fuse_ino_t ino,
      	else if (single_file.ro && (fi->flags & O_ACCMODE) != O_RDONLY)
      		fuse_reply_err(req, EACCES);
      	else
      		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 -EACCES;
     +
    ++	fi->fh = ino;
     +	return 0;
     +}
     +
      void single_file_ll_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
    - 			  struct fuse_file_info *fp)
    + 			  struct fuse_file_info *fi)
      {
      	int ret = 0;
      
      	(void)datasync;
     @@ example/single_file.c: void single_file_ll_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
    - 			ret = -errno;
    + 			ret = errno;
      	}
      
      	fuse_reply_err(req, ret);
      }
      
     +int single_file_hl_fsync(const char *path, int datasync,
    -+			 struct fuse_file_info *fp)
    ++			 struct fuse_file_info *fi)
     +{
    -+	fuse_ino_t ino = single_file_path_to_ino(path);
    ++	fuse_ino_t ino = single_open_file_path_to_ino(fi, path);
     +	int ret = 0;
     +
     +	(void)datasync;
    -+	(void)fp;
     +
     +	if (!ino)
     +		return -ENOENT;
     +
     +	if (ino == SINGLE_FILE_INO) {
     +		ret = fsync(single_file.backing_fd);
16:  e7491358b3afe5 <  -:  -------------- example/hello_ll: port to single-file common code
17:  77cd9cf9807a8f ! 13:  0db39a8f8844ff nullfs: support fuse systemd service mode
    @@ example/meson.build: if not platform.endswith('bsd') and platform != 'dragonfly'
          # According to Conrad Meyer <cem@freebsd.org>, FreeBSD doesn't
          # 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: 'nullfile.socket.in',
    -+                       output: 'nullfile.socket',
    ++        configure_file(input: 'null.socket.in',
    ++                       output: 'null.socket',
     +                       configuration: private_cfg)
     +    endif
      endif
      
    - single_file_examples = [ 'hello_ll' ]
    + single_file_examples = [ ]
      
      if platform.endswith('linux')
          single_file_examples += [ 'service_ll' ]
     
      ## example/null.c ##
     @@
    @@ example/null.c
       * Compile with:
       *
       *     gcc -Wall null.c `pkg-config fuse3 --cflags --libs` -o null
       *
     + * Change the ExecStart line in nullfile@.service:
     + *
    -+ *     ExecStart=/path/to/nullfile
    ++ *     ExecStart=/path/to/null
     + *
    -+ * to point to the actual path of the nullfile binary.
    ++ * to point to the actual path of the null binary.
     + *
    -+ * Finally, install the nullfile@.service and nullfile.socket files to the
    ++ * Finally, install the null@.service and null.socket files to the
     + * systemd service directory, usually /run/systemd/system.
     + *
       * ## Source code ##
       * \include passthrough_fh.c
       */
      
    @@ example/null.c
      #include <stdio.h>
      #include <stdlib.h>
      #include <string.h>
      #include <unistd.h>
      #include <time.h>
      #include <errno.h>
    + 
    ++static mode_t mode = 0644;
    ++
    + static int null_getattr(const char *path, struct stat *stbuf,
    + 			struct fuse_file_info *fi)
    + {
    + 	(void) fi;
    + 
    + 	if(strcmp(path, "/") != 0)
    + 		return -ENOENT;
    + 
    +-	stbuf->st_mode = S_IFREG | 0644;
    ++	stbuf->st_mode = S_IFREG | mode;
    + 	stbuf->st_nlink = 1;
    + 	stbuf->st_uid = getuid();
    + 	stbuf->st_gid = getgid();
    + 	stbuf->st_size = (1ULL << 32); /* 4G */
    + 	stbuf->st_blocks = 0;
    + 	stbuf->st_atime = stbuf->st_mtime = stbuf->st_ctime = time(NULL);
     @@ example/null.c: static const struct fuse_operations null_oper = {
    + 	.truncate	= null_truncate,
    + 	.open		= null_open,
    + 	.read		= null_read,
    + 	.write		= null_write,
    + };
      
    ++static int null_service(struct fuse_service *service, struct fuse_args *args)
    ++{
    ++	int ret = 1;
    ++
    ++	if (fuse_service_append_args(service, args))
    ++		goto err_service;
    ++
    ++	if (fuse_service_finish_file_requests(service))
    ++		goto err_service;
    ++
    ++	fuse_service_expect_mount_mode(service, S_IFREG);
    ++
    ++	/*
    ++	 * In non-service mode, we set up the file to be owned and writable
    ++	 * by the same user that starts the fuse server.  When running in a
    ++	 * container as a dynamic user, we just grant world write access.
    ++	 */
    ++	mode = 0666;
    ++	ret = fuse_service_main(service, args, &null_oper, NULL);
    ++
    ++err_service:
    ++	fuse_service_send_goodbye(service, ret);
    ++	fuse_service_destroy(&service);
    ++	fuse_opt_free_args(args);
    ++	return fuse_service_exit(ret);
    ++}
    ++
      int main(int argc, char *argv[])
      {
      	struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
      	struct fuse_cmdline_opts opts;
      	struct stat stbuf;
     +	struct fuse_service *service = NULL;
    -+	int ret;
     +
     +	if (fuse_service_accept(&service) != 0)
     +		return 1;
     +
    -+	if (fuse_service_accepted(service)) {
    -+		if (fuse_service_append_args(service, &args) != 0)
    -+			return 1;
    -+		if (fuse_service_finish_file_requests(service) != 0)
    -+			return 1;
    -+
    -+		fuse_service_expect_mount_mode(service, S_IFREG);
    -+
    -+		ret = fuse_service_main(service, args.argc, args.argv,
    -+					&null_oper, NULL);
    -+
    -+		fuse_opt_free_args(&args);
    -+		return fuse_service_exit(ret);
    -+	}
    ++	if (fuse_service_accepted(service))
    ++		return null_service(service, &args);
      
      	if (fuse_parse_cmdline(&args, &opts) != 0)
      		return 1;
      	fuse_opt_free_args(&args);
      
      	if (!opts.mountpoint) {
     
    - ## example/nullfile.socket.in (new) ##
    + ## example/null.socket.in (new) ##
     @@
     +# SPDX-License-Identifier: GPL-2.0-or-later
     +#
     +# Copyright (C) 2026 Oracle.  All Rights Reserved.
     +# Author: Darrick J. Wong <djwong@kernel.org>
     +[Unit]
    -+Description=Socket for nullfile Service
    ++Description=Socket for null Service
     +
     +[Socket]
    -+ListenSequentialPacket=@FUSE_SERVICE_SOCKET_DIR_RAW@/nullfile
    ++ListenSequentialPacket=@FUSE_SERVICE_SOCKET_DIR_RAW@/null
     +Accept=yes
    -+SocketMode=0220
    ++SocketMode=@FUSE_SERVICE_SOCKET_PERMS@
     +RemoveOnStop=yes
     +
     +[Install]
     +WantedBy=sockets.target
     
    - ## example/nullfile@.service (new) ##
    + ## example/null@.service (new) ##
     @@
     +# SPDX-License-Identifier: GPL-2.0-or-later
     +#
     +# Copyright (C) 2026 Oracle.  All Rights Reserved.
     +# Author: Darrick J. Wong <djwong@kernel.org>
     +[Unit]
    -+Description=nullfile Sample Fuse Service
    ++Description=null Sample Fuse Service
     +
     +# Don't leave failed units behind, systemd does not clean them up!
     +CollectMode=inactive-or-failed
     +
     +[Service]
     +Type=exec

^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH 01/13] Refactor mount code / move common functions to mount_util.c
  2026-04-09 22:20 [PATCHSET v4] libfuse: run fuse servers as a contained service Darrick J. Wong
@ 2026-04-09 22:20 ` Darrick J. Wong
  2026-04-09 22:21 ` [PATCH 02/13] mount_service: add systemd/inetd socket service mounting helper Darrick J. Wong
                   ` (11 subsequent siblings)
  12 siblings, 0 replies; 24+ messages in thread
From: Darrick J. Wong @ 2026-04-09 22:20 UTC (permalink / raw)
  To: djwong, bschubert; +Cc: miklos, neal, linux-fsdevel, bernd, joannelkoong

From: Bernd Schubert <bschubert@ddn.com>

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.

Signed-off-by: Bernd Schubert <bschubert@ddn.com>
[djwong: extract only the parts we need for mount services]
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 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


diff --git a/lib/mount_common_i.h b/lib/mount_common_i.h
new file mode 100644
index 00000000000000..6bcb055ff1c23f
--- /dev/null
+++ b/lib/mount_common_i.h
@@ -0,0 +1,18 @@
+/*
+ *  FUSE: Filesystem in Userspace
+ *  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
+ *                2026 Bernd Schubert <bernd@bsbernd.com>
+ *
+ *  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_ */
diff --git a/lib/mount_util.h b/lib/mount_util.h
index 9cb9077dd17738..b54392abb8b07d 100644
--- a/lib/mount_util.h
+++ b/lib/mount_util.h
@@ -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_ */
diff --git a/lib/mount.c b/lib/mount.c
index c56a9da1fe8014..2397c3fb2aa26b 100644
--- a/lib/mount.c
+++ b/lib/mount.c
@@ -31,6 +31,7 @@
 #include <sys/wait.h>
 
 #include "fuse_mount_compat.h"
+#include "mount_common_i.h"
 
 #ifdef __NetBSD__
 #include <perfuse.h>
@@ -49,7 +50,6 @@
 #define FUSERMOUNT_PROG		"fusermount3"
 #define FUSE_COMMFD_ENV		"_FUSE_COMMFD"
 #define FUSE_COMMFD2_ENV	"_FUSE_COMMFD2"
-#define FUSE_KERN_DEVICE_ENV	"FUSE_KERN_DEVICE"
 
 #ifndef MS_DIRSYNC
 #define MS_DIRSYNC 128
@@ -510,7 +510,7 @@ static int fuse_mount_sys(const char *mnt, struct mount_opts *mo,
 			  const char *mnt_opts)
 {
 	char tmp[128];
-	const char *devname = getenv(FUSE_KERN_DEVICE_ENV) ?: "/dev/fuse";
+	const char *devname = fuse_mnt_get_devname();
 	char *source = NULL;
 	char *type = NULL;
 	struct stat stbuf;
@@ -550,24 +550,13 @@ static int fuse_mount_sys(const char *mnt, struct mount_opts *mo,
 	if (res == -1)
 		goto out_close;
 
-	source = malloc((mo->fsname ? strlen(mo->fsname) : 0) +
-			(mo->subtype ? strlen(mo->subtype) : 0) +
-			strlen(devname) + 32);
-
-	type = malloc((mo->subtype ? strlen(mo->subtype) : 0) + 32);
+	source = fuse_mnt_build_source(mo);
+	type = fuse_mnt_build_type(mo);
 	if (!type || !source) {
 		fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate memory\n");
 		goto out_close;
 	}
 
-	strcpy(type, mo->blkdev ? "fuseblk" : "fuse");
-	if (mo->subtype) {
-		strcat(type, ".");
-		strcat(type, mo->subtype);
-	}
-	strcpy(source,
-	       mo->fsname ? mo->fsname : (mo->subtype ? mo->subtype : devname));
-
 	res = mount(source, mnt, type, mo->flags, mo->kernel_opts);
 	if (res == -1 && errno == ENODEV && mo->subtype) {
 		/* Probably missing subtype support */
@@ -727,3 +716,37 @@ int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo)
 	free(mnt_opts);
 	return res;
 }
+
+char *fuse_mnt_build_source(const struct mount_opts *mo)
+{
+	const char *devname = fuse_mnt_get_devname();
+	char *source;
+
+	source = malloc((mo->fsname ? strlen(mo->fsname) : 0) +
+			(mo->subtype ? strlen(mo->subtype) : 0) +
+			strlen(devname) + 32);
+	if (!source)
+		return NULL;
+
+	strcpy(source,
+	       mo->fsname ? mo->fsname : (mo->subtype ? mo->subtype : devname));
+
+	return source;
+}
+
+char *fuse_mnt_build_type(const struct mount_opts *mo)
+{
+	char *type;
+
+	type = malloc((mo->subtype ? strlen(mo->subtype) : 0) + 32);
+	if (!type)
+		return NULL;
+
+	strcpy(type, mo->blkdev ? "fuseblk" : "fuse");
+	if (mo->subtype) {
+		strcat(type, ".");
+		strcat(type, mo->subtype);
+	}
+
+	return type;
+}
diff --git a/lib/mount_util.c b/lib/mount_util.c
index 5746e8ed06b736..bdafeda7567fbd 100644
--- a/lib/mount_util.c
+++ b/lib/mount_util.c
@@ -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";
+}
diff --git a/util/fusermount.c b/util/fusermount.c
index a5ed4d26dd4d27..68370468140a59 100644
--- a/util/fusermount.c
+++ b/util/fusermount.c
@@ -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;


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH 02/13] mount_service: add systemd/inetd socket service mounting helper
  2026-04-09 22:20 [PATCHSET v4] libfuse: run fuse servers as a contained service Darrick J. Wong
  2026-04-09 22:20 ` [PATCH 01/13] Refactor mount code / move common functions to mount_util.c Darrick J. Wong
@ 2026-04-09 22:21 ` Darrick J. Wong
  2026-04-14  1:00   ` Darrick J. Wong
                     ` (2 more replies)
  2026-04-09 22:21 ` [PATCH 03/13] mount_service: create high level fuse helpers Darrick J. Wong
                   ` (10 subsequent siblings)
  12 siblings, 3 replies; 24+ messages in thread
From: Darrick J. Wong @ 2026-04-09 22:21 UTC (permalink / raw)
  To: djwong, bschubert; +Cc: miklos, neal, linux-fsdevel, bernd, joannelkoong

From: Darrick J. Wong <djwong@kernel.org>

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.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 include/fuse_service.h                           |  243 ++++
 include/fuse_service_priv.h                      |  133 ++
 lib/mount_common_i.h                             |    3 
 util/mount_service.h                             |   39 +
 .github/workflows/install-ubuntu-dependencies.sh |   12 
 doc/fuservicemount3.8                            |   24 
 doc/meson.build                                  |    3 
 include/meson.build                              |    4 
 lib/fuse_service.c                               | 1099 +++++++++++++++++++
 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                             | 1304 ++++++++++++++++++++++
 19 files changed, 3132 insertions(+), 5 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


diff --git a/include/fuse_service.h b/include/fuse_service.h
new file mode 100644
index 00000000000000..4ffd87a7fbe33c
--- /dev/null
+++ b/include/fuse_service.h
@@ -0,0 +1,243 @@
+/*
+ * FUSE: Filesystem in Userspace
+ * Copyright (C) 2025-2026 Oracle.
+ * Author: Darrick J. Wong <djwong@kernel.org>
+ *
+ * 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_mode(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.
+ *
+ * @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_ */
diff --git a/include/fuse_service_priv.h b/include/fuse_service_priv.h
new file mode 100644
index 00000000000000..8df871ee117875
--- /dev/null
+++ b/include/fuse_service_priv.h
@@ -0,0 +1,133 @@
+/*
+ * FUSE: Filesystem in Userspace
+ * Copyright (C) 2025-2026 Oracle.
+ * Author: Darrick J. Wong <djwong@kernel.org>
+ *
+ * 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_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;
+};
+
+struct fuse_service_hello_reply {
+	struct fuse_service_packet p;
+	uint16_t version;
+};
+
+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_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_bye_command {
+	struct fuse_service_packet p;
+	uint32_t exitcode;
+};
+
+struct fuse_service_mount_command {
+	struct fuse_service_packet p;
+	uint32_t ms_flags;
+	uint16_t expected_fmt;
+};
+
+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_ */
diff --git a/lib/mount_common_i.h b/lib/mount_common_i.h
index 6bcb055ff1c23f..631dff3e6f8aaf 100644
--- a/lib/mount_common_i.h
+++ b/lib/mount_common_i.h
@@ -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_ */
diff --git a/util/mount_service.h b/util/mount_service.h
new file mode 100644
index 00000000000000..f1f95e67ee1afe
--- /dev/null
+++ b/util/mount_service.h
@@ -0,0 +1,39 @@
+/*
+ * FUSE: Filesystem in Userspace
+ * Copyright (C) 2025-2026 Oracle.
+ * Author: Darrick J. Wong <djwong@kernel.org>
+ *
+ * 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.
+ *
+ * @param fstype full fuse filesystem type
+ * @return fuse subtype
+ */
+const char *mount_service_subtype(const char *fstype);
+
+#endif /* MOUNT_SERVICE_H_ */
diff --git a/.github/workflows/install-ubuntu-dependencies.sh b/.github/workflows/install-ubuntu-dependencies.sh
index ef44b6a03eb742..e70a891dc7e3a4 100755
--- a/.github/workflows/install-ubuntu-dependencies.sh
+++ b/.github/workflows/install-ubuntu-dependencies.sh
@@ -70,7 +70,9 @@ install_full() {
         meson \
         ninja-build \
         python3 \
-        python3-pip
+        python3-pip \
+        libsystemd-dev \
+        systemd-dev
 
     echo "Installing Python test dependencies..."
     pip install -r requirements.txt
@@ -104,7 +106,9 @@ install_abicheck() {
         meson \
         ninja-build \
         python3 \
-        python3-pip
+        python3-pip \
+        libsystemd-dev \
+        systemd-dev
 }
 
 install_codeql() {
@@ -115,7 +119,9 @@ install_codeql() {
         ninja-build \
         python3-pytest \
         liburing-dev \
-        libnuma-dev
+        libnuma-dev \
+        libsystemd-dev \
+        systemd-dev
 }
 
 install_cppcheck() {
diff --git a/doc/fuservicemount3.8 b/doc/fuservicemount3.8
new file mode 100644
index 00000000000000..e45d6a89c8b81a
--- /dev/null
+++ b/doc/fuservicemount3.8
@@ -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 <djwong@kernel.org>.
+Debian GNU/Linux distribution.
+.SH SEE ALSO
+.BR fusermount3 (1)
+.BR fusermount (1)
+.BR mount (8)
+.BR fuse (4)
diff --git a/doc/meson.build b/doc/meson.build
index db3e0b26f71975..c105cf3471fdf4 100644
--- a/doc/meson.build
+++ b/doc/meson.build
@@ -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
diff --git a/include/meson.build b/include/meson.build
index bf671977a5a6a9..da51180f87eea2 100644
--- a/include/meson.build
+++ b/include/meson.build
@@ -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')
diff --git a/lib/fuse_service.c b/lib/fuse_service.c
new file mode 100644
index 00000000000000..b775727e7c91e2
--- /dev/null
+++ b/lib/fuse_service.c
@@ -0,0 +1,1099 @@
+/*
+ * FUSE: Filesystem in Userspace
+ * Copyright (C) 2025-2026 Oracle.
+ * Author: Darrick J. Wong <djwong@kernel.org>
+ *
+ * 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(int sockfd, 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(sockfd, &msg, MSG_TRUNC | MSG_CMSG_CLOEXEC);
+	if (size < 0) {
+		int error = errno;
+
+		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;
+	}
+
+	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 int recv_requested_file(int sockfd, 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(sockfd, 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;
+}
+
+int fuse_service_receive_file(struct fuse_service *sf, const char *path,
+			      int *fdp)
+{
+	return recv_requested_file(sf->sockfd, path, fdp);
+}
+
+#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 iovec iov = {
+		.iov_len = sizeof_fuse_service_open_command(strlen(path)),
+	};
+	struct msghdr msg = {
+		.msg_iov = &iov,
+		.msg_iovlen = 1,
+	};
+	struct fuse_service_open_command *cmd;
+	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, iov.iov_len);
+	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);
+	iov.iov_base = cmd;
+
+	size = sendmsg(sf->sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
+	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),
+	};
+	struct iovec iov = {
+		.iov_base = &c,
+		.iov_len = sizeof(c),
+	};
+	struct msghdr msg = {
+		.msg_iov = &iov,
+		.msg_iovlen = 1,
+	};
+	ssize_t size;
+
+	/* already gone? */
+	if (sf->sockfd < 0)
+		return 0;
+
+	size = sendmsg(sf->sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
+	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 find_socket_fd(int nr_fds)
+{
+	struct stat statbuf;
+	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, &statbuf);
+	if (ret) {
+		int error = errno;
+
+		fuse_log(FUSE_LOG_ERR, "fuse: service socket: %s\n",
+			 strerror(error));
+		return -error;
+	}
+
+	if (!S_ISSOCK(statbuf.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;
+	}
+
+	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),
+	};
+	struct iovec iov = {
+		.iov_base = &hello,
+		.iov_len = sizeof(hello),
+	};
+	struct msghdr msg = {
+		.msg_iov = &iov,
+		.msg_iovlen = 1,
+	};
+	uint64_t flags;
+	ssize_t size;
+
+	size = recvmsg(sf->sockfd, &msg, MSG_TRUNC);
+	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_FLAG_ALLOW_OTHER)
+		sf->allow_other = true;
+
+	iov.iov_base = &reply;
+	iov.iov_len = sizeof(reply);
+
+	size = sendmsg(sf->sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
+	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 = recv_requested_file(sf->sockfd, 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 = recv_requested_file(sf->sockfd, 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_string(struct fuse_service *sf, uint32_t command,
+		       const char *value, int *error)
+{
+	struct fuse_service_simple_reply reply = { };
+	struct iovec iov = {
+		.iov_len = sizeof_fuse_service_string_command(strlen(value)),
+	};
+	struct msghdr msg = {
+		.msg_iov = &iov,
+		.msg_iovlen = 1,
+	};
+	struct fuse_service_string_command *cmd;
+	ssize_t size;
+
+	cmd = malloc(iov.iov_len);
+	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);
+	iov.iov_base = cmd;
+
+	size = sendmsg(sf->sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
+	if (size < 0) {
+		int error = errno;
+
+		fuse_log(FUSE_LOG_ERR, "fuse: send service string: %s\n",
+			 strerror(error));
+		return -error;
+	}
+	free(cmd);
+
+	iov.iov_base = &reply;
+	iov.iov_len = sizeof(reply);
+	size = recvmsg(sf->sockfd, &msg, MSG_TRUNC);
+	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;
+	}
+
+	*error = ntohl(reply.error);
+	return 0;
+}
+
+static int send_mount(struct fuse_service *sf, unsigned int ms_flags,
+		      mode_t expected_fmt, int *error)
+{
+	struct fuse_service_simple_reply reply = { };
+	struct fuse_service_mount_command c = {
+		.p.magic = htonl(FUSE_SERVICE_MOUNT_CMD),
+		.ms_flags = htonl(ms_flags),
+		.expected_fmt = htons(expected_fmt),
+	};
+	struct iovec iov = {
+		.iov_base = &c,
+		.iov_len = sizeof(c),
+	};
+	struct msghdr msg = {
+		.msg_iov = &iov,
+		.msg_iovlen = 1,
+	};
+	ssize_t size;
+
+	size = sendmsg(sf->sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
+	if (size < 0) {
+		int error = errno;
+
+		fuse_log(FUSE_LOG_ERR, "fuse: send service mount command: %s\n",
+			 strerror(error));
+		return -error;
+	}
+
+	iov.iov_base = &reply;
+	iov.iov_len = sizeof(reply);
+	size = recvmsg(sf->sockfd, &msg, MSG_TRUNC);
+	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;
+	}
+
+	*error = ntohl(reply.error);
+	return 0;
+}
+
+void fuse_service_expect_mount_mode(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;
+
+	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 */
+	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_string(sf, FUSE_SERVICE_FSOPEN_CMD, 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_string(sf, FUSE_SERVICE_MNTPT_CMD, 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), expected_fmt, &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
+	 */
+	opts->foreground = 1;
+
+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),
+	};
+	struct iovec iov = {
+		.iov_base = &c,
+		.iov_len = sizeof(c),
+	};
+	struct msghdr msg = {
+		.msg_iov = &iov,
+		.msg_iovlen = 1,
+	};
+	ssize_t size;
+
+	/* already gone? */
+	if (sf->sockfd < 0)
+		return 0;
+
+	size = sendmsg(sf->sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
+	if (size < 0) {
+		int error = errno;
+
+		fuse_log(FUSE_LOG_ERR, "fuse: send service unmount: %s\n",
+			 strerror(error));
+		return -error;
+	}
+
+	iov.iov_base = &reply;
+	iov.iov_len = sizeof(reply);
+	size = recvmsg(sf->sockfd, &msg, MSG_TRUNC);
+	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 = malloc(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;
+}
diff --git a/lib/fuse_service_stub.c b/lib/fuse_service_stub.c
new file mode 100644
index 00000000000000..4c7e0fabae7343
--- /dev/null
+++ b/lib/fuse_service_stub.c
@@ -0,0 +1,106 @@
+/*
+ * FUSE: Filesystem in Userspace
+ * Copyright (C) 2025-2026 Oracle.
+ * Author: Darrick J. Wong <djwong@kernel.org>
+ *
+ * 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_mode(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;
+}
diff --git a/lib/fuse_versionscript b/lib/fuse_versionscript
index cce09610316f4b..aa1912c76fb715 100644
--- a/lib/fuse_versionscript
+++ b/lib/fuse_versionscript
@@ -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_mode;
+		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:
diff --git a/lib/helper.c b/lib/helper.c
index 74906fdcbd76d9..819b9a6e4d243c 100644
--- a/lib/helper.c
+++ b/lib/helper.c
@@ -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
  */
diff --git a/lib/meson.build b/lib/meson.build
index fcd95741c9d374..d9a902f74b558f 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -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)
diff --git a/lib/mount.c b/lib/mount.c
index 2397c3fb2aa26b..952d8899dcf218 100644
--- a/lib/mount.c
+++ b/lib/mount.c
@@ -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;
+}
diff --git a/meson.build b/meson.build
index 80c5f1dc0bd356..66425a0d4cc16f 100644
--- a/meson.build
+++ b/meson.build
@@ -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
 #
diff --git a/meson_options.txt b/meson_options.txt
index c1f8fe69467184..193a74c96d0676 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -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))')
diff --git a/util/fuservicemount.c b/util/fuservicemount.c
new file mode 100644
index 00000000000000..9c694a4290f94e
--- /dev/null
+++ b/util/fuservicemount.c
@@ -0,0 +1,18 @@
+/*
+ * FUSE: Filesystem in Userspace
+ * Copyright (C) 2025-2026 Oracle.
+ * Author: Darrick J. Wong <djwong@kernel.org>
+ *
+ * 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);
+}
diff --git a/util/meson.build b/util/meson.build
index 0e4b1cce95377e..04ea5ac201340d 100644
--- a/util/meson.build
+++ b/util/meson.build
@@ -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 ],
diff --git a/util/mount_service.c b/util/mount_service.c
new file mode 100644
index 00000000000000..abe88a0710255b
--- /dev/null
+++ b/util/mount_service.c
@@ -0,0 +1,1304 @@
+/*
+ * FUSE: Filesystem in Userspace
+ * Copyright (C) 2025-2026 Oracle.
+ * Author: Darrick J. Wong <djwong@kernel.org>
+ *
+ * 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;
+
+	/* alleged fuse subtype based on -t cli argument */
+	const char *subtype;
+
+	/* full fuse filesystem type we give to mount() */
+	char *fstype;
+
+	/* 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;
+};
+
+/* Filter out the subtype of the filesystem (e.g. fuse.Y[.Z] -> Y[.Z]) */
+const char *mount_service_subtype(const char *fstype)
+{
+	if (!strncmp(fstype, "fuse.", 5))
+		return fstype + 5;
+	if (!strncmp(fstype, "fuseblk.", 8))
+		return fstype + 8;
+	return fstype;
+}
+
+static int mount_service_init(struct mount_service *mo, int argc, char *argv[])
+{
+	char *fstype = NULL;
+	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;
+	}
+
+	mo->subtype = mount_service_subtype(fstype);
+	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 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) {
+		fprintf(stderr, "%s: opening %s service socket: %s\n",
+			mo->msgtag, mo->subtype, strerror(errno));
+		return -1;
+	}
+
+	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) {
+		fprintf(stderr, "%s: %s: %s\n",
+			mo->msgtag, name.sun_path, strerror(errno));
+		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 = { };
+	struct iovec iov = {
+		.iov_base = &hello,
+		.iov_len = sizeof(hello),
+	};
+	struct msghdr msg = {
+		.msg_iov = &iov,
+		.msg_iovlen = 1,
+	};
+	ssize_t size;
+
+	if (getuid() == 0)
+		hello.flags |= htonl(FUSE_SERVICE_FLAG_ALLOW_OTHER);
+
+	size = sendmsg(mo->sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
+	if (size < 0) {
+		fprintf(stderr, "%s: send hello: %s\n",
+			mo->msgtag, strerror(errno));
+		return -1;
+	}
+
+	iov.iov_base = &reply;
+	iov.iov_len = sizeof(reply);
+
+	size = recvmsg(mo->sockfd, &msg, MSG_TRUNC);
+	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 %zd\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;
+	}
+
+	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 %zd\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 %zd\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 %zd\n",
+			mo->msgtag, written, sizeof(args));
+		return -1;
+	}
+
+	return 0;
+}
+
+static ssize_t __send_fd(int sockfd, 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(sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
+}
+
+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 = malloc(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->sockfd, 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 %zd\n",
+			mo->msgtag, written, req_sz);
+		ret = -1;
+		goto out_req;
+	}
+
+out_req:
+	free(req);
+	return ret;
+}
+
+static ssize_t __send_packet(int sockfd, void *buf, ssize_t buflen)
+{
+	struct iovec iov = {
+		.iov_base = buf,
+		.iov_len = buflen,
+	};
+	struct msghdr msg = {
+		.msg_iov = &iov,
+		.msg_iovlen = 1,
+	};
+
+	return sendmsg(sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
+}
+
+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 = malloc(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->sockfd, 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 %zd\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) {
+		fprintf(stderr, "%s: %s: %s\n",
+			mo->msgtag, fusedev, strerror(errno));
+		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 iovec iov = {
+	};
+	struct msghdr msg = {
+		.msg_iov = &iov,
+		.msg_iovlen = 1,
+	};
+	struct fuse_service_packet *command;
+	ssize_t size;
+
+	size = recvmsg(mo->sockfd, &msg, MSG_PEEK | MSG_TRUNC);
+	if (size < 0) {
+		fprintf(stderr, "%s: peek service command: %s\n",
+			mo->msgtag, strerror(errno));
+		return -1;
+	}
+	if (size == 0) {
+		/* fuse server probably exited early */
+		fprintf(stderr, "%s: fuse server exited without saying goodbye!\n",
+			mo->msgtag);
+		return -1;
+	}
+	if (size < sizeof(struct fuse_service_packet)) {
+		fprintf(stderr, "%s: wrong command packet size %zd, expected at least %zd\n",
+			mo->msgtag, size, sizeof(struct fuse_service_packet));
+		return -1;
+	}
+	if (size > 32768) {
+		fprintf(stderr, "%s: wrong command packet size %zd, expected less than %d\n",
+			mo->msgtag, size, 32768);
+		return -1;
+	}
+
+	command = calloc(1, size + 1);
+	if (!command) {
+		fprintf(stderr, "%s: alloc service command: %s\n",
+			mo->msgtag, strerror(errno));
+		return -1;
+	}
+	iov.iov_base = command;
+	iov.iov_len = size;
+
+	size = recvmsg(mo->sockfd, &msg, MSG_TRUNC);
+	if (size < 0) {
+		fprintf(stderr, "%s: receive service command: %s\n",
+			mo->msgtag, strerror(errno));
+		free(command);
+		return -1;
+	}
+	if (size != iov.iov_len) {
+		fprintf(stderr, "%s: wrong service command size %zd, expected %zd\n",
+			mo->msgtag,
+			size, iov.iov_len);
+		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),
+	};
+	struct iovec iov = {
+		.iov_base = &reply,
+		.iov_len = sizeof(reply),
+	};
+	struct msghdr msg = {
+		.msg_iov = &iov,
+		.msg_iovlen = 1,
+	};
+	ssize_t size;
+
+	size = sendmsg(mo->sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
+	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 statbuf;
+	int ret;
+
+	ret = fstat(fd, &statbuf);
+	if (ret) {
+		int error = errno;
+
+		fprintf(stderr, "%s: %s: %s\n",
+			mo->msgtag, oc->path, strerror(error));
+		return -error;
+	}
+
+	if (!S_ISBLK(statbuf.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 inline bool check_null_endbyte(const void *p, size_t psz)
+{
+	return *((const char *)p + psz - 1) == 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 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);
+	}
+
+	fd = open(oc->path, ntohl(oc->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 int mount_service_handle_fsopen_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: fsopen command too small\n",
+			mo->msgtag);
+		return mount_service_send_reply(mo, EINVAL);
+	}
+
+	if (!check_null_endbyte(p, psz)) {
+		fprintf(stderr, "%s: fsopen command must be null terminated\n",
+			mo->msgtag);
+		return mount_service_send_reply(mo, EINVAL);
+	}
+
+	if (mo->fstype) {
+		fprintf(stderr, "%s: fstype respecified!\n",
+			mo->msgtag);
+		return mount_service_send_reply(mo, EINVAL);
+	}
+
+	mo->fstype = strdup(oc->value);
+	if (!mo->fstype) {
+		int error = errno;
+
+		fprintf(stderr, "%s: alloc fstype 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_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, char *mntpt)
+{
+	struct stat statbuf;
+	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;
+	}
+
+	ret = fstat(mountfd, &statbuf);
+	if (ret) {
+		error = errno;
+		fprintf(stderr, "%s: %s: %s\n", mo->msgtag, mntpt,
+			strerror(error));
+		goto out_mountfd;
+	}
+
+	if (!S_ISDIR(statbuf.st_mode) && !S_ISREG(statbuf.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",
+			mo->msgtag, mntpt);
+		goto out_mountfd;
+	}
+
+	switch (statbuf.st_mode & S_IFMT) {
+	case S_IFREG:
+		/*
+		 * This is a regular file, so we point mount() at the open file
+		 * descriptor.
+		 */
+		asprintf(&mo->real_mountpoint, "/dev/fd/%d", mountfd);
+		break;
+	case S_IFDIR:
+		/*
+		 * Pin the mount so it can't go anywhere.  This only works for
+		 * directories, which is fortunately the common case.
+		 */
+		ret = fchdir(mountfd);
+		if (ret) {
+			error = errno;
+			fprintf(stderr, "%s: %s: %s\n", mo->msgtag, mntpt,
+				strerror(error));
+			goto out_res_mntpt;
+		}
+
+		/*
+		 * Now that we're sitting on the mountpoint directory, we can
+		 * pass "." to mount() and avoid races with directory tree
+		 * mutations.
+		 */
+		mo->real_mountpoint = strdup(".");
+		break;
+	default:
+		/* Should never get here */
+		error = EINVAL;
+		goto out_res_mntpt;
+	}
+	if (!mo->real_mountpoint) {
+		error = ENOMEM;
+		fprintf(stderr, "%s: %s: %s\n", mo->msgtag, mntpt,
+			strerror(error));
+		goto out_res_mntpt;
+	}
+
+	mo->mountpoint = mntpt;
+	mo->mountfd = mountfd;
+	mo->resv_mountpoint = res_mntpt;
+
+	return mount_service_send_reply(mo, 0);
+
+out_res_mntpt:
+	free(res_mntpt);
+out_mountfd:
+	close(mountfd);
+out_error:
+	free(mntpt);
+	return mount_service_send_reply(mo, error);
+}
+
+static int mount_service_handle_mountpoint_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);
+	char *mntpt;
+
+	if (psz < sizeof_fuse_service_string_command(1)) {
+		fprintf(stderr, "%s: mount point command too small\n",
+			mo->msgtag);
+		return mount_service_send_reply(mo, EINVAL);
+	}
+
+	if (!check_null_endbyte(p, psz)) {
+		fprintf(stderr, "%s: mount point command must be null terminated\n",
+			mo->msgtag);
+		return mount_service_send_reply(mo, EINVAL);
+	}
+
+	if (mo->mountpoint) {
+		fprintf(stderr, "%s: mount point respecified!\n",
+			mo->msgtag);
+		return mount_service_send_reply(mo, EINVAL);
+	}
+
+	mntpt = strdup(oc->value);
+	if (!mntpt) {
+		int error = errno;
+
+		fprintf(stderr, "%s: alloc mount point string: %s\n",
+			mo->msgtag, strerror(error));
+		return mount_service_send_reply(mo, error);
+	}
+
+	return attach_to_mountpoint(mo, mntpt);
+}
+
+static inline int format_libfuse_mntopts(char *buf, size_t bufsz,
+					 const struct mount_service *mo,
+					 const struct stat *statbuf)
+{
+	if (mo->mntopts)
+		return snprintf(buf, bufsz,
+				"%s,fd=%i,rootmode=%o,user_id=%u,group_id=%u",
+				mo->mntopts, mo->fusedevfd,
+				statbuf->st_mode & S_IFMT,
+				getuid(), getgid());
+
+	return snprintf(buf, bufsz,
+			"fd=%i,rootmode=%o,user_id=%u,group_id=%u",
+			mo->fusedevfd, statbuf->st_mode & S_IFMT,
+			getuid(), getgid());
+}
+
+static int mount_service_regular_mount(struct mount_service *mo,
+				       struct fuse_service_mount_command *oc,
+				       struct stat *stbuf)
+{
+	char *realmopts;
+	int ret;
+
+	/* Compute the amount of buffer space needed for the mount options */
+	ret = format_libfuse_mntopts(NULL, 0, mo, stbuf);
+	if (ret < 0) {
+		int error = errno;
+
+		fprintf(stderr, "%s: mount option preformatting: %s\n",
+			mo->msgtag, strerror(error));
+		return mount_service_send_reply(mo, error);
+	}
+
+	realmopts = malloc(ret + 1);
+	if (!realmopts) {
+		int error = errno;
+
+		fprintf(stderr, "%s: alloc real mount options string: %s\n",
+			mo->msgtag, strerror(error));
+		return mount_service_send_reply(mo, error);
+	}
+
+	ret = format_libfuse_mntopts(realmopts, ret + 1, mo, stbuf);
+	if (ret < 0) {
+		int error = errno;
+
+		free(realmopts);
+		fprintf(stderr, "%s: mount options formatting: %s\n",
+			mo->msgtag, strerror(error));
+		return mount_service_send_reply(mo, error);
+	}
+
+	ret = mount(mo->source, mo->real_mountpoint, mo->fstype,
+		    ntohl(oc->ms_flags), realmopts);
+	free(realmopts);
+	if (ret) {
+		int error = errno;
+
+		fprintf(stderr, "%s: mount: %s\n",
+			mo->msgtag, strerror(error));
+		return mount_service_send_reply(mo, error);
+	}
+
+	mo->mounted = true;
+	return mount_service_send_reply(mo, 0);
+}
+
+static int mount_service_handle_mount_cmd(struct mount_service *mo,
+					  struct fuse_service_packet *p,
+					  size_t psz)
+{
+	struct stat stbuf;
+	struct fuse_service_mount_command *oc =
+			container_of(p, struct fuse_service_mount_command, p);
+	int ret;
+
+	if (psz != sizeof(struct fuse_service_mount_command)) {
+		fprintf(stderr, "%s: mount command wrong size\n",
+			mo->msgtag);
+		return mount_service_send_reply(mo, EINVAL);
+	}
+
+	if (!mo->fstype) {
+		fprintf(stderr, "%s: missing mount type parameter\n",
+			mo->msgtag);
+		return mount_service_send_reply(mo, EINVAL);
+	}
+
+	if (!mo->source) {
+		fprintf(stderr, "%s: missing mount source parameter\n",
+			mo->msgtag);
+		return mount_service_send_reply(mo, EINVAL);
+	}
+
+	if (!mo->mountpoint) {
+		fprintf(stderr, "%s: missing mount point parameter\n",
+			mo->msgtag);
+		return mount_service_send_reply(mo, EINVAL);
+	}
+
+	/*
+	 * 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(mo->mountfd, &stbuf);
+	if (ret < 0) {
+		int error = errno;
+
+		fprintf(stderr, "%s: %s: %s\n",
+			mo->msgtag, mo->mountpoint, strerror(error));
+		return mount_service_send_reply(mo, error);
+	}
+
+	/* Make sure the mountpoint type matches what the caller wanted */
+	switch (ntohs(oc->expected_fmt)) {
+	case S_IFDIR:
+		if (!S_ISDIR(stbuf.st_mode)) {
+			fprintf(stderr, "%s: %s: %s\n",
+				mo->msgtag, mo->mountpoint, strerror(ENOTDIR));
+			return mount_service_send_reply(mo, ENOTDIR);
+		}
+		break;
+	case S_IFREG:
+		if (!S_ISREG(stbuf.st_mode)) {
+			fprintf(stderr, "%s: %s: %s\n",
+				mo->msgtag, mo->mountpoint, strerror(EISDIR));
+			return mount_service_send_reply(mo, EISDIR);
+		}
+		break;
+	case 0:
+		/* don't care */
+		break;
+	default:
+		fprintf(stderr, "%s: %s: weird expected format 0%o\n",
+			mo->msgtag, mo->mountpoint, ntohs(oc->expected_fmt));
+		return mount_service_send_reply(mo, EINVAL);
+	}
+
+	return mount_service_regular_mount(mo, oc, &stbuf);
+}
+
+static int mount_service_handle_unmount_cmd(struct mount_service *mo,
+					    struct fuse_service_packet *p,
+					    size_t psz)
+{
+	int ret;
+
+	(void)p;
+
+	if (psz != sizeof(struct fuse_service_unmount_command)) {
+		fprintf(stderr, "%s: unmount command wrong size\n",
+			mo->msgtag);
+		return mount_service_send_reply(mo, EINVAL);
+	}
+
+	if (!mo->mounted) {
+		fprintf(stderr, "%s: will not umount before successful mount!\n",
+			mo->msgtag);
+		return mount_service_send_reply(mo, EINVAL);
+	}
+
+	ret = chdir("/");
+	if (ret) {
+		int error = errno;
+
+		fprintf(stderr, "%s: fuse server failed chdir: %s\n",
+			mo->msgtag, strerror(error));
+		return mount_service_send_reply(mo, error);
+	}
+
+	close(mo->mountfd);
+	mo->mountfd = -1;
+
+	/*
+	 * Try to unmount the resolved mountpoint, and hope that we're not the
+	 * victim of a race.
+	 */
+	ret = umount2(mo->resv_mountpoint, MNT_DETACH);
+	if (ret) {
+		int error = errno;
+
+		fprintf(stderr, "%s: fuse server failed unmount: %s\n",
+			mo->msgtag, strerror(error));
+		return mount_service_send_reply(mo, error);
+	}
+
+	mo->mounted = false;
+	return mount_service_send_reply(mo, 0);
+}
+
+static int mount_service_handle_bye_cmd(struct mount_service *mo,
+					struct fuse_service_packet *p,
+					size_t psz)
+{
+	struct fuse_service_bye_command *bc =
+			container_of(p, struct fuse_service_bye_command, p);
+	int ret;
+
+	if (psz != sizeof(struct fuse_service_bye_command)) {
+		fprintf(stderr, "%s: bye command wrong size\n",
+			mo->msgtag);
+		return mount_service_send_reply(mo, EINVAL);
+	}
+
+	ret = ntohl(bc->exitcode);
+	if (ret)
+		fprintf(stderr, "%s: fuse server failed mount, check dmesg/logs for details.\n",
+			mo->msgtag);
+
+	return ret;
+}
+
+static void mount_service_destroy(struct mount_service *mo)
+{
+	close(mo->mountfd);
+	close(mo->fusedevfd);
+	close(mo->argvfd);
+	shutdown(mo->sockfd, SHUT_RDWR);
+	close(mo->sockfd);
+
+	free(mo->source);
+	free(mo->mountpoint);
+	free(mo->real_mountpoint);
+	free(mo->resv_mountpoint);
+	free(mo->mntopts);
+	free(mo->fstype);
+
+	memset(mo, 0, sizeof(*mo));
+	mo->sockfd = -1;
+	mo->argvfd = -1;
+	mo->fusedevfd = -1;
+	mo->mountfd = -1;
+}
+
+int mount_service_main(int argc, char *argv[])
+{
+	const char *fusedev = fuse_mnt_get_devname();
+	struct mount_service mo = { };
+	bool running = true;
+	int ret;
+
+	if (argc < 3 || !strcmp(argv[1], "--help")) {
+		printf("Usage: %s source mountpoint -t type [-o options]\n",
+				argv[0]);
+		return EXIT_FAILURE;
+	}
+
+	if (argc > 0 && argv[0])
+		mo.msgtag = argv[0];
+	else
+		mo.msgtag = "mount.service";
+
+	ret = mount_service_init(&mo, argc, argv);
+	if (ret)
+		return EXIT_FAILURE;
+
+	ret = mount_service_connect(&mo);
+	if (ret == MOUNT_SERVICE_FALLBACK_NEEDED)
+		goto out;
+	if (ret) {
+		ret = EXIT_FAILURE;
+		goto out;
+	}
+
+	ret = mount_service_send_hello(&mo);
+	if (ret) {
+		ret = EXIT_FAILURE;
+		goto out;
+	}
+
+	ret = mount_service_capture_args(&mo, argc, argv);
+	if (ret) {
+		ret = EXIT_FAILURE;
+		goto out;
+	}
+
+	ret = mount_service_send_required_files(&mo, fusedev);
+	if (ret) {
+		ret = EXIT_FAILURE;
+		goto out;
+	}
+
+	while (running) {
+		struct fuse_service_packet *p = NULL;
+		size_t sz;
+
+		ret = mount_service_receive_command(&mo, &p, &sz);
+		if (ret) {
+			ret = EXIT_FAILURE;
+			goto out;
+		}
+
+		switch (ntohl(p->magic)) {
+		case FUSE_SERVICE_OPEN_CMD:
+			ret = mount_service_handle_open_cmd(&mo, p, sz);
+			break;
+		case FUSE_SERVICE_OPEN_BDEV_CMD:
+			ret = mount_service_handle_open_bdev_cmd(&mo, p, sz);
+			break;
+		case FUSE_SERVICE_FSOPEN_CMD:
+			ret = mount_service_handle_fsopen_cmd(&mo, p, sz);
+			break;
+		case FUSE_SERVICE_SOURCE_CMD:
+			ret = mount_service_handle_source_cmd(&mo, p, sz);
+			break;
+		case FUSE_SERVICE_MNTOPTS_CMD:
+			ret = mount_service_handle_mntopts_cmd(&mo, p, sz);
+			break;
+		case FUSE_SERVICE_MNTPT_CMD:
+			ret = mount_service_handle_mountpoint_cmd(&mo, p, sz);
+			break;
+		case FUSE_SERVICE_MOUNT_CMD:
+			ret = mount_service_handle_mount_cmd(&mo, p, sz);
+			break;
+		case FUSE_SERVICE_UNMOUNT_CMD:
+			ret = mount_service_handle_unmount_cmd(&mo, p, sz);
+			break;
+		case FUSE_SERVICE_BYE_CMD:
+			ret = mount_service_handle_bye_cmd(&mo, p, sz);
+			free(p);
+			goto out;
+		default:
+			fprintf(stderr, "%s: unrecognized packet 0x%x\n",
+				mo.msgtag, ntohl(p->magic));
+			ret = EXIT_FAILURE;
+			break;
+		}
+		free(p);
+
+		if (ret) {
+			ret = EXIT_FAILURE;
+			goto out;
+		}
+	}
+
+	ret = EXIT_SUCCESS;
+out:
+	mount_service_destroy(&mo);
+	return ret;
+}


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH 03/13] mount_service: create high level fuse helpers
  2026-04-09 22:20 [PATCHSET v4] libfuse: run fuse servers as a contained service Darrick J. Wong
  2026-04-09 22:20 ` [PATCH 01/13] Refactor mount code / move common functions to mount_util.c Darrick J. Wong
  2026-04-09 22:21 ` [PATCH 02/13] mount_service: add systemd/inetd socket service mounting helper Darrick J. Wong
@ 2026-04-09 22:21 ` Darrick J. Wong
  2026-04-14 23:58   ` Darrick J. Wong
  2026-04-09 22:21 ` [PATCH 04/13] mount_service: use the new mount api for the mount service Darrick J. Wong
                   ` (9 subsequent siblings)
  12 siblings, 1 reply; 24+ messages in thread
From: Darrick J. Wong @ 2026-04-09 22:21 UTC (permalink / raw)
  To: djwong, bschubert; +Cc: miklos, neal, linux-fsdevel, bernd, joannelkoong

From: Darrick J. Wong <djwong@kernel.org>

Create a fuse_main wrapper for fuse services.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
---
 include/fuse.h         |   34 +++++++++++++
 lib/fuse_versionscript |    1 
 lib/helper.c           |  125 ++++++++++++++++++++++++++++++++++++++++++++++--
 3 files changed, 156 insertions(+), 4 deletions(-)


diff --git a/include/fuse.h b/include/fuse.h
index 2bc3a9650c7c8b..129c744e39c46a 100644
--- a/include/fuse.h
+++ b/include/fuse.h
@@ -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					       *
  * ----------------------------------------------------------- */
diff --git a/lib/fuse_versionscript b/lib/fuse_versionscript
index aa1912c76fb715..fc37931b475bdf 100644
--- a/lib/fuse_versionscript
+++ b/lib/fuse_versionscript
@@ -236,6 +236,7 @@ FUSE_3.19 {
 		fuse_service_exit;
 		fuse_service_expect_mount_mode;
 		fuse_service_finish_file_requests;
+		fuse_service_main_real_versioned;
 		fuse_service_parse_cmdline_opts;
 		fuse_service_receive_file;
 		fuse_service_release;
diff --git a/lib/helper.c b/lib/helper.c
index 819b9a6e4d243c..a2b3cc617bca54 100644
--- a/lib/helper.c
+++ b/lib/helper.c
@@ -15,6 +15,7 @@
 #include "fuse_misc.h"
 #include "fuse_opt.h"
 #include "fuse_lowlevel.h"
+#include "fuse_service.h"
 #include "mount_util.h"
 
 #include <stdio.h>
@@ -365,6 +366,126 @@ int fuse_daemonize(int foreground)
 	return 0;
 }
 
+struct fuse *_fuse_new_31(struct fuse_args *args,
+		       const struct fuse_operations *op, size_t op_size,
+		       struct libfuse_version *version,
+		       void *user_data);
+
+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)
+{
+	struct fuse *fuse;
+	struct fuse_cmdline_opts opts;
+	int res;
+	struct fuse_loop_config *loop_config = NULL;
+
+	if (fuse_service_parse_cmdline_opts(args, &opts) != 0) {
+		res = 1;
+		goto out0;
+	}
+
+	if (opts.show_version) {
+		printf("FUSE library version %s\n", PACKAGE_VERSION);
+		fuse_lowlevel_version();
+		res = 0;
+		goto out1;
+	}
+
+	if (opts.show_help) {
+		if (args->argv[0][0] != '\0')
+			printf("usage: %s [options] <mountpoint>\n\n",
+			       args->argv[0]);
+		printf("FUSE options:\n");
+		fuse_cmdline_help();
+		fuse_lib_help(args);
+		res = 0;
+		goto out1;
+	}
+
+	if (!opts.show_help &&
+	    !opts.mountpoint) {
+		fuse_log(FUSE_LOG_ERR, "error: no mountpoint specified\n");
+		res = 2;
+		goto out1;
+	}
+
+	fuse = _fuse_new_31(args, op, op_size, version, user_data);
+	if (fuse == NULL) {
+		res = 3;
+		goto out1;
+	}
+
+	if (fuse_service_session_mount(service, fuse_get_session(fuse),
+				       0, &opts) != 0) {
+		res = 4;
+		goto out2;
+	}
+
+	/* No need to fork when running as a systemd service */
+	if (fuse_daemonize(1) != 0) {
+		res = 5;
+		goto out3;
+	}
+
+	struct fuse_session *se = fuse_get_session(fuse);
+
+	if (fuse_set_signal_handlers(se) != 0) {
+		res = 6;
+		goto out3;
+	}
+
+	if (opts.singlethread) {
+		fuse_service_send_goodbye(service, 0);
+		fuse_service_release(service);
+
+		res = fuse_loop(fuse);
+	} else {
+		loop_config = fuse_loop_cfg_create();
+		if (loop_config == NULL) {
+			res = 7;
+			goto out4;
+		}
+
+		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, opts.max_threads);
+
+		fuse_service_send_goodbye(service, 0);
+		fuse_service_release(service);
+
+		res = fuse_loop_mt(fuse, loop_config);
+	}
+	if (res)
+		res = 8;
+
+	/*
+	 * We've released the mount service helper, so we can't ask it to
+	 * unmount the filesystem.  In other words, once mount() succeeds,
+	 * the user has to unmount the filesystem.
+	 */
+	fuse_remove_signal_handlers(se);
+	goto out2;
+
+out4:
+	fuse_remove_signal_handlers(se);
+out3:
+	fuse_service_session_unmount(service);
+out2:
+	fuse_destroy(fuse);
+out1:
+	fuse_loop_cfg_destroy(loop_config);
+	free(opts.mountpoint);
+out0:
+	fuse_service_send_goodbye(service, res);
+	fuse_service_release(service);
+	return res;
+}
+
 int fuse_main_real_versioned(int argc, char *argv[],
 			     const struct fuse_operations *op, size_t op_size,
 			     struct libfuse_version *version, void *user_data)
@@ -403,10 +524,6 @@ int fuse_main_real_versioned(int argc, char *argv[],
 		goto out1;
 	}
 
-	struct fuse *_fuse_new_31(struct fuse_args *args,
-			       const struct fuse_operations *op, size_t op_size,
-			       struct libfuse_version *version,
-			       void *user_data);
 	fuse = _fuse_new_31(&args, op, op_size, version, user_data);
 	if (fuse == NULL) {
 		res = 3;


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH 04/13] mount_service: use the new mount api for the mount service
  2026-04-09 22:20 [PATCHSET v4] libfuse: run fuse servers as a contained service Darrick J. Wong
                   ` (2 preceding siblings ...)
  2026-04-09 22:21 ` [PATCH 03/13] mount_service: create high level fuse helpers Darrick J. Wong
@ 2026-04-09 22:21 ` Darrick J. Wong
  2026-04-17 22:03   ` Darrick J. Wong
  2026-04-09 22:21 ` [PATCH 05/13] mount_service: update mtab after a successful mount Darrick J. Wong
                   ` (8 subsequent siblings)
  12 siblings, 1 reply; 24+ messages in thread
From: Darrick J. Wong @ 2026-04-09 22:21 UTC (permalink / raw)
  To: djwong, bschubert; +Cc: miklos, neal, linux-fsdevel, bernd, joannelkoong

From: Darrick J. Wong <djwong@kernel.org>

Use the new fsopen/fsmount system calls to mount the filesystem so that
we get somewhat better diagnostics if something gets screwed up.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 lib/fuse_i.h         |    3 
 meson.build          |   15 ++
 util/mount_service.c |  323 ++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 341 insertions(+)


diff --git a/lib/fuse_i.h b/lib/fuse_i.h
index 0ca13d132585f6..1710a872e19c72 100644
--- a/lib/fuse_i.h
+++ b/lib/fuse_i.h
@@ -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);
diff --git a/meson.build b/meson.build
index 66425a0d4cc16f..c8326b79fcee8f 100644
--- a/meson.build
+++ b/meson.build
@@ -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;
+       }
     '''
 }
 
diff --git a/util/mount_service.c b/util/mount_service.c
index abe88a0710255b..246a95101e8d34 100644
--- a/util/mount_service.c
+++ b/util/mount_service.c
@@ -71,6 +71,9 @@ struct mount_service {
 	/* fd for mount point */
 	int mountfd;
 
+	/* fd for fsopen */
+	int fsopenfd;
+
 	/* did we actually mount successfully? */
 	bool mounted;
 };
@@ -94,6 +97,7 @@ static int mount_service_init(struct mount_service *mo, int argc, char *argv[])
 	mo->argvfd = -1;
 	mo->fusedevfd = -1;
 	mo->mountfd = -1;
+	mo->fsopenfd = -1;
 
 	for (i = 0; i < argc; i++) {
 		if (!strcmp(argv[i], "-t") && i + 1 < argc) {
@@ -710,6 +714,26 @@ static int mount_service_handle_open_bdev_cmd(struct mount_service *mo,
 	return mount_service_open_path(mo, S_IFBLK, p, psz);
 }
 
+#ifdef HAVE_NEW_MOUNT_API
+static void try_fsopen(struct mount_service *mo,
+		       struct fuse_service_string_command *oc)
+{
+	/*
+	 * As of Linux 7.0 you can pass subtypes to fsopen, but the manpage for
+	 * fsopen only says that you can pass any value of the second column of
+	 * /proc/filesystems into fsopen.
+	 */
+	if (!strncmp(oc->value, "fuse.", 5))
+		*(oc->value + 4) = 0;
+	else if (!strncmp(oc->value, "fuseblk.", 8))
+		*(oc->value + 7) = 0;
+
+	mo->fsopenfd = fsopen(oc->value, FSOPEN_CLOEXEC);
+}
+#else
+# define try_fsopen(...)	((void)0)
+#endif
+
 static int mount_service_handle_fsopen_cmd(struct mount_service *mo,
 					   const struct fuse_service_packet *p,
 					   size_t psz)
@@ -744,9 +768,45 @@ static int mount_service_handle_fsopen_cmd(struct mount_service *mo,
 		return mount_service_send_reply(mo, error);
 	}
 
+	/* If this fails we fall back on mount(); oc->value is mutated */
+	try_fsopen(mo, oc);
 	return mount_service_send_reply(mo, 0);
 }
 
+#ifdef HAVE_NEW_MOUNT_API
+/* callers must preserve errno */
+static void emit_fsconfig_messages(const struct mount_service *mo)
+{
+	uint8_t buf[BUFSIZ];
+	ssize_t sz;
+
+	while ((sz = read(mo->fsopenfd, buf, sizeof(buf) - 1)) >= 1) {
+		if (buf[sz - 1] == '\n')
+			buf[--sz] = '\0';
+		else
+			buf[sz] = '\0';
+
+		if (!*buf)
+			continue;
+
+		switch (buf[0]) {
+		case 'e':
+			fprintf(stderr, "Error: %s\n", buf + 2);
+			break;
+		case 'w':
+			fprintf(stderr, "Warning: %s\n", buf + 2);
+			break;
+		case 'i':
+			fprintf(stderr, "Info: %s\n", buf + 2);
+			break;
+		default:
+			fprintf(stderr, " %s\n", buf);
+			break;
+		}
+	}
+}
+#endif
+
 static int mount_service_handle_source_cmd(struct mount_service *mo,
 					   const struct fuse_service_packet *p,
 					   size_t psz)
@@ -781,6 +841,21 @@ static int mount_service_handle_source_cmd(struct mount_service *mo,
 		return mount_service_send_reply(mo, error);
 	}
 
+#ifdef HAVE_NEW_MOUNT_API
+	if (mo->fsopenfd >= 0) {
+		int ret = fsconfig(mo->fsopenfd, FSCONFIG_SET_STRING, "source",
+			       oc->value, 0);
+		if (ret) {
+			int error = errno;
+
+			fprintf(stderr, "%s: fsconfig source: %s\n",
+				mo->msgtag, strerror(error));
+			emit_fsconfig_messages(mo);
+			return mount_service_send_reply(mo, error);
+		}
+	}
+#endif
+
 	return mount_service_send_reply(mo, 0);
 }
 
@@ -790,6 +865,8 @@ static int mount_service_handle_mntopts_cmd(struct mount_service *mo,
 {
 	struct fuse_service_string_command *oc =
 			container_of(p, struct fuse_service_string_command, p);
+	char *tokstr = oc->value;
+	char *tok, *savetok;
 
 	if (psz < sizeof_fuse_service_string_command(1)) {
 		fprintf(stderr, "%s: mount options command too small\n",
@@ -818,6 +895,45 @@ static int mount_service_handle_mntopts_cmd(struct mount_service *mo,
 		return mount_service_send_reply(mo, error);
 	}
 
+	/* strtok_r mutates tokstr aka oc->value */
+	while ((tok = strtok_r(tokstr, ",", &savetok)) != NULL) {
+		char *equals = strchr(tok, '=');
+		char oldchar = 0;
+
+		if (equals) {
+			oldchar = *equals;
+			*equals = 0;
+		}
+
+#ifdef HAVE_NEW_MOUNT_API
+		if (mo->fsopenfd >= 0) {
+			int ret;
+
+			if (equals)
+				ret = fsconfig(mo->fsopenfd,
+					       FSCONFIG_SET_STRING, tok,
+					       equals + 1, 0);
+			else
+				ret = fsconfig(mo->fsopenfd,
+					       FSCONFIG_SET_FLAG, tok,
+					       NULL, 0);
+			if (ret) {
+				int error = errno;
+
+				fprintf(stderr, "%s: set mount option: %s\n",
+					mo->msgtag, strerror(error));
+				emit_fsconfig_messages(mo);
+				return mount_service_send_reply(mo, error);
+			}
+		}
+#endif
+
+		if (equals)
+			*equals = oldchar;
+
+		tokstr = NULL;
+	}
+
 	return mount_service_send_reply(mo, 0);
 }
 
@@ -1028,6 +1144,205 @@ static int mount_service_regular_mount(struct mount_service *mo,
 	return mount_service_send_reply(mo, 0);
 }
 
+#ifdef HAVE_NEW_MOUNT_API
+struct ms_to_mount_map {
+	unsigned long ms_flag;
+	unsigned int mount_attr_flag;
+};
+
+static const struct ms_to_mount_map attrs[] = {
+	{ MS_RDONLY,		MOUNT_ATTR_RDONLY },
+	{ MS_NOSUID,		MOUNT_ATTR_NOSUID },
+	{ MS_NODEV,		MOUNT_ATTR_NODEV },
+	{ MS_NOEXEC,		MOUNT_ATTR_NOEXEC },
+	{ MS_RELATIME,		MOUNT_ATTR_RELATIME },
+	{ MS_NOATIME,		MOUNT_ATTR_NOATIME },
+	{ MS_STRICTATIME,	MOUNT_ATTR_STRICTATIME },
+	{ MS_NODIRATIME,	MOUNT_ATTR_NODIRATIME },
+#ifdef MOUNT_ATTR_NOSYMFOLLOW
+	{ MS_NOSYMFOLLOW,	MOUNT_ATTR_NOSYMFOLLOW },
+#endif
+	{ 0, 0 },
+};
+
+static void get_mount_attr_flags(const struct fuse_service_mount_command *oc,
+				 unsigned int *attr_flags,
+				 unsigned long *leftover_ms_flags)
+{
+	const struct ms_to_mount_map *i;
+	unsigned int ms_flags = ntohl(oc->ms_flags);
+	unsigned int mount_attr_flags = 0;
+
+	for (i = attrs; i->ms_flag != 0; i++) {
+		if (ms_flags & i->ms_flag)
+			mount_attr_flags |= i->mount_attr_flag;
+		ms_flags &= ~i->ms_flag;
+	}
+
+	*leftover_ms_flags = ms_flags;
+	*attr_flags = mount_attr_flags;
+}
+
+struct ms_to_str_map {
+	unsigned long ms_flag;
+	const char *string;
+};
+
+static const struct ms_to_str_map strflags[] = {
+	{ MS_SYNCHRONOUS,	"sync" },
+	{ MS_DIRSYNC,		"dirsync" },
+	{ MS_LAZYTIME,		"lazytime" },
+	{ 0, 0 },
+};
+
+static int set_ms_flags(struct mount_service *mo, unsigned long ms_flags)
+{
+	const struct ms_to_str_map *i;
+	int ret;
+
+	for (i = strflags; i->ms_flag != 0; i++) {
+		if (!(ms_flags & i->ms_flag))
+			continue;
+
+		ret = fsconfig(mo->fsopenfd, FSCONFIG_SET_FLAG, i->string,
+			       NULL, 0);
+		if (ret) {
+			int error = errno;
+
+			fprintf(stderr, "%s: set %s option: %s\n",
+				mo->msgtag, i->string, strerror(error));
+			emit_fsconfig_messages(mo);
+
+			errno = error;
+			return -1;
+		}
+		ms_flags &= ~i->ms_flag;
+	}
+
+	/*
+	 * We can't translate all the supplied MS_ flags into MOUNT_ATTR_ flags
+	 * or string flags!  Return a magic code so the caller will fall back
+	 * to regular mount(2).
+	 */
+	if (ms_flags)
+		return FUSE_MOUNT_FALLBACK_NEEDED;
+
+	return 0;
+}
+
+static int mount_service_fsopen_mount(struct mount_service *mo,
+				      struct fuse_service_mount_command *oc,
+				      struct stat *stbuf)
+{
+	char tmp[64];
+	char *dot;
+	unsigned long ms_flags;
+	unsigned int attr_flags;
+	int mfd;
+	int error;
+	int ret;
+
+	get_mount_attr_flags(oc, &attr_flags, &ms_flags);
+
+	ret = set_ms_flags(mo, ms_flags);
+	if (ret == FUSE_MOUNT_FALLBACK_NEEDED)
+		return ret;
+	if (ret) {
+		error = errno;
+		goto fail_mount;
+	}
+
+	snprintf(tmp, sizeof(tmp), "%i", mo->fusedevfd);
+	ret = fsconfig(mo->fsopenfd, FSCONFIG_SET_STRING, "fd", tmp, 0);
+	if (ret) {
+		error = errno;
+		fprintf(stderr, "%s: set fd option: %s\n",
+			mo->msgtag, strerror(error));
+		goto fail_fsconfig;
+	}
+
+	snprintf(tmp, sizeof(tmp), "%o", stbuf->st_mode & S_IFMT);
+	ret = fsconfig(mo->fsopenfd, FSCONFIG_SET_STRING, "rootmode", tmp, 0);
+	if (ret) {
+		error = errno;
+		fprintf(stderr, "%s: set rootmode option: %s\n",
+			mo->msgtag, strerror(error));
+		goto fail_fsconfig;
+	}
+
+	snprintf(tmp, sizeof(tmp), "%u", getuid());
+	ret = fsconfig(mo->fsopenfd, FSCONFIG_SET_STRING, "user_id", tmp, 0);
+	if (ret) {
+		error = errno;
+		fprintf(stderr, "%s: set user_id option: %s\n",
+			mo->msgtag, strerror(error));
+		goto fail_fsconfig;
+	}
+
+	snprintf(tmp, sizeof(tmp), "%u", getgid());
+	ret = fsconfig(mo->fsopenfd, FSCONFIG_SET_STRING, "group_id", tmp, 0);
+	if (ret) {
+		error = errno;
+		fprintf(stderr, "%s: set group_id option: %s\n",
+			mo->msgtag, strerror(error));
+		goto fail_fsconfig;
+	}
+
+	dot = strchr(mo->fstype, '.');
+	if (dot) {
+		ret = fsconfig(mo->fsopenfd, FSCONFIG_SET_STRING, "subtype",
+			       dot + 1, 0);
+		if (ret) {
+			error = errno;
+
+			/* The subtype option came after fsopen */
+			if (error == EINVAL)
+				return FUSE_MOUNT_FALLBACK_NEEDED;
+
+			fprintf(stderr, "%s: set subtype option: %s\n",
+				mo->msgtag, strerror(error));
+			goto fail_fsconfig;
+		}
+	}
+
+	ret = fsconfig(mo->fsopenfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0);
+	if (ret) {
+		error = errno;
+		fprintf(stderr, "%s: creating filesystem: %s\n",
+			mo->msgtag, strerror(error));
+		goto fail_fsconfig;
+	}
+
+	mfd = fsmount(mo->fsopenfd, FSMOUNT_CLOEXEC, attr_flags);
+	if (mfd < 0) {
+		error = errno;
+		fprintf(stderr, "%s: fsmount: %s\n",
+			mo->msgtag, strerror(error));
+		goto fail_fsconfig;
+	}
+
+	ret = move_mount(mfd, "", mo->mountfd, "",
+			 MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_T_EMPTY_PATH);
+	close(mfd);
+	if (ret) {
+		error = errno;
+		fprintf(stderr, "%s: move_mount: %s\n",
+			mo->msgtag, strerror(error));
+		goto fail_mount;
+	}
+
+	mo->mounted = true;
+	return mount_service_send_reply(mo, 0);
+
+fail_fsconfig:
+	emit_fsconfig_messages(mo);
+fail_mount:
+	return mount_service_send_reply(mo, error);
+}
+#else
+# define mount_service_fsopen_mount(...)	(FUSE_MOUNT_FALLBACK_NEEDED)
+#endif
+
 static int mount_service_handle_mount_cmd(struct mount_service *mo,
 					  struct fuse_service_packet *p,
 					  size_t psz)
@@ -1100,6 +1415,12 @@ static int mount_service_handle_mount_cmd(struct mount_service *mo,
 		return mount_service_send_reply(mo, EINVAL);
 	}
 
+	if (mo->fsopenfd >= 0) {
+		ret = mount_service_fsopen_mount(mo, oc, &stbuf);
+		if (ret != FUSE_MOUNT_FALLBACK_NEEDED)
+			return ret;
+	}
+
 	return mount_service_regular_mount(mo, oc, &stbuf);
 }
 
@@ -1179,6 +1500,7 @@ static void mount_service_destroy(struct mount_service *mo)
 	close(mo->mountfd);
 	close(mo->fusedevfd);
 	close(mo->argvfd);
+	close(mo->fsopenfd);
 	shutdown(mo->sockfd, SHUT_RDWR);
 	close(mo->sockfd);
 
@@ -1194,6 +1516,7 @@ static void mount_service_destroy(struct mount_service *mo)
 	mo->argvfd = -1;
 	mo->fusedevfd = -1;
 	mo->mountfd = -1;
+	mo->fsopenfd = -1;
 }
 
 int mount_service_main(int argc, char *argv[])


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH 05/13] mount_service: update mtab after a successful mount
  2026-04-09 22:20 [PATCHSET v4] libfuse: run fuse servers as a contained service Darrick J. Wong
                   ` (3 preceding siblings ...)
  2026-04-09 22:21 ` [PATCH 04/13] mount_service: use the new mount api for the mount service Darrick J. Wong
@ 2026-04-09 22:21 ` Darrick J. Wong
  2026-04-09 22:22 ` [PATCH 06/13] util: hoist the fuse.conf parsing and setuid mode enforcement code Darrick J. Wong
                   ` (7 subsequent siblings)
  12 siblings, 0 replies; 24+ messages in thread
From: Darrick J. Wong @ 2026-04-09 22:21 UTC (permalink / raw)
  To: djwong, bschubert; +Cc: miklos, neal, linux-fsdevel, bernd, joannelkoong

From: Darrick J. Wong <djwong@kernel.org>

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.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 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        |   67 +++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 91 insertions(+)


diff --git a/include/fuse_service_priv.h b/include/fuse_service_priv.h
index 8df871ee117875..d9149379a5b65f 100644
--- a/include/fuse_service_priv.h
+++ b/include/fuse_service_priv.h
@@ -37,6 +37,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 */
diff --git a/lib/mount_common_i.h b/lib/mount_common_i.h
index 631dff3e6f8aaf..541cdebae4f47a 100644
--- a/lib/mount_common_i.h
+++ b/lib/mount_common_i.h
@@ -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);
 
 
diff --git a/lib/fuse_service.c b/lib/fuse_service.c
index b775727e7c91e2..b6044f788ed0d3 100644
--- a/lib/fuse_service.c
+++ b/lib/fuse_service.c
@@ -853,6 +853,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;
@@ -920,6 +921,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), expected_fmt, &error);
 	if (ret)
 		goto out_strings;
@@ -938,6 +952,7 @@ int fuse_service_session_mount(struct fuse_service *sf, struct fuse_session *se,
 	opts->foreground = 1;
 
 out_strings:
+	free(mtabopts);
 	free(mntopts);
 	free(source);
 	free(fstype);
diff --git a/lib/mount.c b/lib/mount.c
index 952d8899dcf218..84c73579ab2daf 100644
--- a/lib/mount.c
+++ b/lib/mount.c
@@ -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;
diff --git a/util/mount_service.c b/util/mount_service.c
index 246a95101e8d34..8e9c721a56fd2a 100644
--- a/util/mount_service.c
+++ b/util/mount_service.c
@@ -59,6 +59,9 @@ struct mount_service {
 	/* mount options */
 	char *mntopts;
 
+	/* mtab options */
+	char *mtabopts;
+
 	/* socket fd */
 	int sockfd;
 
@@ -937,6 +940,43 @@ static int mount_service_handle_mntopts_cmd(struct mount_service *mo,
 	return mount_service_send_reply(mo, 0);
 }
 
+static int mount_service_handle_mtabopts_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: mtab options command too small\n",
+			mo->msgtag);
+		return mount_service_send_reply(mo, EINVAL);
+	}
+
+	if (!check_null_endbyte(p, psz)) {
+		fprintf(stderr, "%s: mtab options command must be null terminated\n",
+			mo->msgtag);
+		return mount_service_send_reply(mo, EINVAL);
+	}
+
+	if (mo->mtabopts) {
+		fprintf(stderr, "%s: mtab options respecified!\n",
+			mo->msgtag);
+		return mount_service_send_reply(mo, EINVAL);
+	}
+
+	mo->mtabopts = strdup(oc->value);
+	if (!mo->mtabopts) {
+		int error = errno;
+
+		fprintf(stderr, "%s: alloc mtab 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, char *mntpt)
 {
 	struct stat statbuf;
@@ -1140,6 +1180,14 @@ static int mount_service_regular_mount(struct mount_service *mo,
 		return mount_service_send_reply(mo, error);
 	}
 
+	/*
+	 * The mount succeeded, so we send a positive reply even if the mtab
+	 * update fails.
+	 */
+	if (mo->mtabopts)
+		fuse_mnt_add_mount(mo->msgtag, mo->source, mo->resv_mountpoint,
+				   mo->fstype, mo->mtabopts);
+
 	mo->mounted = true;
 	return mount_service_send_reply(mo, 0);
 }
@@ -1331,6 +1379,14 @@ static int mount_service_fsopen_mount(struct mount_service *mo,
 		goto fail_mount;
 	}
 
+	/*
+	 * The mount succeeded, so we send a positive reply even if the mtab
+	 * update fails.
+	 */
+	if (mo->mtabopts)
+		fuse_mnt_add_mount(mo->msgtag, mo->source, mo->resv_mountpoint,
+				   mo->fstype, mo->mtabopts);
+
 	mo->mounted = true;
 	return mount_service_send_reply(mo, 0);
 
@@ -1469,6 +1525,13 @@ static int mount_service_handle_unmount_cmd(struct mount_service *mo,
 		return mount_service_send_reply(mo, error);
 	}
 
+	/*
+	 * The unmount succeeded, so we send a positive reply even if the mtab
+	 * update fails.
+	 */
+	if (mo->mtabopts)
+		fuse_mnt_remove_mount(mo->msgtag, mo->resv_mountpoint);
+
 	mo->mounted = false;
 	return mount_service_send_reply(mo, 0);
 }
@@ -1508,6 +1571,7 @@ static void mount_service_destroy(struct mount_service *mo)
 	free(mo->mountpoint);
 	free(mo->real_mountpoint);
 	free(mo->resv_mountpoint);
+	free(mo->mtabopts);
 	free(mo->mntopts);
 	free(mo->fstype);
 
@@ -1596,6 +1660,9 @@ int mount_service_main(int argc, char *argv[])
 		case FUSE_SERVICE_MNTPT_CMD:
 			ret = mount_service_handle_mountpoint_cmd(&mo, p, sz);
 			break;
+		case FUSE_SERVICE_MTABOPTS_CMD:
+			ret = mount_service_handle_mtabopts_cmd(&mo, p, sz);
+			break;
 		case FUSE_SERVICE_MOUNT_CMD:
 			ret = mount_service_handle_mount_cmd(&mo, p, sz);
 			break;


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH 06/13] util: hoist the fuse.conf parsing and setuid mode enforcement code
  2026-04-09 22:20 [PATCHSET v4] libfuse: run fuse servers as a contained service Darrick J. Wong
                   ` (4 preceding siblings ...)
  2026-04-09 22:21 ` [PATCH 05/13] mount_service: update mtab after a successful mount Darrick J. Wong
@ 2026-04-09 22:22 ` Darrick J. Wong
  2026-04-09 22:22 ` [PATCH 07/13] util: fix checkpatch complaints in fuser_conf.[ch] Darrick J. Wong
                   ` (6 subsequent siblings)
  12 siblings, 0 replies; 24+ messages in thread
From: Darrick J. Wong @ 2026-04-09 22:22 UTC (permalink / raw)
  To: djwong, bschubert; +Cc: miklos, neal, linux-fsdevel, bernd, joannelkoong

From: Darrick J. Wong <djwong@kernel.org>

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" <djwong@kernel.org>
---
 util/fuser_conf.h |   52 ++++++++
 util/fuser_conf.c |  354 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 util/fusermount.c |  324 +------------------------------------------------
 util/meson.build  |    6 -
 4 files changed, 417 insertions(+), 319 deletions(-)
 create mode 100644 util/fuser_conf.h
 create mode 100644 util/fuser_conf.c


diff --git a/util/fuser_conf.h b/util/fuser_conf.h
new file mode 100644
index 00000000000000..758580f906748d
--- /dev/null
+++ b/util/fuser_conf.h
@@ -0,0 +1,52 @@
+/*
+ * FUSE: Filesystem in Userspace
+ * Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
+ *
+ * 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);
+
+#endif /* FUSER_CONF_H_ */
diff --git a/util/fuser_conf.c b/util/fuser_conf.c
new file mode 100644
index 00000000000000..dedb017aecfc45
--- /dev/null
+++ b/util/fuser_conf.c
@@ -0,0 +1,354 @@
+/*
+ * FUSE: Filesystem in Userspace
+ * Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
+ *
+ * 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 <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 defined HAVE_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;
+}
diff --git a/util/fusermount.c b/util/fusermount.c
index 68370468140a59..e1396b02560d69 100644
--- a/util/fusermount.c
+++ b/util/fusermount.c
@@ -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)
@@ -1096,7 +861,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 +890,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 +924,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 +970,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);
diff --git a/util/meson.build b/util/meson.build
index 04ea5ac201340d..aa646ef3c77d16 100644
--- a/util/meson.build
+++ b/util/meson.build
@@ -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'],


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH 07/13] util: fix checkpatch complaints in fuser_conf.[ch]
  2026-04-09 22:20 [PATCHSET v4] libfuse: run fuse servers as a contained service Darrick J. Wong
                   ` (5 preceding siblings ...)
  2026-04-09 22:22 ` [PATCH 06/13] util: hoist the fuse.conf parsing and setuid mode enforcement code Darrick J. Wong
@ 2026-04-09 22:22 ` Darrick J. Wong
  2026-04-09 22:22 ` [PATCH 08/13] mount_service: enable unprivileged users in the same manner as fusermount Darrick J. Wong
                   ` (5 subsequent siblings)
  12 siblings, 0 replies; 24+ messages in thread
From: Darrick J. Wong @ 2026-04-09 22:22 UTC (permalink / raw)
  To: djwong, bschubert; +Cc: miklos, neal, linux-fsdevel, bernd, joannelkoong

From: Darrick J. Wong <djwong@kernel.org>

Fix the checkpatch complaints because we touched some code.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 util/fuser_conf.h |    3 ++-
 util/fuser_conf.c |   51 +++++++++++++++++++++++++++++++++------------------
 2 files changed, 35 insertions(+), 19 deletions(-)


diff --git a/util/fuser_conf.h b/util/fuser_conf.h
index 758580f906748d..8af93bcd0937ae 100644
--- a/util/fuser_conf.h
+++ b/util/fuser_conf.h
@@ -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);
diff --git a/util/fuser_conf.c b/util/fuser_conf.c
index dedb017aecfc45..dd11dc5d7d3d43 100644
--- a/util/fuser_conf.c
+++ b/util/fuser_conf.c
@@ -28,7 +28,7 @@
 #include <stdint.h>
 #endif
 
-int user_allow_other = 0;
+int user_allow_other;
 int mount_max = 1000;
 static uid_t oldfsuid;
 static gid_t oldfsgid;
@@ -41,25 +41,27 @@ 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') {
+		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') {
+		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[1] - '0') << 3
+				| (src[2] - '0') << 0;
 			src += 3;
 		} else if (src[0] == '\\') {
 			*dest++ = '\\';
@@ -77,6 +79,7 @@ static int count_fuse_fs_mtab(const char *progname)
 	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));
@@ -85,7 +88,7 @@ static int count_fuse_fs_mtab(const char *progname)
 	while ((entp = GETMNTENT(fp)) != NULL) {
 		if (strcmp(entp->mnt_type, "fuse") == 0 ||
 		    strncmp(entp->mnt_type, "fuse.", 5) == 0)
-			count ++;
+			count++;
 	}
 	endmntent(fp);
 	return count;
@@ -167,12 +170,15 @@ int count_fuse_fs(const char *progname)
 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 >= line && isspace((unsigned char) *s); s--) {
+	}
 	s[1] = '\0';
-	for (s = line; isspace((unsigned char) *s); s++);
+	for (s = line; isspace((unsigned char) *s); s++)
+		; /* empty */
 	if (s != line)
 		memmove(line, s, strlen(s)+1);
 }
@@ -180,11 +186,12 @@ static void strip_line(char *line)
 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])
+	else if (line[0])
 		fprintf(stderr,
 			"%s: unknown parameter in %s at line %i: '%s'\n",
 			progname, FUSE_CONF, linenum, line);
@@ -193,10 +200,12 @@ static void parse_line(const char *line, int linenum, const char *progname)
 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') {
@@ -205,16 +214,18 @@ void read_conf(const char *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);
+			} 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 ++;
+				linenum++;
 		}
 		if (!isnewline) {
-			fprintf(stderr, "%s: reading %s: missing newline at end of file\n", progname, FUSE_CONF);
+			fprintf(stderr, "%s: reading %s: missing newline at end of file\n",
+				progname, FUSE_CONF);
 
 		}
 		if (ferror(fp)) {
@@ -287,6 +298,8 @@ int check_nonroot_dir_access(const char *progname, const char *origmnt,
 	return 0;
 }
 
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+
 int check_nonroot_fstype(const char *progname, const struct statfs *fs_buf)
 {
 	size_t i;
@@ -296,13 +309,15 @@ int check_nonroot_fstype(const char *progname, const struct statfs *fs_buf)
 	 * 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. */
+	 * 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. */
+	 * overlaid.
+	 */
 	typeof(fs_buf->f_type) f_type_whitelist[] = {
 		0x61756673 /* AUFS_SUPER_MAGIC */,
 		0x00000187 /* AUTOFS_SUPER_MAGIC */,
@@ -343,7 +358,7 @@ int check_nonroot_fstype(const char *progname, const struct statfs *fs_buf)
 		0x2FC12FC1 /* ZFS_SUPER_MAGIC */,
 		0x858458f6 /* RAMFS_MAGIC */,
 	};
-	for (i = 0; i < sizeof(f_type_whitelist)/sizeof(f_type_whitelist[0]); i++) {
+	for (i = 0; i < ARRAY_SIZE(f_type_whitelist); i++) {
 		if (f_type_whitelist[i] == fs_buf->f_type)
 			return 0;
 	}


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH 08/13] mount_service: enable unprivileged users in the same manner as fusermount
  2026-04-09 22:20 [PATCHSET v4] libfuse: run fuse servers as a contained service Darrick J. Wong
                   ` (6 preceding siblings ...)
  2026-04-09 22:22 ` [PATCH 07/13] util: fix checkpatch complaints in fuser_conf.[ch] Darrick J. Wong
@ 2026-04-09 22:22 ` Darrick J. Wong
  2026-04-14 23:53   ` Darrick J. Wong
  2026-04-09 22:22 ` [PATCH 09/13] mount.fuse3: integrate systemd service startup Darrick J. Wong
                   ` (4 subsequent siblings)
  12 siblings, 1 reply; 24+ messages in thread
From: Darrick J. Wong @ 2026-04-09 22:22 UTC (permalink / raw)
  To: djwong, bschubert; +Cc: miklos, neal, linux-fsdevel, bernd, joannelkoong

From: Darrick J. Wong <djwong@kernel.org>

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.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 util/mount_service.c |  150 +++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 147 insertions(+), 3 deletions(-)


diff --git a/util/mount_service.c b/util/mount_service.c
index 8e9c721a56fd2a..0f7eae94ada377 100644
--- a/util/mount_service.c
+++ b/util/mount_service.c
@@ -33,6 +33,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 */
@@ -166,7 +167,9 @@ static int mount_service_connect(struct mount_service *mo)
 		return -1;
 	}
 
+	drop_privs();
 	ret = connect(sockfd, (const struct sockaddr *)&name, sizeof(name));
+	restore_privs();
 	if (ret && (errno == ENOENT || errno == ECONNREFUSED)) {
 		fprintf(stderr, "%s: no safe filesystem driver for %s available.\n",
 			mo->msgtag, mo->subtype);
@@ -208,7 +211,7 @@ static int mount_service_send_hello(struct mount_service *mo)
 	};
 	ssize_t size;
 
-	if (getuid() == 0)
+	if (getuid() == 0 || user_allow_other)
 		hello.flags |= htonl(FUSE_SERVICE_FLAG_ALLOW_OTHER);
 
 	size = sendmsg(mo->sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
@@ -490,7 +493,9 @@ static int mount_service_send_required_files(struct mount_service *mo,
 {
 	int ret;
 
+	drop_privs();
 	mo->fusedevfd = open(fusedev, O_RDWR | O_CLOEXEC);
+	restore_privs();
 	if (mo->fusedevfd < 0) {
 		fprintf(stderr, "%s: %s: %s\n",
 			mo->msgtag, fusedev, strerror(errno));
@@ -628,7 +633,9 @@ 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);
+		restore_privs();
 		if (ret) {
 			int error = errno;
 
@@ -675,7 +682,9 @@ static int mount_service_open_path(struct mount_service *mo,
 		return mount_service_send_file_error(mo, EINVAL, oc->path);
 	}
 
-	fd = open(oc->path, ntohl(oc->open_flags), ntohl(oc->create_mode));
+	drop_privs();
+	fd = open(oc->path, ntohl(oc->open_flags) | O_CLOEXEC, ntohl(oc->create_mode));
+	restore_privs();
 	if (fd < 0) {
 		int error = errno;
 
@@ -908,6 +917,20 @@ 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);
+		}
+		if (!strcmp(tok, "blkdev") && getuid() != 0) {
+			fprintf(stderr, "%s: option blkdev is privileged\n",
+				mo->msgtag);
+			return mount_service_send_reply(mo, EPERM);
+		}
+
 #ifdef HAVE_NEW_MOUNT_API
 		if (mo->fsopenfd >= 0) {
 			int ret;
@@ -985,10 +1008,16 @@ static int attach_to_mountpoint(struct mount_service *mo, char *mntpt)
 	int error;
 	int ret;
 
+	drop_privs();
+
 	/*
 	 * Open the alleged mountpoint, make sure it's a dir or a file.
+	 * For unprivileged callers, we only allow mounting on paths that the
+	 * user can write to.
 	 */
-	mountfd = open(mntpt, O_RDONLY | O_CLOEXEC);
+	mountfd = open(mntpt, (getuid() == 0 ? O_RDONLY : O_WRONLY) | O_CLOEXEC);
+	if (mountfd < 0 && errno == EISDIR)
+		mountfd = open(mntpt, O_RDONLY | O_CLOEXEC);
 	if (mountfd < 0) {
 		error = errno;
 		fprintf(stderr, "%s: %s: %s\n", mo->msgtag, mntpt,
@@ -1067,6 +1096,7 @@ static int attach_to_mountpoint(struct mount_service *mo, char *mntpt)
 	mo->mountfd = mountfd;
 	mo->resv_mountpoint = res_mntpt;
 
+	restore_privs();
 	return mount_service_send_reply(mo, 0);
 
 out_res_mntpt:
@@ -1075,6 +1105,7 @@ static int attach_to_mountpoint(struct mount_service *mo, char *mntpt)
 	close(mountfd);
 out_error:
 	free(mntpt);
+	restore_privs();
 	return mount_service_send_reply(mo, error);
 }
 
@@ -1399,6 +1430,77 @@ 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 int mount_service_handle_mount_cmd(struct mount_service *mo,
 					  struct fuse_service_packet *p,
 					  size_t psz)
@@ -1471,6 +1573,44 @@ static int mount_service_handle_mount_cmd(struct mount_service *mo,
 		return mount_service_send_reply(mo, EINVAL);
 	}
 
+	/*
+	 * 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.
+	 */
+	if (getuid() != 0) {
+		struct statfs fs_buf;
+
+		ret = check_nonroot_mount_count(mo->msgtag);
+		if (ret)
+			return mount_service_send_reply(mo, 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 mount_service_send_reply(mo, error);
+		}
+
+		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();
+
+		if (ret)
+			return mount_service_send_reply(mo, EPERM);
+	}
+
 	if (mo->fsopenfd >= 0) {
 		ret = mount_service_fsopen_mount(mo, oc, &stbuf);
 		if (ret != FUSE_MOUNT_FALLBACK_NEEDED)
@@ -1601,6 +1741,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;


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH 09/13] mount.fuse3: integrate systemd service startup
  2026-04-09 22:20 [PATCHSET v4] libfuse: run fuse servers as a contained service Darrick J. Wong
                   ` (7 preceding siblings ...)
  2026-04-09 22:22 ` [PATCH 08/13] mount_service: enable unprivileged users in the same manner as fusermount Darrick J. Wong
@ 2026-04-09 22:22 ` Darrick J. Wong
  2026-04-17 22:41   ` Darrick J. Wong
  2026-04-09 22:23 ` [PATCH 10/13] mount_service: allow installation as a setuid program Darrick J. Wong
                   ` (3 subsequent siblings)
  12 siblings, 1 reply; 24+ messages in thread
From: Darrick J. Wong @ 2026-04-09 22:22 UTC (permalink / raw)
  To: djwong, bschubert; +Cc: miklos, neal, linux-fsdevel, bernd, joannelkoong

From: Darrick J. Wong <djwong@kernel.org>

Teach mount.fuse3 how to start fuse via systemd service, if present.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 util/mount_service.h  |    8 +++
 doc/fuservicemount3.8 |   10 +++-
 meson.build           |    3 +
 util/fuservicemount.c |   48 +++++++++++++++++
 util/meson.build      |   14 ++++-
 util/mount.fuse.c     |  135 +++++++++++++++++++++++++++++++++++++++++--------
 util/mount_service.c  |   28 ++++++++++
 7 files changed, 219 insertions(+), 27 deletions(-)


diff --git a/util/mount_service.h b/util/mount_service.h
index f1f95e67ee1afe..993414ea3422e9 100644
--- a/util/mount_service.h
+++ b/util/mount_service.h
@@ -36,4 +36,12 @@ 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 subtype.
+ *
+ * @param subtype the subtype of a fuse filesystem type (e.g. Y from fuse.Y)
+ * @return true if available, false if not
+ */
+bool mount_service_present(const char *subtype);
+
 #endif /* MOUNT_SERVICE_H_ */
diff --git a/doc/fuservicemount3.8 b/doc/fuservicemount3.8
index e45d6a89c8b81a..aa2167cb4872c6 100644
--- a/doc/fuservicemount3.8
+++ b/doc/fuservicemount3.8
@@ -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 <djwong@kernel.org>.
diff --git a/meson.build b/meson.build
index c8326b79fcee8f..827ec45ad3ad75 100644
--- a/meson.build
+++ b/meson.build
@@ -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))
diff --git a/util/fuservicemount.c b/util/fuservicemount.c
index 9c694a4290f94e..d39d9c486c8997 100644
--- a/util/fuservicemount.c
+++ b/util/fuservicemount.c
@@ -9,10 +9,58 @@
  * 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)
+{
+	const char *subtype;
+
+	if (!fstype) {
+		fprintf(stderr,
+			"fuservicemount: expected fs type for --check\n");
+		return EXIT_FAILURE;
+	}
+
+	subtype = mount_service_subtype(fstype);
+	return mount_service_present(subtype) ? 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.
+	 */
+	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);
 }
diff --git a/util/meson.build b/util/meson.build
index aa646ef3c77d16..85b54d5d322dcb 100644
--- a/util/meson.build
+++ b/util/meson.build
@@ -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')
diff --git a/util/mount.fuse.c b/util/mount.fuse.c
index ec56198ccc1cf0..8836eba8502fd1 100644
--- a/util/mount.fuse.c
+++ b/util/mount.fuse.c
@@ -49,6 +49,9 @@
 #endif
 
 #include "fuse.h"
+#ifdef HAVE_SERVICEMOUNT
+# include "mount_service.h"
+#endif
 
 static char *progname;
 
@@ -233,6 +236,77 @@ static void drop_and_lock_capabilities(void)
 }
 #endif
 
+#ifdef HAVE_SERVICEMOUNT
+#define FUSERVICEMOUNT_PROG	"fuservicemount3"
+
+static int try_service_main(char *argv0, char *type, char *source,
+			    const char *mountpoint, char *options)
+{
+	const char *full_path = FUSERVICEMOUNT_DIR "/" FUSERVICEMOUNT_PROG;
+	char **argv;
+	char dash_o[] = "-o";
+	char dash_t[] = "-t";
+	char *mntpt = strdup(mountpoint);
+	int argc = 5; /* argv[0], -t type, mountpoint, and trailing NULL */
+	int i = 0;
+	int ret;
+
+	if (!mount_service_present(type))
+		return MOUNT_SERVICE_FALLBACK_NEEDED;
+
+	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++] = type;
+	if (options) {
+		argv[i++] = dash_o;
+		argv[i++] = options;
+	}
+	argv[i] = 0;
+
+	if (getuid() != 0) {
+		/*
+		 * First try the install path, then a system install, just like
+		 * we do for fusermount.
+		 */
+		ret = execvp(full_path, argv);
+		if (ret)
+			ret = execvp(FUSERVICEMOUNT_PROG, argv);
+		if (ret) {
+			fprintf(stderr, "%s: could not start %s helper: %s\n",
+				argv[0], FUSERVICEMOUNT_PROG,
+				strerror(errno));
+			ret = MOUNT_SERVICE_FALLBACK_NEEDED;
+		}
+	} 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 +354,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 +375,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 +462,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 linux
 		if (drop_privileges) {
@@ -429,6 +507,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);
diff --git a/util/mount_service.c b/util/mount_service.c
index 0f7eae94ada377..f837595078f2d1 100644
--- a/util/mount_service.c
+++ b/util/mount_service.c
@@ -1836,3 +1836,31 @@ int mount_service_main(int argc, char *argv[])
 	mount_service_destroy(&mo);
 	return ret;
 }
+
+bool mount_service_present(const char *fstype)
+{
+	struct stat stbuf;
+	char path[PATH_MAX];
+	int ret;
+
+	snprintf(path, sizeof(path), FUSE_SERVICE_SOCKET_DIR "/%s", fstype);
+	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;
+}


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH 10/13] mount_service: allow installation as a setuid program
  2026-04-09 22:20 [PATCHSET v4] libfuse: run fuse servers as a contained service Darrick J. Wong
                   ` (8 preceding siblings ...)
  2026-04-09 22:22 ` [PATCH 09/13] mount.fuse3: integrate systemd service startup Darrick J. Wong
@ 2026-04-09 22:23 ` Darrick J. Wong
  2026-04-09 22:23 ` [PATCH 11/13] example/service_ll: create a sample systemd service fuse server Darrick J. Wong
                   ` (2 subsequent siblings)
  12 siblings, 0 replies; 24+ messages in thread
From: Darrick J. Wong @ 2026-04-09 22:23 UTC (permalink / raw)
  To: djwong, bschubert; +Cc: miklos, neal, linux-fsdevel, bernd, joannelkoong

From: Darrick J. Wong <djwong@kernel.org>

Allow installation of the mount service helper as a setuid program so
that regular users can access containerized filesystem drivers.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 README.md              |    3 +++
 test/ci-build.sh       |    7 +++++++
 util/install_helper.sh |    6 ++++++
 util/meson.build       |    3 ++-
 4 files changed, 18 insertions(+), 1 deletion(-)


diff --git a/README.md b/README.md
index bcf1210e6a23fa..b692a6f6915635 100644
--- a/README.md
+++ b/README.md
@@ -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:
diff --git a/test/ci-build.sh b/test/ci-build.sh
index 8b019a0b5e52c1..6fddcb6bc42d72 100755
--- a/test/ci-build.sh
+++ b/test/ci-build.sh
@@ -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}
diff --git a/util/install_helper.sh b/util/install_helper.sh
index 76f2b47fe6c8f9..1c076739b8bfd8 100755
--- a/util/install_helper.sh
+++ b/util/install_helper.sh
@@ -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 [ -x "${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
diff --git a/util/meson.build b/util/meson.build
index 85b54d5d322dcb..e15dd9bbb0c486 100644
--- a/util/meson.build
+++ b/util/meson.build
@@ -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')))


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH 11/13] example/service_ll: create a sample systemd service fuse server
  2026-04-09 22:20 [PATCHSET v4] libfuse: run fuse servers as a contained service Darrick J. Wong
                   ` (9 preceding siblings ...)
  2026-04-09 22:23 ` [PATCH 10/13] mount_service: allow installation as a setuid program Darrick J. Wong
@ 2026-04-09 22:23 ` Darrick J. Wong
  2026-04-14 23:56   ` Darrick J. Wong
  2026-04-17 21:56   ` Darrick J. Wong
  2026-04-09 22:23 ` [PATCH 12/13] example/service: create a sample systemd service for a high-level " Darrick J. Wong
  2026-04-09 22:23 ` [PATCH 13/13] nullfs: support fuse systemd service mode Darrick J. Wong
  12 siblings, 2 replies; 24+ messages in thread
From: Darrick J. Wong @ 2026-04-09 22:23 UTC (permalink / raw)
  To: djwong, bschubert; +Cc: miklos, neal, linux-fsdevel, bernd, joannelkoong

From: Darrick J. Wong <djwong@kernel.org>

Create a simple fuse server that can be run as a systemd service.
I plan to create some more single-file fuse server examples, so most of
the boilerplate code goes in a separate file.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 example/single_file.h        |  149 ++++++++++
 lib/util.h                   |   35 ++
 example/meson.build          |   14 +
 example/service_ll.c         |  308 ++++++++++++++++++++
 example/service_ll.socket.in |   15 +
 example/service_ll@.service  |  102 +++++++
 example/single_file.c        |  646 ++++++++++++++++++++++++++++++++++++++++++
 meson.build                  |    1 
 8 files changed, 1270 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/service_ll@.service
 create mode 100644 example/single_file.c


diff --git a/example/single_file.h b/example/single_file.h
new file mode 100644
index 00000000000000..af91ea8196a408
--- /dev/null
+++ b/example/single_file.h
@@ -0,0 +1,149 @@
+/*
+ * 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;
+
+	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);
+
+/* 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_ */
diff --git a/lib/util.h b/lib/util.h
index 107a2bfdd6105b..6ec6604fb74caf 100644
--- a/lib/util.h
+++ b/lib/util.h
@@ -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_ */
diff --git a/example/meson.build b/example/meson.build
index 76cf2d96db0349..e948f6ba74fdfa 100644
--- a/example/meson.build
+++ b/example/meson.build
@@ -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',
diff --git a/example/service_ll.c b/example/service_ll.c
new file mode 100644
index 00000000000000..da1d64f03be5f2
--- /dev/null
+++ b/example/service_ll.c
@@ -0,0 +1,308 @@
+/*
+ * 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 low-level API and systemd service api
+ *
+ * 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 service_ll@.service:
+ *
+ *     ExecStart=/path/to/service_ll
+ *
+ * to point to the actual path of the service_ll binary.
+ *
+ * Finally, install the service_ll@.service and service_ll.socket files to the
+ * systemd service directory, usually /run/systemd/system.
+ *
+ * ## Source code ##
+ * \include service_ll.c
+ * \include service_ll.socket
+ * \include service_ll@.service
+ * \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_lowlevel.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 "single_file.h"
+
+struct service_ll {
+	struct fuse_session *se;
+	char *device;
+	struct fuse_service *service;
+
+	/* really booleans */
+	int debug;
+};
+
+static struct service_ll ll = { };
+
+static void service_ll_init(void *userdata, struct fuse_conn_info *conn)
+{
+	(void)userdata;
+
+	conn->time_gran = 1;
+}
+
+static void service_ll_read(fuse_req_t req, fuse_ino_t ino, size_t count,
+			    off_t pos, struct fuse_file_info *fi)
+{
+	void *buf = NULL;
+	ssize_t got;
+	int ret;
+
+	if (!is_single_file_ino(ino)) {
+		ret = EIO;
+		goto out_reply;
+	}
+
+	if (ll.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) {
+		ret = ENOSYS;
+		goto out_reply;
+	}
+
+	single_file_check_read(pos, &count);
+
+	if (!count) {
+		fuse_reply_buf(req, buf, 0);
+		return;
+	}
+
+	buf = malloc(count);
+	if (!buf) {
+		ret = ENOMEM;
+		goto out_reply;
+	}
+
+	got = pread(single_file.backing_fd, buf, count, pos);
+	if (got < 0) {
+		ret = errno;
+		goto out_reply;
+	}
+
+	fuse_reply_buf(req, buf, got);
+	goto out_buf;
+
+out_reply:
+	fuse_reply_err(req, ret);
+out_buf:
+	free(buf);
+}
+
+static void service_ll_write(fuse_req_t req, fuse_ino_t ino, const char *buf,
+			     size_t count, off_t pos,
+			     struct fuse_file_info *fi)
+{
+	ssize_t got;
+	int ret;
+
+	if (!is_single_file_ino(ino)) {
+		ret = EIO;
+		goto out_reply;
+	}
+
+	if (ll.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) {
+		ret = ENOSYS;
+		goto out_reply;
+	}
+
+	ret = -single_file_check_write(pos, &count);
+	if (ret)
+		goto out_reply;
+
+	got = pwrite(single_file.backing_fd, buf, count, pos);
+	if (got < 0) {
+		ret = errno;
+		goto out_reply;
+	}
+
+	if (single_file.sync) {
+		ret = fsync(single_file.backing_fd);
+		if (ret < 0) {
+			ret = errno;
+			goto out_reply;
+		}
+	}
+
+	fuse_reply_write(req, got);
+	return;
+
+out_reply:
+	fuse_reply_err(req, ret);
+}
+
+static const struct fuse_lowlevel_ops service_ll_oper = {
+	.lookup		= single_file_ll_lookup,
+	.getattr	= single_file_ll_getattr,
+	.setattr	= single_file_ll_setattr,
+	.readdir	= single_file_ll_readdir,
+	.open		= single_file_ll_open,
+	.statfs		= single_file_ll_statfs,
+	.statx		= single_file_ll_statx,
+	.fsync		= single_file_ll_fsync,
+
+	.init		= service_ll_init,
+	.read		= service_ll_read,
+	.write		= service_ll_write,
+};
+
+#define SERVICE_LL_OPT(t, p, v) { t, offsetof(struct service_ll, p), v }
+
+static struct fuse_opt service_ll_opts[] = {
+	SERVICE_LL_OPT("debug",		debug,			1),
+	SINGLE_FILE_OPT_KEYS,
+	FUSE_OPT_END
+};
+
+static int service_ll_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 (!ll.device) {
+			ll.device = strdup(arg);
+			return 0;
+		}
+		return 1;
+	}
+
+	return 1;
+}
+
+int main(int argc, char *argv[])
+{
+	struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+	struct fuse_cmdline_opts opts = { };
+	int ret = 1;
+
+	if (fuse_service_accept(&ll.service))
+		goto err_args;
+
+	if (!fuse_service_accepted(ll.service))
+		goto err_args;
+
+	if (fuse_service_append_args(ll.service, &args))
+		goto err_service;
+
+	if (fuse_opt_parse(&args, &ll, service_ll_opts, service_ll_opt_proc))
+		goto err_service;
+
+	if (fuse_service_parse_cmdline_opts(&args, &opts))
+		goto err_service;
+
+	if (opts.show_help) {
+		printf("usage: %s [options] <device> <mountpoint>\n\n", argv[0]);
+		fuse_cmdline_help();
+		fuse_lowlevel_help();
+		ret = 0;
+		goto err_service;
+	} else if (opts.show_version) {
+		printf("FUSE library version %s\n", fuse_pkgversion());
+		fuse_lowlevel_version();
+		ret = 0;
+		goto err_service;
+	}
+
+	if (!opts.mountpoint || !ll.device) {
+		printf("usage: %s [options] <device> <mountpoint>\n", argv[0]);
+		printf("       %s --help\n", argv[0]);
+		goto err_service;
+	}
+
+	if (single_file_service_open(ll.service, ll.device))
+		goto err_service;
+
+	if (fuse_service_finish_file_requests(ll.service))
+		goto err_singlefile;
+
+	if (single_file_configure(ll.device, NULL))
+		goto err_singlefile;
+
+	ll.se = fuse_session_new(&args, &service_ll_oper,
+				 sizeof(service_ll_oper), NULL);
+	if (ll.se == NULL)
+		goto err_singlefile;
+
+	if (fuse_set_signal_handlers(ll.se))
+		goto err_session;
+
+	if (fuse_service_session_mount(ll.service, ll.se, S_IFDIR, &opts))
+		goto err_signals;
+
+	fuse_service_send_goodbye(ll.service, 0);
+	fuse_service_release(ll.service);
+
+	fuse_daemonize(opts.foreground);
+
+	/* Block until ctrl+c or fusermount -u */
+	if (opts.singlethread) {
+		ret = fuse_session_loop(ll.se);
+	} else {
+		struct fuse_loop_config *config = fuse_loop_cfg_create();
+
+		fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+		fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+		ret = fuse_session_loop_mt(ll.se, config);
+		fuse_loop_cfg_destroy(config);
+	}
+
+	fuse_session_unmount(ll.se);
+err_signals:
+	fuse_remove_signal_handlers(ll.se);
+err_session:
+	fuse_session_destroy(ll.se);
+err_singlefile:
+	single_file_close();
+err_service:
+	free(opts.mountpoint);
+	free(ll.device);
+	fuse_service_send_goodbye(ll.service, ret);
+	fuse_service_destroy(&ll.service);
+err_args:
+	fuse_opt_free_args(&args);
+	return fuse_service_exit(ret);
+}
diff --git a/example/service_ll.socket.in b/example/service_ll.socket.in
new file mode 100644
index 00000000000000..c41c382878a0cd
--- /dev/null
+++ b/example/service_ll.socket.in
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (C) 2026 Oracle.  All Rights Reserved.
+# Author: Darrick J. Wong <djwong@kernel.org>
+[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
diff --git a/example/service_ll@.service b/example/service_ll@.service
new file mode 100644
index 00000000000000..016d839babe3cc
--- /dev/null
+++ b/example/service_ll@.service
@@ -0,0 +1,102 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (C) 2026 Oracle.  All Rights Reserved.
+# Author: Darrick J. Wong <djwong@kernel.org>
+[Unit]
+Description=service_ll 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_ll
+
+# 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=-1000
diff --git a/example/single_file.c b/example/single_file.c
new file mode 100644
index 00000000000000..b2dedf59de9552
--- /dev/null
+++ b/example/single_file.c
@@ -0,0 +1,646 @@
+/*
+ * 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.
+ */
+#define _GNU_SOURCE
+#include <pthread.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#ifdef __linux__
+#include <linux/fs.h>
+#include <linux/stat.h>
+#endif
+
+#define FUSE_USE_VERSION (FUSE_MAKE_VERSION(3, 19))
+
+#include "fuse_lowlevel.h"
+#include "fuse_service.h"
+#include "single_file.h"
+
+#define min(x, y) ((x) < (y) ? (x) : (y))
+
+#if __has_attribute(__fallthrough__)
+#define fallthrough __attribute__((__fallthrough__))
+#else
+#define fallthrough do {} while (0)
+#endif
+
+struct dirbuf {
+	char *p;
+	size_t size;
+};
+
+struct single_file_stat {
+	struct fuse_entry_param entry;
+};
+
+#define SINGLE_FILE_INO		(FUSE_ROOT_ID + 1)
+
+static const char *single_file_name = "single_file";
+static bool single_file_name_set;
+
+struct single_file single_file = {
+	.backing_fd = -1,
+	.allow_dio = true,
+	.mode = S_IFREG | 0444,
+	.lock = PTHREAD_MUTEX_INITIALIZER,
+};
+
+static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+		       fuse_ino_t ino)
+{
+	struct stat stbuf;
+	size_t oldsize = b->size;
+
+	b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+	b->p = (char *) realloc(b->p, b->size);
+	memset(&stbuf, 0, sizeof(stbuf));
+	stbuf.st_ino = ino;
+	fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+			  b->size);
+}
+
+int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+		      off_t off, size_t maxsize)
+{
+	if (off < bufsize)
+		return fuse_reply_buf(req, buf + off,
+				      min(bufsize - off, maxsize));
+	else
+		return fuse_reply_buf(req, NULL, 0);
+}
+
+bool is_single_file_child(fuse_ino_t parent, const char *name)
+{
+	return  parent == FUSE_ROOT_ID &&
+		strcmp(name, single_file_name) == 0;
+}
+
+bool is_single_file_ino(fuse_ino_t ino)
+{
+	return ino == SINGLE_FILE_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)
+{
+	struct dirbuf b;
+
+	(void) fi;
+
+	switch (ino) {
+	case FUSE_ROOT_ID:
+		break;
+	case SINGLE_FILE_INO:
+		fuse_reply_err(req, ENOTDIR);
+		return;
+	default:
+		fuse_reply_err(req, ENOENT);
+		return;
+	}
+
+	memset(&b, 0, sizeof(b));
+	dirbuf_add(req, &b, ".", FUSE_ROOT_ID);
+	dirbuf_add(req, &b, "..", FUSE_ROOT_ID);
+	dirbuf_add(req, &b, single_file_name, SINGLE_FILE_INO);
+	reply_buf_limited(req, b.p, b.size, off, size);
+	free(b.p);
+}
+
+static bool sf_stat(fuse_ino_t ino, struct single_file_stat *llstat)
+{
+	struct fuse_entry_param *entry = &llstat->entry;
+	struct stat *stbuf = &entry->attr;
+
+	if (ino == FUSE_ROOT_ID) {
+		stbuf->st_mode = S_IFDIR | 0755;
+		stbuf->st_nlink = 2;
+	} else if (ino == SINGLE_FILE_INO) {
+		stbuf->st_mode = single_file.mode;
+		stbuf->st_nlink = 1;
+		stbuf->st_size = single_file.isize;
+		stbuf->st_blksize = single_file.blocksize;
+		stbuf->st_blocks = howmany(single_file.isize, 512);
+		stbuf->st_atim = single_file.atime;
+		stbuf->st_mtim = single_file.mtime;
+	} else {
+		return false;
+	}
+	stbuf->st_ino = ino;
+
+	entry->generation = ino + 1;
+	entry->attr_timeout = 0.0;
+	entry->entry_timeout = 0.0;
+	entry->ino = ino;
+
+	return true;
+}
+
+#if defined(STATX_BASIC_STATS)
+static inline void sf_set_statx_attr(struct statx *stx,
+				     uint64_t statx_flag, int set)
+{
+	if (set)
+		stx->stx_attributes |= statx_flag;
+	stx->stx_attributes_mask |= statx_flag;
+}
+
+static void sf_statx_directio(struct statx *stx)
+{
+	struct statx devx;
+	int ret;
+
+	ret = statx(single_file.backing_fd, "", AT_EMPTY_PATH, STATX_DIOALIGN,
+		    &devx);
+	if (ret)
+		return;
+	if (!(devx.stx_mask & STATX_DIOALIGN))
+		return;
+
+	stx->stx_mask |= STATX_DIOALIGN;
+	stx->stx_dio_mem_align = devx.stx_dio_mem_align;
+	stx->stx_dio_offset_align = devx.stx_dio_offset_align;
+}
+
+static bool sf_statx(fuse_ino_t ino, int statx_mask, struct statx *stx)
+{
+	(void)statx_mask;
+
+	if (ino == FUSE_ROOT_ID) {
+		stx->stx_mode = S_IFDIR | 0755;
+		stx->stx_nlink = 2;
+	} else if (ino == SINGLE_FILE_INO) {
+		stx->stx_mode = single_file.mode;
+		stx->stx_nlink = 1;
+		stx->stx_size = single_file.isize;
+		stx->stx_blksize = single_file.blocksize;
+		stx->stx_blocks = howmany(single_file.isize, 512);
+		stx->stx_atime.tv_sec = single_file.atime.tv_sec;
+		stx->stx_atime.tv_nsec = single_file.atime.tv_nsec;
+		stx->stx_mtime.tv_sec = single_file.mtime.tv_sec;
+		stx->stx_mtime.tv_nsec = single_file.mtime.tv_nsec;
+	} else {
+		return false;
+	}
+	stx->stx_mask = STATX_BASIC_STATS;
+	stx->stx_ino = ino;
+
+	sf_set_statx_attr(stx, STATX_ATTR_IMMUTABLE, single_file.ro);
+	sf_statx_directio(stx);
+
+	return true;
+}
+
+void single_file_ll_statx(fuse_req_t req, fuse_ino_t ino, int flags, int mask,
+			  struct fuse_file_info *fi)
+{
+	struct statx stx = { };
+	bool filled;
+
+	(void)flags;
+	(void)fi;
+
+	pthread_mutex_lock(&single_file.lock);
+	filled = sf_statx(ino, mask, &stx);
+	pthread_mutex_unlock(&single_file.lock);
+	if (!filled)
+		fuse_reply_err(req, ENOENT);
+	else
+		fuse_reply_statx(req, 0, &stx, 0.0);
+}
+#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);
+}
+#endif /* STATX_BASIC_STATS */
+
+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);
+
+	fuse_reply_statfs(req, &buf);
+}
+
+void single_file_ll_getattr(fuse_req_t req, fuse_ino_t ino,
+			    struct fuse_file_info *fi)
+{
+	struct single_file_stat llstat;
+	bool filled;
+
+	(void) fi;
+
+	memset(&llstat, 0, sizeof(llstat));
+	pthread_mutex_lock(&single_file.lock);
+	filled = sf_stat(ino, &llstat);
+	pthread_mutex_unlock(&single_file.lock);
+	if (!filled)
+		fuse_reply_err(req, ENOENT);
+	else
+		fuse_reply_attr(req, &llstat.entry.attr,
+				llstat.entry.attr_timeout);
+}
+
+static void get_now(struct timespec *now)
+{
+#ifdef CLOCK_REALTIME
+	if (!clock_gettime(CLOCK_REALTIME, now))
+		return;
+#endif
+
+	now->tv_sec = time(NULL);
+	now->tv_nsec = 0;
+}
+
+void single_file_ll_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+			    int to_set, struct fuse_file_info *fi)
+{
+	struct timespec now;
+
+	if (ino != SINGLE_FILE_INO)
+		goto deny;
+	if (to_set & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID |
+		      FUSE_SET_ATTR_SIZE))
+		goto deny;
+
+	get_now(&now);
+
+	pthread_mutex_lock(&single_file.lock);
+	if (to_set & FUSE_SET_ATTR_MODE)
+		single_file.mode = (single_file.mode & S_IFMT) |
+				   (attr->st_mode & ~S_IFMT);
+	if (to_set & FUSE_SET_ATTR_ATIME) {
+		if (to_set & FUSE_SET_ATTR_ATIME_NOW)
+			single_file.atime = now;
+		else
+			single_file.atime = attr->st_atim;
+	}
+	if (to_set & FUSE_SET_ATTR_MTIME) {
+		if (to_set & FUSE_SET_ATTR_MTIME_NOW)
+			single_file.mtime = now;
+		else
+			single_file.mtime = attr->st_mtim;
+	}
+	pthread_mutex_unlock(&single_file.lock);
+
+	single_file_ll_getattr(req, ino, fi);
+	return;
+deny:
+	fuse_reply_err(req, EPERM);
+}
+
+void single_file_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+{
+	struct single_file_stat llstat;
+	bool filled;
+
+	if (!is_single_file_child(parent, name)) {
+		fuse_reply_err(req, ENOENT);
+		return;
+	}
+
+	memset(&llstat, 0, sizeof(llstat));
+	pthread_mutex_lock(&single_file.lock);
+	filled = sf_stat(SINGLE_FILE_INO, &llstat);
+	pthread_mutex_unlock(&single_file.lock);
+	if (!filled)
+		fuse_reply_err(req, ENOENT);
+	else
+		fuse_reply_entry(req, &llstat.entry);
+}
+
+void single_file_ll_open(fuse_req_t req, fuse_ino_t ino,
+			 struct fuse_file_info *fi)
+{
+	if (ino != SINGLE_FILE_INO)
+		fuse_reply_err(req, EISDIR);
+	else if (single_file.ro && (fi->flags & O_ACCMODE) != O_RDONLY)
+		fuse_reply_err(req, EACCES);
+	else
+		fuse_reply_open(req, fi);
+}
+
+void single_file_ll_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+			  struct fuse_file_info *fi)
+{
+	int ret = 0;
+
+	(void)datasync;
+	(void)fi;
+
+	if (ino == SINGLE_FILE_INO) {
+		ret = fsync(single_file.backing_fd);
+		if (ret)
+			ret = errno;
+	}
+
+	fuse_reply_err(req, ret);
+}
+
+unsigned long long parse_num_blocks(const char *arg, int log_block_size)
+{
+	char *p;
+	unsigned long long num;
+
+	num = strtoull(arg, &p, 0);
+
+	if (p[0] && p[1])
+		return 0;
+
+	switch (*p) {
+	case 'T': case 't':
+		num <<= 10;
+		fallthrough;
+	case 'G': case 'g':
+		num <<= 10;
+		fallthrough;
+	case 'M': case 'm':
+		num <<= 10;
+		fallthrough;
+	case 'K': case 'k':
+		if (log_block_size < 0)
+			num <<= 10;
+		else
+			num >>= log_block_size;
+		break;
+	case 's':
+		if (log_block_size < 0)
+			num <<= 9;
+		else
+			num >>= (1+log_block_size);
+		break;
+	case '\0':
+		break;
+	default:
+		return 0;
+	}
+	return num;
+}
+
+static int single_file_set_blocksize(const char *arg)
+{
+	unsigned long long l = parse_num_blocks(arg, -1);
+
+	if (l < 512 || l > INT32_MAX || (l & (l - 1)) != 0) {
+		fprintf(stderr, "%s: block size must be power of two between 512 and 2G.\n",
+			arg);
+		return -1;
+	}
+
+	/* do not pass through to libfuse */
+	single_file.blocksize = l;
+	return 0;
+}
+
+static int single_file_set_size(const char *arg)
+{
+	unsigned long long l = parse_num_blocks(arg, -1);
+
+	if (l < 1 || (l & 511) != 0 || l > INT64_MAX) {
+		fprintf(stderr, "%s: size must be multiple of 512 and larger than zero.\n",
+			arg);
+		return -1;
+	}
+
+	/* do not pass through to libfuse */
+	single_file.isize = l;
+	return 0;
+}
+
+int single_file_opt_proc(void *data, const char *arg, int key,
+			 struct fuse_args *outargs)
+{
+	(void)data;
+	(void)outargs;
+
+	switch (key) {
+	case SINGLE_FILE_RO:
+		/* pass through to libfuse */
+		single_file.ro = true;
+		return 1;
+	case SINGLE_FILE_RW:
+		/* pass through to libfuse */
+		single_file.ro = false;
+		return 1;
+	case SINGLE_FILE_REQUIRE_BDEV:
+		single_file.require_bdev = true;
+		return 0;
+	case SINGLE_FILE_DIO:
+		single_file.allow_dio = true;
+		return 0;
+	case SINGLE_FILE_NODIO:
+		single_file.allow_dio = false;
+		return 0;
+	case SINGLE_FILE_SYNC:
+		single_file.sync = true;
+		return 0;
+	case SINGLE_FILE_NOSYNC:
+		single_file.sync = false;
+		return 0;
+	case SINGLE_FILE_BLOCKSIZE:
+		return single_file_set_blocksize(arg + 10);
+	case SINGLE_FILE_SIZE:
+		return single_file_set_size(arg + 5);
+	}
+
+	return 1;
+}
+
+int single_file_service_open(struct fuse_service *sf, const char *path)
+{
+	int open_flags = single_file.ro ? O_RDONLY : O_RDWR;
+	int fd;
+	int ret;
+
+again:
+	if (single_file.require_bdev)
+		ret = fuse_service_request_blockdev(sf, path,
+						    open_flags | O_EXCL, 0, 0,
+						    single_file.blocksize);
+	else
+		ret = fuse_service_request_file(sf, path, open_flags | O_EXCL,
+						0, 0);
+	if (ret)
+		return ret;
+
+	if (!single_file.ro && open_flags == O_RDONLY)
+		single_file.ro = true;
+
+	ret = fuse_service_receive_file(sf, path, &fd);
+	if (ret)
+		return ret;
+
+	/* downgrade from rw to ro if necessary */
+	if ((fd == -EPERM || fd == -EACCES) && open_flags == O_RDWR) {
+		open_flags = O_RDONLY;
+		goto again;
+	}
+
+	if (fd < 0) {
+		fprintf(stderr, "%s: opening file: %s.\n",
+			path, strerror(-fd));
+		return -1;
+	}
+
+	single_file.backing_fd = fd;
+	return 0;
+}
+
+int single_file_check_write(off_t pos, size_t *count)
+{
+	if (pos >= single_file.isize)
+		return -EFBIG;
+
+	if (*count > single_file.isize)
+		*count = single_file.isize;
+	if (pos >= single_file.isize - *count)
+		*count = single_file.isize - pos;
+
+	return 0;
+}
+
+void single_file_check_read(off_t pos, size_t *count)
+{
+	int ret = single_file_check_write(pos, count);
+
+	if (ret)
+		*count = 0;
+}
+
+int single_file_configure(const char *device, const char *filename)
+{
+	struct stat statbuf;
+	unsigned long long backing_size;
+	unsigned int proposed_blocksize;
+	int lbasize;
+	int ret;
+
+	ret = fstat(single_file.backing_fd, &statbuf);
+	if (ret) {
+		perror(device);
+		return -1;
+	}
+	lbasize = statbuf.st_blksize;
+	backing_size = statbuf.st_size;
+
+	if (S_ISBLK(statbuf.st_mode)) {
+#ifdef BLKSSZGET
+		ret = ioctl(single_file.backing_fd, BLKSSZGET, &lbasize);
+		if (ret) {
+			perror(device);
+			return -1;
+		}
+#endif
+
+#ifdef BLKGETSIZE64
+		ret = ioctl(single_file.backing_fd, BLKGETSIZE64, &backing_size);
+		if (ret) {
+			perror(device);
+			return -1;
+		}
+#endif
+	}
+
+	if (backing_size == 0) {
+		fprintf(stderr, "%s: backing file size zero?\n", device);
+		return -1;
+	}
+
+	if (lbasize == 0) {
+		fprintf(stderr, "%s: blocksize zero?\n", device);
+		return -1;
+	}
+
+	proposed_blocksize = single_file.blocksize ? single_file.blocksize :
+						     sysconf(_SC_PAGESIZE);
+	if (lbasize > proposed_blocksize) {
+		fprintf(stderr, "%s: lba size %u smaller than blocksize %u\n",
+			device, lbasize, proposed_blocksize);
+		return -1;
+	}
+
+	if (single_file.isize % proposed_blocksize > 0) {
+		fprintf(stderr, "%s: size parameter %llu not congruent with blocksize %u\n",
+			device, (unsigned long long)single_file.isize,
+			proposed_blocksize);
+		return -1;
+	}
+
+	if (single_file.isize > backing_size) {
+		fprintf(stderr, "%s: file size %llu smaller than size param %llu\n",
+			device, backing_size,
+			(unsigned long long)single_file.isize);
+		return -1;
+	}
+
+	if (!single_file.blocksize)
+		single_file.blocksize = proposed_blocksize;
+	if (!single_file.isize)
+		single_file.isize = backing_size;
+
+	single_file.isize = round_down(single_file.isize, single_file.blocksize);
+	single_file.blocks = single_file.isize / single_file.blocksize;
+
+	return single_file_configure_simple(filename);
+}
+
+int single_file_configure_simple(const char *filename)
+{
+	if (!single_file.blocksize)
+		single_file.blocksize = sysconf(_SC_PAGESIZE);
+
+	if (filename) {
+		char *n = strdup(filename);
+
+		if (!n) {
+			perror(filename);
+			return -1;
+		}
+
+		if (single_file_name_set)
+			free((void *)single_file_name);
+		single_file_name = n;
+		single_file_name_set = true;
+	}
+
+	return 0;
+}
+
+void single_file_close(void)
+{
+	close(single_file.backing_fd);
+	single_file.backing_fd = -1;
+
+	if (single_file_name_set)
+		free((void *)single_file_name);
+	single_file_name_set = false;
+}
diff --git a/meson.build b/meson.build
index 827ec45ad3ad75..de038df8d92071 100644
--- a/meson.build
+++ b/meson.build
@@ -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)
 


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH 12/13] example/service: create a sample systemd service for a high-level fuse server
  2026-04-09 22:20 [PATCHSET v4] libfuse: run fuse servers as a contained service Darrick J. Wong
                   ` (10 preceding siblings ...)
  2026-04-09 22:23 ` [PATCH 11/13] example/service_ll: create a sample systemd service fuse server Darrick J. Wong
@ 2026-04-09 22:23 ` Darrick J. Wong
  2026-04-09 22:23 ` [PATCH 13/13] nullfs: support fuse systemd service mode Darrick J. Wong
  12 siblings, 0 replies; 24+ messages in thread
From: Darrick J. Wong @ 2026-04-09 22:23 UTC (permalink / raw)
  To: djwong, bschubert; +Cc: miklos, neal, linux-fsdevel, bernd, joannelkoong

From: Darrick J. Wong <djwong@kernel.org>

Create a simple high-level fuse server that can be run as a systemd
service.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 example/single_file.h        |   35 ++++++
 example/meson.build          |    6 +
 example/service_hl.c         |  237 +++++++++++++++++++++++++++++++++++++++++
 example/service_hl.socket.in |   15 +++
 example/service_hl@.service  |  102 ++++++++++++++++++
 example/service_ll.c         |    1 
 example/single_file.c        |  244 +++++++++++++++++++++++++++++++++++++++---
 7 files changed, 623 insertions(+), 17 deletions(-)
 create mode 100644 example/service_hl.c
 create mode 100644 example/service_hl.socket.in
 create mode 100644 example/service_hl@.service


diff --git a/example/single_file.h b/example/single_file.h
index af91ea8196a408..76577f77fa113b 100644
--- a/example/single_file.h
+++ b/example/single_file.h
@@ -120,6 +120,7 @@ void single_file_close(void);
 
 /* 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);
 
@@ -145,5 +146,39 @@ 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_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_ */
diff --git a/example/meson.build b/example/meson.build
index e948f6ba74fdfa..19a383f7cd2c74 100644
--- a/example/meson.build
+++ b/example/meson.build
@@ -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',
diff --git a/example/service_hl.c b/example/service_hl.c
new file mode 100644
index 00000000000000..1e9d16bdee23fd
--- /dev/null
+++ b/example/service_hl.c
@@ -0,0 +1,237 @@
+/*
+ * 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 service_hl@.service:
+ *
+ *     ExecStart=/path/to/service_hl
+ *
+ * to point to the actual path of the service_hl binary.
+ *
+ * Finally, install the service_hl@.service 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 service_hl@.service
+ * \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)
+{
+	ssize_t got;
+
+	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;
+
+	got = pread(single_file.backing_fd, buf, count, pos);
+	if (got < 0)
+		return -errno;
+
+	return got;
+}
+
+static int service_hl_write(const char *path, const char *buf, size_t count,
+			    off_t pos, struct fuse_file_info *fi)
+{
+	ssize_t got;
+	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;
+
+	got = pwrite(single_file.backing_fd, buf, count, pos);
+	if (got < 0)
+		return -errno;
+
+	if (single_file.sync) {
+		ret = fsync(single_file.backing_fd);
+		if (ret < 0)
+			return -errno;
+	}
+
+	return got;
+}
+
+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,
+
+	.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_mode(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);
+}
diff --git a/example/service_hl.socket.in b/example/service_hl.socket.in
new file mode 100644
index 00000000000000..46035d6c315b8d
--- /dev/null
+++ b/example/service_hl.socket.in
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (C) 2026 Oracle.  All Rights Reserved.
+# Author: Darrick J. Wong <djwong@kernel.org>
+[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
diff --git a/example/service_hl@.service b/example/service_hl@.service
new file mode 100644
index 00000000000000..883b9c649cbc90
--- /dev/null
+++ b/example/service_hl@.service
@@ -0,0 +1,102 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (C) 2026 Oracle.  All Rights Reserved.
+# Author: Darrick J. Wong <djwong@kernel.org>
+[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=-1000
diff --git a/example/service_ll.c b/example/service_ll.c
index da1d64f03be5f2..954d13b8b62efc 100644
--- a/example/service_ll.c
+++ b/example/service_ll.c
@@ -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 {
diff --git a/example/single_file.c b/example/single_file.c
index b2dedf59de9552..61e296a757efe1 100644
--- a/example/single_file.c
+++ b/example/single_file.c
@@ -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))
@@ -55,6 +58,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)
 {
@@ -90,6 +110,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)
 {
@@ -116,6 +143,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;
@@ -225,32 +283,44 @@ void single_file_ll_statx(fuse_req_t req, fuse_ino_t ino, int flags, int mask,
 }
 #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)
 {
@@ -270,6 +340,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
@@ -318,6 +410,76 @@ 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;
+
+	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;
+
+	pthread_mutex_lock(&single_file.lock);
+	set_time(&ctv[0], &single_file.atime);
+	set_time(&ctv[1], &single_file.mtime);
+	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;
@@ -349,6 +511,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 -EACCES;
+
+	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)
 {
@@ -366,6 +556,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);
+	int ret = 0;
+
+	(void)datasync;
+
+	if (!ino)
+		return -ENOENT;
+
+	if (ino == SINGLE_FILE_INO) {
+		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;


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH 13/13] nullfs: support fuse systemd service mode
  2026-04-09 22:20 [PATCHSET v4] libfuse: run fuse servers as a contained service Darrick J. Wong
                   ` (11 preceding siblings ...)
  2026-04-09 22:23 ` [PATCH 12/13] example/service: create a sample systemd service for a high-level " Darrick J. Wong
@ 2026-04-09 22:23 ` Darrick J. Wong
  12 siblings, 0 replies; 24+ messages in thread
From: Darrick J. Wong @ 2026-04-09 22:23 UTC (permalink / raw)
  To: djwong, bschubert; +Cc: miklos, neal, linux-fsdevel, bernd, joannelkoong

From: Darrick J. Wong <djwong@kernel.org>

This is the only example fuse server that exports a regular file instead
of a directory tree.  Port it to be usable as a systemd fuse service so
that we can test that capability.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
---
 example/meson.build    |    6 +++
 example/null.c         |   51 +++++++++++++++++++++++-
 example/null.socket.in |   15 +++++++
 example/null@.service  |  102 ++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 171 insertions(+), 3 deletions(-)
 create mode 100644 example/null.socket.in
 create mode 100644 example/null@.service


diff --git a/example/meson.build b/example/meson.build
index 19a383f7cd2c74..45dbf26eb355a7 100644
--- a/example/meson.build
+++ b/example/meson.build
@@ -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 = [ ]
diff --git a/example/null.c b/example/null.c
index ec41def40ed5c5..0122a987655370 100644
--- a/example/null.c
+++ b/example/null.c
@@ -17,15 +17,24 @@
  *
  *     gcc -Wall null.c `pkg-config fuse3 --cflags --libs` -o null
  *
+ * Change the ExecStart line in nullfile@.service:
+ *
+ *     ExecStart=/path/to/null
+ *
+ * to point to the actual path of the null binary.
+ *
+ * Finally, install the null@.service and null.socket files to the
+ * systemd service directory, usually /run/systemd/system.
+ *
  * ## Source code ##
  * \include passthrough_fh.c
  */
 
-
-#define FUSE_USE_VERSION 31
+#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 19)
 
 #include <fuse.h>
 #include <fuse_lowlevel.h>
+#include <fuse_service.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -33,6 +42,8 @@
 #include <time.h>
 #include <errno.h>
 
+static mode_t mode = 0644;
+
 static int null_getattr(const char *path, struct stat *stbuf,
 			struct fuse_file_info *fi)
 {
@@ -41,7 +52,7 @@ static int null_getattr(const char *path, struct stat *stbuf,
 	if(strcmp(path, "/") != 0)
 		return -ENOENT;
 
-	stbuf->st_mode = S_IFREG | 0644;
+	stbuf->st_mode = S_IFREG | mode;
 	stbuf->st_nlink = 1;
 	stbuf->st_uid = getuid();
 	stbuf->st_gid = getgid();
@@ -112,11 +123,45 @@ static const struct fuse_operations null_oper = {
 	.write		= null_write,
 };
 
+static int null_service(struct fuse_service *service, struct fuse_args *args)
+{
+	int ret = 1;
+
+	if (fuse_service_append_args(service, args))
+		goto err_service;
+
+	if (fuse_service_finish_file_requests(service))
+		goto err_service;
+
+	fuse_service_expect_mount_mode(service, S_IFREG);
+
+	/*
+	 * In non-service mode, we set up the file to be owned and writable
+	 * by the same user that starts the fuse server.  When running in a
+	 * container as a dynamic user, we just grant world write access.
+	 */
+	mode = 0666;
+	ret = fuse_service_main(service, args, &null_oper, NULL);
+
+err_service:
+	fuse_service_send_goodbye(service, ret);
+	fuse_service_destroy(&service);
+	fuse_opt_free_args(args);
+	return fuse_service_exit(ret);
+}
+
 int main(int argc, char *argv[])
 {
 	struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
 	struct fuse_cmdline_opts opts;
 	struct stat stbuf;
+	struct fuse_service *service = NULL;
+
+	if (fuse_service_accept(&service) != 0)
+		return 1;
+
+	if (fuse_service_accepted(service))
+		return null_service(service, &args);
 
 	if (fuse_parse_cmdline(&args, &opts) != 0)
 		return 1;
diff --git a/example/null.socket.in b/example/null.socket.in
new file mode 100644
index 00000000000000..865e739561a45e
--- /dev/null
+++ b/example/null.socket.in
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (C) 2026 Oracle.  All Rights Reserved.
+# Author: Darrick J. Wong <djwong@kernel.org>
+[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
diff --git a/example/null@.service b/example/null@.service
new file mode 100644
index 00000000000000..f77fbe927217cf
--- /dev/null
+++ b/example/null@.service
@@ -0,0 +1,102 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (C) 2026 Oracle.  All Rights Reserved.
+# Author: Darrick J. Wong <djwong@kernel.org>
+[Unit]
+Description=null 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/null
+
+# 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=-1000


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* Re: [PATCH 02/13] mount_service: add systemd/inetd socket service mounting helper
  2026-04-09 22:21 ` [PATCH 02/13] mount_service: add systemd/inetd socket service mounting helper Darrick J. Wong
@ 2026-04-14  1:00   ` Darrick J. Wong
  2026-04-14 23:48   ` Darrick J. Wong
  2026-04-17 23:19   ` Darrick J. Wong
  2 siblings, 0 replies; 24+ messages in thread
From: Darrick J. Wong @ 2026-04-14  1:00 UTC (permalink / raw)
  To: bschubert; +Cc: miklos, neal, linux-fsdevel, bernd, joannelkoong

On Thu, Apr 09, 2026 at 03:21:04PM -0700, Darrick J. Wong wrote:
> From: Darrick J. Wong <djwong@kernel.org>
> 
> 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.
> 
> Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
> ---
>  include/fuse_service.h                           |  243 ++++
>  include/fuse_service_priv.h                      |  133 ++
>  lib/mount_common_i.h                             |    3 
>  util/mount_service.h                             |   39 +
>  .github/workflows/install-ubuntu-dependencies.sh |   12 
>  doc/fuservicemount3.8                            |   24 
>  doc/meson.build                                  |    3 
>  include/meson.build                              |    4 
>  lib/fuse_service.c                               | 1099 +++++++++++++++++++
>  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                             | 1304 ++++++++++++++++++++++
>  19 files changed, 3132 insertions(+), 5 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
> 
> 

<snip>

> diff --git a/lib/fuse_service.c b/lib/fuse_service.c
> new file mode 100644
> index 00000000000000..b775727e7c91e2
> --- /dev/null
> +++ b/lib/fuse_service.c

<snip>

> +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),
> +	};
> +	struct iovec iov = {
> +		.iov_base = &hello,
> +		.iov_len = sizeof(hello),
> +	};
> +	struct msghdr msg = {
> +		.msg_iov = &iov,
> +		.msg_iovlen = 1,
> +	};
> +	uint64_t flags;

This is a u32 in the socket protocol.

> +	ssize_t size;
> +
> +	size = recvmsg(sf->sockfd, &msg, MSG_TRUNC);
> +	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);

Need to check for unrecognized bits being set here.

<snip>

> diff --git a/util/mount_service.c b/util/mount_service.c
> new file mode 100644
> index 00000000000000..abe88a0710255b
> --- /dev/null
> +++ b/util/mount_service.c

<snip>

> +static int mount_service_handle_fsopen_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: fsopen command too small\n",
> +			mo->msgtag);
> +		return mount_service_send_reply(mo, EINVAL);
> +	}
> +
> +	if (!check_null_endbyte(p, psz)) {
> +		fprintf(stderr, "%s: fsopen command must be null terminated\n",
> +			mo->msgtag);
> +		return mount_service_send_reply(mo, EINVAL);
> +	}
> +
> +	if (mo->fstype) {
> +		fprintf(stderr, "%s: fstype respecified!\n",
> +			mo->msgtag);
> +		return mount_service_send_reply(mo, EINVAL);
> +	}
> +
> +	mo->fstype = strdup(oc->value);

It occurs to me -- the user that ran fuservicemount already *told* us
what the fuse subtype is going to be; they supplied it via the -t
option.  There's no need to pass the subtype to the fuse server only to
make it pass the subtype back to us.  The only thing that the fuse
server needs to tell us is if this is actually a fuseblk server, since
(among other things) it has to be able to *deal* with weird fuseblk
quirks, such as relying on the kernel to prevent other processes from
writing to the block device.

Therefore, the mount_service can just make a copy of the subtype string
from the cli arguments.  The fuse server can tell us if it wants fuseblk
mode (as a flag, instead of copying strings around!).  For the fsmount()
code path, it can set subtype= to the subtype string copied earlier.
For the classic mount() code, it can format "fuse{,blk}.$subtype" into a
string and pass that in.

<snip>

> +static int mount_service_handle_mount_cmd(struct mount_service *mo,
> +					  struct fuse_service_packet *p,
> +					  size_t psz)
> +{
> +	struct stat stbuf;
> +	struct fuse_service_mount_command *oc =
> +			container_of(p, struct fuse_service_mount_command, p);
> +	int ret;
> +
> +	if (psz != sizeof(struct fuse_service_mount_command)) {
> +		fprintf(stderr, "%s: mount command wrong size\n",
> +			mo->msgtag);
> +		return mount_service_send_reply(mo, EINVAL);
> +	}
> +
> +	if (!mo->fstype) {
> +		fprintf(stderr, "%s: missing mount type parameter\n",
> +			mo->msgtag);
> +		return mount_service_send_reply(mo, EINVAL);
> +	}
> +
> +	if (!mo->source) {
> +		fprintf(stderr, "%s: missing mount source parameter\n",
> +			mo->msgtag);
> +		return mount_service_send_reply(mo, EINVAL);
> +	}
> +
> +	if (!mo->mountpoint) {
> +		fprintf(stderr, "%s: missing mount point parameter\n",
> +			mo->msgtag);
> +		return mount_service_send_reply(mo, EINVAL);
> +	}
> +
> +	/*
> +	 * 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(mo->mountfd, &stbuf);
> +	if (ret < 0) {
> +		int error = errno;
> +
> +		fprintf(stderr, "%s: %s: %s\n",
> +			mo->msgtag, mo->mountpoint, strerror(error));
> +		return mount_service_send_reply(mo, error);
> +	}
> +
> +	/* Make sure the mountpoint type matches what the caller wanted */
> +	switch (ntohs(oc->expected_fmt)) {

Now that the mountpoint command opens the mount point, the expected
format bits of the mountpoint should be passed along with the mountpoint
command and checked earlier.  The permission checks need to stay where
they are, though.

--D

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH 02/13] mount_service: add systemd/inetd socket service mounting helper
  2026-04-09 22:21 ` [PATCH 02/13] mount_service: add systemd/inetd socket service mounting helper Darrick J. Wong
  2026-04-14  1:00   ` Darrick J. Wong
@ 2026-04-14 23:48   ` Darrick J. Wong
  2026-04-17 23:19   ` Darrick J. Wong
  2 siblings, 0 replies; 24+ messages in thread
From: Darrick J. Wong @ 2026-04-14 23:48 UTC (permalink / raw)
  To: bschubert; +Cc: miklos, neal, linux-fsdevel, bernd, joannelkoong

On Thu, Apr 09, 2026 at 03:21:04PM -0700, Darrick J. Wong wrote:
> From: Darrick J. Wong <djwong@kernel.org>
> 
> 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.
> 
> Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
> ---
>  include/fuse_service.h                           |  243 ++++
>  include/fuse_service_priv.h                      |  133 ++
>  lib/mount_common_i.h                             |    3 
>  util/mount_service.h                             |   39 +
>  .github/workflows/install-ubuntu-dependencies.sh |   12 
>  doc/fuservicemount3.8                            |   24 
>  doc/meson.build                                  |    3 
>  include/meson.build                              |    4 
>  lib/fuse_service.c                               | 1099 +++++++++++++++++++
>  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                             | 1304 ++++++++++++++++++++++
>  19 files changed, 3132 insertions(+), 5 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
> 
> 
> diff --git a/include/fuse_service.h b/include/fuse_service.h
> new file mode 100644
> index 00000000000000..4ffd87a7fbe33c
> --- /dev/null
> +++ b/include/fuse_service.h
> @@ -0,0 +1,243 @@
> +/*
> + * FUSE: Filesystem in Userspace
> + * Copyright (C) 2025-2026 Oracle.
> + * Author: Darrick J. Wong <djwong@kernel.org>
> + *
> + * 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_mode(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.
> + *
> + * @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_ */
> diff --git a/include/fuse_service_priv.h b/include/fuse_service_priv.h
> new file mode 100644
> index 00000000000000..8df871ee117875
> --- /dev/null
> +++ b/include/fuse_service_priv.h
> @@ -0,0 +1,133 @@
> +/*
> + * FUSE: Filesystem in Userspace
> + * Copyright (C) 2025-2026 Oracle.
> + * Author: Darrick J. Wong <djwong@kernel.org>
> + *
> + * 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_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;
> +};
> +
> +struct fuse_service_hello_reply {
> +	struct fuse_service_packet p;
> +	uint16_t version;

Other stupid complaints -- this struct is u32-aligned, which means that
the code sending these commands silently allocates two extra bytes past
@version, and sends whatever crap that happens to be to the other end of
the socket.  I'll fix the structs to have explicit padding, and then to
use calloc() instead of malloc() to avoid stale memory disclosures.

--D

> +};
> +
> +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_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_bye_command {
> +	struct fuse_service_packet p;
> +	uint32_t exitcode;
> +};
> +
> +struct fuse_service_mount_command {
> +	struct fuse_service_packet p;
> +	uint32_t ms_flags;
> +	uint16_t expected_fmt;
> +};
> +
> +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_ */
> diff --git a/lib/mount_common_i.h b/lib/mount_common_i.h
> index 6bcb055ff1c23f..631dff3e6f8aaf 100644
> --- a/lib/mount_common_i.h
> +++ b/lib/mount_common_i.h
> @@ -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_ */
> diff --git a/util/mount_service.h b/util/mount_service.h
> new file mode 100644
> index 00000000000000..f1f95e67ee1afe
> --- /dev/null
> +++ b/util/mount_service.h
> @@ -0,0 +1,39 @@
> +/*
> + * FUSE: Filesystem in Userspace
> + * Copyright (C) 2025-2026 Oracle.
> + * Author: Darrick J. Wong <djwong@kernel.org>
> + *
> + * 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.
> + *
> + * @param fstype full fuse filesystem type
> + * @return fuse subtype
> + */
> +const char *mount_service_subtype(const char *fstype);
> +
> +#endif /* MOUNT_SERVICE_H_ */
> diff --git a/.github/workflows/install-ubuntu-dependencies.sh b/.github/workflows/install-ubuntu-dependencies.sh
> index ef44b6a03eb742..e70a891dc7e3a4 100755
> --- a/.github/workflows/install-ubuntu-dependencies.sh
> +++ b/.github/workflows/install-ubuntu-dependencies.sh
> @@ -70,7 +70,9 @@ install_full() {
>          meson \
>          ninja-build \
>          python3 \
> -        python3-pip
> +        python3-pip \
> +        libsystemd-dev \
> +        systemd-dev
>  
>      echo "Installing Python test dependencies..."
>      pip install -r requirements.txt
> @@ -104,7 +106,9 @@ install_abicheck() {
>          meson \
>          ninja-build \
>          python3 \
> -        python3-pip
> +        python3-pip \
> +        libsystemd-dev \
> +        systemd-dev
>  }
>  
>  install_codeql() {
> @@ -115,7 +119,9 @@ install_codeql() {
>          ninja-build \
>          python3-pytest \
>          liburing-dev \
> -        libnuma-dev
> +        libnuma-dev \
> +        libsystemd-dev \
> +        systemd-dev
>  }
>  
>  install_cppcheck() {
> diff --git a/doc/fuservicemount3.8 b/doc/fuservicemount3.8
> new file mode 100644
> index 00000000000000..e45d6a89c8b81a
> --- /dev/null
> +++ b/doc/fuservicemount3.8
> @@ -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 <djwong@kernel.org>.
> +Debian GNU/Linux distribution.
> +.SH SEE ALSO
> +.BR fusermount3 (1)
> +.BR fusermount (1)
> +.BR mount (8)
> +.BR fuse (4)
> diff --git a/doc/meson.build b/doc/meson.build
> index db3e0b26f71975..c105cf3471fdf4 100644
> --- a/doc/meson.build
> +++ b/doc/meson.build
> @@ -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
> diff --git a/include/meson.build b/include/meson.build
> index bf671977a5a6a9..da51180f87eea2 100644
> --- a/include/meson.build
> +++ b/include/meson.build
> @@ -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')
> diff --git a/lib/fuse_service.c b/lib/fuse_service.c
> new file mode 100644
> index 00000000000000..b775727e7c91e2
> --- /dev/null
> +++ b/lib/fuse_service.c
> @@ -0,0 +1,1099 @@
> +/*
> + * FUSE: Filesystem in Userspace
> + * Copyright (C) 2025-2026 Oracle.
> + * Author: Darrick J. Wong <djwong@kernel.org>
> + *
> + * 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(int sockfd, 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(sockfd, &msg, MSG_TRUNC | MSG_CMSG_CLOEXEC);
> +	if (size < 0) {
> +		int error = errno;
> +
> +		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;
> +	}
> +
> +	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 int recv_requested_file(int sockfd, 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(sockfd, 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;
> +}
> +
> +int fuse_service_receive_file(struct fuse_service *sf, const char *path,
> +			      int *fdp)
> +{
> +	return recv_requested_file(sf->sockfd, path, fdp);
> +}
> +
> +#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 iovec iov = {
> +		.iov_len = sizeof_fuse_service_open_command(strlen(path)),
> +	};
> +	struct msghdr msg = {
> +		.msg_iov = &iov,
> +		.msg_iovlen = 1,
> +	};
> +	struct fuse_service_open_command *cmd;
> +	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, iov.iov_len);
> +	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);
> +	iov.iov_base = cmd;
> +
> +	size = sendmsg(sf->sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
> +	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),
> +	};
> +	struct iovec iov = {
> +		.iov_base = &c,
> +		.iov_len = sizeof(c),
> +	};
> +	struct msghdr msg = {
> +		.msg_iov = &iov,
> +		.msg_iovlen = 1,
> +	};
> +	ssize_t size;
> +
> +	/* already gone? */
> +	if (sf->sockfd < 0)
> +		return 0;
> +
> +	size = sendmsg(sf->sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
> +	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 find_socket_fd(int nr_fds)
> +{
> +	struct stat statbuf;
> +	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, &statbuf);
> +	if (ret) {
> +		int error = errno;
> +
> +		fuse_log(FUSE_LOG_ERR, "fuse: service socket: %s\n",
> +			 strerror(error));
> +		return -error;
> +	}
> +
> +	if (!S_ISSOCK(statbuf.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;
> +	}
> +
> +	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),
> +	};
> +	struct iovec iov = {
> +		.iov_base = &hello,
> +		.iov_len = sizeof(hello),
> +	};
> +	struct msghdr msg = {
> +		.msg_iov = &iov,
> +		.msg_iovlen = 1,
> +	};
> +	uint64_t flags;
> +	ssize_t size;
> +
> +	size = recvmsg(sf->sockfd, &msg, MSG_TRUNC);
> +	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_FLAG_ALLOW_OTHER)
> +		sf->allow_other = true;
> +
> +	iov.iov_base = &reply;
> +	iov.iov_len = sizeof(reply);
> +
> +	size = sendmsg(sf->sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
> +	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 = recv_requested_file(sf->sockfd, 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 = recv_requested_file(sf->sockfd, 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_string(struct fuse_service *sf, uint32_t command,
> +		       const char *value, int *error)
> +{
> +	struct fuse_service_simple_reply reply = { };
> +	struct iovec iov = {
> +		.iov_len = sizeof_fuse_service_string_command(strlen(value)),
> +	};
> +	struct msghdr msg = {
> +		.msg_iov = &iov,
> +		.msg_iovlen = 1,
> +	};
> +	struct fuse_service_string_command *cmd;
> +	ssize_t size;
> +
> +	cmd = malloc(iov.iov_len);
> +	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);
> +	iov.iov_base = cmd;
> +
> +	size = sendmsg(sf->sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
> +	if (size < 0) {
> +		int error = errno;
> +
> +		fuse_log(FUSE_LOG_ERR, "fuse: send service string: %s\n",
> +			 strerror(error));
> +		return -error;
> +	}
> +	free(cmd);
> +
> +	iov.iov_base = &reply;
> +	iov.iov_len = sizeof(reply);
> +	size = recvmsg(sf->sockfd, &msg, MSG_TRUNC);
> +	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;
> +	}
> +
> +	*error = ntohl(reply.error);
> +	return 0;
> +}
> +
> +static int send_mount(struct fuse_service *sf, unsigned int ms_flags,
> +		      mode_t expected_fmt, int *error)
> +{
> +	struct fuse_service_simple_reply reply = { };
> +	struct fuse_service_mount_command c = {
> +		.p.magic = htonl(FUSE_SERVICE_MOUNT_CMD),
> +		.ms_flags = htonl(ms_flags),
> +		.expected_fmt = htons(expected_fmt),
> +	};
> +	struct iovec iov = {
> +		.iov_base = &c,
> +		.iov_len = sizeof(c),
> +	};
> +	struct msghdr msg = {
> +		.msg_iov = &iov,
> +		.msg_iovlen = 1,
> +	};
> +	ssize_t size;
> +
> +	size = sendmsg(sf->sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
> +	if (size < 0) {
> +		int error = errno;
> +
> +		fuse_log(FUSE_LOG_ERR, "fuse: send service mount command: %s\n",
> +			 strerror(error));
> +		return -error;
> +	}
> +
> +	iov.iov_base = &reply;
> +	iov.iov_len = sizeof(reply);
> +	size = recvmsg(sf->sockfd, &msg, MSG_TRUNC);
> +	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;
> +	}
> +
> +	*error = ntohl(reply.error);
> +	return 0;
> +}
> +
> +void fuse_service_expect_mount_mode(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;
> +
> +	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 */
> +	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_string(sf, FUSE_SERVICE_FSOPEN_CMD, 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_string(sf, FUSE_SERVICE_MNTPT_CMD, 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), expected_fmt, &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
> +	 */
> +	opts->foreground = 1;
> +
> +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),
> +	};
> +	struct iovec iov = {
> +		.iov_base = &c,
> +		.iov_len = sizeof(c),
> +	};
> +	struct msghdr msg = {
> +		.msg_iov = &iov,
> +		.msg_iovlen = 1,
> +	};
> +	ssize_t size;
> +
> +	/* already gone? */
> +	if (sf->sockfd < 0)
> +		return 0;
> +
> +	size = sendmsg(sf->sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
> +	if (size < 0) {
> +		int error = errno;
> +
> +		fuse_log(FUSE_LOG_ERR, "fuse: send service unmount: %s\n",
> +			 strerror(error));
> +		return -error;
> +	}
> +
> +	iov.iov_base = &reply;
> +	iov.iov_len = sizeof(reply);
> +	size = recvmsg(sf->sockfd, &msg, MSG_TRUNC);
> +	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 = malloc(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;
> +}
> diff --git a/lib/fuse_service_stub.c b/lib/fuse_service_stub.c
> new file mode 100644
> index 00000000000000..4c7e0fabae7343
> --- /dev/null
> +++ b/lib/fuse_service_stub.c
> @@ -0,0 +1,106 @@
> +/*
> + * FUSE: Filesystem in Userspace
> + * Copyright (C) 2025-2026 Oracle.
> + * Author: Darrick J. Wong <djwong@kernel.org>
> + *
> + * 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_mode(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;
> +}
> diff --git a/lib/fuse_versionscript b/lib/fuse_versionscript
> index cce09610316f4b..aa1912c76fb715 100644
> --- a/lib/fuse_versionscript
> +++ b/lib/fuse_versionscript
> @@ -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_mode;
> +		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:
> diff --git a/lib/helper.c b/lib/helper.c
> index 74906fdcbd76d9..819b9a6e4d243c 100644
> --- a/lib/helper.c
> +++ b/lib/helper.c
> @@ -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
>   */
> diff --git a/lib/meson.build b/lib/meson.build
> index fcd95741c9d374..d9a902f74b558f 100644
> --- a/lib/meson.build
> +++ b/lib/meson.build
> @@ -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)
> diff --git a/lib/mount.c b/lib/mount.c
> index 2397c3fb2aa26b..952d8899dcf218 100644
> --- a/lib/mount.c
> +++ b/lib/mount.c
> @@ -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;
> +}
> diff --git a/meson.build b/meson.build
> index 80c5f1dc0bd356..66425a0d4cc16f 100644
> --- a/meson.build
> +++ b/meson.build
> @@ -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
>  #
> diff --git a/meson_options.txt b/meson_options.txt
> index c1f8fe69467184..193a74c96d0676 100644
> --- a/meson_options.txt
> +++ b/meson_options.txt
> @@ -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))')
> diff --git a/util/fuservicemount.c b/util/fuservicemount.c
> new file mode 100644
> index 00000000000000..9c694a4290f94e
> --- /dev/null
> +++ b/util/fuservicemount.c
> @@ -0,0 +1,18 @@
> +/*
> + * FUSE: Filesystem in Userspace
> + * Copyright (C) 2025-2026 Oracle.
> + * Author: Darrick J. Wong <djwong@kernel.org>
> + *
> + * 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);
> +}
> diff --git a/util/meson.build b/util/meson.build
> index 0e4b1cce95377e..04ea5ac201340d 100644
> --- a/util/meson.build
> +++ b/util/meson.build
> @@ -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 ],
> diff --git a/util/mount_service.c b/util/mount_service.c
> new file mode 100644
> index 00000000000000..abe88a0710255b
> --- /dev/null
> +++ b/util/mount_service.c
> @@ -0,0 +1,1304 @@
> +/*
> + * FUSE: Filesystem in Userspace
> + * Copyright (C) 2025-2026 Oracle.
> + * Author: Darrick J. Wong <djwong@kernel.org>
> + *
> + * 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;
> +
> +	/* alleged fuse subtype based on -t cli argument */
> +	const char *subtype;
> +
> +	/* full fuse filesystem type we give to mount() */
> +	char *fstype;
> +
> +	/* 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;
> +};
> +
> +/* Filter out the subtype of the filesystem (e.g. fuse.Y[.Z] -> Y[.Z]) */
> +const char *mount_service_subtype(const char *fstype)
> +{
> +	if (!strncmp(fstype, "fuse.", 5))
> +		return fstype + 5;
> +	if (!strncmp(fstype, "fuseblk.", 8))
> +		return fstype + 8;
> +	return fstype;
> +}
> +
> +static int mount_service_init(struct mount_service *mo, int argc, char *argv[])
> +{
> +	char *fstype = NULL;
> +	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;
> +	}
> +
> +	mo->subtype = mount_service_subtype(fstype);
> +	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 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) {
> +		fprintf(stderr, "%s: opening %s service socket: %s\n",
> +			mo->msgtag, mo->subtype, strerror(errno));
> +		return -1;
> +	}
> +
> +	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) {
> +		fprintf(stderr, "%s: %s: %s\n",
> +			mo->msgtag, name.sun_path, strerror(errno));
> +		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 = { };
> +	struct iovec iov = {
> +		.iov_base = &hello,
> +		.iov_len = sizeof(hello),
> +	};
> +	struct msghdr msg = {
> +		.msg_iov = &iov,
> +		.msg_iovlen = 1,
> +	};
> +	ssize_t size;
> +
> +	if (getuid() == 0)
> +		hello.flags |= htonl(FUSE_SERVICE_FLAG_ALLOW_OTHER);
> +
> +	size = sendmsg(mo->sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
> +	if (size < 0) {
> +		fprintf(stderr, "%s: send hello: %s\n",
> +			mo->msgtag, strerror(errno));
> +		return -1;
> +	}
> +
> +	iov.iov_base = &reply;
> +	iov.iov_len = sizeof(reply);
> +
> +	size = recvmsg(mo->sockfd, &msg, MSG_TRUNC);
> +	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 %zd\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;
> +	}
> +
> +	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 %zd\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 %zd\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 %zd\n",
> +			mo->msgtag, written, sizeof(args));
> +		return -1;
> +	}
> +
> +	return 0;
> +}
> +
> +static ssize_t __send_fd(int sockfd, 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(sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
> +}
> +
> +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 = malloc(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->sockfd, 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 %zd\n",
> +			mo->msgtag, written, req_sz);
> +		ret = -1;
> +		goto out_req;
> +	}
> +
> +out_req:
> +	free(req);
> +	return ret;
> +}
> +
> +static ssize_t __send_packet(int sockfd, void *buf, ssize_t buflen)
> +{
> +	struct iovec iov = {
> +		.iov_base = buf,
> +		.iov_len = buflen,
> +	};
> +	struct msghdr msg = {
> +		.msg_iov = &iov,
> +		.msg_iovlen = 1,
> +	};
> +
> +	return sendmsg(sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
> +}
> +
> +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 = malloc(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->sockfd, 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 %zd\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) {
> +		fprintf(stderr, "%s: %s: %s\n",
> +			mo->msgtag, fusedev, strerror(errno));
> +		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 iovec iov = {
> +	};
> +	struct msghdr msg = {
> +		.msg_iov = &iov,
> +		.msg_iovlen = 1,
> +	};
> +	struct fuse_service_packet *command;
> +	ssize_t size;
> +
> +	size = recvmsg(mo->sockfd, &msg, MSG_PEEK | MSG_TRUNC);
> +	if (size < 0) {
> +		fprintf(stderr, "%s: peek service command: %s\n",
> +			mo->msgtag, strerror(errno));
> +		return -1;
> +	}
> +	if (size == 0) {
> +		/* fuse server probably exited early */
> +		fprintf(stderr, "%s: fuse server exited without saying goodbye!\n",
> +			mo->msgtag);
> +		return -1;
> +	}
> +	if (size < sizeof(struct fuse_service_packet)) {
> +		fprintf(stderr, "%s: wrong command packet size %zd, expected at least %zd\n",
> +			mo->msgtag, size, sizeof(struct fuse_service_packet));
> +		return -1;
> +	}
> +	if (size > 32768) {
> +		fprintf(stderr, "%s: wrong command packet size %zd, expected less than %d\n",
> +			mo->msgtag, size, 32768);
> +		return -1;
> +	}
> +
> +	command = calloc(1, size + 1);
> +	if (!command) {
> +		fprintf(stderr, "%s: alloc service command: %s\n",
> +			mo->msgtag, strerror(errno));
> +		return -1;
> +	}
> +	iov.iov_base = command;
> +	iov.iov_len = size;
> +
> +	size = recvmsg(mo->sockfd, &msg, MSG_TRUNC);
> +	if (size < 0) {
> +		fprintf(stderr, "%s: receive service command: %s\n",
> +			mo->msgtag, strerror(errno));
> +		free(command);
> +		return -1;
> +	}
> +	if (size != iov.iov_len) {
> +		fprintf(stderr, "%s: wrong service command size %zd, expected %zd\n",
> +			mo->msgtag,
> +			size, iov.iov_len);
> +		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),
> +	};
> +	struct iovec iov = {
> +		.iov_base = &reply,
> +		.iov_len = sizeof(reply),
> +	};
> +	struct msghdr msg = {
> +		.msg_iov = &iov,
> +		.msg_iovlen = 1,
> +	};
> +	ssize_t size;
> +
> +	size = sendmsg(mo->sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
> +	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 statbuf;
> +	int ret;
> +
> +	ret = fstat(fd, &statbuf);
> +	if (ret) {
> +		int error = errno;
> +
> +		fprintf(stderr, "%s: %s: %s\n",
> +			mo->msgtag, oc->path, strerror(error));
> +		return -error;
> +	}
> +
> +	if (!S_ISBLK(statbuf.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 inline bool check_null_endbyte(const void *p, size_t psz)
> +{
> +	return *((const char *)p + psz - 1) == 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 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);
> +	}
> +
> +	fd = open(oc->path, ntohl(oc->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 int mount_service_handle_fsopen_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: fsopen command too small\n",
> +			mo->msgtag);
> +		return mount_service_send_reply(mo, EINVAL);
> +	}
> +
> +	if (!check_null_endbyte(p, psz)) {
> +		fprintf(stderr, "%s: fsopen command must be null terminated\n",
> +			mo->msgtag);
> +		return mount_service_send_reply(mo, EINVAL);
> +	}
> +
> +	if (mo->fstype) {
> +		fprintf(stderr, "%s: fstype respecified!\n",
> +			mo->msgtag);
> +		return mount_service_send_reply(mo, EINVAL);
> +	}
> +
> +	mo->fstype = strdup(oc->value);
> +	if (!mo->fstype) {
> +		int error = errno;
> +
> +		fprintf(stderr, "%s: alloc fstype 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_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, char *mntpt)
> +{
> +	struct stat statbuf;
> +	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;
> +	}
> +
> +	ret = fstat(mountfd, &statbuf);
> +	if (ret) {
> +		error = errno;
> +		fprintf(stderr, "%s: %s: %s\n", mo->msgtag, mntpt,
> +			strerror(error));
> +		goto out_mountfd;
> +	}
> +
> +	if (!S_ISDIR(statbuf.st_mode) && !S_ISREG(statbuf.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",
> +			mo->msgtag, mntpt);
> +		goto out_mountfd;
> +	}
> +
> +	switch (statbuf.st_mode & S_IFMT) {
> +	case S_IFREG:
> +		/*
> +		 * This is a regular file, so we point mount() at the open file
> +		 * descriptor.
> +		 */
> +		asprintf(&mo->real_mountpoint, "/dev/fd/%d", mountfd);
> +		break;
> +	case S_IFDIR:
> +		/*
> +		 * Pin the mount so it can't go anywhere.  This only works for
> +		 * directories, which is fortunately the common case.
> +		 */
> +		ret = fchdir(mountfd);
> +		if (ret) {
> +			error = errno;
> +			fprintf(stderr, "%s: %s: %s\n", mo->msgtag, mntpt,
> +				strerror(error));
> +			goto out_res_mntpt;
> +		}
> +
> +		/*
> +		 * Now that we're sitting on the mountpoint directory, we can
> +		 * pass "." to mount() and avoid races with directory tree
> +		 * mutations.
> +		 */
> +		mo->real_mountpoint = strdup(".");
> +		break;
> +	default:
> +		/* Should never get here */
> +		error = EINVAL;
> +		goto out_res_mntpt;
> +	}
> +	if (!mo->real_mountpoint) {
> +		error = ENOMEM;
> +		fprintf(stderr, "%s: %s: %s\n", mo->msgtag, mntpt,
> +			strerror(error));
> +		goto out_res_mntpt;
> +	}
> +
> +	mo->mountpoint = mntpt;
> +	mo->mountfd = mountfd;
> +	mo->resv_mountpoint = res_mntpt;
> +
> +	return mount_service_send_reply(mo, 0);
> +
> +out_res_mntpt:
> +	free(res_mntpt);
> +out_mountfd:
> +	close(mountfd);
> +out_error:
> +	free(mntpt);
> +	return mount_service_send_reply(mo, error);
> +}
> +
> +static int mount_service_handle_mountpoint_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);
> +	char *mntpt;
> +
> +	if (psz < sizeof_fuse_service_string_command(1)) {
> +		fprintf(stderr, "%s: mount point command too small\n",
> +			mo->msgtag);
> +		return mount_service_send_reply(mo, EINVAL);
> +	}
> +
> +	if (!check_null_endbyte(p, psz)) {
> +		fprintf(stderr, "%s: mount point command must be null terminated\n",
> +			mo->msgtag);
> +		return mount_service_send_reply(mo, EINVAL);
> +	}
> +
> +	if (mo->mountpoint) {
> +		fprintf(stderr, "%s: mount point respecified!\n",
> +			mo->msgtag);
> +		return mount_service_send_reply(mo, EINVAL);
> +	}
> +
> +	mntpt = strdup(oc->value);
> +	if (!mntpt) {
> +		int error = errno;
> +
> +		fprintf(stderr, "%s: alloc mount point string: %s\n",
> +			mo->msgtag, strerror(error));
> +		return mount_service_send_reply(mo, error);
> +	}
> +
> +	return attach_to_mountpoint(mo, mntpt);
> +}
> +
> +static inline int format_libfuse_mntopts(char *buf, size_t bufsz,
> +					 const struct mount_service *mo,
> +					 const struct stat *statbuf)
> +{
> +	if (mo->mntopts)
> +		return snprintf(buf, bufsz,
> +				"%s,fd=%i,rootmode=%o,user_id=%u,group_id=%u",
> +				mo->mntopts, mo->fusedevfd,
> +				statbuf->st_mode & S_IFMT,
> +				getuid(), getgid());
> +
> +	return snprintf(buf, bufsz,
> +			"fd=%i,rootmode=%o,user_id=%u,group_id=%u",
> +			mo->fusedevfd, statbuf->st_mode & S_IFMT,
> +			getuid(), getgid());
> +}
> +
> +static int mount_service_regular_mount(struct mount_service *mo,
> +				       struct fuse_service_mount_command *oc,
> +				       struct stat *stbuf)
> +{
> +	char *realmopts;
> +	int ret;
> +
> +	/* Compute the amount of buffer space needed for the mount options */
> +	ret = format_libfuse_mntopts(NULL, 0, mo, stbuf);
> +	if (ret < 0) {
> +		int error = errno;
> +
> +		fprintf(stderr, "%s: mount option preformatting: %s\n",
> +			mo->msgtag, strerror(error));
> +		return mount_service_send_reply(mo, error);
> +	}
> +
> +	realmopts = malloc(ret + 1);
> +	if (!realmopts) {
> +		int error = errno;
> +
> +		fprintf(stderr, "%s: alloc real mount options string: %s\n",
> +			mo->msgtag, strerror(error));
> +		return mount_service_send_reply(mo, error);
> +	}
> +
> +	ret = format_libfuse_mntopts(realmopts, ret + 1, mo, stbuf);
> +	if (ret < 0) {
> +		int error = errno;
> +
> +		free(realmopts);
> +		fprintf(stderr, "%s: mount options formatting: %s\n",
> +			mo->msgtag, strerror(error));
> +		return mount_service_send_reply(mo, error);
> +	}
> +
> +	ret = mount(mo->source, mo->real_mountpoint, mo->fstype,
> +		    ntohl(oc->ms_flags), realmopts);
> +	free(realmopts);
> +	if (ret) {
> +		int error = errno;
> +
> +		fprintf(stderr, "%s: mount: %s\n",
> +			mo->msgtag, strerror(error));
> +		return mount_service_send_reply(mo, error);
> +	}
> +
> +	mo->mounted = true;
> +	return mount_service_send_reply(mo, 0);
> +}
> +
> +static int mount_service_handle_mount_cmd(struct mount_service *mo,
> +					  struct fuse_service_packet *p,
> +					  size_t psz)
> +{
> +	struct stat stbuf;
> +	struct fuse_service_mount_command *oc =
> +			container_of(p, struct fuse_service_mount_command, p);
> +	int ret;
> +
> +	if (psz != sizeof(struct fuse_service_mount_command)) {
> +		fprintf(stderr, "%s: mount command wrong size\n",
> +			mo->msgtag);
> +		return mount_service_send_reply(mo, EINVAL);
> +	}
> +
> +	if (!mo->fstype) {
> +		fprintf(stderr, "%s: missing mount type parameter\n",
> +			mo->msgtag);
> +		return mount_service_send_reply(mo, EINVAL);
> +	}
> +
> +	if (!mo->source) {
> +		fprintf(stderr, "%s: missing mount source parameter\n",
> +			mo->msgtag);
> +		return mount_service_send_reply(mo, EINVAL);
> +	}
> +
> +	if (!mo->mountpoint) {
> +		fprintf(stderr, "%s: missing mount point parameter\n",
> +			mo->msgtag);
> +		return mount_service_send_reply(mo, EINVAL);
> +	}
> +
> +	/*
> +	 * 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(mo->mountfd, &stbuf);
> +	if (ret < 0) {
> +		int error = errno;
> +
> +		fprintf(stderr, "%s: %s: %s\n",
> +			mo->msgtag, mo->mountpoint, strerror(error));
> +		return mount_service_send_reply(mo, error);
> +	}
> +
> +	/* Make sure the mountpoint type matches what the caller wanted */
> +	switch (ntohs(oc->expected_fmt)) {
> +	case S_IFDIR:
> +		if (!S_ISDIR(stbuf.st_mode)) {
> +			fprintf(stderr, "%s: %s: %s\n",
> +				mo->msgtag, mo->mountpoint, strerror(ENOTDIR));
> +			return mount_service_send_reply(mo, ENOTDIR);
> +		}
> +		break;
> +	case S_IFREG:
> +		if (!S_ISREG(stbuf.st_mode)) {
> +			fprintf(stderr, "%s: %s: %s\n",
> +				mo->msgtag, mo->mountpoint, strerror(EISDIR));
> +			return mount_service_send_reply(mo, EISDIR);
> +		}
> +		break;
> +	case 0:
> +		/* don't care */
> +		break;
> +	default:
> +		fprintf(stderr, "%s: %s: weird expected format 0%o\n",
> +			mo->msgtag, mo->mountpoint, ntohs(oc->expected_fmt));
> +		return mount_service_send_reply(mo, EINVAL);
> +	}
> +
> +	return mount_service_regular_mount(mo, oc, &stbuf);
> +}
> +
> +static int mount_service_handle_unmount_cmd(struct mount_service *mo,
> +					    struct fuse_service_packet *p,
> +					    size_t psz)
> +{
> +	int ret;
> +
> +	(void)p;
> +
> +	if (psz != sizeof(struct fuse_service_unmount_command)) {
> +		fprintf(stderr, "%s: unmount command wrong size\n",
> +			mo->msgtag);
> +		return mount_service_send_reply(mo, EINVAL);
> +	}
> +
> +	if (!mo->mounted) {
> +		fprintf(stderr, "%s: will not umount before successful mount!\n",
> +			mo->msgtag);
> +		return mount_service_send_reply(mo, EINVAL);
> +	}
> +
> +	ret = chdir("/");
> +	if (ret) {
> +		int error = errno;
> +
> +		fprintf(stderr, "%s: fuse server failed chdir: %s\n",
> +			mo->msgtag, strerror(error));
> +		return mount_service_send_reply(mo, error);
> +	}
> +
> +	close(mo->mountfd);
> +	mo->mountfd = -1;
> +
> +	/*
> +	 * Try to unmount the resolved mountpoint, and hope that we're not the
> +	 * victim of a race.
> +	 */
> +	ret = umount2(mo->resv_mountpoint, MNT_DETACH);
> +	if (ret) {
> +		int error = errno;
> +
> +		fprintf(stderr, "%s: fuse server failed unmount: %s\n",
> +			mo->msgtag, strerror(error));
> +		return mount_service_send_reply(mo, error);
> +	}
> +
> +	mo->mounted = false;
> +	return mount_service_send_reply(mo, 0);
> +}
> +
> +static int mount_service_handle_bye_cmd(struct mount_service *mo,
> +					struct fuse_service_packet *p,
> +					size_t psz)
> +{
> +	struct fuse_service_bye_command *bc =
> +			container_of(p, struct fuse_service_bye_command, p);
> +	int ret;
> +
> +	if (psz != sizeof(struct fuse_service_bye_command)) {
> +		fprintf(stderr, "%s: bye command wrong size\n",
> +			mo->msgtag);
> +		return mount_service_send_reply(mo, EINVAL);
> +	}
> +
> +	ret = ntohl(bc->exitcode);
> +	if (ret)
> +		fprintf(stderr, "%s: fuse server failed mount, check dmesg/logs for details.\n",
> +			mo->msgtag);
> +
> +	return ret;
> +}
> +
> +static void mount_service_destroy(struct mount_service *mo)
> +{
> +	close(mo->mountfd);
> +	close(mo->fusedevfd);
> +	close(mo->argvfd);
> +	shutdown(mo->sockfd, SHUT_RDWR);
> +	close(mo->sockfd);
> +
> +	free(mo->source);
> +	free(mo->mountpoint);
> +	free(mo->real_mountpoint);
> +	free(mo->resv_mountpoint);
> +	free(mo->mntopts);
> +	free(mo->fstype);
> +
> +	memset(mo, 0, sizeof(*mo));
> +	mo->sockfd = -1;
> +	mo->argvfd = -1;
> +	mo->fusedevfd = -1;
> +	mo->mountfd = -1;
> +}
> +
> +int mount_service_main(int argc, char *argv[])
> +{
> +	const char *fusedev = fuse_mnt_get_devname();
> +	struct mount_service mo = { };
> +	bool running = true;
> +	int ret;
> +
> +	if (argc < 3 || !strcmp(argv[1], "--help")) {
> +		printf("Usage: %s source mountpoint -t type [-o options]\n",
> +				argv[0]);
> +		return EXIT_FAILURE;
> +	}
> +
> +	if (argc > 0 && argv[0])
> +		mo.msgtag = argv[0];
> +	else
> +		mo.msgtag = "mount.service";
> +
> +	ret = mount_service_init(&mo, argc, argv);
> +	if (ret)
> +		return EXIT_FAILURE;
> +
> +	ret = mount_service_connect(&mo);
> +	if (ret == MOUNT_SERVICE_FALLBACK_NEEDED)
> +		goto out;
> +	if (ret) {
> +		ret = EXIT_FAILURE;
> +		goto out;
> +	}
> +
> +	ret = mount_service_send_hello(&mo);
> +	if (ret) {
> +		ret = EXIT_FAILURE;
> +		goto out;
> +	}
> +
> +	ret = mount_service_capture_args(&mo, argc, argv);
> +	if (ret) {
> +		ret = EXIT_FAILURE;
> +		goto out;
> +	}
> +
> +	ret = mount_service_send_required_files(&mo, fusedev);
> +	if (ret) {
> +		ret = EXIT_FAILURE;
> +		goto out;
> +	}
> +
> +	while (running) {
> +		struct fuse_service_packet *p = NULL;
> +		size_t sz;
> +
> +		ret = mount_service_receive_command(&mo, &p, &sz);
> +		if (ret) {
> +			ret = EXIT_FAILURE;
> +			goto out;
> +		}
> +
> +		switch (ntohl(p->magic)) {
> +		case FUSE_SERVICE_OPEN_CMD:
> +			ret = mount_service_handle_open_cmd(&mo, p, sz);
> +			break;
> +		case FUSE_SERVICE_OPEN_BDEV_CMD:
> +			ret = mount_service_handle_open_bdev_cmd(&mo, p, sz);
> +			break;
> +		case FUSE_SERVICE_FSOPEN_CMD:
> +			ret = mount_service_handle_fsopen_cmd(&mo, p, sz);
> +			break;
> +		case FUSE_SERVICE_SOURCE_CMD:
> +			ret = mount_service_handle_source_cmd(&mo, p, sz);
> +			break;
> +		case FUSE_SERVICE_MNTOPTS_CMD:
> +			ret = mount_service_handle_mntopts_cmd(&mo, p, sz);
> +			break;
> +		case FUSE_SERVICE_MNTPT_CMD:
> +			ret = mount_service_handle_mountpoint_cmd(&mo, p, sz);
> +			break;
> +		case FUSE_SERVICE_MOUNT_CMD:
> +			ret = mount_service_handle_mount_cmd(&mo, p, sz);
> +			break;
> +		case FUSE_SERVICE_UNMOUNT_CMD:
> +			ret = mount_service_handle_unmount_cmd(&mo, p, sz);
> +			break;
> +		case FUSE_SERVICE_BYE_CMD:
> +			ret = mount_service_handle_bye_cmd(&mo, p, sz);
> +			free(p);
> +			goto out;
> +		default:
> +			fprintf(stderr, "%s: unrecognized packet 0x%x\n",
> +				mo.msgtag, ntohl(p->magic));
> +			ret = EXIT_FAILURE;
> +			break;
> +		}
> +		free(p);
> +
> +		if (ret) {
> +			ret = EXIT_FAILURE;
> +			goto out;
> +		}
> +	}
> +
> +	ret = EXIT_SUCCESS;
> +out:
> +	mount_service_destroy(&mo);
> +	return ret;
> +}
> 
> 

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH 08/13] mount_service: enable unprivileged users in the same manner as fusermount
  2026-04-09 22:22 ` [PATCH 08/13] mount_service: enable unprivileged users in the same manner as fusermount Darrick J. Wong
@ 2026-04-14 23:53   ` Darrick J. Wong
  2026-04-17 22:01     ` Darrick J. Wong
  0 siblings, 1 reply; 24+ messages in thread
From: Darrick J. Wong @ 2026-04-14 23:53 UTC (permalink / raw)
  To: bschubert; +Cc: miklos, neal, linux-fsdevel, bernd, joannelkoong

On Thu, Apr 09, 2026 at 03:22:38PM -0700, Darrick J. Wong wrote:
> From: Darrick J. Wong <djwong@kernel.org>
> 
> 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.
> 
> Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
> ---
>  util/mount_service.c |  150 +++++++++++++++++++++++++++++++++++++++++++++++++-
>  1 file changed, 147 insertions(+), 3 deletions(-)
> 
> 
> diff --git a/util/mount_service.c b/util/mount_service.c
> index 8e9c721a56fd2a..0f7eae94ada377 100644
> --- a/util/mount_service.c
> +++ b/util/mount_service.c
> @@ -33,6 +33,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 */
> @@ -166,7 +167,9 @@ static int mount_service_connect(struct mount_service *mo)
>  		return -1;
>  	}
>  
> +	drop_privs();
>  	ret = connect(sockfd, (const struct sockaddr *)&name, sizeof(name));
> +	restore_privs();
>  	if (ret && (errno == ENOENT || errno == ECONNREFUSED)) {
>  		fprintf(stderr, "%s: no safe filesystem driver for %s available.\n",
>  			mo->msgtag, mo->subtype);
> @@ -208,7 +211,7 @@ static int mount_service_send_hello(struct mount_service *mo)
>  	};
>  	ssize_t size;
>  
> -	if (getuid() == 0)
> +	if (getuid() == 0 || user_allow_other)
>  		hello.flags |= htonl(FUSE_SERVICE_FLAG_ALLOW_OTHER);
>  
>  	size = sendmsg(mo->sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
> @@ -490,7 +493,9 @@ static int mount_service_send_required_files(struct mount_service *mo,
>  {
>  	int ret;
>  
> +	drop_privs();
>  	mo->fusedevfd = open(fusedev, O_RDWR | O_CLOEXEC);
> +	restore_privs();
>  	if (mo->fusedevfd < 0) {
>  		fprintf(stderr, "%s: %s: %s\n",
>  			mo->msgtag, fusedev, strerror(errno));
> @@ -628,7 +633,9 @@ 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);
> +		restore_privs();
>  		if (ret) {
>  			int error = errno;
>  
> @@ -675,7 +682,9 @@ static int mount_service_open_path(struct mount_service *mo,
>  		return mount_service_send_file_error(mo, EINVAL, oc->path);
>  	}
>  
> -	fd = open(oc->path, ntohl(oc->open_flags), ntohl(oc->create_mode));
> +	drop_privs();
> +	fd = open(oc->path, ntohl(oc->open_flags) | O_CLOEXEC, ntohl(oc->create_mode));
> +	restore_privs();
>  	if (fd < 0) {
>  		int error = errno;
>  
> @@ -908,6 +917,20 @@ 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);
> +		}
> +		if (!strcmp(tok, "blkdev") && getuid() != 0) {

blkdev is an internal mount option that libfuse never sends to the
kernel, so there's no need to check it here either.  Sending "blkdev" to
the kernel will just cause mount() EINVAL failures.


> +			fprintf(stderr, "%s: option blkdev is privileged\n",
> +				mo->msgtag);
> +			return mount_service_send_reply(mo, EPERM);
> +		}
> +
>  #ifdef HAVE_NEW_MOUNT_API
>  		if (mo->fsopenfd >= 0) {
>  			int ret;
> @@ -985,10 +1008,16 @@ static int attach_to_mountpoint(struct mount_service *mo, char *mntpt)
>  	int error;
>  	int ret;
>  
> +	drop_privs();
> +
>  	/*
>  	 * Open the alleged mountpoint, make sure it's a dir or a file.
> +	 * For unprivileged callers, we only allow mounting on paths that the
> +	 * user can write to.
>  	 */
> -	mountfd = open(mntpt, O_RDONLY | O_CLOEXEC);
> +	mountfd = open(mntpt, (getuid() == 0 ? O_RDONLY : O_WRONLY) | O_CLOEXEC);
> +	if (mountfd < 0 && errno == EISDIR)
> +		mountfd = open(mntpt, O_RDONLY | O_CLOEXEC);

This mess!

This might add a new requirement that the unprivileged caller has to
have read access to the mountpoint.  This isn't explicitly checked in
fusermount, but I'm pretty sure that the lstat in fusermount will fail
with EACCESS.

That said, if we're unprivileged and fall back to opening readonly, then
we should require that the fd point to a directory.  Note that the
access check just prior to mount()/fsmount() will confirm that the
unprivileged caller could (at least in theory) create a new child entry.

--D

>  	if (mountfd < 0) {
>  		error = errno;
>  		fprintf(stderr, "%s: %s: %s\n", mo->msgtag, mntpt,
> @@ -1067,6 +1096,7 @@ static int attach_to_mountpoint(struct mount_service *mo, char *mntpt)
>  	mo->mountfd = mountfd;
>  	mo->resv_mountpoint = res_mntpt;
>  
> +	restore_privs();
>  	return mount_service_send_reply(mo, 0);
>  
>  out_res_mntpt:
> @@ -1075,6 +1105,7 @@ static int attach_to_mountpoint(struct mount_service *mo, char *mntpt)
>  	close(mountfd);
>  out_error:
>  	free(mntpt);
> +	restore_privs();
>  	return mount_service_send_reply(mo, error);
>  }
>  
> @@ -1399,6 +1430,77 @@ 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 int mount_service_handle_mount_cmd(struct mount_service *mo,
>  					  struct fuse_service_packet *p,
>  					  size_t psz)
> @@ -1471,6 +1573,44 @@ static int mount_service_handle_mount_cmd(struct mount_service *mo,
>  		return mount_service_send_reply(mo, EINVAL);
>  	}
>  
> +	/*
> +	 * 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.
> +	 */
> +	if (getuid() != 0) {
> +		struct statfs fs_buf;
> +
> +		ret = check_nonroot_mount_count(mo->msgtag);
> +		if (ret)
> +			return mount_service_send_reply(mo, 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 mount_service_send_reply(mo, error);
> +		}
> +
> +		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();
> +
> +		if (ret)
> +			return mount_service_send_reply(mo, EPERM);
> +	}
> +
>  	if (mo->fsopenfd >= 0) {
>  		ret = mount_service_fsopen_mount(mo, oc, &stbuf);
>  		if (ret != FUSE_MOUNT_FALLBACK_NEEDED)
> @@ -1601,6 +1741,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;
> 
> 

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH 11/13] example/service_ll: create a sample systemd service fuse server
  2026-04-09 22:23 ` [PATCH 11/13] example/service_ll: create a sample systemd service fuse server Darrick J. Wong
@ 2026-04-14 23:56   ` Darrick J. Wong
  2026-04-17 21:56   ` Darrick J. Wong
  1 sibling, 0 replies; 24+ messages in thread
From: Darrick J. Wong @ 2026-04-14 23:56 UTC (permalink / raw)
  To: bschubert; +Cc: miklos, neal, linux-fsdevel, bernd, joannelkoong

On Thu, Apr 09, 2026 at 03:23:24PM -0700, Darrick J. Wong wrote:
> From: Darrick J. Wong <djwong@kernel.org>
> 
> Create a simple fuse server that can be run as a systemd service.
> I plan to create some more single-file fuse server examples, so most of
> the boilerplate code goes in a separate file.
> 
> Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
> ---
>  example/single_file.h        |  149 ++++++++++
>  lib/util.h                   |   35 ++
>  example/meson.build          |   14 +
>  example/service_ll.c         |  308 ++++++++++++++++++++
>  example/service_ll.socket.in |   15 +
>  example/service_ll@.service  |  102 +++++++
>  example/single_file.c        |  646 ++++++++++++++++++++++++++++++++++++++++++
>  meson.build                  |    1 
>  8 files changed, 1270 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/service_ll@.service
>  create mode 100644 example/single_file.c
> 
> 
> diff --git a/example/single_file.h b/example/single_file.h
> new file mode 100644
> index 00000000000000..af91ea8196a408
> --- /dev/null
> +++ b/example/single_file.h
> @@ -0,0 +1,149 @@
> +/*
> + * 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;
> +
> +	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);
> +
> +/* 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_ */
> diff --git a/lib/util.h b/lib/util.h
> index 107a2bfdd6105b..6ec6604fb74caf 100644
> --- a/lib/util.h
> +++ b/lib/util.h
> @@ -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_ */
> diff --git a/example/meson.build b/example/meson.build
> index 76cf2d96db0349..e948f6ba74fdfa 100644
> --- a/example/meson.build
> +++ b/example/meson.build
> @@ -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',
> diff --git a/example/service_ll.c b/example/service_ll.c
> new file mode 100644
> index 00000000000000..da1d64f03be5f2
> --- /dev/null
> +++ b/example/service_ll.c
> @@ -0,0 +1,308 @@
> +/*
> + * 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 low-level API and systemd service api
> + *
> + * 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 service_ll@.service:
> + *
> + *     ExecStart=/path/to/service_ll
> + *
> + * to point to the actual path of the service_ll binary.
> + *
> + * Finally, install the service_ll@.service and service_ll.socket files to the
> + * systemd service directory, usually /run/systemd/system.
> + *
> + * ## Source code ##
> + * \include service_ll.c
> + * \include service_ll.socket
> + * \include service_ll@.service
> + * \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_lowlevel.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 "single_file.h"
> +
> +struct service_ll {
> +	struct fuse_session *se;
> +	char *device;
> +	struct fuse_service *service;
> +
> +	/* really booleans */
> +	int debug;
> +};
> +
> +static struct service_ll ll = { };
> +
> +static void service_ll_init(void *userdata, struct fuse_conn_info *conn)
> +{
> +	(void)userdata;
> +
> +	conn->time_gran = 1;
> +}
> +
> +static void service_ll_read(fuse_req_t req, fuse_ino_t ino, size_t count,
> +			    off_t pos, struct fuse_file_info *fi)
> +{
> +	void *buf = NULL;
> +	ssize_t got;
> +	int ret;
> +
> +	if (!is_single_file_ino(ino)) {
> +		ret = EIO;
> +		goto out_reply;
> +	}
> +
> +	if (ll.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) {
> +		ret = ENOSYS;
> +		goto out_reply;
> +	}
> +
> +	single_file_check_read(pos, &count);
> +
> +	if (!count) {
> +		fuse_reply_buf(req, buf, 0);
> +		return;
> +	}
> +
> +	buf = malloc(count);
> +	if (!buf) {
> +		ret = ENOMEM;
> +		goto out_reply;
> +	}
> +
> +	got = pread(single_file.backing_fd, buf, count, pos);
> +	if (got < 0) {
> +		ret = errno;
> +		goto out_reply;
> +	}
> +
> +	fuse_reply_buf(req, buf, got);
> +	goto out_buf;
> +
> +out_reply:
> +	fuse_reply_err(req, ret);
> +out_buf:
> +	free(buf);
> +}
> +
> +static void service_ll_write(fuse_req_t req, fuse_ino_t ino, const char *buf,
> +			     size_t count, off_t pos,
> +			     struct fuse_file_info *fi)
> +{
> +	ssize_t got;
> +	int ret;
> +
> +	if (!is_single_file_ino(ino)) {
> +		ret = EIO;
> +		goto out_reply;
> +	}
> +
> +	if (ll.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) {
> +		ret = ENOSYS;
> +		goto out_reply;
> +	}
> +
> +	ret = -single_file_check_write(pos, &count);
> +	if (ret)
> +		goto out_reply;
> +
> +	got = pwrite(single_file.backing_fd, buf, count, pos);
> +	if (got < 0) {
> +		ret = errno;
> +		goto out_reply;
> +	}
> +
> +	if (single_file.sync) {
> +		ret = fsync(single_file.backing_fd);
> +		if (ret < 0) {
> +			ret = errno;
> +			goto out_reply;
> +		}
> +	}
> +
> +	fuse_reply_write(req, got);
> +	return;
> +
> +out_reply:
> +	fuse_reply_err(req, ret);
> +}
> +
> +static const struct fuse_lowlevel_ops service_ll_oper = {
> +	.lookup		= single_file_ll_lookup,
> +	.getattr	= single_file_ll_getattr,
> +	.setattr	= single_file_ll_setattr,
> +	.readdir	= single_file_ll_readdir,
> +	.open		= single_file_ll_open,
> +	.statfs		= single_file_ll_statfs,
> +	.statx		= single_file_ll_statx,
> +	.fsync		= single_file_ll_fsync,
> +
> +	.init		= service_ll_init,
> +	.read		= service_ll_read,
> +	.write		= service_ll_write,
> +};
> +
> +#define SERVICE_LL_OPT(t, p, v) { t, offsetof(struct service_ll, p), v }
> +
> +static struct fuse_opt service_ll_opts[] = {
> +	SERVICE_LL_OPT("debug",		debug,			1),
> +	SINGLE_FILE_OPT_KEYS,
> +	FUSE_OPT_END
> +};
> +
> +static int service_ll_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 (!ll.device) {
> +			ll.device = strdup(arg);
> +			return 0;
> +		}
> +		return 1;
> +	}
> +
> +	return 1;
> +}
> +
> +int main(int argc, char *argv[])
> +{
> +	struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
> +	struct fuse_cmdline_opts opts = { };
> +	int ret = 1;
> +
> +	if (fuse_service_accept(&ll.service))
> +		goto err_args;
> +
> +	if (!fuse_service_accepted(ll.service))
> +		goto err_args;
> +
> +	if (fuse_service_append_args(ll.service, &args))
> +		goto err_service;
> +
> +	if (fuse_opt_parse(&args, &ll, service_ll_opts, service_ll_opt_proc))
> +		goto err_service;
> +
> +	if (fuse_service_parse_cmdline_opts(&args, &opts))
> +		goto err_service;
> +
> +	if (opts.show_help) {
> +		printf("usage: %s [options] <device> <mountpoint>\n\n", argv[0]);
> +		fuse_cmdline_help();
> +		fuse_lowlevel_help();
> +		ret = 0;
> +		goto err_service;
> +	} else if (opts.show_version) {
> +		printf("FUSE library version %s\n", fuse_pkgversion());
> +		fuse_lowlevel_version();
> +		ret = 0;
> +		goto err_service;
> +	}
> +
> +	if (!opts.mountpoint || !ll.device) {
> +		printf("usage: %s [options] <device> <mountpoint>\n", argv[0]);
> +		printf("       %s --help\n", argv[0]);
> +		goto err_service;
> +	}
> +
> +	if (single_file_service_open(ll.service, ll.device))
> +		goto err_service;
> +
> +	if (fuse_service_finish_file_requests(ll.service))
> +		goto err_singlefile;
> +
> +	if (single_file_configure(ll.device, NULL))
> +		goto err_singlefile;
> +
> +	ll.se = fuse_session_new(&args, &service_ll_oper,
> +				 sizeof(service_ll_oper), NULL);
> +	if (ll.se == NULL)
> +		goto err_singlefile;
> +
> +	if (fuse_set_signal_handlers(ll.se))
> +		goto err_session;
> +
> +	if (fuse_service_session_mount(ll.service, ll.se, S_IFDIR, &opts))
> +		goto err_signals;
> +
> +	fuse_service_send_goodbye(ll.service, 0);
> +	fuse_service_release(ll.service);
> +
> +	fuse_daemonize(opts.foreground);

This is unnecessary -- fuse services are already daemons, being systemd
services.  fuse_service_session_mount should just do the chdir and leave
everything alone; it already sets opts->foreground = 1.

> +
> +	/* Block until ctrl+c or fusermount -u */
> +	if (opts.singlethread) {
> +		ret = fuse_session_loop(ll.se);
> +	} else {
> +		struct fuse_loop_config *config = fuse_loop_cfg_create();

NULL pointer check needed here.

> +
> +		fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
> +		fuse_loop_cfg_set_max_threads(config, opts.max_threads);
> +		ret = fuse_session_loop_mt(ll.se, config);
> +		fuse_loop_cfg_destroy(config);
> +	}
> +
> +	fuse_session_unmount(ll.se);

You can't unmount from within a service if you've already sent the
goodby packet, so the fallible calls (e.g.  fuse_loop_cfg_create) should
happen before fuse_service_session_mount.

Also if a service is going to unmount, it should be calling
fuse_service_session_unmount(ll.service).

--D

> +err_signals:
> +	fuse_remove_signal_handlers(ll.se);
> +err_session:
> +	fuse_session_destroy(ll.se);
> +err_singlefile:
> +	single_file_close();
> +err_service:
> +	free(opts.mountpoint);
> +	free(ll.device);
> +	fuse_service_send_goodbye(ll.service, ret);
> +	fuse_service_destroy(&ll.service);
> +err_args:
> +	fuse_opt_free_args(&args);
> +	return fuse_service_exit(ret);
> +}
> diff --git a/example/service_ll.socket.in b/example/service_ll.socket.in
> new file mode 100644
> index 00000000000000..c41c382878a0cd
> --- /dev/null
> +++ b/example/service_ll.socket.in
> @@ -0,0 +1,15 @@
> +# SPDX-License-Identifier: GPL-2.0-or-later
> +#
> +# Copyright (C) 2026 Oracle.  All Rights Reserved.
> +# Author: Darrick J. Wong <djwong@kernel.org>
> +[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
> diff --git a/example/service_ll@.service b/example/service_ll@.service
> new file mode 100644
> index 00000000000000..016d839babe3cc
> --- /dev/null
> +++ b/example/service_ll@.service
> @@ -0,0 +1,102 @@
> +# SPDX-License-Identifier: GPL-2.0-or-later
> +#
> +# Copyright (C) 2026 Oracle.  All Rights Reserved.
> +# Author: Darrick J. Wong <djwong@kernel.org>
> +[Unit]
> +Description=service_ll 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_ll
> +
> +# 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=-1000
> diff --git a/example/single_file.c b/example/single_file.c
> new file mode 100644
> index 00000000000000..b2dedf59de9552
> --- /dev/null
> +++ b/example/single_file.c
> @@ -0,0 +1,646 @@
> +/*
> + * 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.
> + */
> +#define _GNU_SOURCE
> +#include <pthread.h>
> +#include <errno.h>
> +#include <stdlib.h>
> +#include <errno.h>
> +#include <stdio.h>
> +#include <string.h>
> +#include <unistd.h>
> +#include <sys/ioctl.h>
> +#include <sys/stat.h>
> +#ifdef __linux__
> +#include <linux/fs.h>
> +#include <linux/stat.h>
> +#endif
> +
> +#define FUSE_USE_VERSION (FUSE_MAKE_VERSION(3, 19))
> +
> +#include "fuse_lowlevel.h"
> +#include "fuse_service.h"
> +#include "single_file.h"
> +
> +#define min(x, y) ((x) < (y) ? (x) : (y))
> +
> +#if __has_attribute(__fallthrough__)
> +#define fallthrough __attribute__((__fallthrough__))
> +#else
> +#define fallthrough do {} while (0)
> +#endif
> +
> +struct dirbuf {
> +	char *p;
> +	size_t size;
> +};
> +
> +struct single_file_stat {
> +	struct fuse_entry_param entry;
> +};
> +
> +#define SINGLE_FILE_INO		(FUSE_ROOT_ID + 1)
> +
> +static const char *single_file_name = "single_file";
> +static bool single_file_name_set;
> +
> +struct single_file single_file = {
> +	.backing_fd = -1,
> +	.allow_dio = true,
> +	.mode = S_IFREG | 0444,
> +	.lock = PTHREAD_MUTEX_INITIALIZER,
> +};
> +
> +static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
> +		       fuse_ino_t ino)
> +{
> +	struct stat stbuf;
> +	size_t oldsize = b->size;
> +
> +	b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
> +	b->p = (char *) realloc(b->p, b->size);
> +	memset(&stbuf, 0, sizeof(stbuf));
> +	stbuf.st_ino = ino;
> +	fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
> +			  b->size);
> +}
> +
> +int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
> +		      off_t off, size_t maxsize)
> +{
> +	if (off < bufsize)
> +		return fuse_reply_buf(req, buf + off,
> +				      min(bufsize - off, maxsize));
> +	else
> +		return fuse_reply_buf(req, NULL, 0);
> +}
> +
> +bool is_single_file_child(fuse_ino_t parent, const char *name)
> +{
> +	return  parent == FUSE_ROOT_ID &&
> +		strcmp(name, single_file_name) == 0;
> +}
> +
> +bool is_single_file_ino(fuse_ino_t ino)
> +{
> +	return ino == SINGLE_FILE_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)
> +{
> +	struct dirbuf b;
> +
> +	(void) fi;
> +
> +	switch (ino) {
> +	case FUSE_ROOT_ID:
> +		break;
> +	case SINGLE_FILE_INO:
> +		fuse_reply_err(req, ENOTDIR);
> +		return;
> +	default:
> +		fuse_reply_err(req, ENOENT);
> +		return;
> +	}
> +
> +	memset(&b, 0, sizeof(b));
> +	dirbuf_add(req, &b, ".", FUSE_ROOT_ID);
> +	dirbuf_add(req, &b, "..", FUSE_ROOT_ID);
> +	dirbuf_add(req, &b, single_file_name, SINGLE_FILE_INO);
> +	reply_buf_limited(req, b.p, b.size, off, size);
> +	free(b.p);
> +}
> +
> +static bool sf_stat(fuse_ino_t ino, struct single_file_stat *llstat)
> +{
> +	struct fuse_entry_param *entry = &llstat->entry;
> +	struct stat *stbuf = &entry->attr;
> +
> +	if (ino == FUSE_ROOT_ID) {
> +		stbuf->st_mode = S_IFDIR | 0755;
> +		stbuf->st_nlink = 2;
> +	} else if (ino == SINGLE_FILE_INO) {
> +		stbuf->st_mode = single_file.mode;
> +		stbuf->st_nlink = 1;
> +		stbuf->st_size = single_file.isize;
> +		stbuf->st_blksize = single_file.blocksize;
> +		stbuf->st_blocks = howmany(single_file.isize, 512);
> +		stbuf->st_atim = single_file.atime;
> +		stbuf->st_mtim = single_file.mtime;
> +	} else {
> +		return false;
> +	}
> +	stbuf->st_ino = ino;
> +
> +	entry->generation = ino + 1;
> +	entry->attr_timeout = 0.0;
> +	entry->entry_timeout = 0.0;
> +	entry->ino = ino;
> +
> +	return true;
> +}
> +
> +#if defined(STATX_BASIC_STATS)
> +static inline void sf_set_statx_attr(struct statx *stx,
> +				     uint64_t statx_flag, int set)
> +{
> +	if (set)
> +		stx->stx_attributes |= statx_flag;
> +	stx->stx_attributes_mask |= statx_flag;
> +}
> +
> +static void sf_statx_directio(struct statx *stx)
> +{
> +	struct statx devx;
> +	int ret;
> +
> +	ret = statx(single_file.backing_fd, "", AT_EMPTY_PATH, STATX_DIOALIGN,
> +		    &devx);
> +	if (ret)
> +		return;
> +	if (!(devx.stx_mask & STATX_DIOALIGN))
> +		return;
> +
> +	stx->stx_mask |= STATX_DIOALIGN;
> +	stx->stx_dio_mem_align = devx.stx_dio_mem_align;
> +	stx->stx_dio_offset_align = devx.stx_dio_offset_align;
> +}
> +
> +static bool sf_statx(fuse_ino_t ino, int statx_mask, struct statx *stx)
> +{
> +	(void)statx_mask;
> +
> +	if (ino == FUSE_ROOT_ID) {
> +		stx->stx_mode = S_IFDIR | 0755;
> +		stx->stx_nlink = 2;
> +	} else if (ino == SINGLE_FILE_INO) {
> +		stx->stx_mode = single_file.mode;
> +		stx->stx_nlink = 1;
> +		stx->stx_size = single_file.isize;
> +		stx->stx_blksize = single_file.blocksize;
> +		stx->stx_blocks = howmany(single_file.isize, 512);
> +		stx->stx_atime.tv_sec = single_file.atime.tv_sec;
> +		stx->stx_atime.tv_nsec = single_file.atime.tv_nsec;
> +		stx->stx_mtime.tv_sec = single_file.mtime.tv_sec;
> +		stx->stx_mtime.tv_nsec = single_file.mtime.tv_nsec;
> +	} else {
> +		return false;
> +	}
> +	stx->stx_mask = STATX_BASIC_STATS;
> +	stx->stx_ino = ino;
> +
> +	sf_set_statx_attr(stx, STATX_ATTR_IMMUTABLE, single_file.ro);
> +	sf_statx_directio(stx);
> +
> +	return true;
> +}
> +
> +void single_file_ll_statx(fuse_req_t req, fuse_ino_t ino, int flags, int mask,
> +			  struct fuse_file_info *fi)
> +{
> +	struct statx stx = { };
> +	bool filled;
> +
> +	(void)flags;
> +	(void)fi;
> +
> +	pthread_mutex_lock(&single_file.lock);
> +	filled = sf_statx(ino, mask, &stx);
> +	pthread_mutex_unlock(&single_file.lock);
> +	if (!filled)
> +		fuse_reply_err(req, ENOENT);
> +	else
> +		fuse_reply_statx(req, 0, &stx, 0.0);
> +}
> +#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);
> +}
> +#endif /* STATX_BASIC_STATS */
> +
> +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);
> +
> +	fuse_reply_statfs(req, &buf);
> +}
> +
> +void single_file_ll_getattr(fuse_req_t req, fuse_ino_t ino,
> +			    struct fuse_file_info *fi)
> +{
> +	struct single_file_stat llstat;
> +	bool filled;
> +
> +	(void) fi;
> +
> +	memset(&llstat, 0, sizeof(llstat));
> +	pthread_mutex_lock(&single_file.lock);
> +	filled = sf_stat(ino, &llstat);
> +	pthread_mutex_unlock(&single_file.lock);
> +	if (!filled)
> +		fuse_reply_err(req, ENOENT);
> +	else
> +		fuse_reply_attr(req, &llstat.entry.attr,
> +				llstat.entry.attr_timeout);
> +}
> +
> +static void get_now(struct timespec *now)
> +{
> +#ifdef CLOCK_REALTIME
> +	if (!clock_gettime(CLOCK_REALTIME, now))
> +		return;
> +#endif
> +
> +	now->tv_sec = time(NULL);
> +	now->tv_nsec = 0;
> +}
> +
> +void single_file_ll_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
> +			    int to_set, struct fuse_file_info *fi)
> +{
> +	struct timespec now;
> +
> +	if (ino != SINGLE_FILE_INO)
> +		goto deny;
> +	if (to_set & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID |
> +		      FUSE_SET_ATTR_SIZE))
> +		goto deny;
> +
> +	get_now(&now);
> +
> +	pthread_mutex_lock(&single_file.lock);
> +	if (to_set & FUSE_SET_ATTR_MODE)
> +		single_file.mode = (single_file.mode & S_IFMT) |
> +				   (attr->st_mode & ~S_IFMT);
> +	if (to_set & FUSE_SET_ATTR_ATIME) {
> +		if (to_set & FUSE_SET_ATTR_ATIME_NOW)
> +			single_file.atime = now;
> +		else
> +			single_file.atime = attr->st_atim;
> +	}
> +	if (to_set & FUSE_SET_ATTR_MTIME) {
> +		if (to_set & FUSE_SET_ATTR_MTIME_NOW)
> +			single_file.mtime = now;
> +		else
> +			single_file.mtime = attr->st_mtim;
> +	}
> +	pthread_mutex_unlock(&single_file.lock);
> +
> +	single_file_ll_getattr(req, ino, fi);
> +	return;
> +deny:
> +	fuse_reply_err(req, EPERM);
> +}
> +
> +void single_file_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
> +{
> +	struct single_file_stat llstat;
> +	bool filled;
> +
> +	if (!is_single_file_child(parent, name)) {
> +		fuse_reply_err(req, ENOENT);
> +		return;
> +	}
> +
> +	memset(&llstat, 0, sizeof(llstat));
> +	pthread_mutex_lock(&single_file.lock);
> +	filled = sf_stat(SINGLE_FILE_INO, &llstat);
> +	pthread_mutex_unlock(&single_file.lock);
> +	if (!filled)
> +		fuse_reply_err(req, ENOENT);
> +	else
> +		fuse_reply_entry(req, &llstat.entry);
> +}
> +
> +void single_file_ll_open(fuse_req_t req, fuse_ino_t ino,
> +			 struct fuse_file_info *fi)
> +{
> +	if (ino != SINGLE_FILE_INO)
> +		fuse_reply_err(req, EISDIR);
> +	else if (single_file.ro && (fi->flags & O_ACCMODE) != O_RDONLY)
> +		fuse_reply_err(req, EACCES);
> +	else
> +		fuse_reply_open(req, fi);
> +}
> +
> +void single_file_ll_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
> +			  struct fuse_file_info *fi)
> +{
> +	int ret = 0;
> +
> +	(void)datasync;
> +	(void)fi;
> +
> +	if (ino == SINGLE_FILE_INO) {
> +		ret = fsync(single_file.backing_fd);
> +		if (ret)
> +			ret = errno;
> +	}
> +
> +	fuse_reply_err(req, ret);
> +}
> +
> +unsigned long long parse_num_blocks(const char *arg, int log_block_size)
> +{
> +	char *p;
> +	unsigned long long num;
> +
> +	num = strtoull(arg, &p, 0);
> +
> +	if (p[0] && p[1])
> +		return 0;
> +
> +	switch (*p) {
> +	case 'T': case 't':
> +		num <<= 10;
> +		fallthrough;
> +	case 'G': case 'g':
> +		num <<= 10;
> +		fallthrough;
> +	case 'M': case 'm':
> +		num <<= 10;
> +		fallthrough;
> +	case 'K': case 'k':
> +		if (log_block_size < 0)
> +			num <<= 10;
> +		else
> +			num >>= log_block_size;
> +		break;
> +	case 's':
> +		if (log_block_size < 0)
> +			num <<= 9;
> +		else
> +			num >>= (1+log_block_size);
> +		break;
> +	case '\0':
> +		break;
> +	default:
> +		return 0;
> +	}
> +	return num;
> +}
> +
> +static int single_file_set_blocksize(const char *arg)
> +{
> +	unsigned long long l = parse_num_blocks(arg, -1);
> +
> +	if (l < 512 || l > INT32_MAX || (l & (l - 1)) != 0) {
> +		fprintf(stderr, "%s: block size must be power of two between 512 and 2G.\n",
> +			arg);
> +		return -1;
> +	}
> +
> +	/* do not pass through to libfuse */
> +	single_file.blocksize = l;
> +	return 0;
> +}
> +
> +static int single_file_set_size(const char *arg)
> +{
> +	unsigned long long l = parse_num_blocks(arg, -1);
> +
> +	if (l < 1 || (l & 511) != 0 || l > INT64_MAX) {
> +		fprintf(stderr, "%s: size must be multiple of 512 and larger than zero.\n",
> +			arg);
> +		return -1;
> +	}
> +
> +	/* do not pass through to libfuse */
> +	single_file.isize = l;
> +	return 0;
> +}
> +
> +int single_file_opt_proc(void *data, const char *arg, int key,
> +			 struct fuse_args *outargs)
> +{
> +	(void)data;
> +	(void)outargs;
> +
> +	switch (key) {
> +	case SINGLE_FILE_RO:
> +		/* pass through to libfuse */
> +		single_file.ro = true;
> +		return 1;
> +	case SINGLE_FILE_RW:
> +		/* pass through to libfuse */
> +		single_file.ro = false;
> +		return 1;
> +	case SINGLE_FILE_REQUIRE_BDEV:
> +		single_file.require_bdev = true;
> +		return 0;
> +	case SINGLE_FILE_DIO:
> +		single_file.allow_dio = true;
> +		return 0;
> +	case SINGLE_FILE_NODIO:
> +		single_file.allow_dio = false;
> +		return 0;
> +	case SINGLE_FILE_SYNC:
> +		single_file.sync = true;
> +		return 0;
> +	case SINGLE_FILE_NOSYNC:
> +		single_file.sync = false;
> +		return 0;
> +	case SINGLE_FILE_BLOCKSIZE:
> +		return single_file_set_blocksize(arg + 10);
> +	case SINGLE_FILE_SIZE:
> +		return single_file_set_size(arg + 5);
> +	}
> +
> +	return 1;
> +}
> +
> +int single_file_service_open(struct fuse_service *sf, const char *path)
> +{
> +	int open_flags = single_file.ro ? O_RDONLY : O_RDWR;
> +	int fd;
> +	int ret;
> +
> +again:
> +	if (single_file.require_bdev)
> +		ret = fuse_service_request_blockdev(sf, path,
> +						    open_flags | O_EXCL, 0, 0,
> +						    single_file.blocksize);
> +	else
> +		ret = fuse_service_request_file(sf, path, open_flags | O_EXCL,
> +						0, 0);
> +	if (ret)
> +		return ret;
> +
> +	if (!single_file.ro && open_flags == O_RDONLY)
> +		single_file.ro = true;
> +
> +	ret = fuse_service_receive_file(sf, path, &fd);
> +	if (ret)
> +		return ret;
> +
> +	/* downgrade from rw to ro if necessary */
> +	if ((fd == -EPERM || fd == -EACCES) && open_flags == O_RDWR) {
> +		open_flags = O_RDONLY;
> +		goto again;
> +	}
> +
> +	if (fd < 0) {
> +		fprintf(stderr, "%s: opening file: %s.\n",
> +			path, strerror(-fd));
> +		return -1;
> +	}
> +
> +	single_file.backing_fd = fd;
> +	return 0;
> +}
> +
> +int single_file_check_write(off_t pos, size_t *count)
> +{
> +	if (pos >= single_file.isize)
> +		return -EFBIG;
> +
> +	if (*count > single_file.isize)
> +		*count = single_file.isize;
> +	if (pos >= single_file.isize - *count)
> +		*count = single_file.isize - pos;
> +
> +	return 0;
> +}
> +
> +void single_file_check_read(off_t pos, size_t *count)
> +{
> +	int ret = single_file_check_write(pos, count);
> +
> +	if (ret)
> +		*count = 0;
> +}
> +
> +int single_file_configure(const char *device, const char *filename)
> +{
> +	struct stat statbuf;
> +	unsigned long long backing_size;
> +	unsigned int proposed_blocksize;
> +	int lbasize;
> +	int ret;
> +
> +	ret = fstat(single_file.backing_fd, &statbuf);
> +	if (ret) {
> +		perror(device);
> +		return -1;
> +	}
> +	lbasize = statbuf.st_blksize;
> +	backing_size = statbuf.st_size;
> +
> +	if (S_ISBLK(statbuf.st_mode)) {
> +#ifdef BLKSSZGET
> +		ret = ioctl(single_file.backing_fd, BLKSSZGET, &lbasize);
> +		if (ret) {
> +			perror(device);
> +			return -1;
> +		}
> +#endif
> +
> +#ifdef BLKGETSIZE64
> +		ret = ioctl(single_file.backing_fd, BLKGETSIZE64, &backing_size);
> +		if (ret) {
> +			perror(device);
> +			return -1;
> +		}
> +#endif
> +	}
> +
> +	if (backing_size == 0) {
> +		fprintf(stderr, "%s: backing file size zero?\n", device);
> +		return -1;
> +	}
> +
> +	if (lbasize == 0) {
> +		fprintf(stderr, "%s: blocksize zero?\n", device);
> +		return -1;
> +	}
> +
> +	proposed_blocksize = single_file.blocksize ? single_file.blocksize :
> +						     sysconf(_SC_PAGESIZE);
> +	if (lbasize > proposed_blocksize) {
> +		fprintf(stderr, "%s: lba size %u smaller than blocksize %u\n",
> +			device, lbasize, proposed_blocksize);
> +		return -1;
> +	}
> +
> +	if (single_file.isize % proposed_blocksize > 0) {
> +		fprintf(stderr, "%s: size parameter %llu not congruent with blocksize %u\n",
> +			device, (unsigned long long)single_file.isize,
> +			proposed_blocksize);
> +		return -1;
> +	}
> +
> +	if (single_file.isize > backing_size) {
> +		fprintf(stderr, "%s: file size %llu smaller than size param %llu\n",
> +			device, backing_size,
> +			(unsigned long long)single_file.isize);
> +		return -1;
> +	}
> +
> +	if (!single_file.blocksize)
> +		single_file.blocksize = proposed_blocksize;
> +	if (!single_file.isize)
> +		single_file.isize = backing_size;
> +
> +	single_file.isize = round_down(single_file.isize, single_file.blocksize);
> +	single_file.blocks = single_file.isize / single_file.blocksize;
> +
> +	return single_file_configure_simple(filename);
> +}
> +
> +int single_file_configure_simple(const char *filename)
> +{
> +	if (!single_file.blocksize)
> +		single_file.blocksize = sysconf(_SC_PAGESIZE);
> +
> +	if (filename) {
> +		char *n = strdup(filename);
> +
> +		if (!n) {
> +			perror(filename);
> +			return -1;
> +		}
> +
> +		if (single_file_name_set)
> +			free((void *)single_file_name);
> +		single_file_name = n;
> +		single_file_name_set = true;
> +	}
> +
> +	return 0;
> +}
> +
> +void single_file_close(void)
> +{
> +	close(single_file.backing_fd);
> +	single_file.backing_fd = -1;
> +
> +	if (single_file_name_set)
> +		free((void *)single_file_name);
> +	single_file_name_set = false;
> +}
> diff --git a/meson.build b/meson.build
> index 827ec45ad3ad75..de038df8d92071 100644
> --- a/meson.build
> +++ b/meson.build
> @@ -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)
>  
> 
> 

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH 03/13] mount_service: create high level fuse helpers
  2026-04-09 22:21 ` [PATCH 03/13] mount_service: create high level fuse helpers Darrick J. Wong
@ 2026-04-14 23:58   ` Darrick J. Wong
  0 siblings, 0 replies; 24+ messages in thread
From: Darrick J. Wong @ 2026-04-14 23:58 UTC (permalink / raw)
  To: bschubert; +Cc: miklos, neal, linux-fsdevel, bernd, joannelkoong

On Thu, Apr 09, 2026 at 03:21:20PM -0700, Darrick J. Wong wrote:
> From: Darrick J. Wong <djwong@kernel.org>
> 
> Create a fuse_main wrapper for fuse services.
> 
> Signed-off-by: Darrick J. Wong <djwong@kernel.org>
> ---
>  include/fuse.h         |   34 +++++++++++++
>  lib/fuse_versionscript |    1 
>  lib/helper.c           |  125 ++++++++++++++++++++++++++++++++++++++++++++++--
>  3 files changed, 156 insertions(+), 4 deletions(-)
> 
> 
> diff --git a/include/fuse.h b/include/fuse.h
> index 2bc3a9650c7c8b..129c744e39c46a 100644
> --- a/include/fuse.h
> +++ b/include/fuse.h
> @@ -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					       *
>   * ----------------------------------------------------------- */
> diff --git a/lib/fuse_versionscript b/lib/fuse_versionscript
> index aa1912c76fb715..fc37931b475bdf 100644
> --- a/lib/fuse_versionscript
> +++ b/lib/fuse_versionscript
> @@ -236,6 +236,7 @@ FUSE_3.19 {
>  		fuse_service_exit;
>  		fuse_service_expect_mount_mode;
>  		fuse_service_finish_file_requests;
> +		fuse_service_main_real_versioned;
>  		fuse_service_parse_cmdline_opts;
>  		fuse_service_receive_file;
>  		fuse_service_release;
> diff --git a/lib/helper.c b/lib/helper.c
> index 819b9a6e4d243c..a2b3cc617bca54 100644
> --- a/lib/helper.c
> +++ b/lib/helper.c
> @@ -15,6 +15,7 @@
>  #include "fuse_misc.h"
>  #include "fuse_opt.h"
>  #include "fuse_lowlevel.h"
> +#include "fuse_service.h"
>  #include "mount_util.h"
>  
>  #include <stdio.h>
> @@ -365,6 +366,126 @@ int fuse_daemonize(int foreground)
>  	return 0;
>  }
>  
> +struct fuse *_fuse_new_31(struct fuse_args *args,
> +		       const struct fuse_operations *op, size_t op_size,
> +		       struct libfuse_version *version,
> +		       void *user_data);
> +
> +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)
> +{
> +	struct fuse *fuse;
> +	struct fuse_cmdline_opts opts;
> +	int res;
> +	struct fuse_loop_config *loop_config = NULL;
> +
> +	if (fuse_service_parse_cmdline_opts(args, &opts) != 0) {
> +		res = 1;
> +		goto out0;
> +	}
> +
> +	if (opts.show_version) {
> +		printf("FUSE library version %s\n", PACKAGE_VERSION);
> +		fuse_lowlevel_version();
> +		res = 0;
> +		goto out1;
> +	}
> +
> +	if (opts.show_help) {
> +		if (args->argv[0][0] != '\0')
> +			printf("usage: %s [options] <mountpoint>\n\n",
> +			       args->argv[0]);
> +		printf("FUSE options:\n");
> +		fuse_cmdline_help();
> +		fuse_lib_help(args);
> +		res = 0;
> +		goto out1;
> +	}
> +
> +	if (!opts.show_help &&
> +	    !opts.mountpoint) {
> +		fuse_log(FUSE_LOG_ERR, "error: no mountpoint specified\n");
> +		res = 2;
> +		goto out1;
> +	}
> +
> +	fuse = _fuse_new_31(args, op, op_size, version, user_data);
> +	if (fuse == NULL) {
> +		res = 3;
> +		goto out1;
> +	}
> +
> +	if (fuse_service_session_mount(service, fuse_get_session(fuse),

Just use @se below.

> +				       0, &opts) != 0) {
> +		res = 4;
> +		goto out2;
> +	}
> +
> +	/* No need to fork when running as a systemd service */
> +	if (fuse_daemonize(1) != 0) {

I'll change fuse_service_session_mount to do the chdir("/") call and
then we can skip this call entirely.

> +		res = 5;
> +		goto out3;
> +	}
> +
> +	struct fuse_session *se = fuse_get_session(fuse);
> +
> +	if (fuse_set_signal_handlers(se) != 0) {
> +		res = 6;
> +		goto out3;
> +	}
> +
> +	if (opts.singlethread) {
> +		fuse_service_send_goodbye(service, 0);
> +		fuse_service_release(service);
> +
> +		res = fuse_loop(fuse);
> +	} else {
> +		loop_config = fuse_loop_cfg_create();
> +		if (loop_config == NULL) {

Put this fallible call before fuse_service_session_mount and then we
don't have to call fuse_service_session_unmount to back this out.

> +			res = 7;
> +			goto out4;
> +		}
> +
> +		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, opts.max_threads);
> +
> +		fuse_service_send_goodbye(service, 0);
> +		fuse_service_release(service);
> +
> +		res = fuse_loop_mt(fuse, loop_config);
> +	}
> +	if (res)
> +		res = 8;
> +
> +	/*
> +	 * We've released the mount service helper, so we can't ask it to
> +	 * unmount the filesystem.  In other words, once mount() succeeds,
> +	 * the user has to unmount the filesystem.
> +	 */
> +	fuse_remove_signal_handlers(se);
> +	goto out2;
> +
> +out4:
> +	fuse_remove_signal_handlers(se);

Not needing the fuse_service_session_unmount call makes this cleanup
code become much simpler.

--D

> +out3:
> +	fuse_service_session_unmount(service);
> +out2:
> +	fuse_destroy(fuse);
> +out1:
> +	fuse_loop_cfg_destroy(loop_config);
> +	free(opts.mountpoint);
> +out0:
> +	fuse_service_send_goodbye(service, res);
> +	fuse_service_release(service);
> +	return res;
> +}
> +
>  int fuse_main_real_versioned(int argc, char *argv[],
>  			     const struct fuse_operations *op, size_t op_size,
>  			     struct libfuse_version *version, void *user_data)
> @@ -403,10 +524,6 @@ int fuse_main_real_versioned(int argc, char *argv[],
>  		goto out1;
>  	}
>  
> -	struct fuse *_fuse_new_31(struct fuse_args *args,
> -			       const struct fuse_operations *op, size_t op_size,
> -			       struct libfuse_version *version,
> -			       void *user_data);
>  	fuse = _fuse_new_31(&args, op, op_size, version, user_data);
>  	if (fuse == NULL) {
>  		res = 3;
> 
> 

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH 11/13] example/service_ll: create a sample systemd service fuse server
  2026-04-09 22:23 ` [PATCH 11/13] example/service_ll: create a sample systemd service fuse server Darrick J. Wong
  2026-04-14 23:56   ` Darrick J. Wong
@ 2026-04-17 21:56   ` Darrick J. Wong
  1 sibling, 0 replies; 24+ messages in thread
From: Darrick J. Wong @ 2026-04-17 21:56 UTC (permalink / raw)
  To: bschubert; +Cc: miklos, neal, linux-fsdevel, bernd, joannelkoong

On Thu, Apr 09, 2026 at 03:23:24PM -0700, Darrick J. Wong wrote:
> From: Darrick J. Wong <djwong@kernel.org>
> 
> Create a simple fuse server that can be run as a systemd service.
> I plan to create some more single-file fuse server examples, so most of
> the boilerplate code goes in a separate file.
> 
> Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
> ---
>  example/single_file.h        |  149 ++++++++++
>  lib/util.h                   |   35 ++
>  example/meson.build          |   14 +
>  example/service_ll.c         |  308 ++++++++++++++++++++
>  example/service_ll.socket.in |   15 +
>  example/service_ll@.service  |  102 +++++++
>  example/single_file.c        |  646 ++++++++++++++++++++++++++++++++++++++++++
>  meson.build                  |    1 
>  8 files changed, 1270 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/service_ll@.service
>  create mode 100644 example/single_file.c
> 
> 
> diff --git a/example/single_file.h b/example/single_file.h
> new file mode 100644
> index 00000000000000..af91ea8196a408
> --- /dev/null
> +++ b/example/single_file.h
> @@ -0,0 +1,149 @@
> +/*
> + * 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;
> +
> +	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);
> +
> +/* 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_ */
> diff --git a/lib/util.h b/lib/util.h
> index 107a2bfdd6105b..6ec6604fb74caf 100644
> --- a/lib/util.h
> +++ b/lib/util.h
> @@ -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_ */
> diff --git a/example/meson.build b/example/meson.build
> index 76cf2d96db0349..e948f6ba74fdfa 100644
> --- a/example/meson.build
> +++ b/example/meson.build
> @@ -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',
> diff --git a/example/service_ll.c b/example/service_ll.c
> new file mode 100644
> index 00000000000000..da1d64f03be5f2
> --- /dev/null
> +++ b/example/service_ll.c
> @@ -0,0 +1,308 @@
> +/*
> + * 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 low-level API and systemd service api
> + *
> + * 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 service_ll@.service:
> + *
> + *     ExecStart=/path/to/service_ll
> + *
> + * to point to the actual path of the service_ll binary.
> + *
> + * Finally, install the service_ll@.service and service_ll.socket files to the
> + * systemd service directory, usually /run/systemd/system.
> + *
> + * ## Source code ##
> + * \include service_ll.c
> + * \include service_ll.socket
> + * \include service_ll@.service
> + * \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_lowlevel.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 "single_file.h"
> +
> +struct service_ll {
> +	struct fuse_session *se;
> +	char *device;
> +	struct fuse_service *service;
> +
> +	/* really booleans */
> +	int debug;
> +};
> +
> +static struct service_ll ll = { };
> +
> +static void service_ll_init(void *userdata, struct fuse_conn_info *conn)
> +{
> +	(void)userdata;
> +
> +	conn->time_gran = 1;
> +}
> +
> +static void service_ll_read(fuse_req_t req, fuse_ino_t ino, size_t count,
> +			    off_t pos, struct fuse_file_info *fi)
> +{
> +	void *buf = NULL;
> +	ssize_t got;
> +	int ret;
> +
> +	if (!is_single_file_ino(ino)) {
> +		ret = EIO;
> +		goto out_reply;
> +	}
> +
> +	if (ll.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) {
> +		ret = ENOSYS;
> +		goto out_reply;
> +	}
> +
> +	single_file_check_read(pos, &count);
> +
> +	if (!count) {
> +		fuse_reply_buf(req, buf, 0);
> +		return;
> +	}
> +
> +	buf = malloc(count);
> +	if (!buf) {
> +		ret = ENOMEM;
> +		goto out_reply;
> +	}
> +
> +	got = pread(single_file.backing_fd, buf, count, pos);
> +	if (got < 0) {
> +		ret = errno;
> +		goto out_reply;
> +	}
> +
> +	fuse_reply_buf(req, buf, got);
> +	goto out_buf;
> +
> +out_reply:
> +	fuse_reply_err(req, ret);
> +out_buf:
> +	free(buf);
> +}
> +
> +static void service_ll_write(fuse_req_t req, fuse_ino_t ino, const char *buf,
> +			     size_t count, off_t pos,
> +			     struct fuse_file_info *fi)
> +{
> +	ssize_t got;
> +	int ret;
> +
> +	if (!is_single_file_ino(ino)) {
> +		ret = EIO;
> +		goto out_reply;
> +	}
> +
> +	if (ll.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) {
> +		ret = ENOSYS;
> +		goto out_reply;
> +	}
> +
> +	ret = -single_file_check_write(pos, &count);
> +	if (ret)
> +		goto out_reply;
> +
> +	got = pwrite(single_file.backing_fd, buf, count, pos);
> +	if (got < 0) {
> +		ret = errno;
> +		goto out_reply;
> +	}
> +
> +	if (single_file.sync) {
> +		ret = fsync(single_file.backing_fd);
> +		if (ret < 0) {
> +			ret = errno;
> +			goto out_reply;
> +		}
> +	}

Technically speaking, pread and pwrite can return partial results, which
means that they should be run in a loop until @count==0.  The resulting
similar code in this patch and the next one could be refactored into
helpers:


ssize_t single_file_pwrite(const void *buf, size_t count, off_t pos)
{
	ssize_t processed = 0;
	ssize_t got;

	while ((got = pwrite(single_file.backing_fd, buf, count, pos)) > 0) {
		processed += got;
		pos += got;
		buf += got;
		count -= got;
	}

	if (processed > 0) {
		if (single_file.sync) {
			int ret = fsync(single_file.backing_fd);
			if (ret < 0)
				return -errno;
		}

		return processed;
	}

	if (got < 0)
		return -errno;
	return 0;
}

ssize_t single_file_pread(void *buf, size_t count, off_t pos)
{
	ssize_t processed = 0;
	ssize_t got;

	while ((got = pread(single_file.backing_fd, buf, count, pos)) > 0) {
		processed += got;
		pos += got;
		buf += got;
		count -= got;
	}

	if (processed)
		return processed;
	if (got < 0)
		return -errno;
	return 0;
}

--D

> +
> +	fuse_reply_write(req, got);
> +	return;
> +
> +out_reply:
> +	fuse_reply_err(req, ret);
> +}
> +
> +static const struct fuse_lowlevel_ops service_ll_oper = {
> +	.lookup		= single_file_ll_lookup,
> +	.getattr	= single_file_ll_getattr,
> +	.setattr	= single_file_ll_setattr,
> +	.readdir	= single_file_ll_readdir,
> +	.open		= single_file_ll_open,
> +	.statfs		= single_file_ll_statfs,
> +	.statx		= single_file_ll_statx,
> +	.fsync		= single_file_ll_fsync,
> +
> +	.init		= service_ll_init,
> +	.read		= service_ll_read,
> +	.write		= service_ll_write,
> +};
> +
> +#define SERVICE_LL_OPT(t, p, v) { t, offsetof(struct service_ll, p), v }
> +
> +static struct fuse_opt service_ll_opts[] = {
> +	SERVICE_LL_OPT("debug",		debug,			1),
> +	SINGLE_FILE_OPT_KEYS,
> +	FUSE_OPT_END
> +};
> +
> +static int service_ll_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 (!ll.device) {
> +			ll.device = strdup(arg);
> +			return 0;
> +		}
> +		return 1;
> +	}
> +
> +	return 1;
> +}
> +
> +int main(int argc, char *argv[])
> +{
> +	struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
> +	struct fuse_cmdline_opts opts = { };
> +	int ret = 1;
> +
> +	if (fuse_service_accept(&ll.service))
> +		goto err_args;
> +
> +	if (!fuse_service_accepted(ll.service))
> +		goto err_args;
> +
> +	if (fuse_service_append_args(ll.service, &args))
> +		goto err_service;
> +
> +	if (fuse_opt_parse(&args, &ll, service_ll_opts, service_ll_opt_proc))
> +		goto err_service;
> +
> +	if (fuse_service_parse_cmdline_opts(&args, &opts))
> +		goto err_service;
> +
> +	if (opts.show_help) {
> +		printf("usage: %s [options] <device> <mountpoint>\n\n", argv[0]);
> +		fuse_cmdline_help();
> +		fuse_lowlevel_help();
> +		ret = 0;
> +		goto err_service;
> +	} else if (opts.show_version) {
> +		printf("FUSE library version %s\n", fuse_pkgversion());
> +		fuse_lowlevel_version();
> +		ret = 0;
> +		goto err_service;
> +	}
> +
> +	if (!opts.mountpoint || !ll.device) {
> +		printf("usage: %s [options] <device> <mountpoint>\n", argv[0]);
> +		printf("       %s --help\n", argv[0]);
> +		goto err_service;
> +	}
> +
> +	if (single_file_service_open(ll.service, ll.device))
> +		goto err_service;
> +
> +	if (fuse_service_finish_file_requests(ll.service))
> +		goto err_singlefile;
> +
> +	if (single_file_configure(ll.device, NULL))
> +		goto err_singlefile;
> +
> +	ll.se = fuse_session_new(&args, &service_ll_oper,
> +				 sizeof(service_ll_oper), NULL);
> +	if (ll.se == NULL)
> +		goto err_singlefile;
> +
> +	if (fuse_set_signal_handlers(ll.se))
> +		goto err_session;
> +
> +	if (fuse_service_session_mount(ll.service, ll.se, S_IFDIR, &opts))
> +		goto err_signals;
> +
> +	fuse_service_send_goodbye(ll.service, 0);
> +	fuse_service_release(ll.service);
> +
> +	fuse_daemonize(opts.foreground);
> +
> +	/* Block until ctrl+c or fusermount -u */
> +	if (opts.singlethread) {
> +		ret = fuse_session_loop(ll.se);
> +	} else {
> +		struct fuse_loop_config *config = fuse_loop_cfg_create();
> +
> +		fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
> +		fuse_loop_cfg_set_max_threads(config, opts.max_threads);
> +		ret = fuse_session_loop_mt(ll.se, config);
> +		fuse_loop_cfg_destroy(config);
> +	}
> +
> +	fuse_session_unmount(ll.se);
> +err_signals:
> +	fuse_remove_signal_handlers(ll.se);
> +err_session:
> +	fuse_session_destroy(ll.se);
> +err_singlefile:
> +	single_file_close();
> +err_service:
> +	free(opts.mountpoint);
> +	free(ll.device);
> +	fuse_service_send_goodbye(ll.service, ret);
> +	fuse_service_destroy(&ll.service);
> +err_args:
> +	fuse_opt_free_args(&args);
> +	return fuse_service_exit(ret);
> +}
> diff --git a/example/service_ll.socket.in b/example/service_ll.socket.in
> new file mode 100644
> index 00000000000000..c41c382878a0cd
> --- /dev/null
> +++ b/example/service_ll.socket.in
> @@ -0,0 +1,15 @@
> +# SPDX-License-Identifier: GPL-2.0-or-later
> +#
> +# Copyright (C) 2026 Oracle.  All Rights Reserved.
> +# Author: Darrick J. Wong <djwong@kernel.org>
> +[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
> diff --git a/example/service_ll@.service b/example/service_ll@.service
> new file mode 100644
> index 00000000000000..016d839babe3cc
> --- /dev/null
> +++ b/example/service_ll@.service
> @@ -0,0 +1,102 @@
> +# SPDX-License-Identifier: GPL-2.0-or-later
> +#
> +# Copyright (C) 2026 Oracle.  All Rights Reserved.
> +# Author: Darrick J. Wong <djwong@kernel.org>
> +[Unit]
> +Description=service_ll 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_ll
> +
> +# 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=-1000
> diff --git a/example/single_file.c b/example/single_file.c
> new file mode 100644
> index 00000000000000..b2dedf59de9552
> --- /dev/null
> +++ b/example/single_file.c
> @@ -0,0 +1,646 @@
> +/*
> + * 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.
> + */
> +#define _GNU_SOURCE
> +#include <pthread.h>
> +#include <errno.h>
> +#include <stdlib.h>
> +#include <errno.h>
> +#include <stdio.h>
> +#include <string.h>
> +#include <unistd.h>
> +#include <sys/ioctl.h>
> +#include <sys/stat.h>
> +#ifdef __linux__
> +#include <linux/fs.h>
> +#include <linux/stat.h>
> +#endif
> +
> +#define FUSE_USE_VERSION (FUSE_MAKE_VERSION(3, 19))
> +
> +#include "fuse_lowlevel.h"
> +#include "fuse_service.h"
> +#include "single_file.h"
> +
> +#define min(x, y) ((x) < (y) ? (x) : (y))
> +
> +#if __has_attribute(__fallthrough__)
> +#define fallthrough __attribute__((__fallthrough__))
> +#else
> +#define fallthrough do {} while (0)
> +#endif
> +
> +struct dirbuf {
> +	char *p;
> +	size_t size;
> +};
> +
> +struct single_file_stat {
> +	struct fuse_entry_param entry;
> +};
> +
> +#define SINGLE_FILE_INO		(FUSE_ROOT_ID + 1)
> +
> +static const char *single_file_name = "single_file";
> +static bool single_file_name_set;
> +
> +struct single_file single_file = {
> +	.backing_fd = -1,
> +	.allow_dio = true,
> +	.mode = S_IFREG | 0444,
> +	.lock = PTHREAD_MUTEX_INITIALIZER,
> +};
> +
> +static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
> +		       fuse_ino_t ino)
> +{
> +	struct stat stbuf;
> +	size_t oldsize = b->size;
> +
> +	b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
> +	b->p = (char *) realloc(b->p, b->size);
> +	memset(&stbuf, 0, sizeof(stbuf));
> +	stbuf.st_ino = ino;
> +	fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
> +			  b->size);
> +}
> +
> +int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
> +		      off_t off, size_t maxsize)
> +{
> +	if (off < bufsize)
> +		return fuse_reply_buf(req, buf + off,
> +				      min(bufsize - off, maxsize));
> +	else
> +		return fuse_reply_buf(req, NULL, 0);
> +}
> +
> +bool is_single_file_child(fuse_ino_t parent, const char *name)
> +{
> +	return  parent == FUSE_ROOT_ID &&
> +		strcmp(name, single_file_name) == 0;
> +}
> +
> +bool is_single_file_ino(fuse_ino_t ino)
> +{
> +	return ino == SINGLE_FILE_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)
> +{
> +	struct dirbuf b;
> +
> +	(void) fi;
> +
> +	switch (ino) {
> +	case FUSE_ROOT_ID:
> +		break;
> +	case SINGLE_FILE_INO:
> +		fuse_reply_err(req, ENOTDIR);
> +		return;
> +	default:
> +		fuse_reply_err(req, ENOENT);
> +		return;
> +	}
> +
> +	memset(&b, 0, sizeof(b));
> +	dirbuf_add(req, &b, ".", FUSE_ROOT_ID);
> +	dirbuf_add(req, &b, "..", FUSE_ROOT_ID);
> +	dirbuf_add(req, &b, single_file_name, SINGLE_FILE_INO);
> +	reply_buf_limited(req, b.p, b.size, off, size);
> +	free(b.p);
> +}
> +
> +static bool sf_stat(fuse_ino_t ino, struct single_file_stat *llstat)
> +{
> +	struct fuse_entry_param *entry = &llstat->entry;
> +	struct stat *stbuf = &entry->attr;
> +
> +	if (ino == FUSE_ROOT_ID) {
> +		stbuf->st_mode = S_IFDIR | 0755;
> +		stbuf->st_nlink = 2;
> +	} else if (ino == SINGLE_FILE_INO) {
> +		stbuf->st_mode = single_file.mode;
> +		stbuf->st_nlink = 1;
> +		stbuf->st_size = single_file.isize;
> +		stbuf->st_blksize = single_file.blocksize;
> +		stbuf->st_blocks = howmany(single_file.isize, 512);
> +		stbuf->st_atim = single_file.atime;
> +		stbuf->st_mtim = single_file.mtime;
> +	} else {
> +		return false;
> +	}
> +	stbuf->st_ino = ino;
> +
> +	entry->generation = ino + 1;
> +	entry->attr_timeout = 0.0;
> +	entry->entry_timeout = 0.0;
> +	entry->ino = ino;
> +
> +	return true;
> +}
> +
> +#if defined(STATX_BASIC_STATS)
> +static inline void sf_set_statx_attr(struct statx *stx,
> +				     uint64_t statx_flag, int set)
> +{
> +	if (set)
> +		stx->stx_attributes |= statx_flag;
> +	stx->stx_attributes_mask |= statx_flag;
> +}
> +
> +static void sf_statx_directio(struct statx *stx)
> +{
> +	struct statx devx;
> +	int ret;
> +
> +	ret = statx(single_file.backing_fd, "", AT_EMPTY_PATH, STATX_DIOALIGN,
> +		    &devx);
> +	if (ret)
> +		return;
> +	if (!(devx.stx_mask & STATX_DIOALIGN))
> +		return;
> +
> +	stx->stx_mask |= STATX_DIOALIGN;
> +	stx->stx_dio_mem_align = devx.stx_dio_mem_align;
> +	stx->stx_dio_offset_align = devx.stx_dio_offset_align;
> +}
> +
> +static bool sf_statx(fuse_ino_t ino, int statx_mask, struct statx *stx)
> +{
> +	(void)statx_mask;
> +
> +	if (ino == FUSE_ROOT_ID) {
> +		stx->stx_mode = S_IFDIR | 0755;
> +		stx->stx_nlink = 2;
> +	} else if (ino == SINGLE_FILE_INO) {
> +		stx->stx_mode = single_file.mode;
> +		stx->stx_nlink = 1;
> +		stx->stx_size = single_file.isize;
> +		stx->stx_blksize = single_file.blocksize;
> +		stx->stx_blocks = howmany(single_file.isize, 512);
> +		stx->stx_atime.tv_sec = single_file.atime.tv_sec;
> +		stx->stx_atime.tv_nsec = single_file.atime.tv_nsec;
> +		stx->stx_mtime.tv_sec = single_file.mtime.tv_sec;
> +		stx->stx_mtime.tv_nsec = single_file.mtime.tv_nsec;
> +	} else {
> +		return false;
> +	}
> +	stx->stx_mask = STATX_BASIC_STATS;
> +	stx->stx_ino = ino;
> +
> +	sf_set_statx_attr(stx, STATX_ATTR_IMMUTABLE, single_file.ro);
> +	sf_statx_directio(stx);
> +
> +	return true;
> +}
> +
> +void single_file_ll_statx(fuse_req_t req, fuse_ino_t ino, int flags, int mask,
> +			  struct fuse_file_info *fi)
> +{
> +	struct statx stx = { };
> +	bool filled;
> +
> +	(void)flags;
> +	(void)fi;
> +
> +	pthread_mutex_lock(&single_file.lock);
> +	filled = sf_statx(ino, mask, &stx);
> +	pthread_mutex_unlock(&single_file.lock);
> +	if (!filled)
> +		fuse_reply_err(req, ENOENT);
> +	else
> +		fuse_reply_statx(req, 0, &stx, 0.0);
> +}
> +#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);
> +}
> +#endif /* STATX_BASIC_STATS */
> +
> +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);
> +
> +	fuse_reply_statfs(req, &buf);
> +}
> +
> +void single_file_ll_getattr(fuse_req_t req, fuse_ino_t ino,
> +			    struct fuse_file_info *fi)
> +{
> +	struct single_file_stat llstat;
> +	bool filled;
> +
> +	(void) fi;
> +
> +	memset(&llstat, 0, sizeof(llstat));
> +	pthread_mutex_lock(&single_file.lock);
> +	filled = sf_stat(ino, &llstat);
> +	pthread_mutex_unlock(&single_file.lock);
> +	if (!filled)
> +		fuse_reply_err(req, ENOENT);
> +	else
> +		fuse_reply_attr(req, &llstat.entry.attr,
> +				llstat.entry.attr_timeout);
> +}
> +
> +static void get_now(struct timespec *now)
> +{
> +#ifdef CLOCK_REALTIME
> +	if (!clock_gettime(CLOCK_REALTIME, now))
> +		return;
> +#endif
> +
> +	now->tv_sec = time(NULL);
> +	now->tv_nsec = 0;
> +}
> +
> +void single_file_ll_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
> +			    int to_set, struct fuse_file_info *fi)
> +{
> +	struct timespec now;
> +
> +	if (ino != SINGLE_FILE_INO)
> +		goto deny;
> +	if (to_set & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID |
> +		      FUSE_SET_ATTR_SIZE))
> +		goto deny;
> +
> +	get_now(&now);
> +
> +	pthread_mutex_lock(&single_file.lock);
> +	if (to_set & FUSE_SET_ATTR_MODE)
> +		single_file.mode = (single_file.mode & S_IFMT) |
> +				   (attr->st_mode & ~S_IFMT);
> +	if (to_set & FUSE_SET_ATTR_ATIME) {
> +		if (to_set & FUSE_SET_ATTR_ATIME_NOW)
> +			single_file.atime = now;
> +		else
> +			single_file.atime = attr->st_atim;
> +	}
> +	if (to_set & FUSE_SET_ATTR_MTIME) {
> +		if (to_set & FUSE_SET_ATTR_MTIME_NOW)
> +			single_file.mtime = now;
> +		else
> +			single_file.mtime = attr->st_mtim;
> +	}
> +	pthread_mutex_unlock(&single_file.lock);
> +
> +	single_file_ll_getattr(req, ino, fi);
> +	return;
> +deny:
> +	fuse_reply_err(req, EPERM);
> +}
> +
> +void single_file_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
> +{
> +	struct single_file_stat llstat;
> +	bool filled;
> +
> +	if (!is_single_file_child(parent, name)) {
> +		fuse_reply_err(req, ENOENT);
> +		return;
> +	}
> +
> +	memset(&llstat, 0, sizeof(llstat));
> +	pthread_mutex_lock(&single_file.lock);
> +	filled = sf_stat(SINGLE_FILE_INO, &llstat);
> +	pthread_mutex_unlock(&single_file.lock);
> +	if (!filled)
> +		fuse_reply_err(req, ENOENT);
> +	else
> +		fuse_reply_entry(req, &llstat.entry);
> +}
> +
> +void single_file_ll_open(fuse_req_t req, fuse_ino_t ino,
> +			 struct fuse_file_info *fi)
> +{
> +	if (ino != SINGLE_FILE_INO)
> +		fuse_reply_err(req, EISDIR);
> +	else if (single_file.ro && (fi->flags & O_ACCMODE) != O_RDONLY)
> +		fuse_reply_err(req, EACCES);
> +	else
> +		fuse_reply_open(req, fi);
> +}
> +
> +void single_file_ll_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
> +			  struct fuse_file_info *fi)
> +{
> +	int ret = 0;
> +
> +	(void)datasync;
> +	(void)fi;
> +
> +	if (ino == SINGLE_FILE_INO) {
> +		ret = fsync(single_file.backing_fd);
> +		if (ret)
> +			ret = errno;
> +	}
> +
> +	fuse_reply_err(req, ret);
> +}
> +
> +unsigned long long parse_num_blocks(const char *arg, int log_block_size)
> +{
> +	char *p;
> +	unsigned long long num;
> +
> +	num = strtoull(arg, &p, 0);
> +
> +	if (p[0] && p[1])
> +		return 0;
> +
> +	switch (*p) {
> +	case 'T': case 't':
> +		num <<= 10;
> +		fallthrough;
> +	case 'G': case 'g':
> +		num <<= 10;
> +		fallthrough;
> +	case 'M': case 'm':
> +		num <<= 10;
> +		fallthrough;
> +	case 'K': case 'k':
> +		if (log_block_size < 0)
> +			num <<= 10;
> +		else
> +			num >>= log_block_size;
> +		break;
> +	case 's':
> +		if (log_block_size < 0)
> +			num <<= 9;
> +		else
> +			num >>= (1+log_block_size);
> +		break;
> +	case '\0':
> +		break;
> +	default:
> +		return 0;
> +	}
> +	return num;
> +}
> +
> +static int single_file_set_blocksize(const char *arg)
> +{
> +	unsigned long long l = parse_num_blocks(arg, -1);
> +
> +	if (l < 512 || l > INT32_MAX || (l & (l - 1)) != 0) {
> +		fprintf(stderr, "%s: block size must be power of two between 512 and 2G.\n",
> +			arg);
> +		return -1;
> +	}
> +
> +	/* do not pass through to libfuse */
> +	single_file.blocksize = l;
> +	return 0;
> +}
> +
> +static int single_file_set_size(const char *arg)
> +{
> +	unsigned long long l = parse_num_blocks(arg, -1);
> +
> +	if (l < 1 || (l & 511) != 0 || l > INT64_MAX) {
> +		fprintf(stderr, "%s: size must be multiple of 512 and larger than zero.\n",
> +			arg);
> +		return -1;
> +	}
> +
> +	/* do not pass through to libfuse */
> +	single_file.isize = l;
> +	return 0;
> +}
> +
> +int single_file_opt_proc(void *data, const char *arg, int key,
> +			 struct fuse_args *outargs)
> +{
> +	(void)data;
> +	(void)outargs;
> +
> +	switch (key) {
> +	case SINGLE_FILE_RO:
> +		/* pass through to libfuse */
> +		single_file.ro = true;
> +		return 1;
> +	case SINGLE_FILE_RW:
> +		/* pass through to libfuse */
> +		single_file.ro = false;
> +		return 1;
> +	case SINGLE_FILE_REQUIRE_BDEV:
> +		single_file.require_bdev = true;
> +		return 0;
> +	case SINGLE_FILE_DIO:
> +		single_file.allow_dio = true;
> +		return 0;
> +	case SINGLE_FILE_NODIO:
> +		single_file.allow_dio = false;
> +		return 0;
> +	case SINGLE_FILE_SYNC:
> +		single_file.sync = true;
> +		return 0;
> +	case SINGLE_FILE_NOSYNC:
> +		single_file.sync = false;
> +		return 0;
> +	case SINGLE_FILE_BLOCKSIZE:
> +		return single_file_set_blocksize(arg + 10);
> +	case SINGLE_FILE_SIZE:
> +		return single_file_set_size(arg + 5);
> +	}
> +
> +	return 1;
> +}
> +
> +int single_file_service_open(struct fuse_service *sf, const char *path)
> +{
> +	int open_flags = single_file.ro ? O_RDONLY : O_RDWR;
> +	int fd;
> +	int ret;
> +
> +again:
> +	if (single_file.require_bdev)
> +		ret = fuse_service_request_blockdev(sf, path,
> +						    open_flags | O_EXCL, 0, 0,
> +						    single_file.blocksize);
> +	else
> +		ret = fuse_service_request_file(sf, path, open_flags | O_EXCL,
> +						0, 0);
> +	if (ret)
> +		return ret;
> +
> +	if (!single_file.ro && open_flags == O_RDONLY)
> +		single_file.ro = true;
> +
> +	ret = fuse_service_receive_file(sf, path, &fd);
> +	if (ret)
> +		return ret;
> +
> +	/* downgrade from rw to ro if necessary */
> +	if ((fd == -EPERM || fd == -EACCES) && open_flags == O_RDWR) {
> +		open_flags = O_RDONLY;
> +		goto again;
> +	}
> +
> +	if (fd < 0) {
> +		fprintf(stderr, "%s: opening file: %s.\n",
> +			path, strerror(-fd));
> +		return -1;
> +	}
> +
> +	single_file.backing_fd = fd;
> +	return 0;
> +}
> +
> +int single_file_check_write(off_t pos, size_t *count)
> +{
> +	if (pos >= single_file.isize)
> +		return -EFBIG;
> +
> +	if (*count > single_file.isize)
> +		*count = single_file.isize;
> +	if (pos >= single_file.isize - *count)
> +		*count = single_file.isize - pos;
> +
> +	return 0;
> +}
> +
> +void single_file_check_read(off_t pos, size_t *count)
> +{
> +	int ret = single_file_check_write(pos, count);
> +
> +	if (ret)
> +		*count = 0;
> +}
> +
> +int single_file_configure(const char *device, const char *filename)
> +{
> +	struct stat statbuf;
> +	unsigned long long backing_size;
> +	unsigned int proposed_blocksize;
> +	int lbasize;
> +	int ret;
> +
> +	ret = fstat(single_file.backing_fd, &statbuf);
> +	if (ret) {
> +		perror(device);
> +		return -1;
> +	}
> +	lbasize = statbuf.st_blksize;
> +	backing_size = statbuf.st_size;
> +
> +	if (S_ISBLK(statbuf.st_mode)) {
> +#ifdef BLKSSZGET
> +		ret = ioctl(single_file.backing_fd, BLKSSZGET, &lbasize);
> +		if (ret) {
> +			perror(device);
> +			return -1;
> +		}
> +#endif
> +
> +#ifdef BLKGETSIZE64
> +		ret = ioctl(single_file.backing_fd, BLKGETSIZE64, &backing_size);
> +		if (ret) {
> +			perror(device);
> +			return -1;
> +		}
> +#endif
> +	}
> +
> +	if (backing_size == 0) {
> +		fprintf(stderr, "%s: backing file size zero?\n", device);
> +		return -1;
> +	}
> +
> +	if (lbasize == 0) {
> +		fprintf(stderr, "%s: blocksize zero?\n", device);
> +		return -1;
> +	}
> +
> +	proposed_blocksize = single_file.blocksize ? single_file.blocksize :
> +						     sysconf(_SC_PAGESIZE);
> +	if (lbasize > proposed_blocksize) {
> +		fprintf(stderr, "%s: lba size %u smaller than blocksize %u\n",
> +			device, lbasize, proposed_blocksize);
> +		return -1;
> +	}
> +
> +	if (single_file.isize % proposed_blocksize > 0) {
> +		fprintf(stderr, "%s: size parameter %llu not congruent with blocksize %u\n",
> +			device, (unsigned long long)single_file.isize,
> +			proposed_blocksize);
> +		return -1;
> +	}
> +
> +	if (single_file.isize > backing_size) {
> +		fprintf(stderr, "%s: file size %llu smaller than size param %llu\n",
> +			device, backing_size,
> +			(unsigned long long)single_file.isize);
> +		return -1;
> +	}
> +
> +	if (!single_file.blocksize)
> +		single_file.blocksize = proposed_blocksize;
> +	if (!single_file.isize)
> +		single_file.isize = backing_size;
> +
> +	single_file.isize = round_down(single_file.isize, single_file.blocksize);
> +	single_file.blocks = single_file.isize / single_file.blocksize;
> +
> +	return single_file_configure_simple(filename);
> +}
> +
> +int single_file_configure_simple(const char *filename)
> +{
> +	if (!single_file.blocksize)
> +		single_file.blocksize = sysconf(_SC_PAGESIZE);
> +
> +	if (filename) {
> +		char *n = strdup(filename);
> +
> +		if (!n) {
> +			perror(filename);
> +			return -1;
> +		}
> +
> +		if (single_file_name_set)
> +			free((void *)single_file_name);
> +		single_file_name = n;
> +		single_file_name_set = true;
> +	}
> +
> +	return 0;
> +}
> +
> +void single_file_close(void)
> +{
> +	close(single_file.backing_fd);
> +	single_file.backing_fd = -1;
> +
> +	if (single_file_name_set)
> +		free((void *)single_file_name);
> +	single_file_name_set = false;
> +}
> diff --git a/meson.build b/meson.build
> index 827ec45ad3ad75..de038df8d92071 100644
> --- a/meson.build
> +++ b/meson.build
> @@ -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)
>  
> 
> 

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH 08/13] mount_service: enable unprivileged users in the same manner as fusermount
  2026-04-14 23:53   ` Darrick J. Wong
@ 2026-04-17 22:01     ` Darrick J. Wong
  0 siblings, 0 replies; 24+ messages in thread
From: Darrick J. Wong @ 2026-04-17 22:01 UTC (permalink / raw)
  To: bschubert; +Cc: miklos, neal, linux-fsdevel, bernd, joannelkoong

On Tue, Apr 14, 2026 at 04:53:49PM -0700, Darrick J. Wong wrote:
> On Thu, Apr 09, 2026 at 03:22:38PM -0700, Darrick J. Wong wrote:
> > From: Darrick J. Wong <djwong@kernel.org>
> > 
> > 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.
> > 
> > Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
> > ---
> >  util/mount_service.c |  150 +++++++++++++++++++++++++++++++++++++++++++++++++-
> >  1 file changed, 147 insertions(+), 3 deletions(-)
> > 
> > 
> > diff --git a/util/mount_service.c b/util/mount_service.c
> > index 8e9c721a56fd2a..0f7eae94ada377 100644
> > --- a/util/mount_service.c
> > +++ b/util/mount_service.c
> > @@ -33,6 +33,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 */
> > @@ -166,7 +167,9 @@ static int mount_service_connect(struct mount_service *mo)
> >  		return -1;
> >  	}
> >  
> > +	drop_privs();
> >  	ret = connect(sockfd, (const struct sockaddr *)&name, sizeof(name));
> > +	restore_privs();
> >  	if (ret && (errno == ENOENT || errno == ECONNREFUSED)) {

Oops, here I missed that the syscalls under restore_privs() could fail,
thereby blowing away errno.  This should of course be:

	drop_privs();
	ret = connect(...);
	if (ret && (errno == ENOENT || errno == ECONNREFUSED)) {
		restore_privs();
		/* rest of error stuff */
	}


> >  		fprintf(stderr, "%s: no safe filesystem driver for %s available.\n",
> >  			mo->msgtag, mo->subtype);
> > @@ -208,7 +211,7 @@ static int mount_service_send_hello(struct mount_service *mo)
> >  	};
> >  	ssize_t size;
> >  
> > -	if (getuid() == 0)
> > +	if (getuid() == 0 || user_allow_other)
> >  		hello.flags |= htonl(FUSE_SERVICE_FLAG_ALLOW_OTHER);
> >  
> >  	size = sendmsg(mo->sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
> > @@ -490,7 +493,9 @@ static int mount_service_send_required_files(struct mount_service *mo,
> >  {
> >  	int ret;
> >  
> > +	drop_privs();
> >  	mo->fusedevfd = open(fusedev, O_RDWR | O_CLOEXEC);
> > +	restore_privs();

Here too.

> >  	if (mo->fusedevfd < 0) {
> >  		fprintf(stderr, "%s: %s: %s\n",
> >  			mo->msgtag, fusedev, strerror(errno));
> > @@ -628,7 +633,9 @@ 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);
> > +		restore_privs();
> >  		if (ret) {
> >  			int error = errno;
> >  
> > @@ -675,7 +682,9 @@ static int mount_service_open_path(struct mount_service *mo,
> >  		return mount_service_send_file_error(mo, EINVAL, oc->path);
> >  	}
> >  
> > -	fd = open(oc->path, ntohl(oc->open_flags), ntohl(oc->create_mode));
> > +	drop_privs();
> > +	fd = open(oc->path, ntohl(oc->open_flags) | O_CLOEXEC, ntohl(oc->create_mode));
> > +	restore_privs();

And here.

> >  	if (fd < 0) {
> >  		int error = errno;
> >  
> > @@ -908,6 +917,20 @@ static int mount_service_handle_mntopts_cmd(struct mount_service *mo,

mount_service_handle_mntopts_cmd and mount_service_handle_source_cmd
ought only to set mo->mntopts and mo->source at the very end, if
everything that before it succeeded.

> >  			*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);
> > +		}
> > +		if (!strcmp(tok, "blkdev") && getuid() != 0) {
> 
> blkdev is an internal mount option that libfuse never sends to the
> kernel, so there's no need to check it here either.  Sending "blkdev" to
> the kernel will just cause mount() EINVAL failures.
> 
> 
> > +			fprintf(stderr, "%s: option blkdev is privileged\n",
> > +				mo->msgtag);
> > +			return mount_service_send_reply(mo, EPERM);
> > +		}
> > +
> >  #ifdef HAVE_NEW_MOUNT_API
> >  		if (mo->fsopenfd >= 0) {
> >  			int ret;
> > @@ -985,10 +1008,16 @@ static int attach_to_mountpoint(struct mount_service *mo, char *mntpt)
> >  	int error;
> >  	int ret;
> >  
> > +	drop_privs();
> > +
> >  	/*
> >  	 * Open the alleged mountpoint, make sure it's a dir or a file.
> > +	 * For unprivileged callers, we only allow mounting on paths that the
> > +	 * user can write to.
> >  	 */
> > -	mountfd = open(mntpt, O_RDONLY | O_CLOEXEC);
> > +	mountfd = open(mntpt, (getuid() == 0 ? O_RDONLY : O_WRONLY) | O_CLOEXEC);
> > +	if (mountfd < 0 && errno == EISDIR)
> > +		mountfd = open(mntpt, O_RDONLY | O_CLOEXEC);
> 
> This mess!
> 
> This might add a new requirement that the unprivileged caller has to
> have read access to the mountpoint.  This isn't explicitly checked in
> fusermount, but I'm pretty sure that the lstat in fusermount will fail
> with EACCESS.
> 
> That said, if we're unprivileged and fall back to opening readonly, then
> we should require that the fd point to a directory.  Note that the
> access check just prior to mount()/fsmount() will confirm that the
> unprivileged caller could (at least in theory) create a new child entry.
> 
> --D
> 
> >  	if (mountfd < 0) {
> >  		error = errno;
> >  		fprintf(stderr, "%s: %s: %s\n", mo->msgtag, mntpt,
> > @@ -1067,6 +1096,7 @@ static int attach_to_mountpoint(struct mount_service *mo, char *mntpt)
> >  	mo->mountfd = mountfd;
> >  	mo->resv_mountpoint = res_mntpt;
> >  
> > +	restore_privs();
> >  	return mount_service_send_reply(mo, 0);
> >  
> >  out_res_mntpt:
> > @@ -1075,6 +1105,7 @@ static int attach_to_mountpoint(struct mount_service *mo, char *mntpt)
> >  	close(mountfd);
> >  out_error:
> >  	free(mntpt);
> > +	restore_privs();
> >  	return mount_service_send_reply(mo, error);
> >  }
> >  
> > @@ -1399,6 +1430,77 @@ 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 int mount_service_handle_mount_cmd(struct mount_service *mo,
> >  					  struct fuse_service_packet *p,
> >  					  size_t psz)
> > @@ -1471,6 +1573,44 @@ static int mount_service_handle_mount_cmd(struct mount_service *mo,
> >  		return mount_service_send_reply(mo, EINVAL);
> >  	}
> >  
> > +	/*
> > +	 * 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.
> > +	 */
> > +	if (getuid() != 0) {
> > +		struct statfs fs_buf;
> > +
> > +		ret = check_nonroot_mount_count(mo->msgtag);
> > +		if (ret)
> > +			return mount_service_send_reply(mo, 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 mount_service_send_reply(mo, error);
> > +		}
> > +
> > +		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();
> > +
> > +		if (ret)
> > +			return mount_service_send_reply(mo, EPERM);

Codex points out that fusermount grumps and sets MS_NOSUID|MS_NODEV
unconditionally, so we should do that too.  I see that there's a
"mount_flags" table in fusermount.c, so I'll use that as the basis for
forcing flags.

--D

> > +	}
> > +
> >  	if (mo->fsopenfd >= 0) {
> >  		ret = mount_service_fsopen_mount(mo, oc, &stbuf);
> >  		if (ret != FUSE_MOUNT_FALLBACK_NEEDED)
> > @@ -1601,6 +1741,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;
> > 
> > 
> 

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH 04/13] mount_service: use the new mount api for the mount service
  2026-04-09 22:21 ` [PATCH 04/13] mount_service: use the new mount api for the mount service Darrick J. Wong
@ 2026-04-17 22:03   ` Darrick J. Wong
  0 siblings, 0 replies; 24+ messages in thread
From: Darrick J. Wong @ 2026-04-17 22:03 UTC (permalink / raw)
  To: bschubert; +Cc: miklos, neal, linux-fsdevel, bernd, joannelkoong

On Thu, Apr 09, 2026 at 03:21:35PM -0700, Darrick J. Wong wrote:
> From: Darrick J. Wong <djwong@kernel.org>
> 
> Use the new fsopen/fsmount system calls to mount the filesystem so that
> we get somewhat better diagnostics if something gets screwed up.
> 
> Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
> ---
>  lib/fuse_i.h         |    3 
>  meson.build          |   15 ++
>  util/mount_service.c |  323 ++++++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 341 insertions(+)
> 
> 
> diff --git a/lib/fuse_i.h b/lib/fuse_i.h
> index 0ca13d132585f6..1710a872e19c72 100644
> --- a/lib/fuse_i.h
> +++ b/lib/fuse_i.h
> @@ -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);
> diff --git a/meson.build b/meson.build
> index 66425a0d4cc16f..c8326b79fcee8f 100644
> --- a/meson.build
> +++ b/meson.build
> @@ -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;
> +       }
>      '''
>  }
>  
> diff --git a/util/mount_service.c b/util/mount_service.c
> index abe88a0710255b..246a95101e8d34 100644
> --- a/util/mount_service.c
> +++ b/util/mount_service.c
> @@ -71,6 +71,9 @@ struct mount_service {
>  	/* fd for mount point */
>  	int mountfd;
>  
> +	/* fd for fsopen */
> +	int fsopenfd;
> +
>  	/* did we actually mount successfully? */
>  	bool mounted;
>  };
> @@ -94,6 +97,7 @@ static int mount_service_init(struct mount_service *mo, int argc, char *argv[])
>  	mo->argvfd = -1;
>  	mo->fusedevfd = -1;
>  	mo->mountfd = -1;
> +	mo->fsopenfd = -1;
>  
>  	for (i = 0; i < argc; i++) {
>  		if (!strcmp(argv[i], "-t") && i + 1 < argc) {
> @@ -710,6 +714,26 @@ static int mount_service_handle_open_bdev_cmd(struct mount_service *mo,
>  	return mount_service_open_path(mo, S_IFBLK, p, psz);
>  }
>  
> +#ifdef HAVE_NEW_MOUNT_API
> +static void try_fsopen(struct mount_service *mo,
> +		       struct fuse_service_string_command *oc)
> +{
> +	/*
> +	 * As of Linux 7.0 you can pass subtypes to fsopen, but the manpage for
> +	 * fsopen only says that you can pass any value of the second column of
> +	 * /proc/filesystems into fsopen.
> +	 */
> +	if (!strncmp(oc->value, "fuse.", 5))
> +		*(oc->value + 4) = 0;
> +	else if (!strncmp(oc->value, "fuseblk.", 8))
> +		*(oc->value + 7) = 0;
> +
> +	mo->fsopenfd = fsopen(oc->value, FSOPEN_CLOEXEC);
> +}
> +#else
> +# define try_fsopen(...)	((void)0)
> +#endif
> +
>  static int mount_service_handle_fsopen_cmd(struct mount_service *mo,
>  					   const struct fuse_service_packet *p,
>  					   size_t psz)
> @@ -744,9 +768,45 @@ static int mount_service_handle_fsopen_cmd(struct mount_service *mo,
>  		return mount_service_send_reply(mo, error);
>  	}
>  
> +	/* If this fails we fall back on mount(); oc->value is mutated */
> +	try_fsopen(mo, oc);
>  	return mount_service_send_reply(mo, 0);
>  }
>  
> +#ifdef HAVE_NEW_MOUNT_API
> +/* callers must preserve errno */
> +static void emit_fsconfig_messages(const struct mount_service *mo)
> +{
> +	uint8_t buf[BUFSIZ];
> +	ssize_t sz;
> +
> +	while ((sz = read(mo->fsopenfd, buf, sizeof(buf) - 1)) >= 1) {
> +		if (buf[sz - 1] == '\n')
> +			buf[--sz] = '\0';
> +		else
> +			buf[sz] = '\0';
> +
> +		if (!*buf)
> +			continue;
> +
> +		switch (buf[0]) {
> +		case 'e':
> +			fprintf(stderr, "Error: %s\n", buf + 2);
> +			break;
> +		case 'w':
> +			fprintf(stderr, "Warning: %s\n", buf + 2);
> +			break;
> +		case 'i':
> +			fprintf(stderr, "Info: %s\n", buf + 2);
> +			break;
> +		default:
> +			fprintf(stderr, " %s\n", buf);
> +			break;
> +		}
> +	}
> +}
> +#endif
> +
>  static int mount_service_handle_source_cmd(struct mount_service *mo,
>  					   const struct fuse_service_packet *p,
>  					   size_t psz)
> @@ -781,6 +841,21 @@ static int mount_service_handle_source_cmd(struct mount_service *mo,
>  		return mount_service_send_reply(mo, error);
>  	}
>  
> +#ifdef HAVE_NEW_MOUNT_API
> +	if (mo->fsopenfd >= 0) {
> +		int ret = fsconfig(mo->fsopenfd, FSCONFIG_SET_STRING, "source",
> +			       oc->value, 0);
> +		if (ret) {
> +			int error = errno;
> +
> +			fprintf(stderr, "%s: fsconfig source: %s\n",
> +				mo->msgtag, strerror(error));
> +			emit_fsconfig_messages(mo);
> +			return mount_service_send_reply(mo, error);
> +		}
> +	}
> +#endif
> +
>  	return mount_service_send_reply(mo, 0);
>  }
>  
> @@ -790,6 +865,8 @@ static int mount_service_handle_mntopts_cmd(struct mount_service *mo,
>  {
>  	struct fuse_service_string_command *oc =
>  			container_of(p, struct fuse_service_string_command, p);
> +	char *tokstr = oc->value;
> +	char *tok, *savetok;
>  
>  	if (psz < sizeof_fuse_service_string_command(1)) {
>  		fprintf(stderr, "%s: mount options command too small\n",
> @@ -818,6 +895,45 @@ static int mount_service_handle_mntopts_cmd(struct mount_service *mo,
>  		return mount_service_send_reply(mo, error);
>  	}
>  
> +	/* strtok_r mutates tokstr aka oc->value */
> +	while ((tok = strtok_r(tokstr, ",", &savetok)) != NULL) {
> +		char *equals = strchr(tok, '=');
> +		char oldchar = 0;
> +
> +		if (equals) {
> +			oldchar = *equals;
> +			*equals = 0;
> +		}
> +
> +#ifdef HAVE_NEW_MOUNT_API
> +		if (mo->fsopenfd >= 0) {
> +			int ret;
> +
> +			if (equals)
> +				ret = fsconfig(mo->fsopenfd,
> +					       FSCONFIG_SET_STRING, tok,
> +					       equals + 1, 0);
> +			else
> +				ret = fsconfig(mo->fsopenfd,
> +					       FSCONFIG_SET_FLAG, tok,
> +					       NULL, 0);
> +			if (ret) {
> +				int error = errno;
> +
> +				fprintf(stderr, "%s: set mount option: %s\n",
> +					mo->msgtag, strerror(error));
> +				emit_fsconfig_messages(mo);
> +				return mount_service_send_reply(mo, error);
> +			}
> +		}
> +#endif
> +
> +		if (equals)
> +			*equals = oldchar;
> +
> +		tokstr = NULL;
> +	}
> +
>  	return mount_service_send_reply(mo, 0);
>  }
>  
> @@ -1028,6 +1144,205 @@ static int mount_service_regular_mount(struct mount_service *mo,
>  	return mount_service_send_reply(mo, 0);
>  }
>  
> +#ifdef HAVE_NEW_MOUNT_API
> +struct ms_to_mount_map {
> +	unsigned long ms_flag;
> +	unsigned int mount_attr_flag;
> +};
> +
> +static const struct ms_to_mount_map attrs[] = {
> +	{ MS_RDONLY,		MOUNT_ATTR_RDONLY },
> +	{ MS_NOSUID,		MOUNT_ATTR_NOSUID },
> +	{ MS_NODEV,		MOUNT_ATTR_NODEV },
> +	{ MS_NOEXEC,		MOUNT_ATTR_NOEXEC },
> +	{ MS_RELATIME,		MOUNT_ATTR_RELATIME },
> +	{ MS_NOATIME,		MOUNT_ATTR_NOATIME },
> +	{ MS_STRICTATIME,	MOUNT_ATTR_STRICTATIME },
> +	{ MS_NODIRATIME,	MOUNT_ATTR_NODIRATIME },
> +#ifdef MOUNT_ATTR_NOSYMFOLLOW
> +	{ MS_NOSYMFOLLOW,	MOUNT_ATTR_NOSYMFOLLOW },
> +#endif
> +	{ 0, 0 },
> +};
> +
> +static void get_mount_attr_flags(const struct fuse_service_mount_command *oc,
> +				 unsigned int *attr_flags,
> +				 unsigned long *leftover_ms_flags)
> +{
> +	const struct ms_to_mount_map *i;
> +	unsigned int ms_flags = ntohl(oc->ms_flags);
> +	unsigned int mount_attr_flags = 0;
> +
> +	for (i = attrs; i->ms_flag != 0; i++) {
> +		if (ms_flags & i->ms_flag)
> +			mount_attr_flags |= i->mount_attr_flag;
> +		ms_flags &= ~i->ms_flag;
> +	}
> +
> +	*leftover_ms_flags = ms_flags;
> +	*attr_flags = mount_attr_flags;
> +}
> +
> +struct ms_to_str_map {
> +	unsigned long ms_flag;
> +	const char *string;
> +};
> +
> +static const struct ms_to_str_map strflags[] = {
> +	{ MS_SYNCHRONOUS,	"sync" },
> +	{ MS_DIRSYNC,		"dirsync" },
> +	{ MS_LAZYTIME,		"lazytime" },
> +	{ 0, 0 },
> +};
> +
> +static int set_ms_flags(struct mount_service *mo, unsigned long ms_flags)
> +{
> +	const struct ms_to_str_map *i;
> +	int ret;
> +
> +	for (i = strflags; i->ms_flag != 0; i++) {
> +		if (!(ms_flags & i->ms_flag))
> +			continue;
> +
> +		ret = fsconfig(mo->fsopenfd, FSCONFIG_SET_FLAG, i->string,
> +			       NULL, 0);
> +		if (ret) {
> +			int error = errno;
> +
> +			fprintf(stderr, "%s: set %s option: %s\n",
> +				mo->msgtag, i->string, strerror(error));
> +			emit_fsconfig_messages(mo);
> +
> +			errno = error;
> +			return -1;
> +		}
> +		ms_flags &= ~i->ms_flag;
> +	}
> +
> +	/*
> +	 * We can't translate all the supplied MS_ flags into MOUNT_ATTR_ flags
> +	 * or string flags!  Return a magic code so the caller will fall back
> +	 * to regular mount(2).
> +	 */
> +	if (ms_flags)
> +		return FUSE_MOUNT_FALLBACK_NEEDED;
> +
> +	return 0;
> +}
> +
> +static int mount_service_fsopen_mount(struct mount_service *mo,
> +				      struct fuse_service_mount_command *oc,
> +				      struct stat *stbuf)
> +{
> +	char tmp[64];
> +	char *dot;
> +	unsigned long ms_flags;
> +	unsigned int attr_flags;
> +	int mfd;
> +	int error;
> +	int ret;
> +
> +	get_mount_attr_flags(oc, &attr_flags, &ms_flags);
> +
> +	ret = set_ms_flags(mo, ms_flags);
> +	if (ret == FUSE_MOUNT_FALLBACK_NEEDED)
> +		return ret;
> +	if (ret) {
> +		error = errno;
> +		goto fail_mount;
> +	}
> +
> +	snprintf(tmp, sizeof(tmp), "%i", mo->fusedevfd);
> +	ret = fsconfig(mo->fsopenfd, FSCONFIG_SET_STRING, "fd", tmp, 0);
> +	if (ret) {
> +		error = errno;
> +		fprintf(stderr, "%s: set fd option: %s\n",
> +			mo->msgtag, strerror(error));
> +		goto fail_fsconfig;
> +	}
> +
> +	snprintf(tmp, sizeof(tmp), "%o", stbuf->st_mode & S_IFMT);
> +	ret = fsconfig(mo->fsopenfd, FSCONFIG_SET_STRING, "rootmode", tmp, 0);
> +	if (ret) {
> +		error = errno;
> +		fprintf(stderr, "%s: set rootmode option: %s\n",
> +			mo->msgtag, strerror(error));
> +		goto fail_fsconfig;
> +	}
> +
> +	snprintf(tmp, sizeof(tmp), "%u", getuid());
> +	ret = fsconfig(mo->fsopenfd, FSCONFIG_SET_STRING, "user_id", tmp, 0);
> +	if (ret) {
> +		error = errno;
> +		fprintf(stderr, "%s: set user_id option: %s\n",
> +			mo->msgtag, strerror(error));
> +		goto fail_fsconfig;
> +	}
> +
> +	snprintf(tmp, sizeof(tmp), "%u", getgid());
> +	ret = fsconfig(mo->fsopenfd, FSCONFIG_SET_STRING, "group_id", tmp, 0);
> +	if (ret) {
> +		error = errno;
> +		fprintf(stderr, "%s: set group_id option: %s\n",
> +			mo->msgtag, strerror(error));
> +		goto fail_fsconfig;
> +	}
> +
> +	dot = strchr(mo->fstype, '.');
> +	if (dot) {
> +		ret = fsconfig(mo->fsopenfd, FSCONFIG_SET_STRING, "subtype",
> +			       dot + 1, 0);
> +		if (ret) {
> +			error = errno;
> +
> +			/* The subtype option came after fsopen */
> +			if (error == EINVAL)
> +				return FUSE_MOUNT_FALLBACK_NEEDED;
> +
> +			fprintf(stderr, "%s: set subtype option: %s\n",
> +				mo->msgtag, strerror(error));
> +			goto fail_fsconfig;
> +		}
> +	}

This string should be set first so we can avoid wasting time on
fsconfig() calls, in addition to using the @type parameter that was
pased in from fuservicemount instead of whatever string the fuse server
might have fed us.

--D

> +
> +	ret = fsconfig(mo->fsopenfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0);
> +	if (ret) {
> +		error = errno;
> +		fprintf(stderr, "%s: creating filesystem: %s\n",
> +			mo->msgtag, strerror(error));
> +		goto fail_fsconfig;
> +	}
> +
> +	mfd = fsmount(mo->fsopenfd, FSMOUNT_CLOEXEC, attr_flags);
> +	if (mfd < 0) {
> +		error = errno;
> +		fprintf(stderr, "%s: fsmount: %s\n",
> +			mo->msgtag, strerror(error));
> +		goto fail_fsconfig;
> +	}
> +
> +	ret = move_mount(mfd, "", mo->mountfd, "",
> +			 MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_T_EMPTY_PATH);
> +	close(mfd);
> +	if (ret) {
> +		error = errno;
> +		fprintf(stderr, "%s: move_mount: %s\n",
> +			mo->msgtag, strerror(error));
> +		goto fail_mount;
> +	}
> +
> +	mo->mounted = true;
> +	return mount_service_send_reply(mo, 0);
> +
> +fail_fsconfig:
> +	emit_fsconfig_messages(mo);
> +fail_mount:
> +	return mount_service_send_reply(mo, error);
> +}
> +#else
> +# define mount_service_fsopen_mount(...)	(FUSE_MOUNT_FALLBACK_NEEDED)
> +#endif
> +
>  static int mount_service_handle_mount_cmd(struct mount_service *mo,
>  					  struct fuse_service_packet *p,
>  					  size_t psz)
> @@ -1100,6 +1415,12 @@ static int mount_service_handle_mount_cmd(struct mount_service *mo,
>  		return mount_service_send_reply(mo, EINVAL);
>  	}
>  
> +	if (mo->fsopenfd >= 0) {
> +		ret = mount_service_fsopen_mount(mo, oc, &stbuf);
> +		if (ret != FUSE_MOUNT_FALLBACK_NEEDED)
> +			return ret;
> +	}
> +
>  	return mount_service_regular_mount(mo, oc, &stbuf);
>  }
>  
> @@ -1179,6 +1500,7 @@ static void mount_service_destroy(struct mount_service *mo)
>  	close(mo->mountfd);
>  	close(mo->fusedevfd);
>  	close(mo->argvfd);
> +	close(mo->fsopenfd);
>  	shutdown(mo->sockfd, SHUT_RDWR);
>  	close(mo->sockfd);
>  
> @@ -1194,6 +1516,7 @@ static void mount_service_destroy(struct mount_service *mo)
>  	mo->argvfd = -1;
>  	mo->fusedevfd = -1;
>  	mo->mountfd = -1;
> +	mo->fsopenfd = -1;
>  }
>  
>  int mount_service_main(int argc, char *argv[])
> 
> 

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH 09/13] mount.fuse3: integrate systemd service startup
  2026-04-09 22:22 ` [PATCH 09/13] mount.fuse3: integrate systemd service startup Darrick J. Wong
@ 2026-04-17 22:41   ` Darrick J. Wong
  0 siblings, 0 replies; 24+ messages in thread
From: Darrick J. Wong @ 2026-04-17 22:41 UTC (permalink / raw)
  To: bschubert; +Cc: miklos, neal, linux-fsdevel, bernd, joannelkoong

On Thu, Apr 09, 2026 at 03:22:53PM -0700, Darrick J. Wong wrote:
> From: Darrick J. Wong <djwong@kernel.org>
> 
> Teach mount.fuse3 how to start fuse via systemd service, if present.
> 
> Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
> ---
>  util/mount_service.h  |    8 +++
>  doc/fuservicemount3.8 |   10 +++-
>  meson.build           |    3 +
>  util/fuservicemount.c |   48 +++++++++++++++++
>  util/meson.build      |   14 ++++-
>  util/mount.fuse.c     |  135 +++++++++++++++++++++++++++++++++++++++++--------
>  util/mount_service.c  |   28 ++++++++++
>  7 files changed, 219 insertions(+), 27 deletions(-)
> 
> 
> diff --git a/util/mount_service.h b/util/mount_service.h
> index f1f95e67ee1afe..993414ea3422e9 100644
> --- a/util/mount_service.h
> +++ b/util/mount_service.h
> @@ -36,4 +36,12 @@ 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 subtype.
> + *
> + * @param subtype the subtype of a fuse filesystem type (e.g. Y from fuse.Y)
> + * @return true if available, false if not
> + */
> +bool mount_service_present(const char *subtype);
> +
>  #endif /* MOUNT_SERVICE_H_ */
> diff --git a/doc/fuservicemount3.8 b/doc/fuservicemount3.8
> index e45d6a89c8b81a..aa2167cb4872c6 100644
> --- a/doc/fuservicemount3.8
> +++ b/doc/fuservicemount3.8
> @@ -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 <djwong@kernel.org>.
> diff --git a/meson.build b/meson.build
> index c8326b79fcee8f..827ec45ad3ad75 100644
> --- a/meson.build
> +++ b/meson.build
> @@ -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))
> diff --git a/util/fuservicemount.c b/util/fuservicemount.c
> index 9c694a4290f94e..d39d9c486c8997 100644
> --- a/util/fuservicemount.c
> +++ b/util/fuservicemount.c
> @@ -9,10 +9,58 @@
>   * 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)
> +{
> +	const char *subtype;
> +
> +	if (!fstype) {
> +		fprintf(stderr,
> +			"fuservicemount: expected fs type for --check\n");
> +		return EXIT_FAILURE;
> +	}
> +
> +	subtype = mount_service_subtype(fstype);
> +	return mount_service_present(subtype) ? 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.
> +	 */
> +	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);
>  }
> diff --git a/util/meson.build b/util/meson.build
> index aa646ef3c77d16..85b54d5d322dcb 100644
> --- a/util/meson.build
> +++ b/util/meson.build
> @@ -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')
> diff --git a/util/mount.fuse.c b/util/mount.fuse.c
> index ec56198ccc1cf0..8836eba8502fd1 100644
> --- a/util/mount.fuse.c
> +++ b/util/mount.fuse.c
> @@ -49,6 +49,9 @@
>  #endif
>  
>  #include "fuse.h"
> +#ifdef HAVE_SERVICEMOUNT
> +# include "mount_service.h"
> +#endif
>  
>  static char *progname;
>  
> @@ -233,6 +236,77 @@ static void drop_and_lock_capabilities(void)
>  }
>  #endif
>  
> +#ifdef HAVE_SERVICEMOUNT
> +#define FUSERVICEMOUNT_PROG	"fuservicemount3"
> +
> +static int try_service_main(char *argv0, char *type, char *source,
> +			    const char *mountpoint, char *options)
> +{
> +	const char *full_path = FUSERVICEMOUNT_DIR "/" FUSERVICEMOUNT_PROG;
> +	char **argv;
> +	char dash_o[] = "-o";
> +	char dash_t[] = "-t";
> +	char *mntpt = strdup(mountpoint);
> +	int argc = 5; /* argv[0], -t type, mountpoint, and trailing NULL */
> +	int i = 0;
> +	int ret;
> +
> +	if (!mount_service_present(type))
> +		return MOUNT_SERVICE_FALLBACK_NEEDED;
> +
> +	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;

Codex noted that some fuse servers actually don't have a "source"
argument.  If you invoke mount.fuse3 like so:

$ mount.fuse3 null# /tmp/mnt

Then the service activation fails because source is set to the null
terminator just pass the '#' in "null#", and then gets included in argv.

> +	argv[i++] = mntpt;
> +	argv[i++] = dash_t;
> +	argv[i++] = type;
> +	if (options) {
> +		argv[i++] = dash_o;
> +		argv[i++] = options;
> +	}
> +	argv[i] = 0;
> +
> +	if (getuid() != 0) {
> +		/*
> +		 * First try the install path, then a system install, just like
> +		 * we do for fusermount.
> +		 */
> +		ret = execvp(full_path, argv);

No need for the $PATH enabled version of execv if we're providing an
absolute path.

> +		if (ret)
> +			ret = execvp(FUSERVICEMOUNT_PROG, argv);

We shouldn't execvp directly here, because it's possible that the helper
will fail to connect to the service, in which case we could fall back to
the weird sh invocation code below.  Since posix_spawn() is already used
elsewhere, let's do that too.

--D

> +		if (ret) {
> +			fprintf(stderr, "%s: could not start %s helper: %s\n",
> +				argv[0], FUSERVICEMOUNT_PROG,
> +				strerror(errno));
> +			ret = MOUNT_SERVICE_FALLBACK_NEEDED;
> +		}
> +	} 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 +354,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 +375,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 +462,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 linux
>  		if (drop_privileges) {
> @@ -429,6 +507,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);
> diff --git a/util/mount_service.c b/util/mount_service.c
> index 0f7eae94ada377..f837595078f2d1 100644
> --- a/util/mount_service.c
> +++ b/util/mount_service.c
> @@ -1836,3 +1836,31 @@ int mount_service_main(int argc, char *argv[])
>  	mount_service_destroy(&mo);
>  	return ret;
>  }
> +
> +bool mount_service_present(const char *fstype)
> +{
> +	struct stat stbuf;
> +	char path[PATH_MAX];
> +	int ret;
> +
> +	snprintf(path, sizeof(path), FUSE_SERVICE_SOCKET_DIR "/%s", fstype);
> +	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;
> +}
> 
> 

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH 02/13] mount_service: add systemd/inetd socket service mounting helper
  2026-04-09 22:21 ` [PATCH 02/13] mount_service: add systemd/inetd socket service mounting helper Darrick J. Wong
  2026-04-14  1:00   ` Darrick J. Wong
  2026-04-14 23:48   ` Darrick J. Wong
@ 2026-04-17 23:19   ` Darrick J. Wong
  2 siblings, 0 replies; 24+ messages in thread
From: Darrick J. Wong @ 2026-04-17 23:19 UTC (permalink / raw)
  To: bschubert; +Cc: miklos, neal, linux-fsdevel, bernd, joannelkoong

On Thu, Apr 09, 2026 at 03:21:04PM -0700, Darrick J. Wong wrote:
> From: Darrick J. Wong <djwong@kernel.org>
> 
> 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.
> 
> Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
> ---
>  include/fuse_service.h                           |  243 ++++
>  include/fuse_service_priv.h                      |  133 ++
>  lib/mount_common_i.h                             |    3 
>  util/mount_service.h                             |   39 +
>  .github/workflows/install-ubuntu-dependencies.sh |   12 
>  doc/fuservicemount3.8                            |   24 
>  doc/meson.build                                  |    3 
>  include/meson.build                              |    4 
>  lib/fuse_service.c                               | 1099 +++++++++++++++++++
>  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                             | 1304 ++++++++++++++++++++++
>  19 files changed, 3132 insertions(+), 5 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
> 
> 
> diff --git a/include/fuse_service.h b/include/fuse_service.h
> new file mode 100644
> index 00000000000000..4ffd87a7fbe33c
> --- /dev/null
> +++ b/include/fuse_service.h
> @@ -0,0 +1,243 @@
> +/*
> + * FUSE: Filesystem in Userspace
> + * Copyright (C) 2025-2026 Oracle.
> + * Author: Darrick J. Wong <djwong@kernel.org>
> + *
> + * 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_mode(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.
> + *
> + * @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_ */
> diff --git a/include/fuse_service_priv.h b/include/fuse_service_priv.h
> new file mode 100644
> index 00000000000000..8df871ee117875
> --- /dev/null
> +++ b/include/fuse_service_priv.h
> @@ -0,0 +1,133 @@
> +/*
> + * FUSE: Filesystem in Userspace
> + * Copyright (C) 2025-2026 Oracle.
> + * Author: Darrick J. Wong <djwong@kernel.org>
> + *
> + * 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_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;
> +};
> +
> +struct fuse_service_hello_reply {
> +	struct fuse_service_packet p;
> +	uint16_t version;
> +};
> +
> +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_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_bye_command {
> +	struct fuse_service_packet p;
> +	uint32_t exitcode;
> +};
> +
> +struct fuse_service_mount_command {
> +	struct fuse_service_packet p;
> +	uint32_t ms_flags;
> +	uint16_t expected_fmt;
> +};
> +
> +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_ */
> diff --git a/lib/mount_common_i.h b/lib/mount_common_i.h
> index 6bcb055ff1c23f..631dff3e6f8aaf 100644
> --- a/lib/mount_common_i.h
> +++ b/lib/mount_common_i.h
> @@ -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_ */
> diff --git a/util/mount_service.h b/util/mount_service.h
> new file mode 100644
> index 00000000000000..f1f95e67ee1afe
> --- /dev/null
> +++ b/util/mount_service.h
> @@ -0,0 +1,39 @@
> +/*
> + * FUSE: Filesystem in Userspace
> + * Copyright (C) 2025-2026 Oracle.
> + * Author: Darrick J. Wong <djwong@kernel.org>
> + *
> + * 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.
> + *
> + * @param fstype full fuse filesystem type
> + * @return fuse subtype
> + */
> +const char *mount_service_subtype(const char *fstype);
> +
> +#endif /* MOUNT_SERVICE_H_ */
> diff --git a/.github/workflows/install-ubuntu-dependencies.sh b/.github/workflows/install-ubuntu-dependencies.sh
> index ef44b6a03eb742..e70a891dc7e3a4 100755
> --- a/.github/workflows/install-ubuntu-dependencies.sh
> +++ b/.github/workflows/install-ubuntu-dependencies.sh
> @@ -70,7 +70,9 @@ install_full() {
>          meson \
>          ninja-build \
>          python3 \
> -        python3-pip
> +        python3-pip \
> +        libsystemd-dev \
> +        systemd-dev
>  
>      echo "Installing Python test dependencies..."
>      pip install -r requirements.txt
> @@ -104,7 +106,9 @@ install_abicheck() {
>          meson \
>          ninja-build \
>          python3 \
> -        python3-pip
> +        python3-pip \
> +        libsystemd-dev \
> +        systemd-dev
>  }
>  
>  install_codeql() {
> @@ -115,7 +119,9 @@ install_codeql() {
>          ninja-build \
>          python3-pytest \
>          liburing-dev \
> -        libnuma-dev
> +        libnuma-dev \
> +        libsystemd-dev \
> +        systemd-dev
>  }
>  
>  install_cppcheck() {
> diff --git a/doc/fuservicemount3.8 b/doc/fuservicemount3.8
> new file mode 100644
> index 00000000000000..e45d6a89c8b81a
> --- /dev/null
> +++ b/doc/fuservicemount3.8
> @@ -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 <djwong@kernel.org>.
> +Debian GNU/Linux distribution.
> +.SH SEE ALSO
> +.BR fusermount3 (1)
> +.BR fusermount (1)
> +.BR mount (8)
> +.BR fuse (4)
> diff --git a/doc/meson.build b/doc/meson.build
> index db3e0b26f71975..c105cf3471fdf4 100644
> --- a/doc/meson.build
> +++ b/doc/meson.build
> @@ -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
> diff --git a/include/meson.build b/include/meson.build
> index bf671977a5a6a9..da51180f87eea2 100644
> --- a/include/meson.build
> +++ b/include/meson.build
> @@ -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')
> diff --git a/lib/fuse_service.c b/lib/fuse_service.c
> new file mode 100644
> index 00000000000000..b775727e7c91e2
> --- /dev/null
> +++ b/lib/fuse_service.c
> @@ -0,0 +1,1099 @@
> +/*
> + * FUSE: Filesystem in Userspace
> + * Copyright (C) 2025-2026 Oracle.
> + * Author: Darrick J. Wong <djwong@kernel.org>
> + *
> + * 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(int sockfd, 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(sockfd, &msg, MSG_TRUNC | MSG_CMSG_CLOEXEC);

All the iovec/msghdr boilerplate could also get refactored into helpers.

--D

> +	if (size < 0) {
> +		int error = errno;
> +
> +		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;
> +	}
> +
> +	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 int recv_requested_file(int sockfd, 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(sockfd, 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;
> +}
> +
> +int fuse_service_receive_file(struct fuse_service *sf, const char *path,
> +			      int *fdp)
> +{
> +	return recv_requested_file(sf->sockfd, path, fdp);
> +}
> +
> +#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 iovec iov = {
> +		.iov_len = sizeof_fuse_service_open_command(strlen(path)),
> +	};
> +	struct msghdr msg = {
> +		.msg_iov = &iov,
> +		.msg_iovlen = 1,
> +	};
> +	struct fuse_service_open_command *cmd;
> +	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, iov.iov_len);
> +	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);
> +	iov.iov_base = cmd;
> +
> +	size = sendmsg(sf->sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
> +	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),
> +	};
> +	struct iovec iov = {
> +		.iov_base = &c,
> +		.iov_len = sizeof(c),
> +	};
> +	struct msghdr msg = {
> +		.msg_iov = &iov,
> +		.msg_iovlen = 1,
> +	};
> +	ssize_t size;
> +
> +	/* already gone? */
> +	if (sf->sockfd < 0)
> +		return 0;
> +
> +	size = sendmsg(sf->sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
> +	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 find_socket_fd(int nr_fds)
> +{
> +	struct stat statbuf;
> +	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, &statbuf);
> +	if (ret) {
> +		int error = errno;
> +
> +		fuse_log(FUSE_LOG_ERR, "fuse: service socket: %s\n",
> +			 strerror(error));
> +		return -error;
> +	}
> +
> +	if (!S_ISSOCK(statbuf.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;
> +	}
> +
> +	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),
> +	};
> +	struct iovec iov = {
> +		.iov_base = &hello,
> +		.iov_len = sizeof(hello),
> +	};
> +	struct msghdr msg = {
> +		.msg_iov = &iov,
> +		.msg_iovlen = 1,
> +	};
> +	uint64_t flags;
> +	ssize_t size;
> +
> +	size = recvmsg(sf->sockfd, &msg, MSG_TRUNC);
> +	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_FLAG_ALLOW_OTHER)
> +		sf->allow_other = true;
> +
> +	iov.iov_base = &reply;
> +	iov.iov_len = sizeof(reply);
> +
> +	size = sendmsg(sf->sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
> +	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 = recv_requested_file(sf->sockfd, 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 = recv_requested_file(sf->sockfd, 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_string(struct fuse_service *sf, uint32_t command,
> +		       const char *value, int *error)
> +{
> +	struct fuse_service_simple_reply reply = { };
> +	struct iovec iov = {
> +		.iov_len = sizeof_fuse_service_string_command(strlen(value)),
> +	};
> +	struct msghdr msg = {
> +		.msg_iov = &iov,
> +		.msg_iovlen = 1,
> +	};
> +	struct fuse_service_string_command *cmd;
> +	ssize_t size;
> +
> +	cmd = malloc(iov.iov_len);
> +	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);
> +	iov.iov_base = cmd;
> +
> +	size = sendmsg(sf->sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
> +	if (size < 0) {
> +		int error = errno;
> +
> +		fuse_log(FUSE_LOG_ERR, "fuse: send service string: %s\n",
> +			 strerror(error));
> +		return -error;
> +	}
> +	free(cmd);
> +
> +	iov.iov_base = &reply;
> +	iov.iov_len = sizeof(reply);
> +	size = recvmsg(sf->sockfd, &msg, MSG_TRUNC);
> +	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;
> +	}
> +
> +	*error = ntohl(reply.error);
> +	return 0;
> +}
> +
> +static int send_mount(struct fuse_service *sf, unsigned int ms_flags,
> +		      mode_t expected_fmt, int *error)
> +{
> +	struct fuse_service_simple_reply reply = { };
> +	struct fuse_service_mount_command c = {
> +		.p.magic = htonl(FUSE_SERVICE_MOUNT_CMD),
> +		.ms_flags = htonl(ms_flags),
> +		.expected_fmt = htons(expected_fmt),
> +	};
> +	struct iovec iov = {
> +		.iov_base = &c,
> +		.iov_len = sizeof(c),
> +	};
> +	struct msghdr msg = {
> +		.msg_iov = &iov,
> +		.msg_iovlen = 1,
> +	};
> +	ssize_t size;
> +
> +	size = sendmsg(sf->sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
> +	if (size < 0) {
> +		int error = errno;
> +
> +		fuse_log(FUSE_LOG_ERR, "fuse: send service mount command: %s\n",
> +			 strerror(error));
> +		return -error;
> +	}
> +
> +	iov.iov_base = &reply;
> +	iov.iov_len = sizeof(reply);
> +	size = recvmsg(sf->sockfd, &msg, MSG_TRUNC);
> +	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;
> +	}
> +
> +	*error = ntohl(reply.error);
> +	return 0;
> +}
> +
> +void fuse_service_expect_mount_mode(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;
> +
> +	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 */
> +	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_string(sf, FUSE_SERVICE_FSOPEN_CMD, 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_string(sf, FUSE_SERVICE_MNTPT_CMD, 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), expected_fmt, &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
> +	 */
> +	opts->foreground = 1;
> +
> +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),
> +	};
> +	struct iovec iov = {
> +		.iov_base = &c,
> +		.iov_len = sizeof(c),
> +	};
> +	struct msghdr msg = {
> +		.msg_iov = &iov,
> +		.msg_iovlen = 1,
> +	};
> +	ssize_t size;
> +
> +	/* already gone? */
> +	if (sf->sockfd < 0)
> +		return 0;
> +
> +	size = sendmsg(sf->sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
> +	if (size < 0) {
> +		int error = errno;
> +
> +		fuse_log(FUSE_LOG_ERR, "fuse: send service unmount: %s\n",
> +			 strerror(error));
> +		return -error;
> +	}
> +
> +	iov.iov_base = &reply;
> +	iov.iov_len = sizeof(reply);
> +	size = recvmsg(sf->sockfd, &msg, MSG_TRUNC);
> +	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 = malloc(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;
> +}
> diff --git a/lib/fuse_service_stub.c b/lib/fuse_service_stub.c
> new file mode 100644
> index 00000000000000..4c7e0fabae7343
> --- /dev/null
> +++ b/lib/fuse_service_stub.c
> @@ -0,0 +1,106 @@
> +/*
> + * FUSE: Filesystem in Userspace
> + * Copyright (C) 2025-2026 Oracle.
> + * Author: Darrick J. Wong <djwong@kernel.org>
> + *
> + * 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_mode(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;
> +}
> diff --git a/lib/fuse_versionscript b/lib/fuse_versionscript
> index cce09610316f4b..aa1912c76fb715 100644
> --- a/lib/fuse_versionscript
> +++ b/lib/fuse_versionscript
> @@ -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_mode;
> +		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:
> diff --git a/lib/helper.c b/lib/helper.c
> index 74906fdcbd76d9..819b9a6e4d243c 100644
> --- a/lib/helper.c
> +++ b/lib/helper.c
> @@ -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
>   */
> diff --git a/lib/meson.build b/lib/meson.build
> index fcd95741c9d374..d9a902f74b558f 100644
> --- a/lib/meson.build
> +++ b/lib/meson.build
> @@ -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)
> diff --git a/lib/mount.c b/lib/mount.c
> index 2397c3fb2aa26b..952d8899dcf218 100644
> --- a/lib/mount.c
> +++ b/lib/mount.c
> @@ -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;
> +}
> diff --git a/meson.build b/meson.build
> index 80c5f1dc0bd356..66425a0d4cc16f 100644
> --- a/meson.build
> +++ b/meson.build
> @@ -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
>  #
> diff --git a/meson_options.txt b/meson_options.txt
> index c1f8fe69467184..193a74c96d0676 100644
> --- a/meson_options.txt
> +++ b/meson_options.txt
> @@ -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))')
> diff --git a/util/fuservicemount.c b/util/fuservicemount.c
> new file mode 100644
> index 00000000000000..9c694a4290f94e
> --- /dev/null
> +++ b/util/fuservicemount.c
> @@ -0,0 +1,18 @@
> +/*
> + * FUSE: Filesystem in Userspace
> + * Copyright (C) 2025-2026 Oracle.
> + * Author: Darrick J. Wong <djwong@kernel.org>
> + *
> + * 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);
> +}
> diff --git a/util/meson.build b/util/meson.build
> index 0e4b1cce95377e..04ea5ac201340d 100644
> --- a/util/meson.build
> +++ b/util/meson.build
> @@ -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 ],
> diff --git a/util/mount_service.c b/util/mount_service.c
> new file mode 100644
> index 00000000000000..abe88a0710255b
> --- /dev/null
> +++ b/util/mount_service.c
> @@ -0,0 +1,1304 @@
> +/*
> + * FUSE: Filesystem in Userspace
> + * Copyright (C) 2025-2026 Oracle.
> + * Author: Darrick J. Wong <djwong@kernel.org>
> + *
> + * 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;
> +
> +	/* alleged fuse subtype based on -t cli argument */
> +	const char *subtype;
> +
> +	/* full fuse filesystem type we give to mount() */
> +	char *fstype;
> +
> +	/* 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;
> +};
> +
> +/* Filter out the subtype of the filesystem (e.g. fuse.Y[.Z] -> Y[.Z]) */
> +const char *mount_service_subtype(const char *fstype)
> +{
> +	if (!strncmp(fstype, "fuse.", 5))
> +		return fstype + 5;
> +	if (!strncmp(fstype, "fuseblk.", 8))
> +		return fstype + 8;
> +	return fstype;
> +}
> +
> +static int mount_service_init(struct mount_service *mo, int argc, char *argv[])
> +{
> +	char *fstype = NULL;
> +	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;
> +	}
> +
> +	mo->subtype = mount_service_subtype(fstype);
> +	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 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) {
> +		fprintf(stderr, "%s: opening %s service socket: %s\n",
> +			mo->msgtag, mo->subtype, strerror(errno));
> +		return -1;
> +	}
> +
> +	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) {
> +		fprintf(stderr, "%s: %s: %s\n",
> +			mo->msgtag, name.sun_path, strerror(errno));
> +		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 = { };
> +	struct iovec iov = {
> +		.iov_base = &hello,
> +		.iov_len = sizeof(hello),
> +	};
> +	struct msghdr msg = {
> +		.msg_iov = &iov,
> +		.msg_iovlen = 1,
> +	};
> +	ssize_t size;
> +
> +	if (getuid() == 0)
> +		hello.flags |= htonl(FUSE_SERVICE_FLAG_ALLOW_OTHER);
> +
> +	size = sendmsg(mo->sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
> +	if (size < 0) {
> +		fprintf(stderr, "%s: send hello: %s\n",
> +			mo->msgtag, strerror(errno));
> +		return -1;
> +	}
> +
> +	iov.iov_base = &reply;
> +	iov.iov_len = sizeof(reply);
> +
> +	size = recvmsg(mo->sockfd, &msg, MSG_TRUNC);
> +	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 %zd\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;
> +	}
> +
> +	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 %zd\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 %zd\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 %zd\n",
> +			mo->msgtag, written, sizeof(args));
> +		return -1;
> +	}
> +
> +	return 0;
> +}
> +
> +static ssize_t __send_fd(int sockfd, 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(sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
> +}
> +
> +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 = malloc(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->sockfd, 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 %zd\n",
> +			mo->msgtag, written, req_sz);
> +		ret = -1;
> +		goto out_req;
> +	}
> +
> +out_req:
> +	free(req);
> +	return ret;
> +}
> +
> +static ssize_t __send_packet(int sockfd, void *buf, ssize_t buflen)
> +{
> +	struct iovec iov = {
> +		.iov_base = buf,
> +		.iov_len = buflen,
> +	};
> +	struct msghdr msg = {
> +		.msg_iov = &iov,
> +		.msg_iovlen = 1,
> +	};
> +
> +	return sendmsg(sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
> +}
> +
> +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 = malloc(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->sockfd, 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 %zd\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) {
> +		fprintf(stderr, "%s: %s: %s\n",
> +			mo->msgtag, fusedev, strerror(errno));
> +		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 iovec iov = {
> +	};
> +	struct msghdr msg = {
> +		.msg_iov = &iov,
> +		.msg_iovlen = 1,
> +	};
> +	struct fuse_service_packet *command;
> +	ssize_t size;
> +
> +	size = recvmsg(mo->sockfd, &msg, MSG_PEEK | MSG_TRUNC);
> +	if (size < 0) {
> +		fprintf(stderr, "%s: peek service command: %s\n",
> +			mo->msgtag, strerror(errno));
> +		return -1;
> +	}
> +	if (size == 0) {
> +		/* fuse server probably exited early */
> +		fprintf(stderr, "%s: fuse server exited without saying goodbye!\n",
> +			mo->msgtag);
> +		return -1;
> +	}
> +	if (size < sizeof(struct fuse_service_packet)) {
> +		fprintf(stderr, "%s: wrong command packet size %zd, expected at least %zd\n",
> +			mo->msgtag, size, sizeof(struct fuse_service_packet));
> +		return -1;
> +	}
> +	if (size > 32768) {
> +		fprintf(stderr, "%s: wrong command packet size %zd, expected less than %d\n",
> +			mo->msgtag, size, 32768);
> +		return -1;
> +	}
> +
> +	command = calloc(1, size + 1);
> +	if (!command) {
> +		fprintf(stderr, "%s: alloc service command: %s\n",
> +			mo->msgtag, strerror(errno));
> +		return -1;
> +	}
> +	iov.iov_base = command;
> +	iov.iov_len = size;
> +
> +	size = recvmsg(mo->sockfd, &msg, MSG_TRUNC);
> +	if (size < 0) {
> +		fprintf(stderr, "%s: receive service command: %s\n",
> +			mo->msgtag, strerror(errno));
> +		free(command);
> +		return -1;
> +	}
> +	if (size != iov.iov_len) {
> +		fprintf(stderr, "%s: wrong service command size %zd, expected %zd\n",
> +			mo->msgtag,
> +			size, iov.iov_len);
> +		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),
> +	};
> +	struct iovec iov = {
> +		.iov_base = &reply,
> +		.iov_len = sizeof(reply),
> +	};
> +	struct msghdr msg = {
> +		.msg_iov = &iov,
> +		.msg_iovlen = 1,
> +	};
> +	ssize_t size;
> +
> +	size = sendmsg(mo->sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
> +	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 statbuf;
> +	int ret;
> +
> +	ret = fstat(fd, &statbuf);
> +	if (ret) {
> +		int error = errno;
> +
> +		fprintf(stderr, "%s: %s: %s\n",
> +			mo->msgtag, oc->path, strerror(error));
> +		return -error;
> +	}
> +
> +	if (!S_ISBLK(statbuf.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 inline bool check_null_endbyte(const void *p, size_t psz)
> +{
> +	return *((const char *)p + psz - 1) == 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 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);
> +	}
> +
> +	fd = open(oc->path, ntohl(oc->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 int mount_service_handle_fsopen_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: fsopen command too small\n",
> +			mo->msgtag);
> +		return mount_service_send_reply(mo, EINVAL);
> +	}
> +
> +	if (!check_null_endbyte(p, psz)) {
> +		fprintf(stderr, "%s: fsopen command must be null terminated\n",
> +			mo->msgtag);
> +		return mount_service_send_reply(mo, EINVAL);
> +	}
> +
> +	if (mo->fstype) {
> +		fprintf(stderr, "%s: fstype respecified!\n",
> +			mo->msgtag);
> +		return mount_service_send_reply(mo, EINVAL);
> +	}
> +
> +	mo->fstype = strdup(oc->value);
> +	if (!mo->fstype) {
> +		int error = errno;
> +
> +		fprintf(stderr, "%s: alloc fstype 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_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, char *mntpt)
> +{
> +	struct stat statbuf;
> +	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;
> +	}
> +
> +	ret = fstat(mountfd, &statbuf);
> +	if (ret) {
> +		error = errno;
> +		fprintf(stderr, "%s: %s: %s\n", mo->msgtag, mntpt,
> +			strerror(error));
> +		goto out_mountfd;
> +	}
> +
> +	if (!S_ISDIR(statbuf.st_mode) && !S_ISREG(statbuf.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",
> +			mo->msgtag, mntpt);
> +		goto out_mountfd;
> +	}
> +
> +	switch (statbuf.st_mode & S_IFMT) {
> +	case S_IFREG:
> +		/*
> +		 * This is a regular file, so we point mount() at the open file
> +		 * descriptor.
> +		 */
> +		asprintf(&mo->real_mountpoint, "/dev/fd/%d", mountfd);
> +		break;
> +	case S_IFDIR:
> +		/*
> +		 * Pin the mount so it can't go anywhere.  This only works for
> +		 * directories, which is fortunately the common case.
> +		 */
> +		ret = fchdir(mountfd);
> +		if (ret) {
> +			error = errno;
> +			fprintf(stderr, "%s: %s: %s\n", mo->msgtag, mntpt,
> +				strerror(error));
> +			goto out_res_mntpt;
> +		}
> +
> +		/*
> +		 * Now that we're sitting on the mountpoint directory, we can
> +		 * pass "." to mount() and avoid races with directory tree
> +		 * mutations.
> +		 */
> +		mo->real_mountpoint = strdup(".");
> +		break;
> +	default:
> +		/* Should never get here */
> +		error = EINVAL;
> +		goto out_res_mntpt;
> +	}
> +	if (!mo->real_mountpoint) {
> +		error = ENOMEM;
> +		fprintf(stderr, "%s: %s: %s\n", mo->msgtag, mntpt,
> +			strerror(error));
> +		goto out_res_mntpt;
> +	}
> +
> +	mo->mountpoint = mntpt;
> +	mo->mountfd = mountfd;
> +	mo->resv_mountpoint = res_mntpt;
> +
> +	return mount_service_send_reply(mo, 0);
> +
> +out_res_mntpt:
> +	free(res_mntpt);
> +out_mountfd:
> +	close(mountfd);
> +out_error:
> +	free(mntpt);
> +	return mount_service_send_reply(mo, error);
> +}
> +
> +static int mount_service_handle_mountpoint_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);
> +	char *mntpt;
> +
> +	if (psz < sizeof_fuse_service_string_command(1)) {
> +		fprintf(stderr, "%s: mount point command too small\n",
> +			mo->msgtag);
> +		return mount_service_send_reply(mo, EINVAL);
> +	}
> +
> +	if (!check_null_endbyte(p, psz)) {
> +		fprintf(stderr, "%s: mount point command must be null terminated\n",
> +			mo->msgtag);
> +		return mount_service_send_reply(mo, EINVAL);
> +	}
> +
> +	if (mo->mountpoint) {
> +		fprintf(stderr, "%s: mount point respecified!\n",
> +			mo->msgtag);
> +		return mount_service_send_reply(mo, EINVAL);
> +	}
> +
> +	mntpt = strdup(oc->value);
> +	if (!mntpt) {
> +		int error = errno;
> +
> +		fprintf(stderr, "%s: alloc mount point string: %s\n",
> +			mo->msgtag, strerror(error));
> +		return mount_service_send_reply(mo, error);
> +	}
> +
> +	return attach_to_mountpoint(mo, mntpt);
> +}
> +
> +static inline int format_libfuse_mntopts(char *buf, size_t bufsz,
> +					 const struct mount_service *mo,
> +					 const struct stat *statbuf)
> +{
> +	if (mo->mntopts)
> +		return snprintf(buf, bufsz,
> +				"%s,fd=%i,rootmode=%o,user_id=%u,group_id=%u",
> +				mo->mntopts, mo->fusedevfd,
> +				statbuf->st_mode & S_IFMT,
> +				getuid(), getgid());
> +
> +	return snprintf(buf, bufsz,
> +			"fd=%i,rootmode=%o,user_id=%u,group_id=%u",
> +			mo->fusedevfd, statbuf->st_mode & S_IFMT,
> +			getuid(), getgid());
> +}
> +
> +static int mount_service_regular_mount(struct mount_service *mo,
> +				       struct fuse_service_mount_command *oc,
> +				       struct stat *stbuf)
> +{
> +	char *realmopts;
> +	int ret;
> +
> +	/* Compute the amount of buffer space needed for the mount options */
> +	ret = format_libfuse_mntopts(NULL, 0, mo, stbuf);
> +	if (ret < 0) {
> +		int error = errno;
> +
> +		fprintf(stderr, "%s: mount option preformatting: %s\n",
> +			mo->msgtag, strerror(error));
> +		return mount_service_send_reply(mo, error);
> +	}
> +
> +	realmopts = malloc(ret + 1);
> +	if (!realmopts) {
> +		int error = errno;
> +
> +		fprintf(stderr, "%s: alloc real mount options string: %s\n",
> +			mo->msgtag, strerror(error));
> +		return mount_service_send_reply(mo, error);
> +	}
> +
> +	ret = format_libfuse_mntopts(realmopts, ret + 1, mo, stbuf);
> +	if (ret < 0) {
> +		int error = errno;
> +
> +		free(realmopts);
> +		fprintf(stderr, "%s: mount options formatting: %s\n",
> +			mo->msgtag, strerror(error));
> +		return mount_service_send_reply(mo, error);
> +	}
> +
> +	ret = mount(mo->source, mo->real_mountpoint, mo->fstype,
> +		    ntohl(oc->ms_flags), realmopts);
> +	free(realmopts);
> +	if (ret) {
> +		int error = errno;
> +
> +		fprintf(stderr, "%s: mount: %s\n",
> +			mo->msgtag, strerror(error));
> +		return mount_service_send_reply(mo, error);
> +	}
> +
> +	mo->mounted = true;
> +	return mount_service_send_reply(mo, 0);
> +}
> +
> +static int mount_service_handle_mount_cmd(struct mount_service *mo,
> +					  struct fuse_service_packet *p,
> +					  size_t psz)
> +{
> +	struct stat stbuf;
> +	struct fuse_service_mount_command *oc =
> +			container_of(p, struct fuse_service_mount_command, p);
> +	int ret;
> +
> +	if (psz != sizeof(struct fuse_service_mount_command)) {
> +		fprintf(stderr, "%s: mount command wrong size\n",
> +			mo->msgtag);
> +		return mount_service_send_reply(mo, EINVAL);
> +	}
> +
> +	if (!mo->fstype) {
> +		fprintf(stderr, "%s: missing mount type parameter\n",
> +			mo->msgtag);
> +		return mount_service_send_reply(mo, EINVAL);
> +	}
> +
> +	if (!mo->source) {
> +		fprintf(stderr, "%s: missing mount source parameter\n",
> +			mo->msgtag);
> +		return mount_service_send_reply(mo, EINVAL);
> +	}
> +
> +	if (!mo->mountpoint) {
> +		fprintf(stderr, "%s: missing mount point parameter\n",
> +			mo->msgtag);
> +		return mount_service_send_reply(mo, EINVAL);
> +	}
> +
> +	/*
> +	 * 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(mo->mountfd, &stbuf);
> +	if (ret < 0) {
> +		int error = errno;
> +
> +		fprintf(stderr, "%s: %s: %s\n",
> +			mo->msgtag, mo->mountpoint, strerror(error));
> +		return mount_service_send_reply(mo, error);
> +	}
> +
> +	/* Make sure the mountpoint type matches what the caller wanted */
> +	switch (ntohs(oc->expected_fmt)) {
> +	case S_IFDIR:
> +		if (!S_ISDIR(stbuf.st_mode)) {
> +			fprintf(stderr, "%s: %s: %s\n",
> +				mo->msgtag, mo->mountpoint, strerror(ENOTDIR));
> +			return mount_service_send_reply(mo, ENOTDIR);
> +		}
> +		break;
> +	case S_IFREG:
> +		if (!S_ISREG(stbuf.st_mode)) {
> +			fprintf(stderr, "%s: %s: %s\n",
> +				mo->msgtag, mo->mountpoint, strerror(EISDIR));
> +			return mount_service_send_reply(mo, EISDIR);
> +		}
> +		break;
> +	case 0:
> +		/* don't care */
> +		break;
> +	default:
> +		fprintf(stderr, "%s: %s: weird expected format 0%o\n",
> +			mo->msgtag, mo->mountpoint, ntohs(oc->expected_fmt));
> +		return mount_service_send_reply(mo, EINVAL);
> +	}
> +
> +	return mount_service_regular_mount(mo, oc, &stbuf);
> +}
> +
> +static int mount_service_handle_unmount_cmd(struct mount_service *mo,
> +					    struct fuse_service_packet *p,
> +					    size_t psz)
> +{
> +	int ret;
> +
> +	(void)p;
> +
> +	if (psz != sizeof(struct fuse_service_unmount_command)) {
> +		fprintf(stderr, "%s: unmount command wrong size\n",
> +			mo->msgtag);
> +		return mount_service_send_reply(mo, EINVAL);
> +	}
> +
> +	if (!mo->mounted) {
> +		fprintf(stderr, "%s: will not umount before successful mount!\n",
> +			mo->msgtag);
> +		return mount_service_send_reply(mo, EINVAL);
> +	}
> +
> +	ret = chdir("/");
> +	if (ret) {
> +		int error = errno;
> +
> +		fprintf(stderr, "%s: fuse server failed chdir: %s\n",
> +			mo->msgtag, strerror(error));
> +		return mount_service_send_reply(mo, error);
> +	}
> +
> +	close(mo->mountfd);
> +	mo->mountfd = -1;
> +
> +	/*
> +	 * Try to unmount the resolved mountpoint, and hope that we're not the
> +	 * victim of a race.
> +	 */
> +	ret = umount2(mo->resv_mountpoint, MNT_DETACH);
> +	if (ret) {
> +		int error = errno;
> +
> +		fprintf(stderr, "%s: fuse server failed unmount: %s\n",
> +			mo->msgtag, strerror(error));
> +		return mount_service_send_reply(mo, error);
> +	}
> +
> +	mo->mounted = false;
> +	return mount_service_send_reply(mo, 0);
> +}
> +
> +static int mount_service_handle_bye_cmd(struct mount_service *mo,
> +					struct fuse_service_packet *p,
> +					size_t psz)
> +{
> +	struct fuse_service_bye_command *bc =
> +			container_of(p, struct fuse_service_bye_command, p);
> +	int ret;
> +
> +	if (psz != sizeof(struct fuse_service_bye_command)) {
> +		fprintf(stderr, "%s: bye command wrong size\n",
> +			mo->msgtag);
> +		return mount_service_send_reply(mo, EINVAL);
> +	}
> +
> +	ret = ntohl(bc->exitcode);
> +	if (ret)
> +		fprintf(stderr, "%s: fuse server failed mount, check dmesg/logs for details.\n",
> +			mo->msgtag);
> +
> +	return ret;
> +}
> +
> +static void mount_service_destroy(struct mount_service *mo)
> +{
> +	close(mo->mountfd);
> +	close(mo->fusedevfd);
> +	close(mo->argvfd);
> +	shutdown(mo->sockfd, SHUT_RDWR);
> +	close(mo->sockfd);
> +
> +	free(mo->source);
> +	free(mo->mountpoint);
> +	free(mo->real_mountpoint);
> +	free(mo->resv_mountpoint);
> +	free(mo->mntopts);
> +	free(mo->fstype);
> +
> +	memset(mo, 0, sizeof(*mo));
> +	mo->sockfd = -1;
> +	mo->argvfd = -1;
> +	mo->fusedevfd = -1;
> +	mo->mountfd = -1;
> +}
> +
> +int mount_service_main(int argc, char *argv[])
> +{
> +	const char *fusedev = fuse_mnt_get_devname();
> +	struct mount_service mo = { };
> +	bool running = true;
> +	int ret;
> +
> +	if (argc < 3 || !strcmp(argv[1], "--help")) {
> +		printf("Usage: %s source mountpoint -t type [-o options]\n",
> +				argv[0]);
> +		return EXIT_FAILURE;
> +	}
> +
> +	if (argc > 0 && argv[0])
> +		mo.msgtag = argv[0];
> +	else
> +		mo.msgtag = "mount.service";
> +
> +	ret = mount_service_init(&mo, argc, argv);
> +	if (ret)
> +		return EXIT_FAILURE;
> +
> +	ret = mount_service_connect(&mo);
> +	if (ret == MOUNT_SERVICE_FALLBACK_NEEDED)
> +		goto out;
> +	if (ret) {
> +		ret = EXIT_FAILURE;
> +		goto out;
> +	}
> +
> +	ret = mount_service_send_hello(&mo);
> +	if (ret) {
> +		ret = EXIT_FAILURE;
> +		goto out;
> +	}
> +
> +	ret = mount_service_capture_args(&mo, argc, argv);
> +	if (ret) {
> +		ret = EXIT_FAILURE;
> +		goto out;
> +	}
> +
> +	ret = mount_service_send_required_files(&mo, fusedev);
> +	if (ret) {
> +		ret = EXIT_FAILURE;
> +		goto out;
> +	}
> +
> +	while (running) {
> +		struct fuse_service_packet *p = NULL;
> +		size_t sz;
> +
> +		ret = mount_service_receive_command(&mo, &p, &sz);
> +		if (ret) {
> +			ret = EXIT_FAILURE;
> +			goto out;
> +		}
> +
> +		switch (ntohl(p->magic)) {
> +		case FUSE_SERVICE_OPEN_CMD:
> +			ret = mount_service_handle_open_cmd(&mo, p, sz);
> +			break;
> +		case FUSE_SERVICE_OPEN_BDEV_CMD:
> +			ret = mount_service_handle_open_bdev_cmd(&mo, p, sz);
> +			break;
> +		case FUSE_SERVICE_FSOPEN_CMD:
> +			ret = mount_service_handle_fsopen_cmd(&mo, p, sz);
> +			break;
> +		case FUSE_SERVICE_SOURCE_CMD:
> +			ret = mount_service_handle_source_cmd(&mo, p, sz);
> +			break;
> +		case FUSE_SERVICE_MNTOPTS_CMD:
> +			ret = mount_service_handle_mntopts_cmd(&mo, p, sz);
> +			break;
> +		case FUSE_SERVICE_MNTPT_CMD:
> +			ret = mount_service_handle_mountpoint_cmd(&mo, p, sz);
> +			break;
> +		case FUSE_SERVICE_MOUNT_CMD:
> +			ret = mount_service_handle_mount_cmd(&mo, p, sz);
> +			break;
> +		case FUSE_SERVICE_UNMOUNT_CMD:
> +			ret = mount_service_handle_unmount_cmd(&mo, p, sz);
> +			break;
> +		case FUSE_SERVICE_BYE_CMD:
> +			ret = mount_service_handle_bye_cmd(&mo, p, sz);
> +			free(p);
> +			goto out;
> +		default:
> +			fprintf(stderr, "%s: unrecognized packet 0x%x\n",
> +				mo.msgtag, ntohl(p->magic));
> +			ret = EXIT_FAILURE;
> +			break;
> +		}
> +		free(p);
> +
> +		if (ret) {
> +			ret = EXIT_FAILURE;
> +			goto out;
> +		}
> +	}
> +
> +	ret = EXIT_SUCCESS;
> +out:
> +	mount_service_destroy(&mo);
> +	return ret;
> +}
> 
> 

^ permalink raw reply	[flat|nested] 24+ messages in thread

end of thread, other threads:[~2026-04-17 23:19 UTC | newest]

Thread overview: 24+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-09 22:20 [PATCHSET v4] libfuse: run fuse servers as a contained service Darrick J. Wong
2026-04-09 22:20 ` [PATCH 01/13] Refactor mount code / move common functions to mount_util.c Darrick J. Wong
2026-04-09 22:21 ` [PATCH 02/13] mount_service: add systemd/inetd socket service mounting helper Darrick J. Wong
2026-04-14  1:00   ` Darrick J. Wong
2026-04-14 23:48   ` Darrick J. Wong
2026-04-17 23:19   ` Darrick J. Wong
2026-04-09 22:21 ` [PATCH 03/13] mount_service: create high level fuse helpers Darrick J. Wong
2026-04-14 23:58   ` Darrick J. Wong
2026-04-09 22:21 ` [PATCH 04/13] mount_service: use the new mount api for the mount service Darrick J. Wong
2026-04-17 22:03   ` Darrick J. Wong
2026-04-09 22:21 ` [PATCH 05/13] mount_service: update mtab after a successful mount Darrick J. Wong
2026-04-09 22:22 ` [PATCH 06/13] util: hoist the fuse.conf parsing and setuid mode enforcement code Darrick J. Wong
2026-04-09 22:22 ` [PATCH 07/13] util: fix checkpatch complaints in fuser_conf.[ch] Darrick J. Wong
2026-04-09 22:22 ` [PATCH 08/13] mount_service: enable unprivileged users in the same manner as fusermount Darrick J. Wong
2026-04-14 23:53   ` Darrick J. Wong
2026-04-17 22:01     ` Darrick J. Wong
2026-04-09 22:22 ` [PATCH 09/13] mount.fuse3: integrate systemd service startup Darrick J. Wong
2026-04-17 22:41   ` Darrick J. Wong
2026-04-09 22:23 ` [PATCH 10/13] mount_service: allow installation as a setuid program Darrick J. Wong
2026-04-09 22:23 ` [PATCH 11/13] example/service_ll: create a sample systemd service fuse server Darrick J. Wong
2026-04-14 23:56   ` Darrick J. Wong
2026-04-17 21:56   ` Darrick J. Wong
2026-04-09 22:23 ` [PATCH 12/13] example/service: create a sample systemd service for a high-level " Darrick J. Wong
2026-04-09 22:23 ` [PATCH 13/13] nullfs: support fuse systemd service mode Darrick J. Wong

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox