From: Bernd Schubert <bernd@bsbernd.com>
To: "Darrick J. Wong" <djwong@kernel.org>
Cc: linux-fsdevel@vger.kernel.org, Miklos Szeredi <miklos@szeredi.hu>,
Joanne Koong <joannelkoong@gmail.com>, Kevin Chen <kchen@ddn.com>,
Bernd Schubert <bschubert@ddn.com>
Subject: Re: [PATCH v2 14/25] fuse mount: Support synchronous FUSE_INIT (privileged daemon)
Date: Mon, 20 Apr 2026 00:36:01 +0200 [thread overview]
Message-ID: <dbcae738-b675-491e-b8f8-e3ad0928b47f@bsbernd.com> (raw)
In-Reply-To: <20260330184418.GX6202@frogsfrogsfrogs>
On 3/30/26 20:44, Darrick J. Wong wrote:
> On Thu, Mar 26, 2026 at 10:34:47PM +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)
>> 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_daemonize.h | 7 ++
>> include/fuse_lowlevel.h | 12 +++
>> lib/fuse_daemonize.c | 6 ++
>> lib/fuse_i.h | 15 ++++
>> lib/fuse_lowlevel.c | 190 ++++++++++++++++++++++++++++++++++++++++++++++-
>> lib/mount.c | 5 +-
>> 6 files changed, 230 insertions(+), 5 deletions(-)
>>
>> diff --git a/include/fuse_daemonize.h b/include/fuse_daemonize.h
>> index c35dddd668b399535c53b44ab06c65fc0b3ddefa..6215e42c635ba5956cb23ba0832dfc291ab8dede 100644
>> --- a/include/fuse_daemonize.h
>> +++ b/include/fuse_daemonize.h
>> @@ -66,6 +66,13 @@ bool fuse_daemonize_is_active(void);
>> */
>> void fuse_daemonize_set_mounted(void);
>>
>> +/**
>> + * Check if daemonization is used.
>> + *
>> + * @return true if used, false otherwise
>> + */
>> +bool fuse_daemonize_is_used(void);
>
> These new fuse_daemonize_* function names are confusing --
> if fuse_daemonize_is_used() then I should be calling everything *but*
> fuse_daemonize().
>
> I wonder if a better name would be fuse_daemonize_early_* for the new
> functions?
I don't have a strong opinion on either, renamed to your suggestions.
Just lets avoid further renames as part of the introduction patch, takes
too much of my time to fix merge conflicts with later changes. Not
diffcult to solve, just time consuming. If we decide before the next
libfuse release to rename again - not beautiful but takes much less time.
>
>> +
>> #ifdef __cplusplus
>> }
>> #endif
>> 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 865acad7db56dbe5ed8a1bee52e7353627e89b75..97cfad7be879beacf69b020b7af78d512a224fd5 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>
>> @@ -290,3 +291,8 @@ void fuse_daemonize_set_mounted(void)
>> {
>> daemonize.mounted = true;
>> }
>> +
>> +bool fuse_daemonize_is_used(void)
>> +{
>> + return daemonize.active;
>> +}
>> diff --git a/lib/fuse_i.h b/lib/fuse_i.h
>> index 6d63c9fd2149eb4ae3b0e0170640a4ce2eed4705..164401e226eb727192a49e1cc7b38a75f031643b 100644
>> --- a/lib/fuse_i.h
>> +++ b/lib/fuse_i.h
>> @@ -112,6 +112,9 @@ struct fuse_session {
>>
>> /* synchronous FUSE_INIT support */
>> bool want_sync_init;
>> + pthread_t init_thread;
>> + int init_error;
>> + int init_wakeup_fd;
>>
>> /* io_uring */
>> struct fuse_session_uring uring;
>> @@ -221,7 +224,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 +262,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 a7be40cbb012361ad664a9ced3d38042ba52c681..0dd10e0ed53508e4716703f2f82aa35ad853b247 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,170 @@ 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 (true) {
>> + 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, res;
>> +
>> + if (!se->want_sync_init &&
>> + (se->uring.enable && !fuse_daemonize_is_used())) {
>> + 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) {
>> + err = -errno;
>> + if (err != ENOTTY) {
>> + fuse_log(
>> + FUSE_LOG_ERR,
>> + "fuse: failed to enable sync init: %s\n",
>> + strerror(errno));
>> + } else {
>> + /*
>> + * ENOTTY means kernel doesn't support sync init,not an
>> + * error
>> + */
>> + if (se->debug)
>> + fuse_log(
>> + FUSE_LOG_DEBUG,
>> + "fuse: kernel doesn't support sync init\n");
>> + err = 0;
>> + }
>> + return err;
>> + }
>> +
>> + if (se->debug)
>> + fuse_log(FUSE_LOG_DEBUG,
>> + "fuse: synchronous FUSE_INIT enabled\n");
>> +
>> + se->init_error = 0;
>> +
>> + 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_wakeup_fd == -1)
>> + return 0;
>> +
>> + 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: %s\n",
>> + strerror(-se->init_error));
>> + return -1;
>> + }
>> +
>> + if (fuse_session_exited(se)) {
>> + fuse_log(FUSE_LOG_ERR, "FUSE_INIT failed: session exited\n");
>> + return -1;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> static int fuse_session_mount_new_api(struct fuse_session *se,
>> const char *mountpoint)
>> {
>> @@ -4426,6 +4591,15 @@ 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)
>> + goto err;
>> +
>> 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) {
>> @@ -4435,13 +4609,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");
>
> Should fuse_session_mount_new_api return a nonzero value if waiting
> doesn't work?
I debated that internally forth and back already, came to the conclusion
that if that happens the mount actually succeeded. I don't have a strong
onion here. And assert would work, but would not be beautiful either.
>
>>
>> free(mnt_opts);
>> free(mnt_opts_with_fd);
>> @@ -4451,8 +4628,8 @@ err:
>> static int fuse_session_mount_new_api(struct fuse_session *se,
>> const char *mountpoint)
>> {
>> - (void)se;
>> - (void)mountpoint;
>> + (void) se;
>> + (void) mountpoint;
>
> Unrelated change?
Removed, must have been introduced by clangd/-format.
Thanks,
Bernd
next prev parent reply other threads:[~2026-04-19 22:36 UTC|newest]
Thread overview: 74+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-03-26 21:34 [PATCH v2 00/25] libfuse: Add support for synchronous init Bernd Schubert
2026-03-26 21:34 ` [PATCH v2 01/25] ci-build: Add environment logging Bernd Schubert
2026-03-27 3:20 ` Darrick J. Wong
2026-03-26 21:34 ` [PATCH v2 02/25] Add 'STRCPY' to the checkpatch ignore option Bernd Schubert
2026-03-26 21:34 ` [PATCH v2 03/25] checkpatch.pl: Add _Atomic to $Attribute patttern Bernd Schubert
2026-03-26 21:34 ` [PATCH v2 04/25] Add a new daemonize API Bernd Schubert
2026-03-27 22:06 ` Darrick J. Wong
2026-03-27 23:07 ` Bernd Schubert
2026-03-28 4:01 ` Darrick J. Wong
2026-03-30 17:45 ` Darrick J. Wong
2026-03-30 18:26 ` Bernd Schubert
2026-03-30 21:25 ` Darrick J. Wong
2026-03-30 17:55 ` Darrick J. Wong
2026-04-18 18:26 ` Bernd Schubert
2026-03-26 21:34 ` [PATCH v2 05/25] Sync fuse_kernel.h with linux-6.18 Bernd Schubert
2026-03-26 21:34 ` [PATCH v2 06/25] mount.c: Split fuse_mount_sys to prepare privileged sync FUSE_INIT Bernd Schubert
2026-03-26 21:34 ` [PATCH v2 07/25] Add FUSE_MOUNT_FALLBACK_NEEDED define for -2 mount errors Bernd Schubert
2026-03-27 3:20 ` Darrick J. Wong
2026-03-26 21:34 ` [PATCH v2 08/25] Refactor mount code / move common functions to mount_util.c Bernd Schubert
2026-03-26 21:34 ` [PATCH v2 09/25] Use asprintf() for fuse_mnt_build_{source,type} Bernd Schubert
2026-03-27 3:24 ` Darrick J. Wong
2026-03-30 15:34 ` Bernd Schubert
2026-03-26 21:34 ` [PATCH v2 10/25] lib/mount.c: Remove some BSD ifdefs Bernd Schubert
2026-03-27 3:28 ` Darrick J. Wong
2026-03-26 21:34 ` [PATCH v2 11/25] Move 'struct mount_flags' to util.h Bernd Schubert
2026-03-30 18:11 ` Darrick J. Wong
2026-03-26 21:34 ` [PATCH v2 12/25] conftest.py: Add more valgrind filter patterns Bernd Schubert
2026-03-30 18:16 ` Darrick J. Wong
2026-03-26 21:34 ` [PATCH v2 13/25] Add support for the new linux mount API Bernd Schubert
2026-03-30 18:27 ` Darrick J. Wong
2026-04-19 17:45 ` Bernd Schubert
2026-04-20 15:48 ` Darrick J. Wong
2026-03-26 21:34 ` [PATCH v2 14/25] fuse mount: Support synchronous FUSE_INIT (privileged daemon) Bernd Schubert
2026-03-30 18:44 ` Darrick J. Wong
2026-04-19 22:36 ` Bernd Schubert [this message]
2026-04-20 15:53 ` Darrick J. Wong
2026-04-20 16:40 ` Bernd Schubert
2026-04-20 16:43 ` Darrick J. Wong
2026-03-26 21:34 ` [PATCH v2 15/25] Add fuse_session_set_debug() to enable debug output without foreground Bernd Schubert
2026-03-26 21:34 ` [PATCH v2 16/25] Move more generic mount code to mount_util.{c,h} Bernd Schubert
2026-03-30 18:47 ` Darrick J. Wong
2026-03-26 21:34 ` [PATCH v2 17/25] Split the fusermount do_mount function Bernd Schubert
2026-03-30 18:48 ` Darrick J. Wong
2026-03-26 21:34 ` [PATCH v2 18/25] fusermout: Remove the large read check Bernd Schubert
2026-03-27 3:32 ` Darrick J. Wong
2026-03-30 15:26 ` Bernd Schubert
2026-03-30 17:57 ` Darrick J. Wong
2026-03-26 21:34 ` [PATCH v2 19/25] fusermount: Refactor extract_x_options Bernd Schubert
2026-03-26 21:34 ` [PATCH v2 20/25] Make fusermount work bidirectional for sync init Bernd Schubert
2026-03-30 19:03 ` Darrick J. Wong
2026-04-19 23:18 ` Bernd Schubert
2026-04-20 15:55 ` Darrick J. Wong
2026-03-26 21:34 ` [PATCH v2 21/25] New mount API: Filter out "user=" Bernd Schubert
2026-03-27 3:32 ` Darrick J. Wong
2026-03-26 21:34 ` [PATCH v2 22/25] Add support for sync-init of unprivileged daemons Bernd Schubert
2026-03-31 0:54 ` Darrick J. Wong
2026-04-20 0:02 ` Bernd Schubert
2026-04-20 16:31 ` Darrick J. Wong
2026-03-26 21:34 ` [PATCH v2 23/25] Move fuse_mnt_build_{source,type} to mount_util.c Bernd Schubert
2026-03-30 19:04 ` Darrick J. Wong
2026-03-26 21:34 ` [PATCH v2 24/25] Add mount and daemonization README documents Bernd Schubert
2026-03-31 1:17 ` Darrick J. Wong
2026-04-20 0:21 ` Bernd Schubert
2026-04-20 16:39 ` Darrick J. Wong
2026-03-26 21:34 ` [PATCH v2 25/25] Add a background debug option to passthrough hp Bernd Schubert
2026-03-30 19:04 ` Darrick J. Wong
2026-04-07 12:04 ` fuse-devel list on kernel.org Amir Goldstein
2026-04-07 12:25 ` Bernd Schubert
2026-04-07 12:29 ` Amir Goldstein
2026-04-07 18:05 ` Bernd Schubert
2026-04-07 19:10 ` Darrick J. Wong
2026-04-08 8:21 ` Amir Goldstein
2026-04-13 12:23 ` Amir Goldstein
2026-04-13 12:36 ` Bernd Schubert
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=dbcae738-b675-491e-b8f8-e3ad0928b47f@bsbernd.com \
--to=bernd@bsbernd.com \
--cc=bschubert@ddn.com \
--cc=djwong@kernel.org \
--cc=joannelkoong@gmail.com \
--cc=kchen@ddn.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