mirror of
https://github.com/libfuse/libfuse.git
synced 2026-07-07 14:47:41 +08:00
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:
committed by
Bernd Schubert
parent
09db3a87ad
commit
d07e5f3668
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
@@ -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_ */
|
||||
|
||||
|
||||
+98
-11
@@ -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 */
|
||||
/* 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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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_ */
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user