mount.fuse3: integrate systemd service startup

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

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
This commit is contained in:
Darrick J. Wong
2026-03-04 13:48:55 -08:00
committed by Bernd Schubert
parent 96702c5b80
commit f127c2de34
7 changed files with 268 additions and 28 deletions
+9 -1
View File
@@ -7,12 +7,20 @@ fuservicemount3 \- mount a FUSE filesystem that runs as a system socket service
.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>.
+2 -1
View File
@@ -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))
+47
View File
@@ -9,10 +9,57 @@
* This program wraps the mounting of FUSE filesystems that run in systemd
*/
#define _GNU_SOURCE
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "fuse_config.h"
#include "mount_service.h"
static int check_service(const char *fstype)
{
if (!fstype) {
fprintf(stderr,
"fuservicemount: expected fs type for --check\n");
return EXIT_FAILURE;
}
return mount_service_present(fstype) ? EXIT_SUCCESS : EXIT_FAILURE;
}
int main(int argc, char *argv[])
{
char *fstype = NULL;
bool check = false;
int i;
/*
* If the user passes us exactly the args -t FSTYPE --check then
* we'll just check if there's a service for the FSTYPE fuse server.
* This doesn't tell us if the listening socket is actually connected
* to anything.
*/
for (i = 1; i < argc; i++) {
if (!strcmp(argv[i], "--check")) {
if (check) {
check = false;
break;
}
check = true;
} else if (!strcmp(argv[i], "-t") && i + 1 < argc) {
if (fstype) {
check = false;
break;
}
fstype = argv[i + 1];
i++;
} else {
check = false;
break;
}
}
if (check)
return check_service(fstype);
return mount_service_main(argc, argv);
}
+10 -5
View File
@@ -7,22 +7,27 @@ executable('fusermount3', ['fusermount.c', '../lib/mount_util.c', '../lib/mount_
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')
+148 -21
View File
@@ -6,6 +6,9 @@
See the file GPL2.txt.
*/
/* For environ */
#define _GNU_SOURCE
#include "fuse_config.h"
#include <stdio.h>
@@ -17,6 +20,9 @@
#include <fcntl.h>
#include <pwd.h>
#include <sys/wait.h>
#ifdef HAVE_SERVICEMOUNT
#include <spawn.h>
#endif
#ifdef linux
#include <sys/prctl.h>
@@ -49,6 +55,9 @@
#endif
#include "fuse.h"
#ifdef HAVE_SERVICEMOUNT
# include "mount_service.h"
#endif
static char *progname;
@@ -233,6 +242,105 @@ static void drop_and_lock_capabilities(void)
}
#endif
#ifdef HAVE_SERVICEMOUNT
#define FUSERVICEMOUNT_PROG "fuservicemount3"
static int mount_service_child(char **argv)
{
const char *full_path = FUSERVICEMOUNT_DIR "/" FUSERVICEMOUNT_PROG;
pid_t child_pid;
int child_status;
int ret;
/*
* First try the install path, then a system install, just like we do
* for fusermount. See man 7 environ for the global environ pointer.
*/
ret = posix_spawn(&child_pid, full_path, NULL, NULL,
(char *const *)argv, environ);
if (ret)
ret = posix_spawnp(&child_pid, FUSERVICEMOUNT_PROG, NULL, NULL,
(char * const *)argv, environ);
if (ret) {
fprintf(stderr, "%s: could not start %s helper: %s\n",
argv[0], FUSERVICEMOUNT_PROG, strerror(ret));
return MOUNT_SERVICE_FALLBACK_NEEDED;
}
ret = waitpid(child_pid, &child_status, 0);
if (ret < 0) {
fprintf(stderr, "%s: could not wait for %s helper: %s\n",
argv[0], FUSERVICEMOUNT_PROG, strerror(errno));
return MOUNT_SERVICE_FALLBACK_NEEDED;
}
if (WIFEXITED(child_status))
return WEXITSTATUS(child_status);
/* terminated due to signal or coredump */
return EXIT_FAILURE;
}
static int try_service_main(char *argv0, char *fstype, char *source,
const char *mountpoint, char *options)
{
char **argv;
char dash_o[] = "-o";
char dash_t[] = "-t";
char *mntpt;
int argc = 5; /* argv[0], -t type, mountpoint, and trailing NULL */
int i = 0;
int ret;
if (!mount_service_present(fstype))
return MOUNT_SERVICE_FALLBACK_NEEDED;
/* This can be an empty string if "mount.fuse3 null# /tmp/a" */
if (source && source[0] == 0)
source = NULL;
mntpt = strdup(mountpoint);
if (!mntpt) {
perror("mountpoint allocation");
return -1;
}
if (source)
argc++;
if (options)
argc += 2;
argv = calloc(argc, sizeof(char *));
if (!argv) {
perror("argv allocation");
free(mntpt);
return -1;
}
argv[i++] = argv0;
if (source)
argv[i++] = source;
argv[i++] = mntpt;
argv[i++] = dash_t;
argv[i++] = fstype;
if (options) {
argv[i++] = dash_o;
argv[i++] = options;
}
argv[i] = 0;
if (getuid() != 0) {
ret = mount_service_child(argv);
} else {
/* We're root, just do the mount directly. */
ret = mount_service_main(argc - 1, argv);
}
free(argv);
free(mntpt);
return ret;
}
#endif
int main(int argc, char *argv[])
{
char *type = NULL;
@@ -280,9 +388,7 @@ int main(int argc, char *argv[])
mountpoint = argv[2];
for (i = 3; i < argc; i++) {
if (strcmp(argv[i], "-v") == 0) {
continue;
} else if (strcmp(argv[i], "-t") == 0) {
if (strcmp(argv[i], "-t") == 0) {
i++;
if (i == argc) {
@@ -303,6 +409,30 @@ int main(int argc, char *argv[])
progname);
exit(1);
}
}
}
if (!type) {
if (source) {
dup_source = xstrdup(source);
type = dup_source;
source = strchr(type, '#');
if (source)
*source++ = '\0';
if (!type[0]) {
fprintf(stderr, "%s: empty filesystem type\n",
progname);
exit(1);
}
} else {
fprintf(stderr, "%s: empty source\n", progname);
exit(1);
}
}
for (i = 3; i < argc; i++) {
if (strcmp(argv[i], "-v") == 0) {
continue;
} else if (strcmp(argv[i], "-o") == 0) {
char *opts;
const char *opt;
@@ -366,24 +496,6 @@ int main(int argc, char *argv[])
if (suid)
options = add_option("suid", options);
if (!type) {
if (source) {
dup_source = xstrdup(source);
type = dup_source;
source = strchr(type, '#');
if (source)
*source++ = '\0';
if (!type[0]) {
fprintf(stderr, "%s: empty filesystem type\n",
progname);
exit(1);
}
} else {
fprintf(stderr, "%s: empty source\n", progname);
exit(1);
}
}
if (setuid_name && setuid_name[0]) {
#ifdef linux
if (drop_privileges) {
@@ -429,6 +541,21 @@ int main(int argc, char *argv[])
drop_and_lock_capabilities();
}
#endif
#ifdef HAVE_SERVICEMOUNT
/*
* Now that we know the desired filesystem type, see if we can find
* a socket service implementing that, if we haven't selected any weird
* options that would prevent that.
*/
if (!pass_fuse_fd && !(setuid_name && setuid_name[0])) {
int ret = try_service_main(argv[0], type, source, mountpoint,
options);
if (ret != MOUNT_SERVICE_FALLBACK_NEEDED)
return ret;
}
#endif
add_arg(&command, type);
if (source)
add_arg(&command, source);
+43
View File
@@ -2066,3 +2066,46 @@ out:
mount_service_destroy(&mo);
return ret;
}
bool mount_service_present(const char *fstype)
{
struct sockaddr_un name;
struct stat stbuf;
char path[PATH_MAX];
const char *subtype;
ssize_t written;
int ret;
subtype = mount_service_subtype(fstype);
if (!subtype)
return false;
/*
* The full path to the socket must fit within the AF_UNIX socket path
* buffer, which is much shorter than PATH_MAX.
*/
written = snprintf(path, sizeof(path), FUSE_SERVICE_SOCKET_DIR "/%s",
subtype);
if (written >= sizeof(name.sun_path) || written >= sizeof(path))
return false;
ret = stat(path, &stbuf);
if (ret)
return false;
if (!S_ISSOCK(stbuf.st_mode))
return false;
#ifdef HAVE_FACCESSAT
/*
* Can we write to the service socket with the real uid? This accounts
* for setuid and ACLs on the socket. Note that we connect() to the
* socket having dropped setuid privileges.
*/
ret = faccessat(AT_FDCWD, path, W_OK, 0);
#else
/* Can we write to the service socket with the real uid? */
ret = access(path, W_OK);
#endif
return ret == 0;
}
+9
View File
@@ -37,4 +37,13 @@ int mount_service_main(int argc, char *argv[]);
*/
const char *mount_service_subtype(const char *fstype);
/**
* Discover if there is a fuse service socket for the given fuse filesystem type.
* The type must not contain a path separator.
*
* @param fstype the type of a fuse filesystem type (e.g. fuse.Y, fuseblk.Y, or Y)
* @return true if available, false if not
*/
bool mount_service_present(const char *fstype);
#endif /* MOUNT_SERVICE_H_ */