Add support for sync-init of unprivileged daemons

This makes use of the bidirectional fusermount. Added is
doc/README.mount, which explains the new bidirectional
communication with fusermount.

Signed-off-by: Bernd Schubert <bschubert@ddn.com>
This commit is contained in:
Bernd Schubert
2026-03-23 17:59:14 +01:00
committed by Bernd Schubert
parent 09db3a87ad
commit d07e5f3668
10 changed files with 279 additions and 36 deletions
+3
View File
@@ -1658,6 +1658,9 @@ int main(int argc, char *argv[])
if (teardown_watchog == NULL)
goto err_out4;
/* required here for sync init */
fuse_daemonize_early_success();
if (options.count("single"))
ret = fuse_session_loop(se);
else
+21
View File
@@ -41,6 +41,27 @@ extern "C" {
int fuse_daemonize_early_start(unsigned int flags);
/**
* Signal daemonization success to parent and cleanup.
*
* To be called from the child process after a successful mount, when
* synchronous FUSE_INIT is used (FUSE_INIT as part of the mount).
*
* Automatically called for async FUSE_INIT.
*
*/
void fuse_daemonize_early_success(void);
/**
*
* Note: For synchronous FUSE_INIT, this must be called after
* fuse_session_mount() and before the first call to
* fuse_session_loop*(). For asynchronous FUSE_INIT, this should
* be called in the file system ->init() callback.
*
* In order to simplify application code, this should be called from
* the file system ->init() callback *and* after fuse_session_mount.
* Libfuse knows internally if this is a sync or async FUSE_INIT
* and will only signal the parent if the mount was completed.
* Signal daemonization failure to parent and cleanup.
*
* To be called from the child process on any kind of error.
+8
View File
@@ -2442,6 +2442,14 @@ int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf);
*/
void fuse_session_want_sync_init(struct fuse_session *se);
/**
* Check if the connection / session is using synchronous FUSE_INIT
*
* @param conn the connection
* @return true if using synchronous FUSE_INIT, false otherwise
*/
bool fuse_conn_is_sync_init(const struct fuse_conn_info *conn);
/**
* Enable debug output
*
+10 -4
View File
@@ -41,7 +41,8 @@
bool watcher_started;
_Atomic bool active;
_Atomic bool daemonized;
_Atomic bool mounted;
_Atomic bool mounted; /* fuse_session_mount() completed */
_Atomic bool got_init; /* got FUSE_INIT */
};
/* Global daemonization object pointer */
@@ -241,9 +242,9 @@ static void fuse_daemonize_early_signal(int status)
if (!dm->active)
errx(EINVAL, "%s: not active and cannot signal status", __func__);
/* Warn because there might be races */
if (status == FUSE_DAEMONIZE_SUCCESS && !dm->mounted)
fprintf(stderr, "fuse daemonize success without being mounted\n");
/* The file system is not mounted yet - don't signal parent */
if (status == FUSE_DAEMONIZE_SUCCESS && (!dm->mounted || !dm->got_init))
return;
dm->active = false;
@@ -303,6 +304,11 @@ void fuse_daemonize_early_set_mounted(void)
daemonize.mounted = true;
}
void fuse_daemonize_set_got_init(void)
{
daemonize.got_init = true;
}
/*
* defined in fuse_common.h, but fuse_common.h is outside of the scope
* of this file - duplicated definition is better here.
+3 -11
View File
@@ -20,17 +20,6 @@
*/
void fuse_daemonize_early_set_mounted(void);
/**
* Signal daemonization success to parent and cleanup.
*
* To be called from the child process after successful mount, when
* sychronous FUSE_INIT is used (FUSE_INIT as part of the mount)
* Automatically called for async FUSE_INIT.
*
* Not exposed to the ABI yet, as sync FUSE_INIT is not implemented yet.
*/
void fuse_daemonize_early_success(void);
/*
* Check if daemonization is used.
*
@@ -38,5 +27,8 @@ void fuse_daemonize_early_success(void);
*/
bool fuse_daemonize_is_used(void);
/* Set on handling FUSE_INIT */
void fuse_daemonize_set_got_init(void);
#endif /* FUSE_DAEMONIZE_I_H_ */
+102 -15
View File
@@ -20,6 +20,7 @@
#include "util.h"
#include "fuse_uring_i.h"
#include "fuse_daemonize_i.h"
#include "fuse_daemonize.h"
#if defined(__linux__)
#include "mount_i_linux.h"
#endif
@@ -41,6 +42,7 @@
#include <assert.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <stdalign.h>
#include <poll.h>
@@ -3023,6 +3025,7 @@ _do_init(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in,
* over the thread scheduling.
*/
se->got_init = 1;
fuse_daemonize_set_got_init();
send_reply_ok(req, &outarg, outargsize);
if (enable_io_uring)
fuse_uring_wake_ring_threads(se);
@@ -4565,6 +4568,8 @@ static int session_wait_sync_init_completion(struct fuse_session *se)
se->init_wakeup_fd = -1;
}
se->init_thread = 0;
if (se->init_error != 0) {
fuse_log(FUSE_LOG_ERR, "fuse: init worker failed: %s\n",
strerror(-se->init_error));
@@ -4579,10 +4584,64 @@ static int session_wait_sync_init_completion(struct fuse_session *se)
return 0;
}
/*
* Handle fallback to fusermount3 when privileged mount fails with EPERM.
* Returns: new fd on success, negative error code on failure
*/
static int new_api_fusermount(struct fuse_session *se,
const char *mountpoint,
const char *mnt_opts,
int *sock_fd, pid_t *fusermount_pid)
{
int fd, err;
if (se->debug)
fuse_log(FUSE_LOG_DEBUG,
"fuse: privileged mount failed with EPERM, falling back to fusermount3\n");
/* Terminate worker thread with wrong fd */
if (session_wait_sync_init_completion(se) < 0)
fuse_log(FUSE_LOG_ERR, "fuse: sync init completion failed\n");
/* Call fusermount3 with --sync-init */
fd = mount_fusermount_obtain_fd(mountpoint, se->mo, mnt_opts, sock_fd,
fusermount_pid);
if (fd < 0) {
fuse_log(FUSE_LOG_ERR,
"fuse: fusermount3 sync-init failed\n");
return -ENOTSUP;
}
/* Start worker thread with correct fd from fusermount3 */
se->fd = fd;
err = session_start_sync_init(se, fd);
if (err) {
fuse_log(FUSE_LOG_ERR,
"fuse: failed to start sync init worker\n");
return err;
}
/* Send proceed signal and wait for mount result */
err = fuse_fusermount_proceed_mnt(*sock_fd);
if (err < 0)
return -EIO;
return fd;
}
/*
* Mount using the new Linux mount API (fsopen/fsconfig/fsmount/move_mount)
* Sync-init is only supported with the new API, as the mount might hang
* in case of daemon crash during FUSE_INIT. That also means once the sync init
* ioctl succeed fallback is not allowed anymore.
* Returns: fd on success, -1 on failure
*/
static int fuse_session_mount_new_api(struct fuse_session *se,
const char *mountpoint)
{
int fd = -1;
int sock_fd = -1;
pid_t fusermount_pid = -1;
int res, err;
char *mnt_opts = NULL;
char *mnt_opts_with_fd = NULL;
@@ -4599,34 +4658,55 @@ static int fuse_session_mount_new_api(struct fuse_session *se,
fd = fuse_kern_mount_prepare(mountpoint, se->mo);
if (fd == -1) {
fuse_log(FUSE_LOG_ERR, "Mount preparation failed.\n");
err = -EIO;
goto err;
}
/*
* Enable synchronous FUSE_INIT and start worker thread, sync init
* failure is not an error
*/
se->fd = fd;
err = session_start_sync_init(se, fd);
if (err)
goto err;
snprintf(fd_opt, sizeof(fd_opt), "fd=%i", fd);
err = -ENOMEM;
if (fuse_opt_add_opt(&mnt_opts_with_fd, mnt_opts) == -1 ||
fuse_opt_add_opt(&mnt_opts_with_fd, fd_opt) == -1) {
err = -ENOMEM;
goto err;
}
/* Try to mount directly */
err = fuse_kern_fsmount_mo(mountpoint, se->mo, mnt_opts_with_fd);
/* If mount failed with EPERM, fall back to fusermount3 with sync-init */
if (err < 0 && errno == EPERM) {
close(fd);
se->fd = -1;
fd = new_api_fusermount(se, mountpoint, mnt_opts,
&sock_fd, &fusermount_pid);
if (fd < 0) {
err = fd;
goto err_with_sock;
}
err = 0;
} else if (err < 0) {
/* Mount failed with non-EPERM error, bail out */
goto err;
}
err_with_sock:
if (sock_fd >= 0) {
close(sock_fd);
/* Reap fusermount3 child process to prevent zombie */
if (fusermount_pid > 0)
waitpid(fusermount_pid, NULL, 0);
}
err:
if (err < 0) {
/* Close fd first to unblock worker thread */
if (fd >= 0)
close(fd);
fd = -1;
se->fd = -1;
se->error = -errno;
se->error = err;
}
/* Wait for synchronous FUSE_INIT to complete */
if (session_wait_sync_init_completion(se) < 0)
@@ -4694,16 +4774,16 @@ int fuse_session_mount(struct fuse_session *se, const char *_mountpoint)
goto out;
}
/* new linux mount api */
/* new linux mount api (and sync init) */
fd = fuse_session_mount_new_api(se, mountpoint);
if (fd >= 0)
goto out;
/* fall back to old API */
se->error = 0; /* reset error of new api */
fd = fuse_kern_mount(mountpoint, se->mo);
if (fd < 0)
goto error_out;
/* fall back to old API, possible as long as another fd is used */
if (fd < 0) {
se->error = 0; /* reset error of new api */
fd = fuse_kern_mount(mountpoint, se->mo);
if (fd < 0)
goto error_out;
}
out:
se->fd = fd;
@@ -5026,3 +5106,10 @@ void fuse_session_set_debug(struct fuse_session *se)
{
se->debug = 1;
}
bool fuse_conn_is_sync_init(const struct fuse_conn_info *conn)
{
const struct fuse_session *se = container_of(conn, struct fuse_session, conn);
return se->is_sync_init;
}
+1 -2
View File
@@ -228,8 +228,7 @@ FUSE_3.19 {
fuse_session_stop_teardown_watchdog;
fuse_lowlevel_notify_prune;
fuse_daemonize_early_start;
# Not exposed yet for now, as called automatically
# fuse_daemonize_early_success;
fuse_daemonize_early_success;
fuse_daemonize_early_fail;
fuse_daemonize_early_is_active;
fuse_session_set_debug;
+124 -2
View File
@@ -37,6 +37,7 @@
#define FUSERMOUNT_PROG "fusermount3"
#define FUSE_COMMFD_ENV "_FUSE_COMMFD"
#define FUSE_COMMFD2_ENV "_FUSE_COMMFD2"
#define ARG_FD_ENTRY_SIZE 30
enum { KEY_KERN_FLAG,
KEY_KERN_OPT,
@@ -306,7 +307,7 @@ static int setup_auto_unmount(const char *mountpoint, int quiet)
return -1;
}
char arg_fd_entry[30];
char arg_fd_entry[ARG_FD_ENTRY_SIZE];
snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[0]);
setenv(FUSE_COMMFD_ENV, arg_fd_entry, 1);
/*
@@ -379,7 +380,7 @@ static int fuse_mount_fusermount(const char *mountpoint, const struct mount_opts
return -1;
}
char arg_fd_entry[30];
char arg_fd_entry[ARG_FD_ENTRY_SIZE];
snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[0]);
setenv(FUSE_COMMFD_ENV, arg_fd_entry, 1);
/*
@@ -439,6 +440,127 @@ static int fuse_mount_fusermount(const char *mountpoint, const struct mount_opts
return fd;
}
/*
* Mount using fusermount3 with --sync-init flag for bidirectional fd exchange
* Used by new mount API when privileged mount fails with EPERM
*
* Returns: fd of /dev/fuse opened by fusermount on success, -1 on failure
* On success, *sock_fd_out contains the socket fd for signaling fusermount3
*/
int mount_fusermount_obtain_fd(const char *mountpoint, struct mount_opts *mo,
const char *opts, int *sock_fd_out,
pid_t *pid_out)
{
int fds[2];
pid_t pid;
int res;
char arg_fd_entry[ARG_FD_ENTRY_SIZE];
posix_spawn_file_actions_t action;
int fd, status;
(void)mo;
if (!mountpoint) {
fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n");
return -1;
}
res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds);
if (res == -1) {
fuse_log(FUSE_LOG_ERR, "Running %s: socketpair() failed: %s\n",
FUSERMOUNT_PROG, strerror(errno));
return -1;
}
snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[0]);
setenv(FUSE_COMMFD_ENV, arg_fd_entry, 1);
snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[1]);
setenv(FUSE_COMMFD2_ENV, arg_fd_entry, 1);
char const *const argv[] = {
FUSERMOUNT_PROG,
"--sync-init",
"-o", opts ? opts : "",
"--",
mountpoint,
NULL,
};
posix_spawn_file_actions_init(&action);
posix_spawn_file_actions_addclose(&action, fds[1]);
status = fusermount_posix_spawn(&action, argv, &pid);
posix_spawn_file_actions_destroy(&action);
if (status != 0) {
close(fds[0]);
close(fds[1]);
return -1;
}
close(fds[0]);
fd = receive_fd(fds[1]);
if (fd < 0) {
close(fds[1]);
waitpid(pid, NULL, 0);
return -1;
}
fcntl(fd, F_SETFD, FD_CLOEXEC);
/* Return socket fd for later signaling */
*sock_fd_out = fds[1];
*pid_out = pid;
return fd;
}
/*
* Send proceed signal to fusermount3 and wait for mount result
* Returns: 0 on success, -1 on failure
*/
int fuse_fusermount_proceed_mnt(int sock_fd)
{
char buf = '\0';
ssize_t res;
/* Send proceed signal */
do {
res = send(sock_fd, &buf, 1, 0);
} while (res == -1 && errno == EINTR);
if (res != 1) {
fuse_log(FUSE_LOG_ERR, "fuse: failed to send proceed signal: %s\n",
strerror(errno));
return -1;
}
/* Wait for mount result from fusermount3 (4-byte error code) */
int32_t status;
do {
res = recv(sock_fd, &status, sizeof(status), 0);
} while (res == -1 && errno == EINTR);
if (res != sizeof(status)) {
if (res == 0)
fuse_log(FUSE_LOG_ERR, "fuse: fusermount3 closed connection\n");
else
fuse_log(FUSE_LOG_ERR, "fuse: failed to receive mount status: %s\n",
strerror(errno));
return -1;
}
if (status != 0) {
if (status != -EPERM)
fuse_log(FUSE_LOG_ERR, "fuse: fusermount3 mount failed: %s\n",
strerror(-status));
return -1;
}
return 0;
}
#ifndef O_CLOEXEC
#define O_CLOEXEC 0
#endif
+7
View File
@@ -55,4 +55,11 @@ int fuse_kern_fsmount_mo(const char *mnt, const struct mount_opts *mo,
const char *mnt_opts);
char *fuse_mnt_build_source(const struct mount_opts *mo);
char *fuse_mnt_build_type(const struct mount_opts *mo);
int mount_fusermount_obtain_fd(const char *mountpoint,
struct mount_opts *mo,
const char *opts, int *sock_fd_out,
pid_t *pid_out);
int fuse_fusermount_proceed_mnt(int sock_fd);
#endif /* FUSE_MOUNT_I_LINUX_H_ */
-2
View File
@@ -1321,7 +1321,6 @@ struct mount_context {
char *mnt_opts;
char *x_opts;
char *kern_mnt_opts; /* mnt_opts with removed x_opts */
const char *type;
};
/*
@@ -1437,7 +1436,6 @@ static int mount_fuse_finish_fsmount(const char *mnt,
/* Store results in context */
ctx->source = mp.source;
ctx->type = mp.type;
ctx->mnt_opts = final_mnt_opts;
*type = mp.type;