From: "Darrick J. Wong" <djwong@kernel.org>
To: Bernd Schubert <bernd@bsbernd.com>
Cc: linux-fsdevel@vger.kernel.org, Miklos Szeredi <miklos@szeredi.hu>,
Joanne Koong <joannelkoong@gmail.com>,
Bernd Schubert <bschubert@ddn.com>
Subject: Re: [PATCH 12/19] fuse mount: Support synchronous FUSE_INIT (privileged daemon)
Date: Mon, 23 Mar 2026 17:03:50 -0700 [thread overview]
Message-ID: <20260324000350.GM6202@frogsfrogsfrogs> (raw)
In-Reply-To: <20260323-fuse-init-before-mount-v1-12-a52d3040af69@bsbernd.com>
On Mon, Mar 23, 2026 at 06:45:07PM +0100, Bernd Schubert wrote:
> From: Bernd Schubert <bschubert@ddn.com>
>
> Add synchronous FUSE_INIT processing during mount() to
> enable early daemonization with proper error reporting
> to the parent process.
>
> A new mount thread is needed that handles FUSE_INIT and
> possible other requests at mount time (like getxattr for selinux).
> The kernel sends FUSE_INIT during the mount() syscall. Without a thread
> to process it, mount() blocks forever.
>
> Mount thread lifetime:
> Created before mount() syscall in fuse_start_sync_init_worker()
> Processes requests until se->mount_finished is set (after mount() returns)
> Joined after successful mount in fuse_wait_sync_init_completion()
> Cancelled if mount fails (direct → fusermount3 fallback)
Hrmm, so the main thread (of the child process after daemonization)
calls mount(), having started a child thread to read and process
FUSE_INIT + any other mount-related fuse requests?
Later I see an eventfd being used to signal this background thread that
it should exit and (presumably) let the real request processing queues
take over. I'm curious why an eventfd here when pipes were used
earlier?
> Key changes:
>
> Add init_thread, init_error, mount_finished to struct fuse_session
> Use FUSE_DEV_IOC_SYNC_INIT ioctl for kernel support
> Fall back to async FUSE_INIT if unsupported
> Auto-enabled when fuse_daemonize_active() or via
> fuse_session_want_sync_init()
> Allows parent to report mount/init failures instead of
> exiting immediately after fork.
>
> Note: For now synchronous FUSE_INIT is only supported for privileged
> mounts.
>
> Signed-off-by: Bernd Schubert <bschubert@ddn.com>
> ---
> include/fuse_lowlevel.h | 12 +++
> lib/fuse_daemonize.c | 8 ++
> lib/fuse_i.h | 16 ++++
> lib/fuse_lowlevel.c | 192 ++++++++++++++++++++++++++++++++++++++++++++++--
> lib/mount.c | 5 +-
> lib/mount_fsmount.c | 5 +-
> 6 files changed, 229 insertions(+), 9 deletions(-)
>
> diff --git a/include/fuse_lowlevel.h b/include/fuse_lowlevel.h
> index ee0bd8d71d95e4d57ebb4873dca0f2b36e22a649..d8626f85bdaf497534cd2835a589e30f1f4e2466 100644
> --- a/include/fuse_lowlevel.h
> +++ b/include/fuse_lowlevel.h
> @@ -2429,6 +2429,18 @@ void fuse_session_process_buf(struct fuse_session *se,
> */
> int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf);
>
> +/**
> + * Request synchronous FUSE_INIT, i.e. FUSE_INIT is handled by the
> + * kernel before mount is returned.
> + *
> + * As FUSE_INIT also starts io-uring ring threads, fork() must not be
> + * called after this if io-uring is enabled. Also see
> + * fuse_session_daemonize_start().
> + *
> + * This must be called before fuse_session_mount() to have any effect.
> + */
> +void fuse_session_want_sync_init(struct fuse_session *se);
> +
> /**
> * Check if the request is submitted through fuse-io-uring
> */
> diff --git a/lib/fuse_daemonize.c b/lib/fuse_daemonize.c
> index 5d191e7d737d04876d02ef6bd526061c003d2ab7..1a32ef74093f231091b4f541e5b9136bff72024f 100644
> --- a/lib/fuse_daemonize.c
> +++ b/lib/fuse_daemonize.c
> @@ -9,6 +9,7 @@
> #define _GNU_SOURCE
>
> #include "fuse_daemonize.h"
> +#include "fuse_i.h"
>
> #include <fcntl.h>
> #include <poll.h>
> @@ -282,3 +283,10 @@ bool fuse_daemonize_active(void)
>
> return dm != NULL && (dm->daemonized || dm->active);
> }
> +
> +bool fuse_daemonize_set(void)
> +{
> + struct fuse_daemonize *dm = atomic_load(&daemonize);
> +
> + return dm != NULL;
> +}
> diff --git a/lib/fuse_i.h b/lib/fuse_i.h
> index 6d63c9fd2149eb4ae3b0e0170640a4ce2eed4705..93ab7ac2fadf9395af70487c7626cc57c2948d56 100644
> --- a/lib/fuse_i.h
> +++ b/lib/fuse_i.h
> @@ -112,6 +112,10 @@ struct fuse_session {
>
> /* synchronous FUSE_INIT support */
> bool want_sync_init;
> + pthread_t init_thread;
> + int init_error;
> + _Atomic bool terminate_mount_worker;
> + int init_wakeup_fd;
>
> /* io_uring */
> struct fuse_session_uring uring;
> @@ -221,7 +225,11 @@ void fuse_chan_put(struct fuse_chan *ch);
> /* Mount-related functions */
> void fuse_mount_version(void);
> void fuse_kern_unmount(const char *mountpoint, int fd);
> +int fuse_kern_mount_get_base_mnt_opts(struct mount_opts *mo, char **mnt_optsp);
> int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo);
> +int fuse_kern_mount_prepare(const char *mountpoint, struct mount_opts *mo);
> +int fuse_kern_do_mount(const char *mountpoint, struct mount_opts *mo,
> + const char *mnt_opts);
>
> int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov,
> int count);
> @@ -255,6 +263,14 @@ int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *c
> */
> int fuse_loop_cfg_verify(struct fuse_loop_config *config);
>
> +/**
> + * Check if daemonization is set.
> + *
> + * @return true if set, false otherwise
> + */
> +bool fuse_daemonize_set(void);
> +
> +
>
> /*
> * This can be changed dynamically on recent kernels through the
> diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c
> index 626233df20f49fa89cd9327f94340169d7061f75..b10def03f3666757d312f87f177a560483691d6f 100644
> --- a/lib/fuse_lowlevel.c
> +++ b/lib/fuse_lowlevel.c
> @@ -4230,6 +4230,7 @@ fuse_session_new_versioned(struct fuse_args *args,
> goto out1;
> }
> se->fd = -1;
> + se->init_wakeup_fd = -1;
> se->conn.max_write = FUSE_DEFAULT_MAX_PAGES_LIMIT * getpagesize();
> se->bufsize = se->conn.max_write + FUSE_BUFFER_HEADER_SIZE;
> se->conn.max_readahead = UINT_MAX;
> @@ -4402,6 +4403,167 @@ int fuse_session_custom_io_30(struct fuse_session *se,
> }
>
> #if defined(HAVE_NEW_MOUNT_API)
> +
> +/* Worker thread for synchronous FUSE_INIT */
> +static void *session_sync_init_worker(void *data)
> +{
> + struct fuse_session *se = (struct fuse_session *)data;
> + struct fuse_buf fbuf = {
> + .mem = NULL,
> + };
> + struct pollfd pfds[2];
> + int res;
> +
> + pfds[0].fd = se->fd;
> + pfds[0].events = POLLIN;
> + pfds[0].revents = 0;
> + pfds[1].fd = se->init_wakeup_fd;
> + pfds[1].events = POLLIN;
> + pfds[1].revents = 0;
> +
> + /*
> + * Process requests until mount completes. With SELinux there may be
> + * additional requests (like getattr) after FUSE_INIT before mount
> + * returns.
> + */
> + while (!atomic_load(&se->terminate_mount_worker)) {
We don't ever *read* the init_wakeup_fd event, so I wonder if the
_Atomic flag here is really needed? I gather we need acquire semantics
or something?
> + res = poll(pfds, 2, -1);
> + if (res == -1) {
> + if (errno == EINTR)
> + continue;
> + se->init_error = -errno;
> + break;
> + }
> +
> + if (pfds[1].revents & POLLIN)
> + break;
> +
> + if (pfds[0].revents & POLLIN) {
> + res = fuse_session_receive_buf_internal(se, &fbuf, NULL);
> + if (res == -EINTR)
> + continue;
> + if (res <= 0) {
> + se->init_error = res < 0 ? res : -EINVAL;
> + break;
> + }
> +
> + fuse_session_process_buf_internal(se, &fbuf, NULL);
> + }
> + }
> +
> + fuse_buf_free(&fbuf);
> + return NULL;
> +}
> +
> +/* Enable synchronous FUSE_INIT and start worker thread */
> +static int session_start_sync_init(struct fuse_session *se, int fd)
> +{
> + int err;
> + int res;
> +
> + if (!se->want_sync_init &&
> + (se->uring.enable && !fuse_daemonize_set())) {
> + if (se->debug)
> + fuse_log(FUSE_LOG_DEBUG,
> + "fuse: sync init not enabled\n");
> + return 0;
> + }
> +
> + /* Try to enable synchronous FUSE_INIT */
> + res = ioctl(fd, FUSE_DEV_IOC_SYNC_INIT);
> + if (res) {
> + /* ENOTTY means kernel doesn't support sync init - not an error */
> + if (errno != ENOTTY) {
> + fuse_log(
> + FUSE_LOG_ERR,
> + "fuse: failed to enable sync init: %s\n",
> + strerror(errno));
> + } else if (se->debug) {
> + fuse_log(
> + FUSE_LOG_DEBUG,
> + "fuse: kernel doesn't support sync init\n");
> + }
> + return -ENOTTY;
> + }
> +
> + if (se->debug)
> + fuse_log(FUSE_LOG_DEBUG,
> + "fuse: synchronous FUSE_INIT enabled\n");
> +
> + se->init_error = 0;
> + se->terminate_mount_worker = false;
> +
> + se->init_wakeup_fd = eventfd(0, EFD_CLOEXEC);
> + if (se->init_wakeup_fd == -1) {
> + fuse_log(
> + FUSE_LOG_ERR,
> + "fuse: failed to create eventfd for init worker: %s\n",
> + strerror(errno));
> + return -EIO;
> + }
> +
> + err = pthread_create(&se->init_thread, NULL,
> + session_sync_init_worker, se);
> + if (err != 0) {
> + fuse_log(
> + FUSE_LOG_ERR,
> + "fuse: failed to create init worker thread: %s\n",
> + strerror(err));
> + close(se->init_wakeup_fd);
> + se->init_wakeup_fd = -1;
> + return -EIO;
> + }
> +
> + return 0;
> +}
> +
> +/* Wait for synchronous FUSE_INIT to complete */
> +static int session_wait_sync_init_completion(struct fuse_session *se)
> +{
> + void *retval;
> + int err;
> + uint64_t val = 1;
> +
> + if (se->init_thread == 0)
pthread_t is supposed to be an opaque datatype, so "0" could be a valid
value for a running thread. For that matter, it could be a struct. I
think you have to have a separate flag (or just use init_wakeup_fd >= 0)
to determine if we're doing a synchronous init.
> + return 0;
> +
> + se->terminate_mount_worker = true;
> +
> + if (se->init_wakeup_fd != -1) {
> + ssize_t res = write(se->init_wakeup_fd, &val, sizeof(val));
> +
> + if (res != sizeof(val)) {
> + fuse_log(FUSE_LOG_ERR,
> + "fuse: failed to signal init worker: %s\n",
> + strerror(errno));
> + }
> + }
> +
> + err = pthread_join(se->init_thread, &retval);
> + if (err != 0) {
> + fuse_log(FUSE_LOG_ERR, "fuse: failed to join init worker thread: %s\n",
> + strerror(err));
> + return -1;
> + }
> +
> + if (se->init_wakeup_fd != -1) {
> + close(se->init_wakeup_fd);
> + se->init_wakeup_fd = -1;
> + }
> +
> + if (se->init_error != 0) {
> + fuse_log(FUSE_LOG_ERR, "fuse: init worker failed: %d\n", se->init_error);
Could you strerror(-se->init_error) to give the user a real error
message?
> + return -1;
> + }
> +
> + if (fuse_session_exited(se)) {
> + fuse_log(FUSE_LOG_ERR, "FUSE_INIT failed: session exited\n");
> + return -1;
> + }
> +
> + return 0;
> +}
> +
> /* Only linux supports sync FUSE_INIT so far */
> static int fuse_session_mount_new_api(struct fuse_session *se,
> const char *mountpoint)
> @@ -4414,8 +4576,7 @@ static int fuse_session_mount_new_api(struct fuse_session *se,
>
> res = fuse_kern_mount_get_base_mnt_opts(se->mo, &mnt_opts);
> if (res == -1) {
> - fuse_log(FUSE_LOG_ERR,
> - "fuse: failed to get base mount options\n");
> + fuse_log(FUSE_LOG_ERR, "fuse: failed to get base mount options\n");
Unrelated change?
> err = -EIO;
> goto err;
> }
> @@ -4427,6 +4588,17 @@ static int fuse_session_mount_new_api(struct fuse_session *se,
> 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) {
> + /* ENOTTY means kernel doesn't support sync init - not an error */
> + if (err != -ENOTTY)
> + goto err;
> + }
Perhaps session_start_sync_init should not return ENOTTY, since you
already check in session_wait_sync_init_completion if the synchronous
init worker has been started?
> snprintf(fd_opt, sizeof(fd_opt), "fd=%i", fd);
> if (fuse_opt_add_opt(&mnt_opts_with_fd, mnt_opts) == -1 ||
> fuse_opt_add_opt(&mnt_opts_with_fd, fd_opt) == -1) {
> @@ -4436,13 +4608,16 @@ static int fuse_session_mount_new_api(struct fuse_session *se,
>
> err = fuse_kern_fsmount_mo(mountpoint, se->mo, mnt_opts_with_fd);
> err:
> - if (err) {
> + if (err < 0) {
> if (fd >= 0)
> close(fd);
> fd = -1;
> se->fd = -1;
> se->error = -errno;
> }
> + /* Wait for synchronous FUSE_INIT to complete */
> + if (session_wait_sync_init_completion(se) < 0)
> + fuse_log(FUSE_LOG_ERR, "fuse: sync init completion failed\n");
>
> free(mnt_opts);
> free(mnt_opts_with_fd);
> @@ -4452,8 +4627,8 @@ err:
> static int fuse_session_mount_new_api(struct fuse_session *se,
> const char *mountpoint)
> {
> - (void)se;
> - (void)mountpoint;
> + (void) se;
> + (void) mountpoint;
>
> return -1;
> }
> @@ -4825,3 +5000,10 @@ void fuse_session_stop_teardown_watchdog(void *data)
> pthread_join(tt->thread_id, NULL);
> fuse_tt_destruct(tt);
> }
> +
> +void fuse_session_want_sync_init(struct fuse_session *se)
> +{
> + if (se == NULL)
> + return;
> + se->want_sync_init = true;
> +}
> diff --git a/lib/mount.c b/lib/mount.c
> index 30fd4d2f9bbb84c817b2363b2075456acd1c1255..12df49d9109cf918cc41aa75c5fdf84231d4d5ff 100644
> --- a/lib/mount.c
> +++ b/lib/mount.c
> @@ -30,6 +30,7 @@
> #include <sys/socket.h>
> #include <sys/un.h>
> #include <sys/wait.h>
> +#include <sys/ioctl.h>
>
> #include "fuse_mount_compat.h"
>
> @@ -529,8 +530,8 @@ int fuse_kern_fsmount_mo(const char *mnt, struct mount_opts *mo,
> * Returns: 0 on success, -1 on failure,
> * FUSE_MOUNT_FALLBACK_NEEDED if fusermount should be used
> */
> -static int fuse_kern_do_mount(const char *mnt, struct mount_opts *mo,
> - const char *mnt_opts)
> +int fuse_kern_do_mount(const char *mnt, struct mount_opts *mo,
> + const char *mnt_opts)
> {
> char *source = NULL;
> char *type = NULL;
> diff --git a/lib/mount_fsmount.c b/lib/mount_fsmount.c
> index cba998bc60c783a5edc0c16570f7e5512b7f1253..f1fec790bb80f8815d485a068dc7efdff1746309 100644
> --- a/lib/mount_fsmount.c
> +++ b/lib/mount_fsmount.c
> @@ -287,8 +287,9 @@ int fuse_kern_fsmount(const char *mnt, unsigned long flags, int blkdev,
> /* Try to open filesystem context */
> fsfd = fsopen(type, FSOPEN_CLOEXEC);
> if (fsfd == -1) {
> - fprintf(stderr, "fuse: fsopen(%s) failed: %s\n", type,
> - strerror(errno));
> + if (errno != EPERM)
> + fprintf(stderr, "fuse: fsopen(%s) failed: %s\n", type,
> + strerror(errno));
More unrelated changes?
--D
> return -1;
> }
>
>
> --
> 2.43.0
>
>
next prev parent reply other threads:[~2026-03-24 0:03 UTC|newest]
Thread overview: 61+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-03-23 17:44 [PATCH 00/19] libfuse: Add support for synchronous init Bernd Schubert
2026-03-23 17:44 ` [PATCH 01/19] ci-build: Add environment logging Bernd Schubert
2026-03-23 17:44 ` [PATCH 02/19] Add 'STRCPY' to the checkpatch ignore option Bernd Schubert
2026-03-23 21:03 ` Darrick J. Wong
2026-03-23 17:44 ` [PATCH 03/19] checkpatch.pl: Add _Atomic to $Attribute patttern Bernd Schubert
2026-03-23 21:09 ` Darrick J. Wong
2026-03-23 17:44 ` [PATCH 04/19] Add a new daemonize API Bernd Schubert
2026-03-23 22:28 ` Darrick J. Wong
2026-03-24 17:36 ` Bernd Schubert
2026-03-24 22:20 ` Darrick J. Wong
2026-03-23 17:45 ` [PATCH 05/19] Sync fuse_kernel.h with linux-6.18 Bernd Schubert
2026-03-23 21:16 ` Darrick J. Wong
2026-03-23 17:45 ` [PATCH 06/19] mount.c: Split fuse_mount_sys to prepare privileged sync FUSE_INIT Bernd Schubert
2026-03-23 22:34 ` Darrick J. Wong
2026-03-23 17:45 ` [PATCH 07/19] Add FUSE_MOUNT_FALLBACK_NEEDED define for -2 mount errors Bernd Schubert
2026-03-23 22:36 ` Darrick J. Wong
2026-03-24 18:03 ` Bernd Schubert
2026-03-23 17:45 ` [PATCH 08/19] Refactor mount code / move common functions to mount_util.c Bernd Schubert
2026-03-23 22:40 ` Darrick J. Wong
2026-03-23 17:45 ` [PATCH 09/19] Move mount flags to mount_i.h Bernd Schubert
2026-03-23 22:45 ` Darrick J. Wong
2026-03-24 18:40 ` Bernd Schubert
2026-03-23 17:45 ` [PATCH 10/19] conftest.py: Add more valgrind filter patterns Bernd Schubert
2026-03-23 17:45 ` [PATCH 11/19] Add support for the new linux mount API Bernd Schubert
2026-03-23 23:42 ` Darrick J. Wong
2026-03-24 20:16 ` Bernd Schubert
2026-03-24 22:46 ` Darrick J. Wong
2026-03-23 17:45 ` [PATCH 12/19] fuse mount: Support synchronous FUSE_INIT (privileged daemon) Bernd Schubert
2026-03-24 0:03 ` Darrick J. Wong [this message]
2026-03-24 20:42 ` Bernd Schubert
2026-03-24 22:50 ` Darrick J. Wong
2026-03-25 7:52 ` Bernd Schubert
2026-03-25 16:42 ` Darrick J. Wong
2026-03-26 19:32 ` Bernd Schubert
2026-03-26 22:33 ` Darrick J. Wong
2026-03-23 17:45 ` [PATCH 13/19] Add fuse_session_set_debug() to enable debug output without foreground Bernd Schubert
2026-03-24 0:04 ` Darrick J. Wong
2026-03-23 17:45 ` [PATCH 14/19] Move more generic mount code to mount_util.{c,h} Bernd Schubert
2026-03-24 0:06 ` Darrick J. Wong
2026-03-24 20:57 ` Bernd Schubert
2026-03-23 17:45 ` [PATCH 15/19] Split the fusermount do_mount function Bernd Schubert
2026-03-24 0:14 ` Darrick J. Wong
2026-03-24 21:05 ` Bernd Schubert
2026-03-24 22:53 ` Darrick J. Wong
2026-03-23 17:45 ` [PATCH 16/19] fusermount: Refactor extract_x_options Bernd Schubert
2026-03-24 0:18 ` Darrick J. Wong
2026-03-23 17:45 ` [PATCH 17/19] Make fusermount work bidirectional for sync init Bernd Schubert
2026-03-24 19:35 ` Darrick J. Wong
2026-03-24 21:24 ` Bernd Schubert
2026-03-24 22:59 ` Darrick J. Wong
2026-03-25 19:48 ` Bernd Schubert
2026-03-25 22:03 ` Darrick J. Wong
2026-03-23 17:45 ` [PATCH 18/19] New mount API: Filter out "user=" Bernd Schubert
2026-03-24 19:51 ` Darrick J. Wong
2026-03-24 20:01 ` Bernd Schubert
2026-03-24 23:02 ` Darrick J. Wong
2026-03-23 17:45 ` [PATCH 19/19] Add support for sync-init of unprivileged daemons Bernd Schubert
2026-03-24 20:21 ` Darrick J. Wong
2026-03-24 21:53 ` Bernd Schubert
2026-03-24 23:13 ` Darrick J. Wong
2026-03-24 0:19 ` [PATCH 00/19] libfuse: Add support for synchronous init Darrick J. Wong
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260324000350.GM6202@frogsfrogsfrogs \
--to=djwong@kernel.org \
--cc=bernd@bsbernd.com \
--cc=bschubert@ddn.com \
--cc=joannelkoong@gmail.com \
--cc=linux-fsdevel@vger.kernel.org \
--cc=miklos@szeredi.hu \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox