Re: [PATCH 09/13] mount.fuse3: integrate systemd service startup

Darrick J. Wong
2026-04-17T22:41:44Z

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;
> +}
>
>