From: Bernd Schubert 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 --- example/passthrough_hp.cc | 5 +- include/fuse_daemonize.h | 16 ++++-- include/fuse_lowlevel.h | 8 +++ lib/fuse_daemonize.c | 14 ++++-- lib/fuse_i.h | 1 + lib/fuse_lowlevel.c | 121 ++++++++++++++++++++++++++++++++++++-------- lib/mount.c | 126 +++++++++++++++++++++++++++++++++++++++++++++- lib/mount_i_linux.h | 7 +++ util/fusermount.c | 2 - 9 files changed, 266 insertions(+), 34 deletions(-) diff --git a/example/passthrough_hp.cc b/example/passthrough_hp.cc index bad435077697e8832cf5a5195c17f2f873f2dfe6..cca865973ab5e8e9c1faa0fa28eb292a172a4812 100644 --- a/example/passthrough_hp.cc +++ b/example/passthrough_hp.cc @@ -245,7 +245,7 @@ static void sfs_init(void *userdata, fuse_conn_info *conn) /* Try a large IO by default */ conn->max_write = 4 * 1024 * 1024; - /* Signal successful init to parent */ + /* required here for async init */ fuse_daemonize_success(); } @@ -1661,6 +1661,9 @@ int main(int argc, char *argv[]) if (teardown_watchog == NULL) goto err_out4; + /* required here for sync init */ + fuse_daemonize_success(); + if (options.count("single")) ret = fuse_session_loop(se); else diff --git a/include/fuse_daemonize.h b/include/fuse_daemonize.h index 6215e42c635ba5956cb23ba0832dfc291ab8dede..6d3c4a6134f408d0f0a08b87495bde65d491286d 100644 --- a/include/fuse_daemonize.h +++ b/include/fuse_daemonize.h @@ -42,6 +42,16 @@ int fuse_daemonize_start(unsigned int flags); /** * Signal daemonization success to parent and cleanup. + * + * 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. */ void fuse_daemonize_success(void); @@ -60,12 +70,12 @@ void fuse_daemonize_fail(int err); bool fuse_daemonize_is_active(void); /** - * Set mounted flag. - * - * Called from fuse_session_mount(). + * Set mounted flag. Called from fuse_session_mount(). */ void fuse_daemonize_set_mounted(void); +void fuse_daemonize_set_got_init(void); + /** * Check if daemonization is used. * diff --git a/include/fuse_lowlevel.h b/include/fuse_lowlevel.h index d85929e291a77de8caad7d6b3d9ac5b092ce0e62..ba7c4b3ac788f19f30fbf120fe8f6ae5851b425e 100644 --- a/include/fuse_lowlevel.h +++ b/include/fuse_lowlevel.h @@ -2441,6 +2441,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(struct fuse_conn_info *conn); + /** * Enable debug output * diff --git a/lib/fuse_daemonize.c b/lib/fuse_daemonize.c index 97cfad7be879beacf69b020b7af78d512a224fd5..d4a34da15114fc0742394e72510946b63a98a252 100644 --- a/lib/fuse_daemonize.c +++ b/lib/fuse_daemonize.c @@ -40,7 +40,8 @@ struct fuse_daemonize { 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 */ @@ -238,9 +239,9 @@ static void fuse_daemonize_signal(int status) struct fuse_daemonize *dm = &daemonize; int rc; - /* 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; @@ -292,6 +293,11 @@ void fuse_daemonize_set_mounted(void) daemonize.mounted = true; } +void fuse_daemonize_set_got_init(void) +{ + daemonize.got_init = true; +} + bool fuse_daemonize_is_used(void) { return daemonize.active; diff --git a/lib/fuse_i.h b/lib/fuse_i.h index 164401e226eb727192a49e1cc7b38a75f031643b..20e9c275cbffade69d3fd440d1b48b371135bd84 100644 --- a/lib/fuse_i.h +++ b/lib/fuse_i.h @@ -75,6 +75,7 @@ struct fuse_timeout_thread; struct fuse_session { _Atomic(char *)mountpoint; int fd; + _Atomic bool is_sync_init; struct fuse_custom_io *io; struct mount_opts *mo; int debug; diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index 4445e134a7ff5be508306f74db9d9c56e3582070..c4bb227b51a5226543adf7f037fb2c4604a5f978 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -41,6 +41,7 @@ #include #include #include +#include #include #include @@ -3024,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); @@ -4456,7 +4458,8 @@ static void *session_sync_init_worker(void *data) } /* Enable synchronous FUSE_INIT and start worker thread */ -static int session_start_sync_init(struct fuse_session *se, int fd) +static int session_start_sync_init(struct fuse_session *se, int fd, + bool *fall_back) { int err, res; @@ -4491,6 +4494,12 @@ static int session_start_sync_init(struct fuse_session *se, int fd) return err; } + /* + * If we get here, we know that sync init is enabled and fall back + * to the old mount API is not allowed + */ + *fall_back = false; + if (se->debug) fuse_log(FUSE_LOG_DEBUG, "fuse: synchronous FUSE_INIT enabled\n"); @@ -4553,6 +4562,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)); @@ -4567,10 +4578,19 @@ static int session_wait_sync_init_completion(struct fuse_session *se) return 0; } +/* + * 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) + const char *mountpoint, bool *fall_back) { 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; @@ -4587,34 +4607,85 @@ 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 - */ + *fall_back = true; se->fd = fd; - err = session_start_sync_init(se, fd); + err = session_start_sync_init(se, fd, fall_back); 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) { + 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"); + + /* Close the privileged fd */ + close(fd); + fd = -1; + se->fd = -1; + *fall_back = true; /* reset */ + + /* Call fusermount3 with --sync-init */ + err = -ENOTSUP; + 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"); + goto err; + } + + /* Start worker thread with correct fd from fusermount3 */ + se->fd = fd; + err = session_start_sync_init(se, fd, fall_back); + if (err) + fuse_log(FUSE_LOG_ERR, + "fuse: failed to start sync init worker\n"); + + /* Send proceed signal and wait for mount result */ + err = fuse_fusermount_proceed_mnt(sock_fd); + if (err < 0) { + err = -EIO; + goto err_with_sock; + } + } 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) @@ -4626,10 +4697,11 @@ err: } #else static int fuse_session_mount_new_api(struct fuse_session *se, - const char *mountpoint) + const char *mountpoint, bool *fall_back) { (void) se; (void) mountpoint; + (void) fall_back; return -1; } @@ -4639,6 +4711,7 @@ int fuse_session_mount(struct fuse_session *se, const char *_mountpoint) { int fd; char *mountpoint; + bool fall_back; if (_mountpoint == NULL) { fuse_log(FUSE_LOG_ERR, "Invalid null-ptr mountpoint!\n"); @@ -4682,21 +4755,18 @@ int fuse_session_mount(struct fuse_session *se, const char *_mountpoint) return 0; } - /* new linux mount api */ - fd = fuse_session_mount_new_api(se, mountpoint); - if (fd >= 0) - goto out; + /* new linux mount api (and sync init) */ + fd = fuse_session_mount_new_api(se, mountpoint, &fall_back); /* 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; + if (fall_back && 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; - - /* Save mountpoint */ se->mountpoint = mountpoint; fuse_daemonize_set_mounted(); @@ -5015,3 +5085,10 @@ void fuse_session_set_debug(struct fuse_session *se) { se->debug = 1; } + +bool fuse_conn_is_sync_init(struct fuse_conn_info *conn) +{ + struct fuse_session *se = container_of(conn, struct fuse_session, conn); + + return se->is_sync_init; +} diff --git a/lib/mount.c b/lib/mount.c index 408e9d36896048fc167e264c95b6f6e31d86679f..f0d12525310a9ad239c0f447af60bd49b105a091 100644 --- a/lib/mount.c +++ b/lib/mount.c @@ -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, struct mount_opts *mo, 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, struct mount_opts *mo, 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 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 diff --git a/lib/mount_i_linux.h b/lib/mount_i_linux.h index 597b380076fccc1d38fd4d0b9108fc92a1adfa62..ae98296a334a2aa39c4f3e5c3e4e3c136ae38cd3 100644 --- a/lib/mount_i_linux.h +++ b/lib/mount_i_linux.h @@ -43,4 +43,11 @@ int fuse_kern_fsmount_mo(const char *mnt, struct mount_opts *mo, 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_ */ diff --git a/util/fusermount.c b/util/fusermount.c index 31bf959024ae0cd2a4e50974589bfab30a100b0a..bf442416c83d6da3f80eca5c9814df29f61bec56 100644 --- a/util/fusermount.c +++ b/util/fusermount.c @@ -1323,7 +1323,6 @@ struct mount_context { char *source; char *mnt_opts; char *x_opts; - const char *type; }; /* @@ -1447,7 +1446,6 @@ static int mount_fuse_finish_fsmount(const char *mnt, const char *opts, /* Store results in context */ ctx->source = mp.source; - ctx->type = mp.type; ctx->mnt_opts = final_mnt_opts; *type = mp.type; -- 2.43.0