From: Bernd Schubert <bernd@bsbernd.com>
To: linux-fsdevel@vger.kernel.org
Cc: Miklos Szeredi <miklos@szeredi.hu>,
Joanne Koong <joannelkoong@gmail.com>,
"Darrick J. Wong" <djwong@kernel.org>,
Bernd Schubert <bernd@bsbernd.com>,
Bernd Schubert <bschubert@ddn.com>
Subject: [PATCH 12/19] fuse mount: Support synchronous FUSE_INIT (privileged daemon)
Date: Mon, 23 Mar 2026 18:45:07 +0100 [thread overview]
Message-ID: <20260323-fuse-init-before-mount-v1-12-a52d3040af69@bsbernd.com> (raw)
In-Reply-To: <20260323-fuse-init-before-mount-v1-0-a52d3040af69@bsbernd.com>
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_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)) {
+ 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)
+ 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);
+ 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");
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;
+ }
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));
return -1;
}
--
2.43.0
next prev parent reply other threads:[~2026-03-23 17:45 UTC|newest]
Thread overview: 59+ 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 ` Bernd Schubert [this message]
2026-03-24 0:03 ` [PATCH 12/19] fuse mount: Support synchronous FUSE_INIT (privileged daemon) Darrick J. Wong
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-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=20260323-fuse-init-before-mount-v1-12-a52d3040af69@bsbernd.com \
--to=bernd@bsbernd.com \
--cc=bschubert@ddn.com \
--cc=djwong@kernel.org \
--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