public inbox for linux-fsdevel@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 00/25] libfuse: Add support for synchronous init
@ 2026-03-26 21:34 Bernd Schubert
  2026-03-26 21:34 ` [PATCH v2 01/25] ci-build: Add environment logging Bernd Schubert
                   ` (25 more replies)
  0 siblings, 26 replies; 56+ messages in thread
From: Bernd Schubert @ 2026-03-26 21:34 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Miklos Szeredi, Joanne Koong, Darrick J. Wong, Kevin Chen,
	Bernd Schubert, Bernd Schubert

I'm taking Darricks example here and posting API changing libfuse
changes to linux-fsdevel. We should consider to create a fuse
specific list if that is too much. The existing
fuse-devel@lists.sourceforge.net is rather hopeless due to lack
of spam filtering.

The first few patches in this series are just preparation, after that
follow the important parts:

1) New daemonize API, see that commit for details. In short, the existing
fuse_daemonize() was not sufficient for complex daemons and is impossible
to use with sync-init and the current way to start fuse-io-uring ring
threads.

2) Support for privileged daemons, still rather straight forward, but
requires the startup of a worker thread that handles requests until
mount is complete.

3) Privileged daemons - requirement is to update the API with fusermount,
because fuse_session_mount_new_api() needs to obtain the /dev/fuse file
descriptor, then start the worker thread with that fd and then continue
the actual mount through fusermount.

To: linux-fsdevel@vger.kernel.org
Cc: Miklos Szeredi <miklos@szeredi.hu>
Cc: Joanne Koong <joannelkoong@gmail.com>
Cc: Darrick J. Wong <djwong@kernel.org>
Cc: Kevin Chen <kchen@ddn.com>
Joanne Koong <joannelkoong@gmail.com>

Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
---
Changes in v2:
- Fixed review comments from Darrick (thanks a lot for the review)
- Fixing some review comments required quite some restructuring
  to handle BSD (like using an include in fusermount.c for
  fuse_kern_fsmount()
- fuse_mnt_build_{source,type} are moved from mount.c to
  mount_util.c and also not using struct mount_opts anymore -
  fusermount.c and mount_fsmount.c can now use these functions
- A rather important change for fuse_daemonize_success(), it needs
  to be called from fuse_lowlevel_ops::init() and after fuse_session_mount(),
  it will figure out itself which of these two calls will actually signal
  success to the parent - depends on if sync or async FUSE_INIT is used.
- fuse_daemonize.c used a static variable for the state object 
  instead of dynamically allocated. Especially with latest additions to
  handle sync init correctly there were too many races. I consider to 
  change that file to C++ or Rust in the future to get reference count
  pointer to self destruct the object.
- The new doc/READMEs are in their own commit now
- Link to v1: https://lore.kernel.org/r/20260323-fuse-init-before-mount-v1-0-a52d3040af69@bsbernd.com

---
Bernd Schubert (25):
      ci-build: Add environment logging
      Add 'STRCPY' to the checkpatch ignore option
      checkpatch.pl: Add _Atomic to $Attribute patttern
      Add a new daemonize API
      Sync fuse_kernel.h with linux-6.18
      mount.c: Split fuse_mount_sys to prepare privileged sync FUSE_INIT
      Add FUSE_MOUNT_FALLBACK_NEEDED define for -2 mount errors
      Refactor mount code / move common functions to mount_util.c
      Use asprintf() for fuse_mnt_build_{source,type}
      lib/mount.c: Remove some BSD ifdefs
      Move 'struct mount_flags' to util.h
      conftest.py: Add more valgrind filter patterns
      Add support for the new linux mount API
      fuse mount: Support synchronous FUSE_INIT (privileged daemon)
      Add fuse_session_set_debug() to enable debug output without foreground
      Move more generic mount code to mount_util.{c,h}
      Split the fusermount do_mount function
      fusermout: Remove the large read check
      fusermount: Refactor extract_x_options
      Make fusermount work bidirectional for sync init
      New mount API: Filter out "user="
      Add support for sync-init of unprivileged daemons
      Move fuse_mnt_build_{source,type} to mount_util.c
      Add mount and daemonization README documents
      Add a background debug option to passthrough hp

 .github/workflows/checkpatch.yml |   2 +-
 checkpatch.pl                    |   3 +-
 doc/README.daemonize             | 197 ++++++++++++
 doc/README.fusermount            | 362 ++++++++++++++++++++++
 doc/README.mount                 |  86 ++++++
 doc/README.sync-init             | 184 +++++++++++
 example/passthrough_hp.cc        |  62 ++--
 include/fuse_daemonize.h         |  91 ++++++
 include/fuse_kernel.h            |   1 +
 include/fuse_lowlevel.h          |  32 ++
 include/fuse_mount_compat.h      |   5 +
 include/meson.build              |   3 +-
 lib/fuse_daemonize.c             | 304 ++++++++++++++++++
 lib/fuse_i.h                     |  27 +-
 lib/fuse_lowlevel.c              | 351 ++++++++++++++++++++-
 lib/fuse_versionscript           |   5 +
 lib/helper.c                     |  13 +-
 lib/meson.build                  |   6 +-
 lib/mount.c                      | 354 +++++++++++++--------
 lib/mount_bsd.c                  |   2 +
 lib/mount_common_i.h             |  29 ++
 lib/mount_fsmount.c              | 462 ++++++++++++++++++++++++++++
 lib/mount_i_linux.h              |  51 +++
 lib/mount_util.c                 | 119 ++++++-
 lib/mount_util.h                 |  25 ++
 meson.build                      |  19 +-
 test/ci-build.sh                 |  16 +
 test/conftest.py                 |   7 +-
 test/test_want_conversion.c      |   1 +
 util/fusermount.c                | 649 +++++++++++++++++++++++++++++----------
 util/meson.build                 |   2 +-
 31 files changed, 3137 insertions(+), 333 deletions(-)
---
base-commit: 9eba0f3c9e8b5af7b252093bb6f81f086bb35563
change-id: 20260323-fuse-init-before-mount-8f5b09a1acf1

Best regards,
-- 
Bernd Schubert <bernd@bsbernd.com>


^ permalink raw reply	[flat|nested] 56+ messages in thread

* [PATCH v2 01/25] ci-build: Add environment logging
  2026-03-26 21:34 [PATCH v2 00/25] libfuse: Add support for synchronous init Bernd Schubert
@ 2026-03-26 21:34 ` 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
                   ` (24 subsequent siblings)
  25 siblings, 1 reply; 56+ messages in thread
From: Bernd Schubert @ 2026-03-26 21:34 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Miklos Szeredi, Joanne Koong, Darrick J. Wong, Kevin Chen,
	Bernd Schubert, Bernd Schubert

From: Bernd Schubert <bschubert@ddn.com>

It is very hard to see what exactly fails, as everything
is run from the same script.

Signed-off-by: Bernd Schubert <bschubert@ddn.com>
---
 test/ci-build.sh | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/test/ci-build.sh b/test/ci-build.sh
index 8b019a0b5e52c1af0eee510ede21785d584e8414..799f13b2911fac47b0bbb2d445ec22a6f40f065d 100755
--- a/test/ci-build.sh
+++ b/test/ci-build.sh
@@ -30,6 +30,20 @@ export LSAN_OPTIONS="suppressions=$(pwd)/lsan_suppress.txt"
 export ASAN_OPTIONS="detect_leaks=1"
 export CC
 
+log_env()
+{
+    echo "=== Environment ==="
+    echo "CC: ${CC}"
+    echo "CXX: ${CXX}"
+    echo "LSAN_OPTIONS: ${LSAN_OPTIONS}"
+    echo "ASAN_OPTIONS: ${ASAN_OPTIONS}"
+    echo "UBSAN_OPTIONS: ${UBSAN_OPTIONS}"
+    echo "FUSE_URING_ENABLE: ${FUSE_URING_ENABLE}"
+    echo "FUSE_URING_QUEUE_DEPTH: ${FUSE_URING_QUEUE_DEPTH}"
+    echo "Valgrind: ${TEST_WITH_VALGRIND}"
+    echo "==================="
+}
+
 non_sanitized_build()
 (
     echo "Standard build (without sanitizers)"
@@ -54,6 +68,7 @@ non_sanitized_build()
             build_opts=''
         fi
 
+        log_env
         meson setup -Dprefix=${PREFIX_DIR} -D werror=true ${build_opts} "${SOURCE_DIR}" || (cat meson-logs/meson-log.txt; false)
         ninja
         sudo env PATH=$PATH ninja install
@@ -79,6 +94,7 @@ sanitized_build()
 
     mkdir build-san; pushd build-san
 
+    log_env
     meson setup -Dprefix=${PREFIX_DIR} -D werror=true\
            "${SOURCE_DIR}" \
            || (cat meson-logs/meson-log.txt; false)

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 02/25] Add 'STRCPY' to the checkpatch ignore option
  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-26 21:34 ` Bernd Schubert
  2026-03-26 21:34 ` [PATCH v2 03/25] checkpatch.pl: Add _Atomic to $Attribute patttern Bernd Schubert
                   ` (23 subsequent siblings)
  25 siblings, 0 replies; 56+ messages in thread
From: Bernd Schubert @ 2026-03-26 21:34 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Miklos Szeredi, Joanne Koong, Darrick J. Wong, Kevin Chen,
	Bernd Schubert, Bernd Schubert

From: Bernd Schubert <bschubert@ddn.com>

checkpatch.pl is a kernel copy and the strcpy warning is not valid
for libfuse.

Signed-off-by: Bernd Schubert <bschubert@ddn.com>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
---
 .github/workflows/checkpatch.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/checkpatch.yml b/.github/workflows/checkpatch.yml
index 4a98c91cddeab686c782a1d34cd5d15f86f0b42d..8dead95b065bc81449a98fa7ea94fac7463d2334 100644
--- a/.github/workflows/checkpatch.yml
+++ b/.github/workflows/checkpatch.yml
@@ -35,7 +35,7 @@ jobs:
           fi
           subject=$(git log -1 --format=%s $commit)
           echo "Checking commit: $commit - $subject"
-          if ! ./checkpatch.pl --max-line-length=100 --no-tree --ignore MAINTAINERS,SPDX_LICENSE_TAG,COMMIT_MESSAGE,FILE_PATH_CHANGES,EMAIL_SUBJECT,AVOID_EXTERNS,GIT_COMMIT_ID,ENOSYS_SYSCALL,ENOSYS,FROM_SIGN_OFF_MISMATCH,QUOTED_COMMIT_ID,PREFER_ATTRIBUTE_ALWAYS_UNUSED,PREFER_DEFINED_ATTRIBUTE_MACRO  -g $commit; then
+          if ! ./checkpatch.pl --max-line-length=100 --no-tree --ignore MAINTAINERS,SPDX_LICENSE_TAG,COMMIT_MESSAGE,FILE_PATH_CHANGES,EMAIL_SUBJECT,AVOID_EXTERNS,GIT_COMMIT_ID,ENOSYS_SYSCALL,ENOSYS,FROM_SIGN_OFF_MISMATCH,QUOTED_COMMIT_ID,PREFER_ATTRIBUTE_ALWAYS_UNUSED,PREFER_DEFINED_ATTRIBUTE_MACRO,STRCPY  -g $commit; then
             echo "checkpatch.pl found issues in commit $commit - $subject"
             exit 1
           fi

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 03/25] checkpatch.pl: Add _Atomic to $Attribute patttern
  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-26 21:34 ` [PATCH v2 02/25] Add 'STRCPY' to the checkpatch ignore option Bernd Schubert
@ 2026-03-26 21:34 ` Bernd Schubert
  2026-03-26 21:34 ` [PATCH v2 04/25] Add a new daemonize API Bernd Schubert
                   ` (22 subsequent siblings)
  25 siblings, 0 replies; 56+ messages in thread
From: Bernd Schubert @ 2026-03-26 21:34 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Miklos Szeredi, Joanne Koong, Darrick J. Wong, Kevin Chen,
	Bernd Schubert, Bernd Schubert

From: Bernd Schubert <bschubert@ddn.com>

WARNING: Missing a blank line after declarations
+       int init_error;
+       _Atomic bool terminate_mount_worker;

This change should go to linux upstream.

Signed-off-by: Bernd Schubert <bschubert@ddn.com>
Acked-by: "Darrick J. Wong" <djwong@kernel.org>
---
 checkpatch.pl | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/checkpatch.pl b/checkpatch.pl
index 9eed3683ad76caffbbb2418e5dbea7551d374406..2d44e0e6edac303731c7637de00ed35d7b1dfcb7 100755
--- a/checkpatch.pl
+++ b/checkpatch.pl
@@ -520,7 +520,8 @@ our $Attribute	= qr{
 			____cacheline_aligned_in_smp|
 			____cacheline_internodealigned_in_smp|
 			__weak|
-			__alloc_size\s*\(\s*\d+\s*(?:,\s*\d+\s*)?\)
+			__alloc_size\s*\(\s*\d+\s*(?:,\s*\d+\s*)?\)|
+			_Atomic
 		  }x;
 our $Modifier;
 our $Inline	= qr{inline|__always_inline|noinline|__inline|__inline__};

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 04/25] Add a new daemonize API
  2026-03-26 21:34 [PATCH v2 00/25] libfuse: Add support for synchronous init Bernd Schubert
                   ` (2 preceding siblings ...)
  2026-03-26 21:34 ` [PATCH v2 03/25] checkpatch.pl: Add _Atomic to $Attribute patttern Bernd Schubert
@ 2026-03-26 21:34 ` Bernd Schubert
  2026-03-27 22:06   ` Darrick J. Wong
  2026-03-30 17:55   ` Darrick J. Wong
  2026-03-26 21:34 ` [PATCH v2 05/25] Sync fuse_kernel.h with linux-6.18 Bernd Schubert
                   ` (21 subsequent siblings)
  25 siblings, 2 replies; 56+ messages in thread
From: Bernd Schubert @ 2026-03-26 21:34 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Miklos Szeredi, Joanne Koong, Darrick J. Wong, Kevin Chen,
	Bernd Schubert

Existing example/ file systems do the fuse_daemonize() after
fuse_session_mount() - i.e. after the mount point is already
established. Though, these example/ daemons do not start
extra threads and do not need network initialization either.

fuse_daemonize() also does not allow to return notification
from the forked child to the parent.

Complex fuse file system daemons often want the order of
1) fork - parent watches, child does the work

Child:
    2) start extra threads and system initialization (like network
       connection and RDMA memory registration) from the fork child.
    3) Start the fuse session after everything else succeeded

Parent:
    Report child initialization success or failure

A new API is introduced to overcome the limitations of
fuse_daemonize()

fuse_daemonize_start() - fork, but foreground process does not
terminate yet and watches the background.

fuse_daemonize_success() / fuse_daemonize_fail() - background
daemon signals to the foreground process success or failure.

fuse_daemonize_active() - helper function for the high level
interface, which needs to handle both APIs. fuse_daemonize()
is called within fuse_main(), which now needs to know if the caller
actually already used the new API itself.

The object 'struct fuse_daemonize *' is allocated dynamically
and stored a global variable in fuse_daemonize.c, because
- high level fuse_main_real_versioned() needs to know
if already daemonized
- high level daemons do not have access to struct fuse_session
- FUSE_SYNC_INIT in later commits can only be done if the new
API (or a file system internal) daemonization is used.

Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
---
 example/passthrough_hp.cc   |  18 ++-
 include/fuse_daemonize.h    |  74 +++++++++++
 include/meson.build         |   3 +-
 lib/fuse_daemonize.c        | 292 ++++++++++++++++++++++++++++++++++++++++++++
 lib/fuse_i.h                |   4 +-
 lib/fuse_lowlevel.c         |   3 +
 lib/fuse_versionscript      |   4 +
 lib/helper.c                |  13 +-
 lib/meson.build             |   3 +-
 test/test_want_conversion.c |   1 +
 10 files changed, 404 insertions(+), 11 deletions(-)

diff --git a/example/passthrough_hp.cc b/example/passthrough_hp.cc
index 9f795c5546ee8312e1393c5b8fcebfd77724fb49..bad435077697e8832cf5a5195c17f2f873f2dfe6 100644
--- a/example/passthrough_hp.cc
+++ b/example/passthrough_hp.cc
@@ -55,6 +55,7 @@
 #include <errno.h>
 #include <ftw.h>
 #include <fuse_lowlevel.h>
+#include <fuse_daemonize.h>
 #include <inttypes.h>
 #include <string.h>
 #include <sys/file.h>
@@ -243,6 +244,9 @@ 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 */
+	fuse_daemonize_success();
 }
 
 static void sfs_getattr(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi)
@@ -1580,6 +1584,7 @@ int main(int argc, char *argv[])
 {
 	struct fuse_loop_config *loop_config = NULL;
 	void *teardown_watchog = NULL;
+	unsigned int daemon_flags = 0;
 
 	// Parse command line options
 	auto options{ parse_options(argc, argv) };
@@ -1638,10 +1643,14 @@ int main(int argc, char *argv[])
 
 	fuse_loop_cfg_set_clone_fd(loop_config, fs.clone_fd);
 
-	if (fuse_session_mount(se, argv[2]) != 0)
+	/* Start daemonization before mount so parent can report mount failure */
+	if (fs.foreground)
+		daemon_flags |= FUSE_DAEMONIZE_NO_BACKGROUND;
+	if (fuse_daemonize_start(daemon_flags) != 0)
 		goto err_out3;
 
-	fuse_daemonize(fs.foreground);
+	if (fuse_session_mount(se, argv[2]) != 0)
+		goto err_out4;
 
 	if (!fs.foreground)
 		fuse_log_enable_syslog("passthrough-hp", LOG_PID | LOG_CONS,
@@ -1650,7 +1659,7 @@ int main(int argc, char *argv[])
 	teardown_watchog = fuse_session_start_teardown_watchdog(
 		se, fs.root.stop_timeout_secs, NULL, NULL);
 	if (teardown_watchog == NULL)
-		goto err_out3;
+		goto err_out4;
 
 	if (options.count("single"))
 		ret = fuse_session_loop(se);
@@ -1659,6 +1668,9 @@ int main(int argc, char *argv[])
 
 	fuse_session_unmount(se);
 
+err_out4:
+	if (fuse_daemonize_is_active())
+		fuse_daemonize_fail(ret);
 err_out3:
 	fuse_remove_signal_handlers(se);
 err_out2:
diff --git a/include/fuse_daemonize.h b/include/fuse_daemonize.h
new file mode 100644
index 0000000000000000000000000000000000000000..c35dddd668b399535c53b44ab06c65fc0b3ddefa
--- /dev/null
+++ b/include/fuse_daemonize.h
@@ -0,0 +1,74 @@
+/*
+ * FUSE: Filesystem in Userspace
+ * Copyright (C) 2026 Bernd Schubert <bsbernd.com>
+ *
+ * This program can be distributed under the terms of the GNU LGPLv2.
+ * See the file COPYING.LIB.
+ *
+ */
+
+#ifndef FUSE_DAEMONIZE_H_
+#define FUSE_DAEMONIZE_H_
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Flags for fuse_daemonize_start()
+ */
+#define FUSE_DAEMONIZE_NO_CHDIR      (1 << 0)
+#define FUSE_DAEMONIZE_NO_BACKGROUND (1 << 1)
+
+/**
+ * Start daemonization process.
+ *
+ * Unless FUSE_DAEMONIZE_NO_BACKGROUND is set, this forks the process.
+ * The parent waits for a signal from the child via fuse_daemonize_success()
+ * or fuse_daemonize_fail().
+ * The child returns from this call and continues setup.
+ *
+ * Unless FUSE_DAEMONIZE_NO_CHDIR is set, changes directory to "/".
+ *
+ * Must be called before fuse_session_mount().
+ *
+ * @param flags combination of FUSE_DAEMONIZE_* flags
+ * @return 0 on success, negative errno on error
+ */
+int fuse_daemonize_start(unsigned int flags);
+
+/**
+ * Signal daemonization success to parent and cleanup.
+ */
+void fuse_daemonize_success(void);
+
+/**
+ * Signal daemonization failure to parent and cleanup.
+ *
+ * @param err error code to pass to parent
+ */
+void fuse_daemonize_fail(int err);
+
+/**
+ * Check if daemonization is active and waiting for signal.
+ *
+ * @return true if active, false otherwise
+ */
+bool fuse_daemonize_is_active(void);
+
+/**
+ * Set mounted flag.
+ *
+ * Called from fuse_session_mount().
+ */
+void fuse_daemonize_set_mounted(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FUSE_DAEMONIZE_H_ */
+
diff --git a/include/meson.build b/include/meson.build
index bf671977a5a6a9142bd67aceabd8a919e3d968d0..cfbaf52ac5d84369e92948c631e2fcfdd04ac2eb 100644
--- a/include/meson.build
+++ b/include/meson.build
@@ -1,4 +1,5 @@
 libfuse_headers = [ 'fuse.h', 'fuse_common.h', 'fuse_lowlevel.h',
-	            'fuse_opt.h', 'cuse_lowlevel.h', 'fuse_log.h' ]
+	            'fuse_opt.h', 'cuse_lowlevel.h', 'fuse_log.h',
+	            'fuse_daemonize.h' ]
 
 install_headers(libfuse_headers, subdir: 'fuse3')
diff --git a/lib/fuse_daemonize.c b/lib/fuse_daemonize.c
new file mode 100644
index 0000000000000000000000000000000000000000..865acad7db56dbe5ed8a1bee52e7353627e89b75
--- /dev/null
+++ b/lib/fuse_daemonize.c
@@ -0,0 +1,292 @@
+/*
+ * FUSE: Filesystem in Userspace
+ * Copyright (C) 2026 Bernd Schubert <bsbernd.com>
+ *
+ * This program can be distributed under the terms of the GNU LGPLv2.
+ * See the file COPYING.LIB.
+ */
+
+#define _GNU_SOURCE
+
+#include "fuse_daemonize.h"
+
+#include <fcntl.h>
+#include <poll.h>
+#include <pthread.h>
+#include <stdatomic.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <errno.h>
+
+/**
+ * Status values for fuse_daemonize_success() and fuse_daemonize_fail()
+ */
+#define FUSE_DAEMONIZE_SUCCESS 0
+#define FUSE_DAEMONIZE_FAILURE 1
+
+/* Private/internal data  */
+struct fuse_daemonize {
+	unsigned int flags;
+	int signal_pipe_wr;	/* write end for signaling parent */
+	int death_pipe_rd;	/* read end, POLLHUP when parent dies */
+	int stop_pipe_rd;	/* read end for stop signal */
+	int stop_pipe_wr;	/* write end for stop signal */
+	pthread_t watcher;
+	bool watcher_started;
+	_Atomic bool active;
+	_Atomic bool daemonized;
+	_Atomic bool mounted;
+};
+
+/* Global daemonization object pointer */
+static struct fuse_daemonize daemonize = {
+	.signal_pipe_wr = -1,
+	.death_pipe_rd = -1,
+	.stop_pipe_rd = -1,
+	.stop_pipe_wr = -1,
+};
+
+/* Watcher thread: polls for parent death or stop signal */
+static void *parent_watcher_thread(void *arg)
+{
+	struct fuse_daemonize *di = arg;
+	struct pollfd pfd[2];
+
+	pfd[0].fd = di->death_pipe_rd;
+	pfd[0].events = POLLIN;
+	pfd[1].fd = di->stop_pipe_rd;
+	pfd[1].events = POLLIN;
+
+	while (1) {
+		int rc = poll(pfd, 2, -1);
+
+		if (rc < 0)
+			continue;
+
+		/* Parent died - death pipe write end closed */
+		if (pfd[0].revents & (POLLHUP | POLLERR))
+			_exit(EXIT_FAILURE);
+
+		/* Stop signal received */
+		if (pfd[1].revents & POLLIN)
+			break;
+	}
+	return NULL;
+}
+
+static int start_parent_watcher(struct fuse_daemonize *daemonize)
+{
+	int rc;
+
+	rc = pthread_create(&daemonize->watcher, NULL, parent_watcher_thread,
+			    daemonize);
+	if (rc != 0) {
+		fprintf(stderr, "fuse_daemonize: pthread_create: %s\n",
+			strerror(rc));
+		return -rc;
+	}
+	daemonize->watcher_started = true;
+	return 0;
+}
+
+static void stop_parent_watcher(struct fuse_daemonize *daemonize)
+{
+	char byte = 0;
+
+	if (daemonize && daemonize->watcher_started) {
+		/* Signal watcher to stop */
+		if (write(daemonize->stop_pipe_wr, &byte, 1) != 1)
+			perror("fuse_daemonize: stop write");
+		pthread_join(daemonize->watcher, NULL);
+		daemonize->watcher_started = false;
+	}
+}
+
+static int daemonize_child(struct fuse_daemonize *daemonize)
+{
+	int stop_pipe[2], err = 0;
+
+	if (pipe(stop_pipe) == -1) {
+		err = -errno;
+		perror("fuse_daemonize_start: stop pipe");
+		return err;
+	}
+	daemonize->stop_pipe_rd = stop_pipe[0];
+	daemonize->stop_pipe_wr = stop_pipe[1];
+
+	if (setsid() == -1) {
+		err = -errno;
+		perror("fuse_daemonize_start: setsid");
+		goto err_close_stop;
+	}
+
+	/* Close stdin immediately */
+	int nullfd = open("/dev/null", O_RDWR, 0);
+
+	if (nullfd != -1) {
+		(void)dup2(nullfd, 0);
+		if (nullfd > 0)
+			close(nullfd);
+	}
+
+	/* Start watcher thread to detect parent death */
+	err = start_parent_watcher(daemonize);
+	if (err)
+		goto err_close_stop;
+
+	daemonize->daemonized = true;
+	return 0;
+
+err_close_stop:
+	close(daemonize->stop_pipe_rd);
+	close(daemonize->stop_pipe_wr);
+	return err;
+}
+
+/* Fork and daemonize. Returns 0 in child, never returns in parent. */
+static int do_daemonize(struct fuse_daemonize *daemonize)
+{
+	int signal_pipe[2], death_pipe[2], err;
+
+	if (pipe(signal_pipe) == -1) {
+		err = -errno;
+		perror("fuse_daemonize_start: signal pipe");
+		return err;
+	}
+
+	if (pipe(death_pipe) == -1) {
+		err = -errno;
+		perror("fuse_daemonize_start: death pipe");
+		close(signal_pipe[0]);
+		close(signal_pipe[1]);
+		return err;
+	}
+
+	switch (fork()) {
+	case -1:
+		err = -errno;
+		perror("fuse_daemonize_start: fork");
+		close(signal_pipe[0]);
+		close(signal_pipe[1]);
+		close(death_pipe[0]);
+		close(death_pipe[1]);
+		return err;
+
+	case 0:
+		/* Child: signal write end, death read end */
+		close(signal_pipe[0]);
+		close(death_pipe[1]);
+		daemonize->signal_pipe_wr = signal_pipe[1];
+		daemonize->death_pipe_rd = death_pipe[0];
+		return daemonize_child(daemonize);
+
+	default: {
+		/* Parent: signal read end, death write end (kept open) */
+		int status;
+		ssize_t res;
+
+		close(signal_pipe[1]);
+		close(death_pipe[0]);
+
+		res = read(signal_pipe[0], &status, sizeof(status));
+		close(signal_pipe[0]);
+		close(death_pipe[1]);
+
+		if (res != sizeof(status))
+			_exit(EXIT_FAILURE);
+		_exit(status);
+	}
+	}
+}
+
+int fuse_daemonize_start(unsigned int flags)
+{
+	struct fuse_daemonize *dm = &daemonize;
+	int err = 0;
+
+	dm->flags = flags;
+	dm->signal_pipe_wr = -1;
+	dm->death_pipe_rd = -1;
+	dm->stop_pipe_rd = -1;
+	dm->stop_pipe_wr = -1;
+	dm->active = true;
+
+	if (!(flags & FUSE_DAEMONIZE_NO_CHDIR))
+		(void)chdir("/");
+
+	if (!(flags & FUSE_DAEMONIZE_NO_BACKGROUND))
+		err = do_daemonize(dm);
+
+	return err;
+}
+
+static void close_if_valid(int *fd)
+{
+	if (*fd != -1) {
+		close(*fd);
+		*fd = -1;
+	}
+}
+
+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");
+
+	dm->active = false;
+
+	/* Stop watcher before signaling - parent will exit after this */
+	stop_parent_watcher(dm);
+
+	/* Signal status to parent */
+	if (dm->signal_pipe_wr != -1) {
+		rc = write(dm->signal_pipe_wr, &status, sizeof(status));
+		if (rc != sizeof(status))
+			fprintf(stderr, "%s: write failed\n", __func__);
+	}
+
+	/* Redirect stdout/stderr to /dev/null on success */
+	if (status == FUSE_DAEMONIZE_SUCCESS && dm->daemonized) {
+		int nullfd = open("/dev/null", O_RDWR, 0);
+
+		if (nullfd != -1) {
+			(void)dup2(nullfd, 1);
+			(void)dup2(nullfd, 2);
+			if (nullfd > 2)
+				close(nullfd);
+		}
+	}
+
+	close_if_valid(&dm->signal_pipe_wr);
+	close_if_valid(&dm->death_pipe_rd);
+	close_if_valid(&dm->stop_pipe_rd);
+	close_if_valid(&dm->stop_pipe_wr);
+}
+
+void fuse_daemonize_success(void)
+{
+	fuse_daemonize_signal(FUSE_DAEMONIZE_SUCCESS);
+}
+
+void fuse_daemonize_fail(int err)
+{
+	fuse_daemonize_signal(err);
+}
+
+bool fuse_daemonize_is_active(void)
+{
+	return daemonize.daemonized || daemonize.active;
+}
+
+void fuse_daemonize_set_mounted(void)
+{
+	daemonize.mounted = true;
+}
diff --git a/lib/fuse_i.h b/lib/fuse_i.h
index 65d2f68f7f30918a3c3ee4d473796cb013428a8f..9e3c5dc5021e210a2778e975a37ab609af324010 100644
--- a/lib/fuse_i.h
+++ b/lib/fuse_i.h
@@ -17,7 +17,6 @@
 #include <semaphore.h>
 #include <stdint.h>
 #include <stdbool.h>
-#include <errno.h>
 #include <stdatomic.h>
 
 #define MIN(a, b) \
@@ -110,6 +109,9 @@ struct fuse_session {
 	/* true if reading requests from /dev/fuse are handled internally */
 	bool buf_reallocable;
 
+	/* synchronous FUSE_INIT support */
+	bool want_sync_init;
+
 	/* io_uring */
 	struct fuse_session_uring uring;
 
diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c
index 3234f0ce3b246a4c2c40dc0757177de91b6608b2..ccff6a768f0b8c32469abda9405ff29623f3fff7 100644
--- a/lib/fuse_lowlevel.c
+++ b/lib/fuse_lowlevel.c
@@ -19,6 +19,7 @@
 #include "mount_util.h"
 #include "util.h"
 #include "fuse_uring_i.h"
+#include "fuse_daemonize.h"
 
 #include <pthread.h>
 #include <stdatomic.h>
@@ -4451,6 +4452,8 @@ int fuse_session_mount(struct fuse_session *se, const char *_mountpoint)
 	/* Save mountpoint */
 	se->mountpoint = mountpoint;
 
+	fuse_daemonize_set_mounted();
+
 	return 0;
 
 error_out:
diff --git a/lib/fuse_versionscript b/lib/fuse_versionscript
index cce09610316f4b0b1d6836dd0e63686342b70037..dc6ed0135fb8d82937c756c3fb04a7fcb48fe1f4 100644
--- a/lib/fuse_versionscript
+++ b/lib/fuse_versionscript
@@ -227,6 +227,10 @@ FUSE_3.19 {
 		fuse_session_start_teardown_watchdog;
 		fuse_session_stop_teardown_watchdog;
 		fuse_lowlevel_notify_prune;
+		fuse_daemonize_start;
+		fuse_daemonize_success;
+		fuse_daemonize_fail;
+		fuse_daemonize_is_active;
 } FUSE_3.18;
 
 # Local Variables:
diff --git a/lib/helper.c b/lib/helper.c
index 5c13b93a473181f027eba01e0bfefd78875ede3e..35285be19aa0cc390a432d16701b9eefa16ec12a 100644
--- a/lib/helper.c
+++ b/lib/helper.c
@@ -15,6 +15,7 @@
 #include "fuse_misc.h"
 #include "fuse_opt.h"
 #include "fuse_lowlevel.h"
+#include "fuse_daemonize.h"
 #include "mount_util.h"
 
 #include <stdio.h>
@@ -352,17 +353,19 @@ int fuse_main_real_versioned(int argc, char *argv[],
 		goto out1;
 	}
 
+	struct fuse_session *se = fuse_get_session(fuse);
 	if (fuse_mount(fuse,opts.mountpoint) != 0) {
 		res = 4;
 		goto out2;
 	}
 
-	if (fuse_daemonize(opts.foreground) != 0) {
-		res = 5;
-		goto out3;
+	if (!fuse_daemonize_is_active()) {
+		/* Avoid daemonizing if we are already daemonized by the newer API */
+		if (fuse_daemonize(opts.foreground) != 0) {
+			res = 5;
+			goto out3;
+		}
 	}
-
-	struct fuse_session *se = fuse_get_session(fuse);
 	if (fuse_set_signal_handlers(se) != 0) {
 		res = 6;
 		goto out3;
diff --git a/lib/meson.build b/lib/meson.build
index fcd95741c9d3748fa01d9ec52b417aca66745f26..5bd449ebffe7c9229df904d647d990c6c47f80b5 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -2,7 +2,8 @@ libfuse_sources = ['fuse.c', 'fuse_i.h', 'fuse_loop.c', 'fuse_loop_mt.c',
                    'fuse_lowlevel.c', 'fuse_misc.h', 'fuse_opt.c',
                    'fuse_signals.c', 'buffer.c', 'cuse_lowlevel.c',
                    'helper.c', 'modules/subdir.c', 'mount_util.c',
-                   'fuse_log.c', 'compat.c', 'util.c', 'util.h' ]
+                   'fuse_log.c', 'compat.c', 'util.c', 'util.h',
+                   'fuse_daemonize.c' ]
 
 if host_machine.system().startswith('linux')
    libfuse_sources += [ 'mount.c' ]
diff --git a/test/test_want_conversion.c b/test/test_want_conversion.c
index db731edbfe1be8230ae16b422f798603b4a3bb82..48e6dd2dc6084425a0462bba000563c6083160be 100644
--- a/test/test_want_conversion.c
+++ b/test/test_want_conversion.c
@@ -8,6 +8,7 @@
 #include <inttypes.h>
 #include <stdbool.h>
 #include <err.h>
+#include <errno.h>
 
 static void print_conn_info(const char *prefix, struct fuse_conn_info *conn)
 {

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 05/25] Sync fuse_kernel.h with linux-6.18
  2026-03-26 21:34 [PATCH v2 00/25] libfuse: Add support for synchronous init Bernd Schubert
                   ` (3 preceding siblings ...)
  2026-03-26 21:34 ` [PATCH v2 04/25] Add a new daemonize API Bernd Schubert
@ 2026-03-26 21:34 ` 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
                   ` (20 subsequent siblings)
  25 siblings, 0 replies; 56+ messages in thread
From: Bernd Schubert @ 2026-03-26 21:34 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Miklos Szeredi, Joanne Koong, Darrick J. Wong, Kevin Chen,
	Bernd Schubert, Bernd Schubert

From: Bernd Schubert <bschubert@ddn.com>

Signed-off-by: Bernd Schubert <bschubert@ddn.com>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
---
 include/fuse_kernel.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/include/fuse_kernel.h b/include/fuse_kernel.h
index f0dee3d6cf51b0eb83104c1f30f70b8ba9d710cd..c13e1f9a2f12bd39f535188cb5466688eba42263 100644
--- a/include/fuse_kernel.h
+++ b/include/fuse_kernel.h
@@ -1138,6 +1138,7 @@ struct fuse_backing_map {
 #define FUSE_DEV_IOC_BACKING_OPEN	_IOW(FUSE_DEV_IOC_MAGIC, 1, \
 					     struct fuse_backing_map)
 #define FUSE_DEV_IOC_BACKING_CLOSE	_IOW(FUSE_DEV_IOC_MAGIC, 2, uint32_t)
+#define FUSE_DEV_IOC_SYNC_INIT		_IO(FUSE_DEV_IOC_MAGIC, 3)
 
 struct fuse_lseek_in {
 	uint64_t	fh;

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 06/25] mount.c: Split fuse_mount_sys to prepare privileged sync FUSE_INIT
  2026-03-26 21:34 [PATCH v2 00/25] libfuse: Add support for synchronous init Bernd Schubert
                   ` (4 preceding siblings ...)
  2026-03-26 21:34 ` [PATCH v2 05/25] Sync fuse_kernel.h with linux-6.18 Bernd Schubert
@ 2026-03-26 21:34 ` Bernd Schubert
  2026-03-26 21:34 ` [PATCH v2 07/25] Add FUSE_MOUNT_FALLBACK_NEEDED define for -2 mount errors Bernd Schubert
                   ` (19 subsequent siblings)
  25 siblings, 0 replies; 56+ messages in thread
From: Bernd Schubert @ 2026-03-26 21:34 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Miklos Szeredi, Joanne Koong, Darrick J. Wong, Kevin Chen,
	Bernd Schubert, Bernd Schubert

From: Bernd Schubert <bschubert@ddn.com>

Synchronous FUSE_INIT needs to spawn a worker thread to handle
FUSE_INIT before starting the mount. For that it needs to have
the device file descriptor - this is preparation work and
fuse_mount_sys() is split into fuse_kern_mount_prepare()
and fuse_kern_do_mount().

Signed-off-by: Bernd Schubert <bschubert@ddn.com>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
---
 lib/mount.c | 88 ++++++++++++++++++++++++++++++++++++++++++++++++-------------
 1 file changed, 69 insertions(+), 19 deletions(-)

diff --git a/lib/mount.c b/lib/mount.c
index 398bf9e7a86743ac3c99f0cc2975e8db92346013..6e404451cc9edc8e35434cc31f25612cfc4edca1 100644
--- a/lib/mount.c
+++ b/lib/mount.c
@@ -506,13 +506,11 @@ static int fuse_mount_fusermount(const char *mountpoint, struct mount_opts *mo,
 #define O_CLOEXEC 0
 #endif
 
-static int fuse_mount_sys(const char *mnt, struct mount_opts *mo,
-			  const char *mnt_opts)
+static int fuse_kern_mount_prepare(const char *mnt,
+				   struct mount_opts *mo)
 {
 	char tmp[128];
 	const char *devname = getenv(FUSE_KERN_DEVICE_ENV) ?: "/dev/fuse";
-	char *source = NULL;
-	char *type = NULL;
 	struct stat stbuf;
 	int fd;
 	int res;
@@ -524,32 +522,58 @@ static int fuse_mount_sys(const char *mnt, struct mount_opts *mo,
 
 	res = stat(mnt, &stbuf);
 	if (res == -1) {
-		fuse_log(FUSE_LOG_ERR, "fuse: failed to access mountpoint %s: %s\n",
-			mnt, strerror(errno));
+		fuse_log(FUSE_LOG_ERR,
+			 "fuse: failed to access mountpoint %s: %s\n", mnt,
+			 strerror(errno));
 		return -1;
 	}
 
 	fd = open(devname, O_RDWR | O_CLOEXEC);
 	if (fd == -1) {
 		if (errno == ENODEV || errno == ENOENT)
-			fuse_log(FUSE_LOG_ERR,
+			fuse_log(
+				FUSE_LOG_ERR,
 				"fuse: device %s not found. Kernel module not loaded?\n",
 				devname);
 		else
 			fuse_log(FUSE_LOG_ERR, "fuse: failed to open %s: %s\n",
-				devname, strerror(errno));
+				 devname, strerror(errno));
 		return -1;
 	}
 	if (!O_CLOEXEC)
 		fcntl(fd, F_SETFD, FD_CLOEXEC);
 
-	snprintf(tmp, sizeof(tmp),  "fd=%i,rootmode=%o,user_id=%u,group_id=%u",
+	snprintf(tmp, sizeof(tmp), "fd=%i,rootmode=%o,user_id=%u,group_id=%u",
 		 fd, stbuf.st_mode & S_IFMT, getuid(), getgid());
 
 	res = fuse_opt_add_opt(&mo->kernel_opts, tmp);
 	if (res == -1)
 		goto out_close;
 
+	return fd;
+
+out_close:
+	close(fd);
+	return -1;
+}
+
+/**
+ * Complete the mount operation with an already-opened fd
+ * @mnt: mountpoint
+ * @mo: mount options
+ * @mnt_opts: mount options to pass to the kernel
+ *
+ * Returns: 0 on success, -1 on failure, -2 if fusermount should be used
+ */
+static int fuse_kern_do_mount(const char *mnt, struct mount_opts *mo,
+				  const char *mnt_opts)
+{
+	const char *devname = getenv(FUSE_KERN_DEVICE_ENV) ?: "/dev/fuse";
+	char *source = NULL;
+	char *type = NULL;
+	int res;
+
+	res = -ENOMEM;
 	source = malloc((mo->fsname ? strlen(mo->fsname) : 0) +
 			(mo->subtype ? strlen(mo->subtype) : 0) +
 			strlen(devname) + 32);
@@ -593,10 +617,11 @@ static int fuse_mount_sys(const char *mnt, struct mount_opts *mo,
 			if (mo->blkdev && errno == ENODEV &&
 			    !fuse_mnt_check_fuseblk())
 				fuse_log(FUSE_LOG_ERR,
-					"fuse: 'fuseblk' support missing\n");
+					 "fuse: 'fuseblk' support missing\n");
 			else
-				fuse_log(FUSE_LOG_ERR, "fuse: mount failed: %s\n",
-					strerror(errno_save));
+				fuse_log(FUSE_LOG_ERR,
+					 "fuse: mount failed: %s\n",
+					 strerror(errno_save));
 		}
 
 		goto out_close;
@@ -619,17 +644,35 @@ static int fuse_mount_sys(const char *mnt, struct mount_opts *mo,
 	free(type);
 	free(source);
 
-	return fd;
+	return 0;
 
 out_umount:
 	umount2(mnt, 2); /* lazy umount */
 out_close:
 	free(type);
 	free(source);
-	close(fd);
 	return res;
 }
 
+static int fuse_mount_sys(const char *mnt, struct mount_opts *mo,
+				  const char *mnt_opts)
+{
+	int fd;
+	int res;
+
+	fd = fuse_kern_mount_prepare(mnt, mo);
+	if (fd == -1)
+		return -1;
+
+	res = fuse_kern_do_mount(mnt, mo, mnt_opts);
+	if (res) {
+		close(fd);
+		return res;
+	}
+
+	return fd;
+}
+
 static int get_mnt_flag_opts(char **mnt_optsp, int flags)
 {
 	int i;
@@ -678,6 +721,17 @@ void destroy_mount_opts(struct mount_opts *mo)
 	free(mo);
 }
 
+static int fuse_kern_mount_get_base_mnt_opts(struct mount_opts *mo,
+					     char **mnt_optsp)
+{
+	if (get_mnt_flag_opts(mnt_optsp, mo->flags) == -1)
+		return -1;
+	if (mo->kernel_opts && fuse_opt_add_opt(mnt_optsp, mo->kernel_opts) == -1)
+		return -1;
+	if (mo->mtab_opts &&  fuse_opt_add_opt(mnt_optsp, mo->mtab_opts) == -1)
+		return -1;
+	return 0;
+}
 
 int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo)
 {
@@ -685,11 +739,7 @@ int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo)
 	char *mnt_opts = NULL;
 
 	res = -1;
-	if (get_mnt_flag_opts(&mnt_opts, mo->flags) == -1)
-		goto out;
-	if (mo->kernel_opts && fuse_opt_add_opt(&mnt_opts, mo->kernel_opts) == -1)
-		goto out;
-	if (mo->mtab_opts &&  fuse_opt_add_opt(&mnt_opts, mo->mtab_opts) == -1)
+	if (fuse_kern_mount_get_base_mnt_opts(mo, &mnt_opts) == -1)
 		goto out;
 
 	res = fuse_mount_sys(mountpoint, mo, mnt_opts);

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 07/25] Add FUSE_MOUNT_FALLBACK_NEEDED define for -2 mount errors
  2026-03-26 21:34 [PATCH v2 00/25] libfuse: Add support for synchronous init Bernd Schubert
                   ` (5 preceding siblings ...)
  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 ` 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
                   ` (18 subsequent siblings)
  25 siblings, 1 reply; 56+ messages in thread
From: Bernd Schubert @ 2026-03-26 21:34 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Miklos Szeredi, Joanne Koong, Darrick J. Wong, Kevin Chen,
	Bernd Schubert, Bernd Schubert

From: Bernd Schubert <bschubert@ddn.com>

Magic numbers in the code are not good - we better use a define.

Signed-off-by: Bernd Schubert <bschubert@ddn.com>
---
 lib/fuse_i.h | 3 +++
 lib/mount.c  | 6 +++---
 2 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/lib/fuse_i.h b/lib/fuse_i.h
index 9e3c5dc5021e210a2778e975a37ab609af324010..a2438a15afdd3b5f62dd35bc3514f9f6853f00c3 100644
--- a/lib/fuse_i.h
+++ b/lib/fuse_i.h
@@ -217,6 +217,9 @@ struct fuse_chan *fuse_chan_get(struct fuse_chan *ch);
  */
 void fuse_chan_put(struct fuse_chan *ch);
 
+/* Special return value for mount functions to indicate fallback to fusermount3 is needed */
+#define FUSE_MOUNT_FALLBACK_NEEDED (-2)
+
 struct mount_opts *parse_mount_opts(struct fuse_args *args);
 void destroy_mount_opts(struct mount_opts *mo);
 void fuse_mount_version(void);
diff --git a/lib/mount.c b/lib/mount.c
index 6e404451cc9edc8e35434cc31f25612cfc4edca1..dec9d52274c13536648cacef959789f472c5682c 100644
--- a/lib/mount.c
+++ b/lib/mount.c
@@ -563,7 +563,7 @@ out_close:
  * @mo: mount options
  * @mnt_opts: mount options to pass to the kernel
  *
- * Returns: 0 on success, -1 on failure, -2 if fusermount should be used
+ * 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)
@@ -611,7 +611,7 @@ static int fuse_kern_do_mount(const char *mnt, struct mount_opts *mo,
 		 * case try falling back to fusermount3
 		 */
 		if (errno == EPERM) {
-			res = -2;
+			res = FUSE_MOUNT_FALLBACK_NEEDED;
 		} else {
 			int errno_save = errno;
 			if (mo->blkdev && errno == ENODEV &&
@@ -749,7 +749,7 @@ int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo)
 			umount2(mountpoint, MNT_DETACH); /* lazy umount */
 			res = -1;
 		}
-	} else if (res == -2) {
+	} else if (res == FUSE_MOUNT_FALLBACK_NEEDED) {
 		if (mo->fusermount_opts &&
 		    fuse_opt_add_opt(&mnt_opts, mo->fusermount_opts) == -1)
 			goto out;

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 08/25] Refactor mount code / move common functions to mount_util.c
  2026-03-26 21:34 [PATCH v2 00/25] libfuse: Add support for synchronous init Bernd Schubert
                   ` (6 preceding siblings ...)
  2026-03-26 21:34 ` [PATCH v2 07/25] Add FUSE_MOUNT_FALLBACK_NEEDED define for -2 mount errors Bernd Schubert
@ 2026-03-26 21:34 ` Bernd Schubert
  2026-03-26 21:34 ` [PATCH v2 09/25] Use asprintf() for fuse_mnt_build_{source,type} Bernd Schubert
                   ` (17 subsequent siblings)
  25 siblings, 0 replies; 56+ messages in thread
From: Bernd Schubert @ 2026-03-26 21:34 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Miklos Szeredi, Joanne Koong, Darrick J. Wong, Kevin Chen,
	Bernd Schubert, Bernd Schubert

From: Bernd Schubert <bschubert@ddn.com>

Also create the new "mount_i.h", which is independent of the
the rest of libfuse.

This is preparation for the new mount API, which goes into a new file.

Signed-off-by: Bernd Schubert <bschubert@ddn.com>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
---
 lib/fuse_i.h         | 10 ++----
 lib/mount.c          | 96 ++++++++++++++++++++++++++++------------------------
 lib/mount_bsd.c      |  1 +
 lib/mount_common_i.h | 31 +++++++++++++++++
 lib/mount_i_linux.h  | 32 ++++++++++++++++++
 lib/mount_util.c     | 24 +++++++++++++
 lib/mount_util.h     |  6 ++++
 7 files changed, 148 insertions(+), 52 deletions(-)

diff --git a/lib/fuse_i.h b/lib/fuse_i.h
index a2438a15afdd3b5f62dd35bc3514f9f6853f00c3..6d63c9fd2149eb4ae3b0e0170640a4ce2eed4705 100644
--- a/lib/fuse_i.h
+++ b/lib/fuse_i.h
@@ -19,14 +19,15 @@
 #include <stdbool.h>
 #include <stdatomic.h>
 
+#ifndef MIN
 #define MIN(a, b) \
 ({									\
 	typeof(a) _a = (a);						\
 	typeof(b) _b = (b);						\
 	_a < _b ? _a : _b;						\
 })
+#endif
 
-struct mount_opts;
 struct fuse_ring_pool;
 
 struct fuse_req {
@@ -217,13 +218,8 @@ struct fuse_chan *fuse_chan_get(struct fuse_chan *ch);
  */
 void fuse_chan_put(struct fuse_chan *ch);
 
-/* Special return value for mount functions to indicate fallback to fusermount3 is needed */
-#define FUSE_MOUNT_FALLBACK_NEEDED (-2)
-
-struct mount_opts *parse_mount_opts(struct fuse_args *args);
-void destroy_mount_opts(struct mount_opts *mo);
+/* Mount-related functions */
 void fuse_mount_version(void);
-unsigned get_max_read(struct mount_opts *o);
 void fuse_kern_unmount(const char *mountpoint, int fd);
 int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo);
 
diff --git a/lib/mount.c b/lib/mount.c
index dec9d52274c13536648cacef959789f472c5682c..fe353e2cc4579adb47473cac5db7d1bae2defb2c 100644
--- a/lib/mount.c
+++ b/lib/mount.c
@@ -16,6 +16,7 @@
 #include "fuse_misc.h"
 #include "fuse_opt.h"
 #include "mount_util.h"
+#include "mount_i_linux.h"
 
 #include <stdio.h>
 #include <stdlib.h>
@@ -49,7 +50,6 @@
 #define FUSERMOUNT_PROG		"fusermount3"
 #define FUSE_COMMFD_ENV		"_FUSE_COMMFD"
 #define FUSE_COMMFD2_ENV	"_FUSE_COMMFD2"
-#define FUSE_KERN_DEVICE_ENV	"FUSE_KERN_DEVICE"
 
 #ifndef MS_DIRSYNC
 #define MS_DIRSYNC 128
@@ -65,20 +65,6 @@ enum {
 	KEY_RO,
 };
 
-struct mount_opts {
-	int allow_other;
-	int flags;
-	int auto_unmount;
-	int blkdev;
-	char *fsname;
-	char *subtype;
-	char *subtype_opt;
-	char *mtab_opts;
-	char *fusermount_opts;
-	char *kernel_opts;
-	unsigned max_read;
-};
-
 #define FUSE_MOUNT_OPT(t, p) { t, offsetof(struct mount_opts, p), 1 }
 
 static const struct fuse_opt fuse_mount_opts[] = {
@@ -197,7 +183,7 @@ static const struct mount_flags mount_flags[] = {
 	{NULL,	    0,		    0}
 };
 
-unsigned get_max_read(struct mount_opts *o)
+unsigned int get_max_read(struct mount_opts *o)
 {
 	return o->max_read;
 }
@@ -510,7 +496,7 @@ static int fuse_kern_mount_prepare(const char *mnt,
 				   struct mount_opts *mo)
 {
 	char tmp[128];
-	const char *devname = getenv(FUSE_KERN_DEVICE_ENV) ?: "/dev/fuse";
+	const char *devname = fuse_mnt_get_devname();
 	struct stat stbuf;
 	int fd;
 	int res;
@@ -563,35 +549,24 @@ out_close:
  * @mo: mount options
  * @mnt_opts: mount options to pass to the kernel
  *
- * Returns: 0 on success, -1 on failure, FUSE_MOUNT_FALLBACK_NEEDED if fusermount should be used
+ * 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)
 {
-	const char *devname = getenv(FUSE_KERN_DEVICE_ENV) ?: "/dev/fuse";
 	char *source = NULL;
 	char *type = NULL;
 	int res;
 
 	res = -ENOMEM;
-	source = malloc((mo->fsname ? strlen(mo->fsname) : 0) +
-			(mo->subtype ? strlen(mo->subtype) : 0) +
-			strlen(devname) + 32);
-
-	type = malloc((mo->subtype ? strlen(mo->subtype) : 0) + 32);
+	source = fuse_mnt_build_source(mo);
+	type = fuse_mnt_build_type(mo);
 	if (!type || !source) {
 		fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate memory\n");
 		goto out_close;
 	}
 
-	strcpy(type, mo->blkdev ? "fuseblk" : "fuse");
-	if (mo->subtype) {
-		strcat(type, ".");
-		strcat(type, mo->subtype);
-	}
-	strcpy(source,
-	       mo->fsname ? mo->fsname : (mo->subtype ? mo->subtype : devname));
-
 	res = mount(source, mnt, type, mo->flags, mo->kernel_opts);
 	if (res == -1 && errno == ENODEV && mo->subtype) {
 		/* Probably missing subtype support */
@@ -627,20 +602,10 @@ static int fuse_kern_do_mount(const char *mnt, struct mount_opts *mo,
 		goto out_close;
 	}
 
-#ifndef IGNORE_MTAB
-	if (geteuid() == 0) {
-		char *newmnt = fuse_mnt_resolve_path("fuse", mnt);
-		res = -1;
-		if (!newmnt)
-			goto out_umount;
+	res = fuse_mnt_add_mount_helper(mnt, source, type, mnt_opts);
+	if (res == -1)
+		goto out_umount;
 
-		res = fuse_mnt_add_mount("fuse", source, newmnt, type,
-					 mnt_opts);
-		free(newmnt);
-		if (res == -1)
-			goto out_umount;
-	}
-#endif /* IGNORE_MTAB */
 	free(type);
 	free(source);
 
@@ -777,3 +742,44 @@ out:
 	free(mnt_opts);
 	return res;
 }
+
+const char *fuse_mnt_get_devname(void)
+{
+	const char *devname = getenv(FUSE_KERN_DEVICE_ENV);
+
+	return devname ? devname : "/dev/fuse";
+}
+
+char *fuse_mnt_build_source(const struct mount_opts *mo)
+{
+	const char *devname = fuse_mnt_get_devname();
+	char *source;
+
+	source = malloc((mo->fsname ? strlen(mo->fsname) : 0) +
+			(mo->subtype ? strlen(mo->subtype) : 0) +
+			strlen(devname) + 32);
+	if (!source)
+		return NULL;
+
+	strcpy(source,
+	       mo->fsname ? mo->fsname : (mo->subtype ? mo->subtype : devname));
+
+	return source;
+}
+
+char *fuse_mnt_build_type(const struct mount_opts *mo)
+{
+	char *type;
+
+	type = malloc((mo->subtype ? strlen(mo->subtype) : 0) + 32);
+	if (!type)
+		return NULL;
+
+	strcpy(type, mo->blkdev ? "fuseblk" : "fuse");
+	if (mo->subtype) {
+		strcat(type, ".");
+		strcat(type, mo->subtype);
+	}
+
+	return type;
+}
diff --git a/lib/mount_bsd.c b/lib/mount_bsd.c
index c12ab322e7dc86d5c8d2f2abc6b71488e82523cf..c5b831160f826c0e4620626a72c4b93427d18bab 100644
--- a/lib/mount_bsd.c
+++ b/lib/mount_bsd.c
@@ -10,6 +10,7 @@
 
 #include "fuse_config.h"
 #include "fuse_i.h"
+#include "mount_common_i.h"
 #include "fuse_misc.h"
 #include "fuse_opt.h"
 #include "util.h"
diff --git a/lib/mount_common_i.h b/lib/mount_common_i.h
new file mode 100644
index 0000000000000000000000000000000000000000..34f0f9893918a14c83a57a3212f80cfc96ed2e6d
--- /dev/null
+++ b/lib/mount_common_i.h
@@ -0,0 +1,31 @@
+/*
+ *  FUSE: Filesystem in Userspace
+ *  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
+ *                2026 Bernd Schubert <bernd@bsbernd.com>
+ *
+ *  This program can be distributed under the terms of the GNU LGPLv2.
+ *  See the file LGPL2.txt
+ */
+
+#ifndef FUSE_MOUNT_COMMON_I_H_
+#define FUSE_MOUNT_COMMON_I_H_
+
+/* Forward declaration for fuse_args */
+struct fuse_args;
+struct mount_opts;
+
+/* Special return value for mount functions to indicate fallback to fusermount3 is needed */
+#define FUSE_MOUNT_FALLBACK_NEEDED (-2)
+
+/* Environment variable for FUSE kernel device */
+#define FUSE_KERN_DEVICE_ENV "FUSE_KERN_DEVICE"
+
+/* Mount options management functions */
+struct mount_opts *parse_mount_opts(struct fuse_args *args);
+void destroy_mount_opts(struct mount_opts *mo);
+unsigned int get_max_read(struct mount_opts *o);
+char *fuse_mnt_build_source(const struct mount_opts *mo);
+char *fuse_mnt_build_type(const struct mount_opts *mo);
+
+
+#endif /* FUSE_MOUNT_COMMON_I_H_ */
diff --git a/lib/mount_i_linux.h b/lib/mount_i_linux.h
new file mode 100644
index 0000000000000000000000000000000000000000..254b75151f35578ce22197776e6fa4aceb04150e
--- /dev/null
+++ b/lib/mount_i_linux.h
@@ -0,0 +1,32 @@
+/*
+ *  FUSE: Filesystem in Userspace
+ *  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
+ *                2026 Bernd Schubert <bernd@bsbernd.com>
+ *
+ *  This program can be distributed under the terms of the GNU LGPLv2.
+ *  See the file LGPL2.txt
+ */
+
+#ifndef FUSE_MOUNT_I_LINUX_H_
+#define FUSE_MOUNT_I_LINUX_H_
+
+/* Forward declaration for fuse_args */
+struct fuse_args;
+
+/* Mount options structure */
+struct mount_opts {
+	int allow_other;
+	int flags;
+	int auto_unmount;
+	int blkdev;
+	char *fsname;
+	char *subtype;
+	char *subtype_opt;
+	char *mtab_opts;
+	char *fusermount_opts;
+	char *kernel_opts;
+	unsigned int max_read;
+};
+
+
+#endif /* FUSE_MOUNT_I_LINUX_H_ */
diff --git a/lib/mount_util.c b/lib/mount_util.c
index 8c0cdf72d978da68c95125964416b92668104924..a42a02a0b92f98778abb2c491cdc7a01260f56ee 100644
--- a/lib/mount_util.c
+++ b/lib/mount_util.c
@@ -375,3 +375,27 @@ int fuse_mnt_parse_fuse_fd(const char *mountpoint)
 
 	return -1;
 }
+
+int fuse_mnt_add_mount_helper(const char *mnt, const char *source,
+			       const char *type, const char *mnt_opts)
+{
+#ifndef IGNORE_MTAB
+	if (geteuid() == 0) {
+		char *newmnt = fuse_mnt_resolve_path("fuse", mnt);
+		int res;
+
+		if (!newmnt)
+			return -1;
+
+		res = fuse_mnt_add_mount("fuse", source, newmnt, type,
+					 mnt_opts);
+		free(newmnt);
+		return res;
+	}
+#endif
+	(void)mnt;
+	(void)source;
+	(void)type;
+	(void)mnt_opts;
+	return 0;
+}
diff --git a/lib/mount_util.h b/lib/mount_util.h
index 9cb9077dd177381d712e34e7118bf73572142c6a..4688d00091f001b3ccd36b1ef3b7e799031ed773 100644
--- a/lib/mount_util.h
+++ b/lib/mount_util.h
@@ -7,6 +7,7 @@
 */
 
 #include <sys/types.h>
+#include "mount_common_i.h" // IWYU pragma: keep
 
 int fuse_mnt_add_mount(const char *progname, const char *fsname,
 		       const char *mnt, const char *type, const char *opts);
@@ -16,3 +17,8 @@ int fuse_mnt_umount(const char *progname, const char *abs_mnt,
 char *fuse_mnt_resolve_path(const char *progname, const char *orig);
 int fuse_mnt_check_fuseblk(void);
 int fuse_mnt_parse_fuse_fd(const char *mountpoint);
+
+/* Helper functions for mount operations */
+const char *fuse_mnt_get_devname(void);
+int fuse_mnt_add_mount_helper(const char *mnt, const char *source,
+			       const char *type, const char *mnt_opts);

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 09/25] Use asprintf() for fuse_mnt_build_{source,type}
  2026-03-26 21:34 [PATCH v2 00/25] libfuse: Add support for synchronous init Bernd Schubert
                   ` (7 preceding siblings ...)
  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 ` Bernd Schubert
  2026-03-27  3:24   ` Darrick J. Wong
  2026-03-26 21:34 ` [PATCH v2 10/25] lib/mount.c: Remove some BSD ifdefs Bernd Schubert
                   ` (16 subsequent siblings)
  25 siblings, 1 reply; 56+ messages in thread
From: Bernd Schubert @ 2026-03-26 21:34 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Miklos Szeredi, Joanne Koong, Darrick J. Wong, Kevin Chen,
	Bernd Schubert, Bernd Schubert

Simplify the code by using asprintf.

Suggested-by: "Darrick J. Wong" <djwong@kernel.org>
Signed-off-by: Bernd Schubert <bschubert@ddn.com>
---
 lib/mount.c | 28 +++++++++++++---------------
 1 file changed, 13 insertions(+), 15 deletions(-)

diff --git a/lib/mount.c b/lib/mount.c
index fe353e2cc4579adb47473cac5db7d1bae2defb2c..68f9219d2b8beee51346b198ce826138b9528e73 100644
--- a/lib/mount.c
+++ b/lib/mount.c
@@ -754,32 +754,30 @@ char *fuse_mnt_build_source(const struct mount_opts *mo)
 {
 	const char *devname = fuse_mnt_get_devname();
 	char *source;
+	int ret;
 
-	source = malloc((mo->fsname ? strlen(mo->fsname) : 0) +
-			(mo->subtype ? strlen(mo->subtype) : 0) +
-			strlen(devname) + 32);
-	if (!source)
+	ret = asprintf(&source, "%s",
+		       mo->fsname ? mo->fsname :
+				    (mo->subtype ? mo->subtype : devname));
+	if (ret == -1)
 		return NULL;
 
-	strcpy(source,
-	       mo->fsname ? mo->fsname : (mo->subtype ? mo->subtype : devname));
-
 	return source;
 }
 
 char *fuse_mnt_build_type(const struct mount_opts *mo)
 {
 	char *type;
+	int ret;
 
-	type = malloc((mo->subtype ? strlen(mo->subtype) : 0) + 32);
-	if (!type)
+	if (mo->subtype)
+		ret = asprintf(&type, "%s.%s", mo->blkdev ? "fuseblk" : "fuse",
+			       mo->subtype);
+	else
+		ret = asprintf(&type, "%s", mo->blkdev ? "fuseblk" : "fuse");
+
+	if (ret == -1)
 		return NULL;
 
-	strcpy(type, mo->blkdev ? "fuseblk" : "fuse");
-	if (mo->subtype) {
-		strcat(type, ".");
-		strcat(type, mo->subtype);
-	}
-
 	return type;
 }

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 10/25] lib/mount.c: Remove some BSD ifdefs
  2026-03-26 21:34 [PATCH v2 00/25] libfuse: Add support for synchronous init Bernd Schubert
                   ` (8 preceding siblings ...)
  2026-03-26 21:34 ` [PATCH v2 09/25] Use asprintf() for fuse_mnt_build_{source,type} Bernd Schubert
@ 2026-03-26 21:34 ` 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
                   ` (15 subsequent siblings)
  25 siblings, 1 reply; 56+ messages in thread
From: Bernd Schubert @ 2026-03-26 21:34 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Miklos Szeredi, Joanne Koong, Darrick J. Wong, Kevin Chen,
	Bernd Schubert, Bernd Schubert

BSD has its own mount_bsd.c, mount.c should be linux only.

Also remove MS_DIRSYNC defintion, which was introduced a long
time ago - all use cases of recent libfuse should have picked
it up.

Some ifdefs stay, as they will be moved in the next patch.

Suggested-by: "Darrick J. Wong" <djwong@kernel.org>
Signed-off-by: Bernd Schubert <bschubert@ddn.com>
---
 lib/mount.c | 29 +++++++++--------------------
 1 file changed, 9 insertions(+), 20 deletions(-)

diff --git a/lib/mount.c b/lib/mount.c
index 68f9219d2b8beee51346b198ce826138b9528e73..899cd2d0a13a9332a564b10d1c22836fb2cd1710 100644
--- a/lib/mount.c
+++ b/lib/mount.c
@@ -43,27 +43,20 @@
 #define MS_SYNCHRONOUS	MNT_SYNCHRONOUS
 #define MS_NOATIME	MNT_NOATIME
 #define MS_NOSYMFOLLOW	MNT_NOSYMFOLLOW
-
-#define umount2(mnt, flags) unmount(mnt, (flags == 2) ? MNT_FORCE : 0)
 #endif
 
 #define FUSERMOUNT_PROG		"fusermount3"
 #define FUSE_COMMFD_ENV		"_FUSE_COMMFD"
 #define FUSE_COMMFD2_ENV	"_FUSE_COMMFD2"
 
-#ifndef MS_DIRSYNC
-#define MS_DIRSYNC 128
-#endif
-
-enum {
-	KEY_KERN_FLAG,
-	KEY_KERN_OPT,
-	KEY_FUSERMOUNT_OPT,
-	KEY_SUBTYPE_OPT,
-	KEY_MTAB_OPT,
-	KEY_ALLOW_OTHER,
-	KEY_RO,
-};
+	enum { KEY_KERN_FLAG,
+	       KEY_KERN_OPT,
+	       KEY_FUSERMOUNT_OPT,
+	       KEY_SUBTYPE_OPT,
+	       KEY_MTAB_OPT,
+	       KEY_ALLOW_OTHER,
+	       KEY_RO,
+	};
 
 #define FUSE_MOUNT_OPT(t, p) { t, offsetof(struct mount_opts, p), 1 }
 
@@ -177,9 +170,7 @@ static const struct mount_flags mount_flags[] = {
 	{"nostrictatime",   MS_STRICTATIME,	0},
 	{"symfollow",	    MS_NOSYMFOLLOW,	0},
 	{"nosymfollow",	    MS_NOSYMFOLLOW,	1},
-#ifndef __NetBSD__
-	{"dirsync", MS_DIRSYNC,	    1},
-#endif
+	{"dirsync",	    MS_DIRSYNC,	    1},
 	{NULL,	    0,		    0}
 };
 
@@ -264,8 +255,6 @@ static int receive_fd(int fd)
 	msg.msg_namelen = 0;
 	msg.msg_iov = &iov;
 	msg.msg_iovlen = 1;
-	/* old BSD implementations should use msg_accrights instead of
-	 * msg_control; the interface is different. */
 	msg.msg_control = ccmsg;
 	msg.msg_controllen = sizeof(ccmsg);
 

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 11/25] Move 'struct mount_flags' to util.h
  2026-03-26 21:34 [PATCH v2 00/25] libfuse: Add support for synchronous init Bernd Schubert
                   ` (9 preceding siblings ...)
  2026-03-26 21:34 ` [PATCH v2 10/25] lib/mount.c: Remove some BSD ifdefs Bernd Schubert
@ 2026-03-26 21:34 ` 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
                   ` (14 subsequent siblings)
  25 siblings, 1 reply; 56+ messages in thread
From: Bernd Schubert @ 2026-03-26 21:34 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Miklos Szeredi, Joanne Koong, Darrick J. Wong, Kevin Chen,
	Bernd Schubert

We actually need these in fusermount.c and for the
new mount API, which goes into its own file.

Also extend the struct with the extra safe field
used by fusermount.c

Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
---
 include/fuse_mount_compat.h |  5 +++++
 lib/mount.c                 | 39 --------------------------------
 lib/mount_bsd.c             |  1 +
 lib/mount_i_linux.h         |  4 ++--
 lib/mount_util.c            | 54 ++++++++++++++++++++++++++++++++++++++++++++-
 lib/mount_util.h            |  9 ++++++++
 util/fusermount.c           | 34 ----------------------------
 7 files changed, 70 insertions(+), 76 deletions(-)

diff --git a/include/fuse_mount_compat.h b/include/fuse_mount_compat.h
index be8d576cf959fba7acb25440fafd43f7e973964e..1b0eb00c3eb79ad2d58a9e86ac2836d3f7d3b2fb 100644
--- a/include/fuse_mount_compat.h
+++ b/include/fuse_mount_compat.h
@@ -11,6 +11,9 @@
 #ifndef FUSE_MOUNT_COMPAT_H_
 #define FUSE_MOUNT_COMPAT_H_
 
+#if !defined(__NetBSD__) && !defined(__FreeBSD__) && !defined(__DragonFly__) && \
+	!defined(__FreeBSD_kernel__)
+
 #include <sys/mount.h>
 
 /* Some libc don't define MS_*, so define them manually
@@ -46,4 +49,6 @@
 #define UMOUNT_UNUSED	0x80000000	/* Flag guaranteed to be unused */
 #endif
 
+#endif /* BSD */
+
 #endif /* FUSE_MOUNT_COMPAT_H_ */
diff --git a/lib/mount.c b/lib/mount.c
index 899cd2d0a13a9332a564b10d1c22836fb2cd1710..43920a06ac03747595bde95441252e9cac77ce88 100644
--- a/lib/mount.c
+++ b/lib/mount.c
@@ -33,18 +33,6 @@
 
 #include "fuse_mount_compat.h"
 
-#ifdef __NetBSD__
-#include <perfuse.h>
-
-#define MS_RDONLY	MNT_RDONLY
-#define MS_NOSUID	MNT_NOSUID
-#define MS_NODEV	MNT_NODEV
-#define MS_NOEXEC	MNT_NOEXEC
-#define MS_SYNCHRONOUS	MNT_SYNCHRONOUS
-#define MS_NOATIME	MNT_NOATIME
-#define MS_NOSYMFOLLOW	MNT_NOSYMFOLLOW
-#endif
-
 #define FUSERMOUNT_PROG		"fusermount3"
 #define FUSE_COMMFD_ENV		"_FUSE_COMMFD"
 #define FUSE_COMMFD2_ENV	"_FUSE_COMMFD2"
@@ -147,33 +135,6 @@ void fuse_mount_version(void)
 			 FUSERMOUNT_PROG);
 }
 
-struct mount_flags {
-	const char *opt;
-	unsigned long flag;
-	int on;
-};
-
-static const struct mount_flags mount_flags[] = {
-	{"rw",	    MS_RDONLY,	    0},
-	{"ro",	    MS_RDONLY,	    1},
-	{"suid",    MS_NOSUID,	    0},
-	{"nosuid",  MS_NOSUID,	    1},
-	{"dev",	    MS_NODEV,	    0},
-	{"nodev",   MS_NODEV,	    1},
-	{"exec",    MS_NOEXEC,	    0},
-	{"noexec",  MS_NOEXEC,	    1},
-	{"async",   MS_SYNCHRONOUS, 0},
-	{"sync",    MS_SYNCHRONOUS, 1},
-	{"noatime", MS_NOATIME,	    1},
-	{"nodiratime",	    MS_NODIRATIME,	1},
-	{"norelatime",	    MS_RELATIME,	0},
-	{"nostrictatime",   MS_STRICTATIME,	0},
-	{"symfollow",	    MS_NOSYMFOLLOW,	0},
-	{"nosymfollow",	    MS_NOSYMFOLLOW,	1},
-	{"dirsync",	    MS_DIRSYNC,	    1},
-	{NULL,	    0,		    0}
-};
-
 unsigned int get_max_read(struct mount_opts *o)
 {
 	return o->max_read;
diff --git a/lib/mount_bsd.c b/lib/mount_bsd.c
index c5b831160f826c0e4620626a72c4b93427d18bab..09631932eff443d21743dd55dcd5345dd581227f 100644
--- a/lib/mount_bsd.c
+++ b/lib/mount_bsd.c
@@ -16,6 +16,7 @@
 #include "util.h"
 
 #include <sys/param.h>
+#include <sys/mount.h>
 #include "fuse_mount_compat.h"
 
 #include <sys/wait.h>
diff --git a/lib/mount_i_linux.h b/lib/mount_i_linux.h
index 254b75151f35578ce22197776e6fa4aceb04150e..1bfc78b32dc4fd434870e4fff1a37e0c340d207e 100644
--- a/lib/mount_i_linux.h
+++ b/lib/mount_i_linux.h
@@ -10,7 +10,8 @@
 #ifndef FUSE_MOUNT_I_LINUX_H_
 #define FUSE_MOUNT_I_LINUX_H_
 
-/* Forward declaration for fuse_args */
+#include <sys/mount.h>
+
 struct fuse_args;
 
 /* Mount options structure */
@@ -28,5 +29,4 @@ struct mount_opts {
 	unsigned int max_read;
 };
 
-
 #endif /* FUSE_MOUNT_I_LINUX_H_ */
diff --git a/lib/mount_util.c b/lib/mount_util.c
index a42a02a0b92f98778abb2c491cdc7a01260f56ee..9ff711023fcb41fe24f1de21aaf811cb1c12d25e 100644
--- a/lib/mount_util.c
+++ b/lib/mount_util.c
@@ -34,9 +34,61 @@
 #include <sys/param.h>
 
 #if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__)
-#define umount2(mnt, flags) unmount(mnt, ((flags) == 2) ? MNT_FORCE : 0)
+#include <sys/mount.h>
+#ifdef __NetBSD__
+#include <perfuse.h>
 #endif
 
+#define umount2(mnt, flags) unmount(mnt, ((flags) == 2) ? MNT_FORCE : 0)
+
+#define MS_RDONLY	MNT_RDONLY
+#define MS_NOSUID	MNT_NOSUID
+#ifdef MNT_NODEV
+#define MS_NODEV	MNT_NODEV
+#else
+#define MS_NODEV	0
+#endif
+#define MS_NOEXEC	MNT_NOEXEC
+#define MS_SYNCHRONOUS	MNT_SYNCHRONOUS
+#define MS_NOATIME	MNT_NOATIME
+#define MS_NOSYMFOLLOW	0
+#define MS_DIRSYNC	0
+
+/* BSD doesn't have these, define as 0 */
+#ifndef MS_NODIRATIME
+#define MS_NODIRATIME 0
+#endif
+#ifndef MS_RELATIME
+#define MS_RELATIME 0
+#endif
+#ifndef MS_STRICTATIME
+#define MS_STRICTATIME 0
+#endif
+
+#endif /* BSD */
+
+/* XXX This table is duplicated in util/fusermount.c */
+const struct mount_flags mount_flags[] = {
+	{"rw",	    MS_RDONLY,	    0, 1},
+	{"ro",	    MS_RDONLY,	    1, 1},
+	{"suid",    MS_NOSUID,	    0, 0},
+	{"nosuid",  MS_NOSUID,	    1, 1},
+	{"dev",	    MS_NODEV,	    0, 1},
+	{"nodev",   MS_NODEV,	    1, 1},
+	{"exec",    MS_NOEXEC,	    0, 1},
+	{"noexec",  MS_NOEXEC,	    1, 1},
+	{"async",   MS_SYNCHRONOUS, 0, 1},
+	{"sync",    MS_SYNCHRONOUS, 1, 1},
+	{"noatime", MS_NOATIME,	    1, 1},
+	{"nodiratime",	    MS_NODIRATIME,	1, 1},
+	{"norelatime",	    MS_RELATIME,	0, 1},
+	{"nostrictatime",   MS_STRICTATIME,	0, 1},
+	{"symfollow",	    MS_NOSYMFOLLOW,	0, 1},
+	{"nosymfollow",	    MS_NOSYMFOLLOW,	1, 1},
+	{"dirsync",	    MS_DIRSYNC,		1, 1},
+	{NULL,	    0,		    0, 0}
+};
+
 #ifdef IGNORE_MTAB
 #define mtab_needs_update(mnt) 0
 #else
diff --git a/lib/mount_util.h b/lib/mount_util.h
index 4688d00091f001b3ccd36b1ef3b7e799031ed773..e62052b626875abd6118b845de162d6c5c41857a 100644
--- a/lib/mount_util.h
+++ b/lib/mount_util.h
@@ -9,6 +9,15 @@
 #include <sys/types.h>
 #include "mount_common_i.h" // IWYU pragma: keep
 
+/* Mount flags mapping structure */
+struct mount_flags {
+	const char *opt;
+	unsigned long flag;
+	int on;
+	int safe; /* used by fusermount */
+};
+extern const struct mount_flags mount_flags[];
+
 int fuse_mnt_add_mount(const char *progname, const char *fsname,
 		       const char *mnt, const char *type, const char *opts);
 int fuse_mnt_remove_mount(const char *progname, const char *mnt);
diff --git a/util/fusermount.c b/util/fusermount.c
index f17b44f51142c682b339d0ce2287f7c00d644454..f66c4e8e0e809351384ec10cf50ba4d3d3a831b0 100644
--- a/util/fusermount.c
+++ b/util/fusermount.c
@@ -759,40 +759,6 @@ static int begins_with(const char *s, const char *beg)
 		return 0;
 }
 
-struct mount_flags {
-	const char *opt;
-	unsigned long flag;
-	int on;
-	int safe;
-};
-
-static struct mount_flags mount_flags[] = {
-	{"rw",	    MS_RDONLY,	    0, 1},
-	{"ro",	    MS_RDONLY,	    1, 1},
-	{"suid",    MS_NOSUID,	    0, 0},
-	{"nosuid",  MS_NOSUID,	    1, 1},
-	{"dev",	    MS_NODEV,	    0, 0},
-	{"nodev",   MS_NODEV,	    1, 1},
-	{"exec",    MS_NOEXEC,	    0, 1},
-	{"noexec",  MS_NOEXEC,	    1, 1},
-	{"async",   MS_SYNCHRONOUS, 0, 1},
-	{"sync",    MS_SYNCHRONOUS, 1, 1},
-	{"atime",   MS_NOATIME,	    0, 1},
-	{"noatime", MS_NOATIME,	    1, 1},
-	{"diratime",        MS_NODIRATIME,  0, 1},
-	{"nodiratime",      MS_NODIRATIME,  1, 1},
-	{"lazytime",        MS_LAZYTIME,    1, 1},
-	{"nolazytime",      MS_LAZYTIME,    0, 1},
-	{"relatime",        MS_RELATIME,    1, 1},
-	{"norelatime",      MS_RELATIME,    0, 1},
-	{"strictatime",     MS_STRICTATIME, 1, 1},
-	{"nostrictatime",   MS_STRICTATIME, 0, 1},
-	{"dirsync", MS_DIRSYNC,	    1, 1},
-	{"symfollow",       MS_NOSYMFOLLOW, 0, 1},
-	{"nosymfollow",     MS_NOSYMFOLLOW, 1, 1},
-	{NULL,	    0,		    0, 0}
-};
-
 static int find_mount_flag(const char *s, unsigned len, int *on, int *flag)
 {
 	int i;

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 12/25] conftest.py: Add more valgrind filter patterns
  2026-03-26 21:34 [PATCH v2 00/25] libfuse: Add support for synchronous init Bernd Schubert
                   ` (10 preceding siblings ...)
  2026-03-26 21:34 ` [PATCH v2 11/25] Move 'struct mount_flags' to util.h Bernd Schubert
@ 2026-03-26 21:34 ` 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
                   ` (13 subsequent siblings)
  25 siblings, 1 reply; 56+ messages in thread
From: Bernd Schubert @ 2026-03-26 21:34 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Miklos Szeredi, Joanne Koong, Darrick J. Wong, Kevin Chen,
	Bernd Schubert, Bernd Schubert

From: Bernd Schubert <bschubert@ddn.com>

Valgrind complains that it does not know the fsmount syscall,
pytest checks for warnings, find that and fails the test

--14936-- WARNING: unhandled amd64-linux syscall: 430
--14936-- You may be able to write your own handler.
--14936-- Read the file README_MISSING_SYSCALL_OR_IOCTL.
--14936-- Nevertheless we consider this a bug.  Please report
--14936-- it at http://valgrind.org/support/bug_reports.html.
fuse: fsopen(fuse) failed: Function not implemented
=========================== short test summary info

Obviously we want to filter our valgrind warnings.

Signed-off-by: Bernd Schubert <bschubert@ddn.com>
---
 test/conftest.py | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/test/conftest.py b/test/conftest.py
index 291c9199b9860d664600638213382bd8cc182104..45e9672a92e687980ba93cb25ef711ad1c2e3633 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -67,11 +67,16 @@ class OutputChecker:
             cp = re.compile(pattern, flags)
             (buf, cnt) = cp.subn('', buf, count=count)
 
+        # Filter out Valgrind output lines before checking for suspicious words
+        # ==PID== prefix: Valgrind standard messages (errors, info)
+        # --PID-- prefix: Valgrind warnings (e.g., unhandled syscalls)
+        buf = re.sub(r'^==[0-9]+== .*$', '', buf, flags=re.MULTILINE)
+        buf = re.sub(r'^--[0-9]+-- .*$', '', buf, flags=re.MULTILINE)
+
         patterns = [ r'\b{}\b'.format(x) for x in
                      ('exception', 'error', 'warning', 'fatal', 'traceback',
                         'fault', 'crash(?:ed)?', 'abort(?:ed)',
                         'uninitiali[zs]ed') ]
-        patterns += ['^==[0-9]+== ']
 
         for pattern in patterns:
             cp = re.compile(pattern, re.IGNORECASE | re.MULTILINE)

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 13/25] Add support for the new linux mount API
  2026-03-26 21:34 [PATCH v2 00/25] libfuse: Add support for synchronous init Bernd Schubert
                   ` (11 preceding siblings ...)
  2026-03-26 21:34 ` [PATCH v2 12/25] conftest.py: Add more valgrind filter patterns Bernd Schubert
@ 2026-03-26 21:34 ` Bernd Schubert
  2026-03-30 18:27   ` Darrick J. Wong
  2026-03-26 21:34 ` [PATCH v2 14/25] fuse mount: Support synchronous FUSE_INIT (privileged daemon) Bernd Schubert
                   ` (12 subsequent siblings)
  25 siblings, 1 reply; 56+ messages in thread
From: Bernd Schubert @ 2026-03-26 21:34 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Miklos Szeredi, Joanne Koong, Darrick J. Wong, Kevin Chen,
	Bernd Schubert, Bernd Schubert

From: Bernd Schubert <bschubert@ddn.com>

So far only supported for fuse_session_mount(), which is called
from high and low level API, but not yet supported for
fuse_open_channel(), which used for privilege drop through
mount.fuse. Main goal for the new API is support for synchronous
FUSE_INIT and I don't think that is going to work with
fuse_open_channel(). At least not with io-uring support as long
as it is started from FUSE_INIT.

Signed-off-by: Bernd Schubert <bschubert@ddn.com>
---
 lib/fuse_lowlevel.c |  74 ++++++++-
 lib/meson.build     |   3 +
 lib/mount.c         |  27 +++-
 lib/mount_fsmount.c | 454 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 lib/mount_i_linux.h |  14 ++
 meson.build         |  19 ++-
 6 files changed, 583 insertions(+), 8 deletions(-)

diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c
index ccff6a768f0b8c32469abda9405ff29623f3fff7..a7be40cbb012361ad664a9ced3d38042ba52c681 100644
--- a/lib/fuse_lowlevel.c
+++ b/lib/fuse_lowlevel.c
@@ -20,6 +20,9 @@
 #include "util.h"
 #include "fuse_uring_i.h"
 #include "fuse_daemonize.h"
+#if defined(__linux__)
+#include "mount_i_linux.h"
+#endif
 
 #include <pthread.h>
 #include <stdatomic.h>
@@ -4398,6 +4401,63 @@ int fuse_session_custom_io_30(struct fuse_session *se,
 			offsetof(struct fuse_custom_io, clone_fd), fd);
 }
 
+#if defined(HAVE_NEW_MOUNT_API)
+static int fuse_session_mount_new_api(struct fuse_session *se,
+				      const char *mountpoint)
+{
+	int fd = -1;
+	int res, err;
+	char *mnt_opts = NULL;
+	char *mnt_opts_with_fd = NULL;
+	char fd_opt[32];
+
+	res = fuse_kern_mount_get_base_mnt_opts(se->mo, &mnt_opts);
+	err = -EIO;
+	if (res == -1) {
+		fuse_log(FUSE_LOG_ERR,
+			 "fuse: failed to get base mount options\n");
+		goto err;
+	}
+
+	fd = fuse_kern_mount_prepare(mountpoint, se->mo);
+	if (fd == -1) {
+		fuse_log(FUSE_LOG_ERR, "Mount preparation failed.\n");
+		err = -EIO;
+		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) {
+		err = -ENOMEM;
+		goto err;
+	}
+
+	err = fuse_kern_fsmount_mo(mountpoint, se->mo, mnt_opts_with_fd);
+err:
+	if (err) {
+		if (fd >= 0)
+			close(fd);
+		fd = -1;
+		se->fd = -1;
+		se->error = -errno;
+	}
+
+	free(mnt_opts);
+	free(mnt_opts_with_fd);
+	return fd;
+}
+#else
+static int fuse_session_mount_new_api(struct fuse_session *se,
+				      const char *mountpoint)
+{
+	(void)se;
+	(void)mountpoint;
+
+	return -1;
+}
+#endif
+
 int fuse_session_mount(struct fuse_session *se, const char *_mountpoint)
 {
 	int fd;
@@ -4425,6 +4485,8 @@ int fuse_session_mount(struct fuse_session *se, const char *_mountpoint)
 			close(fd);
 	} while (fd >= 0 && fd <= 2);
 
+	/* Open channel */
+
 	/*
 	 * To allow FUSE daemons to run without privileges, the caller may open
 	 * /dev/fuse before launching the file system and pass on the file
@@ -4443,10 +4505,18 @@ int fuse_session_mount(struct fuse_session *se, const char *_mountpoint)
 		return 0;
 	}
 
-	/* Open channel */
+	/* new linux mount api */
+	fd = fuse_session_mount_new_api(se, mountpoint);
+	if (fd >= 0)
+		goto out;
+
+	/* fall back to old API */
+	se->error = 0; /* reset error of new api */
 	fd = fuse_kern_mount(mountpoint, se->mo);
-	if (fd == -1)
+	if (fd < 0)
 		goto error_out;
+
+out:
 	se->fd = fd;
 
 	/* Save mountpoint */
diff --git a/lib/meson.build b/lib/meson.build
index 5bd449ebffe7c9229df904d647d990c6c47f80b5..5fd738a589c5aba97a738d5eedbf0f9962e4adfc 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -7,6 +7,9 @@ libfuse_sources = ['fuse.c', 'fuse_i.h', 'fuse_loop.c', 'fuse_loop_mt.c',
 
 if host_machine.system().startswith('linux')
    libfuse_sources += [ 'mount.c' ]
+   if private_cfg.get('HAVE_NEW_MOUNT_API', false)
+      libfuse_sources += [ 'mount_fsmount.c' ]
+   endif
 else
    libfuse_sources += [ 'mount_bsd.c' ]
 endif
diff --git a/lib/mount.c b/lib/mount.c
index 43920a06ac03747595bde95441252e9cac77ce88..e8c65363d36a56f483f82434f642e785da4d0341 100644
--- a/lib/mount.c
+++ b/lib/mount.c
@@ -442,8 +442,8 @@ static int fuse_mount_fusermount(const char *mountpoint, struct mount_opts *mo,
 #define O_CLOEXEC 0
 #endif
 
-static int fuse_kern_mount_prepare(const char *mnt,
-				   struct mount_opts *mo)
+int fuse_kern_mount_prepare(const char *mnt,
+			    struct mount_opts *mo)
 {
 	char tmp[128];
 	const char *devname = fuse_mnt_get_devname();
@@ -493,6 +493,26 @@ out_close:
 	return -1;
 }
 
+#if defined(HAVE_NEW_MOUNT_API)
+/**
+ * Wrapper for fuse_kern_fsmount that accepts struct mount_opts
+ * @mnt: mountpoint
+ * @mo: mount options
+ * @mnt_opts: mount options to pass to the kernel
+ *
+ * Returns: 0 on success, -1 on failure with errno set
+ */
+int fuse_kern_fsmount_mo(const char *mnt, struct mount_opts *mo,
+			 const char *mnt_opts)
+{
+	const char *devname = fuse_mnt_get_devname();
+
+	return fuse_kern_fsmount(mnt, mo->flags, mo->blkdev, mo->fsname,
+				 mo->subtype, devname, mo->kernel_opts,
+				 mnt_opts);
+}
+#endif
+
 /**
  * Complete the mount operation with an already-opened fd
  * @mnt: mountpoint
@@ -636,8 +656,7 @@ void destroy_mount_opts(struct mount_opts *mo)
 	free(mo);
 }
 
-static int fuse_kern_mount_get_base_mnt_opts(struct mount_opts *mo,
-					     char **mnt_optsp)
+int fuse_kern_mount_get_base_mnt_opts(struct mount_opts *mo, char **mnt_optsp)
 {
 	if (get_mnt_flag_opts(mnt_optsp, mo->flags) == -1)
 		return -1;
diff --git a/lib/mount_fsmount.c b/lib/mount_fsmount.c
new file mode 100644
index 0000000000000000000000000000000000000000..fbe3647a8923828dba819458b815c21bbd2beaad
--- /dev/null
+++ b/lib/mount_fsmount.c
@@ -0,0 +1,454 @@
+/*
+ *  FUSE: Filesystem in Userspace
+ *  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
+ *                2026 Bernd Schubert <bernd@bsbernd.com>
+ *
+ * New Linux mount API (fsopen/fsconfig/fsmount/move_mount) support.
+ *
+ *  This program can be distributed under the terms of the GNU LGPLv2.
+ *  See the file LGPL2.txt.
+ */
+
+#define _GNU_SOURCE
+
+#include "fuse_config.h"
+#include "fuse_misc.h"
+#include "mount_util.h"
+#include "mount_i_linux.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/mount.h>
+#include <sys/syscall.h>
+
+/*
+ * Mount attribute flags for fsmount() - from linux/mount.h
+ * This file is only compiled conditionally when support for the new
+ * mount API is detected - only flags that were not in the initial linux
+ * commit introducing that API are defined here.
+ */
+#ifndef MOUNT_ATTR_NOSYMFOLLOW
+#define MOUNT_ATTR_NOSYMFOLLOW  0x00200000
+#endif
+
+/*
+ * Convert MS_* mount flags to MOUNT_ATTR_* mount attributes.
+ * These flags are passed to fsmount(), not fsconfig().
+ * Mount attributes control mount-point level behavior.
+ */
+static unsigned long ms_flags_to_mount_attrs(unsigned long flags)
+{
+	unsigned long attrs = 0;
+
+	if (flags & MS_NOSUID)
+		attrs |= MOUNT_ATTR_NOSUID;
+	if (flags & MS_NODEV)
+		attrs |= MOUNT_ATTR_NODEV;
+	if (flags & MS_NOEXEC)
+		attrs |= MOUNT_ATTR_NOEXEC;
+	if (flags & MS_NOATIME)
+		attrs |= MOUNT_ATTR_NOATIME;
+	else if (flags & MS_RELATIME)
+		attrs |= MOUNT_ATTR_RELATIME;
+	else if (flags & MS_STRICTATIME)
+		attrs |= MOUNT_ATTR_STRICTATIME;
+	if (flags & MS_NODIRATIME)
+		attrs |= MOUNT_ATTR_NODIRATIME;
+	if (flags & MS_NOSYMFOLLOW)
+		attrs |= MOUNT_ATTR_NOSYMFOLLOW;
+
+	return attrs;
+}
+
+/*
+ * Read and print kernel error messages from fsopen fd.
+ * The kernel can provide detailed error/warning/info messages via the
+ * filesystem context fd that are more informative than strerror(errno).
+ */
+static void log_fsconfig_kmsg(int fd)
+{
+	char buf[4096];
+	int err, sz = 0;
+
+	err = errno;
+
+	while ((sz = read(fd, buf, sizeof(buf) - 1)) != -1) {
+		if (sz <= 0)
+			continue;
+		if (buf[sz - 1] == '\n')
+			buf[--sz] = '\0';
+		else
+			buf[sz] = '\0';
+
+		if (!*buf)
+			continue;
+
+		switch (buf[0]) {
+		case 'e':
+			fprintf(stderr, " Error: %s\n", buf + 2);
+			break;
+		case 'w':
+			fprintf(stderr, " Warning: %s\n", buf + 2);
+			break;
+		case 'i':
+			fprintf(stderr, " Info: %s\n", buf + 2);
+			break;
+		default:
+			fprintf(stderr, " %s\n", buf);
+			break;
+		}
+	}
+
+	errno = err;
+}
+
+/*
+ * Apply VFS superblock flags to the filesystem context.
+ * Only handles flags that are filesystem parameters (ro, sync, dirsync).
+ * Mount attributes (nosuid, nodev, etc.) are handled separately via fsmount().
+ */
+static int apply_mount_flags(int fsfd, unsigned long flags)
+{
+	int res, save_errno;
+
+	/* Handle read-only flag */
+	if (flags & MS_RDONLY) {
+		const char *flag = "ro";
+
+		res = fsconfig(fsfd, FSCONFIG_SET_FLAG, flag, NULL, 0);
+		if (res == -1) {
+			save_errno = errno;
+			fprintf(stderr, "fuse: fsconfig SET_FLAG %s failed:", flag);
+			log_fsconfig_kmsg(fsfd);
+			return -save_errno;
+		}
+	}
+
+	/* Handle sync flag */
+	if (flags & MS_SYNCHRONOUS) {
+		const char *flag = "sync";
+
+		res = fsconfig(fsfd, FSCONFIG_SET_FLAG, flag, NULL, 0);
+		if (res == -1) {
+			save_errno = errno;
+			fprintf(stderr, "fuse: fsconfig SET_FLAG %s failed:", flag);
+			log_fsconfig_kmsg(fsfd);
+			return -save_errno;
+		}
+	}
+
+#ifndef __NetBSD__
+	/* Handle dirsync flag */
+	if (flags & MS_DIRSYNC) {
+		const char *flag = "dirsync";
+
+		res = fsconfig(fsfd, FSCONFIG_SET_FLAG, flag, NULL, 0);
+		if (res == -1) {
+			save_errno = errno;
+			fprintf(stderr, "fuse: fsconfig SET_FLAG %s failed:", flag);
+			log_fsconfig_kmsg(fsfd);
+			return -save_errno;
+		}
+	}
+#endif
+
+	return 0;
+}
+
+static int apply_opt_fd(int fsfd, const char *value)
+{
+	int res, save_errno;
+
+	/* The fd parameter is a u32 value, not a file descriptor to pass */
+	res = fsconfig(fsfd, FSCONFIG_SET_STRING, "fd", value, 0);
+	if (res == -1) {
+		save_errno = errno;
+		fprintf(stderr, "fuse: fsconfig SET_STRING fd=%s failed:",
+			value);
+		log_fsconfig_kmsg(fsfd);
+		return -save_errno;
+	}
+	return 0;
+}
+
+static int apply_opt_string(int fsfd, const char *key, const char *value)
+{
+	int res, save_errno;
+
+	res = fsconfig(fsfd, FSCONFIG_SET_STRING, key, value, 0);
+	save_errno = errno;
+	if (res == -1) {
+		log_fsconfig_kmsg(fsfd);
+		fprintf(stderr, "fuse: fsconfig SET_STRING %s=%s failed: ",
+			key, value);
+		return -save_errno;
+	}
+	return 0;
+}
+
+static int apply_opt_flag(int fsfd, const char *opt)
+{
+	int res, save_errno;
+
+	res = fsconfig(fsfd, FSCONFIG_SET_FLAG, opt, NULL, 0);
+	if (res == -1) {
+		save_errno = errno;
+		fprintf(stderr, "fuse: fsconfig SET_FLAG %s failed:", opt);
+		log_fsconfig_kmsg(fsfd);
+		return -save_errno;
+	}
+	return 0;
+}
+
+static int apply_opt_key_value(int fsfd, char *opt)
+{
+	char *eq;
+	const char *key;
+	const char *value;
+
+	eq = strchr(opt, '=');
+	if (!eq)
+		return apply_opt_flag(fsfd, opt);
+
+	*eq = '\0';
+	key = opt;
+	value = eq + 1;
+
+	if (strcmp(key, "fd") == 0)
+		return apply_opt_fd(fsfd, value);
+
+	return apply_opt_string(fsfd, key, value);
+}
+
+/**
+ * Check if an option is a mount attribute (handled by fsmount, not fsconfig)
+ */
+static int is_mount_attr_opt(const char *opt)
+{
+	/* These options are mount attributes passed to fsmount(), not fsconfig() */
+	return strcmp(opt, "nosuid") == 0 ||
+	       strcmp(opt, "suid") == 0 ||
+	       strcmp(opt, "nodev") == 0 ||
+	       strcmp(opt, "dev") == 0 ||
+	       strcmp(opt, "noexec") == 0 ||
+	       strcmp(opt, "exec") == 0 ||
+	       strcmp(opt, "noatime") == 0 ||
+	       strcmp(opt, "atime") == 0 ||
+	       strcmp(opt, "nodiratime") == 0 ||
+	       strcmp(opt, "diratime") == 0 ||
+	       strcmp(opt, "relatime") == 0 ||
+	       strcmp(opt, "norelatime") == 0 ||
+	       strcmp(opt, "strictatime") == 0 ||
+	       strcmp(opt, "nostrictatime") == 0 ||
+	       strcmp(opt, "nosymfollow") == 0 ||
+	       strcmp(opt, "symfollow") == 0;
+}
+
+/*
+ * Parse kernel options string and apply via fsconfig
+ * Options are comma-separated key=value pairs
+ */
+static int apply_mount_opts(int fsfd, const char *opts)
+{
+	char *opts_copy;
+	char *opt;
+	char *saveptr;
+	int res;
+
+	if (!opts || !*opts)
+		return 0;
+
+	opts_copy = strdup(opts);
+	if (!opts_copy) {
+		fprintf(stderr, "fuse: failed to allocate memory\n");
+		return -ENOMEM;
+	}
+
+	opt = strtok_r(opts_copy, ",", &saveptr);
+	while (opt) {
+		/*
+		 * Skip mount attributes, they're handled by fsmount()
+		 * not fsconfig().
+		 *
+		 * These string options (nosuid, nodev, etc.) are reconstructed
+		 * from MS_* flags by get_mnt_flag_opts() in lib/mount.c and
+		 * get_mnt_opts() in util/fusermount.c. Both the library path
+		 * (via fuse_kern_mount_get_base_mnt_opts) and fusermount3 path
+		 * rebuild these strings from the flags bitmask and pass them in
+		 * mnt_opts. They must be filtered here because they are mount
+		 * attributes (passed to fsmount via MOUNT_ATTR_*), not
+		 * filesystem parameters (which would be passed to fsconfig).
+		 */
+		if (!is_mount_attr_opt(opt)) {
+			res = apply_opt_key_value(fsfd, opt);
+			if (res < 0) {
+				free(opts_copy);
+				return res;
+			}
+		}
+		opt = strtok_r(NULL, ",", &saveptr);
+	}
+
+	free(opts_copy);
+	return 0;
+}
+
+
+/**
+ * Mount using the new Linux mount API (fsopen/fsconfig/fsmount/move_mount)
+ * @mnt: mountpoint
+ * @flags: mount flags (MS_NOSUID, MS_NODEV, etc.)
+ * @blkdev: 1 for fuseblk, 0 for fuse
+ * @fsname: filesystem name (or NULL)
+ * @subtype: filesystem subtype (or NULL)
+ * @source_dev: device name for building source string
+ * @kernel_opts: kernel mount options string
+ * @mnt_opts: additional mount options to pass to the kernel
+ *
+ * Returns: 0 on success, -1 on failure with errno set
+ */
+int fuse_kern_fsmount(const char *mnt, unsigned long flags, int blkdev,
+		      const char *fsname, const char *subtype,
+		      const char *source_dev, const char *kernel_opts,
+		      const char *mnt_opts)
+{
+	const char *type;
+	char *source = NULL;
+	int fsfd = -1;
+	int mntfd = -1;
+	int err, res;
+	unsigned long mount_attrs;
+
+	/* Determine filesystem type */
+	type = blkdev ? "fuseblk" : "fuse";
+
+	/* Try to open filesystem context */
+	fsfd = fsopen(type, FSOPEN_CLOEXEC);
+	if (fsfd == -1) {
+		if (errno != EPERM)
+			fprintf(stderr, "fuse: fsopen(%s) failed: %s\n", type,
+				strerror(errno));
+		return -1;
+	}
+
+	/* Build source string */
+	source = malloc((fsname ? strlen(fsname) : 0) +
+			(subtype ? strlen(subtype) : 0) +
+			strlen(source_dev) + 32);
+	err = -ENOMEM;
+	if (!source) {
+		fprintf(stderr, "fuse: failed to allocate memory\n");
+		goto out_close_fsfd;
+	}
+
+	strcpy(source, fsname ? fsname : (subtype ? subtype : source_dev));
+
+	/* Configure source */
+	res = fsconfig(fsfd, FSCONFIG_SET_STRING, "source", source, 0);
+	if (res == -1) {
+		err = -errno;
+		log_fsconfig_kmsg(fsfd);
+		fprintf(stderr, "fuse: fsconfig source failed: %s\n",
+			strerror(errno));
+		goto out_free;
+	}
+
+	/* Apply VFS superblock flags (ro, sync, dirsync) */
+	err = apply_mount_flags(fsfd, flags);
+	if (err < 0) {
+		log_fsconfig_kmsg(fsfd);
+		fprintf(stderr, "fuse: failed to apply mount flags\n");
+		goto out_free;
+	}
+
+	/* Apply kernel options */
+	err = apply_mount_opts(fsfd, kernel_opts);
+	if (err < 0) {
+		log_fsconfig_kmsg(fsfd);
+		fprintf(stderr,
+			"fuse: failed to apply kernel options '%s'\n",
+			kernel_opts);
+		goto out_free;
+	}
+
+	/* Apply additional mount options */
+	err = apply_mount_opts(fsfd, mnt_opts);
+	if (err < 0) {
+		log_fsconfig_kmsg(fsfd);
+		fprintf(stderr,
+			"fuse: failed to apply additional mount options '%s'\n",
+			mnt_opts);
+		goto out_free;
+	}
+
+	/* Create the filesystem instance */
+	res = fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0);
+	if (res == -1) {
+		err = -errno;
+		log_fsconfig_kmsg(fsfd);
+		fprintf(stderr, "fuse: fsconfig CREATE failed: %s\n",
+			strerror(errno));
+		goto out_free;
+	}
+
+	/* Convert MS_* flags to MOUNT_ATTR_* for fsmount() */
+	mount_attrs = ms_flags_to_mount_attrs(flags);
+
+	/* Create mount object with mount attributes */
+	mntfd = fsmount(fsfd, FSMOUNT_CLOEXEC, mount_attrs);
+	if (mntfd == -1) {
+		err = -errno;
+		log_fsconfig_kmsg(fsfd);
+		fprintf(stderr, "fuse: fsmount failed: %s\n",
+			strerror(errno));
+		goto out_free;
+	}
+
+	close(fsfd);
+	fsfd = -1;
+
+	/* Attach to mount point */
+	if (move_mount(mntfd, "", AT_FDCWD, mnt, MOVE_MOUNT_F_EMPTY_PATH) ==
+	    -1) {
+		err = -errno;
+		fprintf(stderr, "fuse: move_mount failed: %s\n",
+			strerror(errno));
+		goto out_close_mntfd;
+	}
+
+	err = fuse_mnt_add_mount_helper(mnt, source, type, mnt_opts);
+	if (err == -1)
+		goto out_umount;
+
+	close(mntfd);
+	free(source);
+	return 0;
+
+out_umount:
+	{
+		/* race free umount */
+		char fd_path[64];
+
+		snprintf(fd_path, sizeof(fd_path), "/proc/self/fd/%d", mntfd);
+		if (umount2(fd_path, MNT_DETACH) == -1 && errno != EINVAL) {
+			fprintf(stderr,
+				"fuse: cleanup umount failed: %s\n",
+				strerror(errno));
+		}
+	}
+out_close_mntfd:
+	if (mntfd != -1)
+		close(mntfd);
+out_free:
+	free(source);
+out_close_fsfd:
+	if (fsfd != -1)
+		close(fsfd);
+	errno = -err;
+	return -1;
+}
+
diff --git a/lib/mount_i_linux.h b/lib/mount_i_linux.h
index 1bfc78b32dc4fd434870e4fff1a37e0c340d207e..34c7386715155e8640cac68c218a1e1062990ce6 100644
--- a/lib/mount_i_linux.h
+++ b/lib/mount_i_linux.h
@@ -29,4 +29,18 @@ struct mount_opts {
 	unsigned int max_read;
 };
 
+int fuse_kern_mount_prepare(const char *mnt, struct mount_opts *mo);
+
+int fuse_kern_mount_get_base_mnt_opts(struct mount_opts *mo, char **mnt_optsp);
+
+int fuse_kern_fsmount(const char *mnt, unsigned long flags, int blkdev,
+		      const char *fsname, const char *subtype,
+		      const char *source_dev, const char *kernel_opts,
+		      const char *mnt_opts);
+
+int fuse_kern_fsmount_mo(const char *mnt, struct mount_opts *mo,
+			 const char *mnt_opts);
+
+int fuse_kern_fsmount_mo(const char *mnt, struct mount_opts *mo,
+			 const char *mnt_opts);
 #endif /* FUSE_MOUNT_I_LINUX_H_ */
diff --git a/meson.build b/meson.build
index 80c5f1dc0bd3565c11ad3084dac08e28dab611dc..465a7bbfd14fe9c45897f197ef37db76079d20ee 100644
--- a/meson.build
+++ b/meson.build
@@ -30,7 +30,7 @@ if platform == 'darwin'
         'https://www.fuse-t.org/ instead')
 elif platform == 'cygwin' or platform == 'windows'
   error('libfuse does not support Windows.\n' +
-        'Take a look at http://www.secfs.net/winfsp/ instead')       
+        'Take a look at http://www.secfs.net/winfsp/ instead')
 endif
 
 cc = meson.get_compiler('c')
@@ -118,6 +118,21 @@ special_funcs = {
 	    return -1;
 	  }
 	}
+    ''',
+    'new_mount_api': '''
+	#define _GNU_SOURCE
+	#include <sys/mount.h>
+	#include <linux/mount.h>
+	#include <unistd.h>
+	#include <fcntl.h>
+
+	int main(void) {
+	    int fsfd = fsopen("fuse", FSOPEN_CLOEXEC);
+	    int res = fsconfig(fsfd, FSCONFIG_SET_STRING, "source", "test", 0);
+	    int mntfd = fsmount(fsfd, FSMOUNT_CLOEXEC, 0);
+	    res = move_mount(mntfd, "", AT_FDCWD, "/mnt", MOVE_MOUNT_F_EMPTY_PATH);
+	    return 0;
+	}
     '''
 }
 
@@ -308,7 +323,7 @@ configure_file(output: 'libfuse_config.h',
 include_dirs = include_directories('include', 'lib', '.')
 
 # Common dependencies
-thread_dep = dependency('threads') 
+thread_dep = dependency('threads')
 
 #
 # Read build files from sub-directories

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 14/25] fuse mount: Support synchronous FUSE_INIT (privileged daemon)
  2026-03-26 21:34 [PATCH v2 00/25] libfuse: Add support for synchronous init Bernd Schubert
                   ` (12 preceding siblings ...)
  2026-03-26 21:34 ` [PATCH v2 13/25] Add support for the new linux mount API Bernd Schubert
@ 2026-03-26 21:34 ` Bernd Schubert
  2026-03-30 18:44   ` 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
                   ` (11 subsequent siblings)
  25 siblings, 1 reply; 56+ messages in thread
From: Bernd Schubert @ 2026-03-26 21:34 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Miklos Szeredi, Joanne Koong, Darrick J. Wong, Kevin Chen,
	Bernd Schubert, Bernd Schubert

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);
+
 #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");
 
 	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;
 
 	return -1;
 }
@@ -4826,3 +5003,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 e8c65363d36a56f483f82434f642e785da4d0341..f19817e2675713e988bb91fc658c52b36468462b 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"
 
@@ -522,8 +523,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;

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 15/25] Add fuse_session_set_debug() to enable debug output without foreground
  2026-03-26 21:34 [PATCH v2 00/25] libfuse: Add support for synchronous init Bernd Schubert
                   ` (13 preceding siblings ...)
  2026-03-26 21:34 ` [PATCH v2 14/25] fuse mount: Support synchronous FUSE_INIT (privileged daemon) Bernd Schubert
@ 2026-03-26 21:34 ` Bernd Schubert
  2026-03-26 21:34 ` [PATCH v2 16/25] Move more generic mount code to mount_util.{c,h} Bernd Schubert
                   ` (10 subsequent siblings)
  25 siblings, 0 replies; 56+ messages in thread
From: Bernd Schubert @ 2026-03-26 21:34 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Miklos Szeredi, Joanne Koong, Darrick J. Wong, Kevin Chen,
	Bernd Schubert, Bernd Schubert

From: Bernd Schubert <bschubert@ddn.com>

One might want to print debug out in background mode, which the command
line parameter does not easily allow. Or one might want to enable and
disable at run time.

Signed-off-by: Bernd Schubert <bschubert@ddn.com>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
---
 include/fuse_lowlevel.h | 12 ++++++++++++
 lib/fuse_lowlevel.c     |  5 +++++
 lib/fuse_versionscript  |  1 +
 3 files changed, 18 insertions(+)

diff --git a/include/fuse_lowlevel.h b/include/fuse_lowlevel.h
index d8626f85bdaf497534cd2835a589e30f1f4e2466..d85929e291a77de8caad7d6b3d9ac5b092ce0e62 100644
--- a/include/fuse_lowlevel.h
+++ b/include/fuse_lowlevel.h
@@ -2441,6 +2441,18 @@ int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf);
  */
 void fuse_session_want_sync_init(struct fuse_session *se);
 
+/**
+ * Enable debug output
+ *
+ * This allows to enable debug output without a command line parameter and
+ * without the enforcement of the command line parameter to run in foreground.
+ * The daemon needs to handle either fuse_log output via stderr, or
+ * redirection to its own logs or via syslog.
+ *
+ * @param se the session
+ */
+void fuse_session_set_debug(struct fuse_session *se);
+
 /**
  * Check if the request is submitted through fuse-io-uring
  */
diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c
index 0dd10e0ed53508e4716703f2f82aa35ad853b247..4445e134a7ff5be508306f74db9d9c56e3582070 100644
--- a/lib/fuse_lowlevel.c
+++ b/lib/fuse_lowlevel.c
@@ -5010,3 +5010,8 @@ void fuse_session_want_sync_init(struct fuse_session *se)
 		return;
 	se->want_sync_init = true;
 }
+
+void fuse_session_set_debug(struct fuse_session *se)
+{
+	se->debug = 1;
+}
diff --git a/lib/fuse_versionscript b/lib/fuse_versionscript
index dc6ed0135fb8d82937c756c3fb04a7fcb48fe1f4..3e1ea641a8be58ae9f68319873d3db4cbb902d3b 100644
--- a/lib/fuse_versionscript
+++ b/lib/fuse_versionscript
@@ -231,6 +231,7 @@ FUSE_3.19 {
 		fuse_daemonize_success;
 		fuse_daemonize_fail;
 		fuse_daemonize_is_active;
+		fuse_session_set_debug;
 } FUSE_3.18;
 
 # Local Variables:

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 16/25] Move more generic mount code to mount_util.{c,h}
  2026-03-26 21:34 [PATCH v2 00/25] libfuse: Add support for synchronous init Bernd Schubert
                   ` (14 preceding siblings ...)
  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 ` 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
                   ` (9 subsequent siblings)
  25 siblings, 1 reply; 56+ messages in thread
From: Bernd Schubert @ 2026-03-26 21:34 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Miklos Szeredi, Joanne Koong, Darrick J. Wong, Kevin Chen,
	Bernd Schubert, Bernd Schubert

From: Bernd Schubert <bschubert@ddn.com>

This is to allow fusermount to use the code from mount_fsmount.c.
I.e. avoid code dup and add just re-use the new linux api mount
functions from that file for fusermount.

Signed-off-by: Bernd Schubert <bschubert@ddn.com>
---
 lib/mount.c          |  7 -------
 lib/mount_common_i.h |  2 --
 lib/mount_i_linux.h  |  4 ++--
 lib/mount_util.c     | 10 ++++++++++
 lib/mount_util.h     |  5 +++++
 5 files changed, 17 insertions(+), 11 deletions(-)

diff --git a/lib/mount.c b/lib/mount.c
index f19817e2675713e988bb91fc658c52b36468462b..408e9d36896048fc167e264c95b6f6e31d86679f 100644
--- a/lib/mount.c
+++ b/lib/mount.c
@@ -713,13 +713,6 @@ out:
 	return res;
 }
 
-const char *fuse_mnt_get_devname(void)
-{
-	const char *devname = getenv(FUSE_KERN_DEVICE_ENV);
-
-	return devname ? devname : "/dev/fuse";
-}
-
 char *fuse_mnt_build_source(const struct mount_opts *mo)
 {
 	const char *devname = fuse_mnt_get_devname();
diff --git a/lib/mount_common_i.h b/lib/mount_common_i.h
index 34f0f9893918a14c83a57a3212f80cfc96ed2e6d..fc9ab443cc0cf6cf8438dd2ca486f6e37c8704e6 100644
--- a/lib/mount_common_i.h
+++ b/lib/mount_common_i.h
@@ -24,8 +24,6 @@ struct mount_opts;
 struct mount_opts *parse_mount_opts(struct fuse_args *args);
 void destroy_mount_opts(struct mount_opts *mo);
 unsigned int get_max_read(struct mount_opts *o);
-char *fuse_mnt_build_source(const struct mount_opts *mo);
-char *fuse_mnt_build_type(const struct mount_opts *mo);
 
 
 #endif /* FUSE_MOUNT_COMMON_I_H_ */
diff --git a/lib/mount_i_linux.h b/lib/mount_i_linux.h
index 34c7386715155e8640cac68c218a1e1062990ce6..597b380076fccc1d38fd4d0b9108fc92a1adfa62 100644
--- a/lib/mount_i_linux.h
+++ b/lib/mount_i_linux.h
@@ -41,6 +41,6 @@ int fuse_kern_fsmount(const char *mnt, unsigned long flags, int blkdev,
 int fuse_kern_fsmount_mo(const char *mnt, struct mount_opts *mo,
 			 const char *mnt_opts);
 
-int fuse_kern_fsmount_mo(const char *mnt, 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);
 #endif /* FUSE_MOUNT_I_LINUX_H_ */
diff --git a/lib/mount_util.c b/lib/mount_util.c
index 9ff711023fcb41fe24f1de21aaf811cb1c12d25e..b6b75e60874ae9b2ec0c96f2a62b4ec1943a2a00 100644
--- a/lib/mount_util.c
+++ b/lib/mount_util.c
@@ -10,6 +10,9 @@
 
 #include "fuse_config.h"
 #include "mount_util.h"
+#ifdef __linux__
+#include "mount_i_linux.h"
+#endif
 
 #include <stdio.h>
 #include <unistd.h>
@@ -451,3 +454,10 @@ int fuse_mnt_add_mount_helper(const char *mnt, const char *source,
 	(void)mnt_opts;
 	return 0;
 }
+
+const char *fuse_mnt_get_devname(void)
+{
+	const char *devname = getenv(FUSE_KERN_DEVICE_ENV);
+
+	return devname ? devname : "/dev/fuse";
+}
diff --git a/lib/mount_util.h b/lib/mount_util.h
index e62052b626875abd6118b845de162d6c5c41857a..b83db556d5fdb4bd8dd5e1750c8d11faf7373d82 100644
--- a/lib/mount_util.h
+++ b/lib/mount_util.h
@@ -6,6 +6,9 @@
   See the file LGPL2.txt.
 */
 
+#ifndef FUSE_MOUNT_UTIL_H_
+#define FUSE_MOUNT_UTIL_H_
+
 #include <sys/types.h>
 #include "mount_common_i.h" // IWYU pragma: keep
 
@@ -31,3 +34,5 @@ int fuse_mnt_parse_fuse_fd(const char *mountpoint);
 const char *fuse_mnt_get_devname(void);
 int fuse_mnt_add_mount_helper(const char *mnt, const char *source,
 			       const char *type, const char *mnt_opts);
+
+#endif /* FUSE_MOUNT_UTIL_H_ */

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 17/25] Split the fusermount do_mount function
  2026-03-26 21:34 [PATCH v2 00/25] libfuse: Add support for synchronous init Bernd Schubert
                   ` (15 preceding siblings ...)
  2026-03-26 21:34 ` [PATCH v2 16/25] Move more generic mount code to mount_util.{c,h} Bernd Schubert
@ 2026-03-26 21:34 ` 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
                   ` (8 subsequent siblings)
  25 siblings, 1 reply; 56+ messages in thread
From: Bernd Schubert @ 2026-03-26 21:34 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Miklos Szeredi, Joanne Koong, Darrick J. Wong, Kevin Chen,
	Bernd Schubert, Bernd Schubert

From: Bernd Schubert <bschubert@ddn.com>

We will need new API and old API and need to pass the options to
two different functions - factor out the option parsing.

Signed-off-by: Bernd Schubert <bschubert@ddn.com>
---
 util/fusermount.c | 298 +++++++++++++++++++++++++++++++++++++-----------------
 1 file changed, 205 insertions(+), 93 deletions(-)

diff --git a/util/fusermount.c b/util/fusermount.c
index f66c4e8e0e809351384ec10cf50ba4d3d3a831b0..8d7598998788f72ea05c2a065a88cb5efd61df35 100644
--- a/util/fusermount.c
+++ b/util/fusermount.c
@@ -883,30 +883,132 @@ static int mount_notrunc(const char *source, const char *target,
 	return mount(source, target, filesystemtype, mountflags, data);
 }
 
+struct mount_params {
+	/* Input parameters */
+	int fd;                  /* /dev/fuse file descriptor */
+	mode_t rootmode;         /* Root mode from stat */
+	const char *dev;         /* Device path (/dev/fuse) */
 
-static int do_mount(const char *mnt, const char **typep, mode_t rootmode,
-		    int fd, const char *opts, const char *dev, char **sourcep,
-		    char **mnt_optsp)
+	/* Parsed mount options */
+	unsigned long flags;     /* Mount flags (MS_NOSUID, etc.) */
+	char *optbuf;           /* Kernel mount options buffer */
+	char *fsname;           /* Filesystem name from options */
+	char *subtype;          /* Subtype from options */
+	int blkdev;             /* Block device flag */
+
+	/* Generated mount parameters */
+	char *source;           /* Mount source string */
+	char *type;             /* Filesystem type string */
+	char *mnt_opts;         /* Mount table options */
+
+	/* Pointer for optbuf manipulation */
+	char *optbuf_end;       /* Points to end of optbuf for sprintf */
+};
+
+static void free_mount_params(struct mount_params *mp)
+{
+	free(mp->optbuf);
+	free(mp->fsname);
+	free(mp->subtype);
+	free(mp->source);
+	free(mp->type);
+	free(mp->mnt_opts);
+}
+
+/*
+ * Check if option is deprecated large_read.
+ *
+ * Returns true if the option should be skipped (large_read on kernel > 2.4),
+ * false otherwise (all other options or large_read on old kernels).
+ */
+static bool check_large_read(const char *opt, unsigned int len)
+{
+	struct utsname utsname;
+	unsigned int kmaj, kmin;
+	int res;
+
+	if (!opt_eq(opt, len, "large_read"))
+		return false;
+
+	res = uname(&utsname);
+	if (res == 0 &&
+	    sscanf(utsname.release, "%u.%u", &kmaj, &kmin) == 2 &&
+	    (kmaj > 2 || (kmaj == 2 && kmin > 4))) {
+		fprintf(stderr,
+			"%s: note: 'large_read' mount option is deprecated for %i.%i kernels\n",
+			progname, kmaj, kmin);
+		return true;
+	}
+	return false;
+}
+
+/*
+ * Check if user has permission to use allow_other or allow_root options.
+ *
+ * Returns -1 if permission denied, 0 if allowed or option is not
+ * allow_other/allow_root.
+ */
+static int check_allow_permission(const char *opt, unsigned int len)
+{
+	if (getuid() != 0 && !user_allow_other &&
+	    (opt_eq(opt, len, "allow_other") || opt_eq(opt, len, "allow_root"))) {
+		fprintf(stderr, "%s: option %.*s only allowed if 'user_allow_other' is set in %s\n",
+			progname, len, opt, FUSE_CONF);
+		return -1;
+	}
+	return 0;
+}
+
+/*
+ * Process generic mount option.
+ *
+ * Handles mount flags (ro, rw, suid, etc.), kernel options
+ * (default_permissions, allow_other, max_read, blksize), or exits on
+ * unknown options.
+ */
+static int process_generic_option(const char *opt, unsigned int len,
+				   unsigned long *flags, char **dest)
+{
+	int on;
+	int flag;
+
+	if (find_mount_flag(opt, len, &on, &flag)) {
+		if (on)
+			*flags |= flag;
+		else
+			*flags &= ~flag;
+		return 0;
+	}
+
+	if (opt_eq(opt, len, "default_permissions") ||
+	    opt_eq(opt, len, "allow_other") ||
+	    begins_with(opt, "max_read=") ||
+	    begins_with(opt, "blksize=")) {
+		memcpy(*dest, opt, len);
+		*dest += len;
+		**dest = ',';
+		(*dest)++;
+		return 0;
+	}
+
+	fprintf(stderr, "%s: unknown option '%.*s'\n", progname, len, opt);
+	exit(1);
+}
+
+static int prepare_mount(const char *opts, struct mount_params *mp)
 {
 	int res;
-	int flags = MS_NOSUID | MS_NODEV;
-	char *optbuf;
-	char *mnt_opts = NULL;
 	const char *s;
 	char *d;
-	char *fsname = NULL;
-	char *subtype = NULL;
-	char *source = NULL;
-	char *type = NULL;
-	int blkdev = 0;
 
-	optbuf = (char *) malloc(strlen(opts) + 128);
-	if (!optbuf) {
+	mp->flags = MS_NOSUID | MS_NODEV;
+	mp->optbuf = (char *) malloc(strlen(opts) + 128);
+	if (!mp->optbuf) {
 		fprintf(stderr, "%s: failed to allocate memory\n", progname);
 		return -1;
 	}
 
-	for (s = opts, d = optbuf; *s;) {
+	for (s = opts, d = mp->optbuf; *s;) {
 		unsigned len;
 		const char *fsname_str = "fsname=";
 		const char *subtype_str = "subtype=";
@@ -919,10 +1021,10 @@ static int do_mount(const char *mnt, const char **typep, mode_t rootmode,
 				break;
 		}
 		if (begins_with(s, fsname_str)) {
-			if (!get_string_opt(s, len, fsname_str, &fsname))
+			if (!get_string_opt(s, len, fsname_str, &mp->fsname))
 				goto err;
 		} else if (begins_with(s, subtype_str)) {
-			if (!get_string_opt(s, len, subtype_str, &subtype))
+			if (!get_string_opt(s, len, subtype_str, &mp->subtype))
 				goto err;
 		} else if (opt_eq(s, len, "blkdev")) {
 			if (getuid() != 0) {
@@ -931,7 +1033,7 @@ static int do_mount(const char *mnt, const char **typep, mode_t rootmode,
 					progname);
 				goto err;
 			}
-			blkdev = 1;
+			mp->blkdev = 1;
 		} else if (opt_eq(s, len, "auto_unmount")) {
 			auto_unmount = 1;
 		} else if (!opt_eq(s, len, "nonempty") &&
@@ -939,122 +1041,132 @@ static int do_mount(const char *mnt, const char **typep, mode_t rootmode,
 			   !begins_with(s, "rootmode=") &&
 			   !begins_with(s, "user_id=") &&
 			   !begins_with(s, "group_id=")) {
-			int on;
-			int flag;
-			int skip_option = 0;
-			if (opt_eq(s, len, "large_read")) {
-				struct utsname utsname;
-				unsigned kmaj, kmin;
-				res = uname(&utsname);
-				if (res == 0 &&
-				    sscanf(utsname.release, "%u.%u",
-					   &kmaj, &kmin) == 2 &&
-				    (kmaj > 2 || (kmaj == 2 && kmin > 4))) {
-					fprintf(stderr, "%s: note: 'large_read' mount option is deprecated for %i.%i kernels\n", progname, kmaj, kmin);
-					skip_option = 1;
-				}
-			}
-			if (getuid() != 0 && !user_allow_other &&
-			    (opt_eq(s, len, "allow_other") ||
-			     opt_eq(s, len, "allow_root"))) {
-				fprintf(stderr, "%s: option %.*s only allowed if 'user_allow_other' is set in %s\n", progname, len, s, FUSE_CONF);
+			bool skip;
+
+			if (check_allow_permission(s, len) == -1)
 				goto err;
-			}
-			if (!skip_option) {
-				if (find_mount_flag(s, len, &on, &flag)) {
-					if (on)
-						flags |= flag;
-					else
-						flags  &= ~flag;
-				} else if (opt_eq(s, len, "default_permissions") ||
-					   opt_eq(s, len, "allow_other") ||
-					   begins_with(s, "max_read=") ||
-					   begins_with(s, "blksize=")) {
-					memcpy(d, s, len);
-					d += len;
-					*d++ = ',';
-				} else {
-					fprintf(stderr, "%s: unknown option '%.*s'\n", progname, len, s);
-					exit(1);
-				}
-			}
+
+			skip = check_large_read(s, len);
+
+			/*
+			 * Skip deprecated large_read to avoid passing it to
+			 * kernel which would reject it as unknown option.
+			 */
+			if (!skip)
+				process_generic_option(s, len, &mp->flags, &d);
 		}
 		s += len;
 		if (*s)
 			s++;
 	}
 	*d = '\0';
-	res = get_mnt_opts(flags, optbuf, &mnt_opts);
+	res = get_mnt_opts(mp->flags, mp->optbuf, &mp->mnt_opts);
 	if (res == -1)
 		goto err;
 
+	mp->optbuf_end = d;
+
 	sprintf(d, "fd=%i,rootmode=%o,user_id=%u,group_id=%u",
-		fd, rootmode, getuid(), getgid());
+		mp->fd, mp->rootmode, getuid(), getgid());
 
-	source = malloc((fsname ? strlen(fsname) : 0) +
-			(subtype ? strlen(subtype) : 0) + strlen(dev) + 32);
+	mp->source = malloc((mp->fsname ? strlen(mp->fsname) : 0) +
+			(mp->subtype ? strlen(mp->subtype) : 0) + strlen(mp->dev) + 32);
 
-	type = malloc((subtype ? strlen(subtype) : 0) + 32);
-	if (!type || !source) {
+	mp->type = malloc((mp->subtype ? strlen(mp->subtype) : 0) + 32);
+	if (!mp->type || !mp->source) {
 		fprintf(stderr, "%s: failed to allocate memory\n", progname);
 		goto err;
 	}
 
-	if (subtype)
-		sprintf(type, "%s.%s", blkdev ? "fuseblk" : "fuse", subtype);
+	if (mp->subtype)
+		sprintf(mp->type, "%s.%s", mp->blkdev ? "fuseblk" : "fuse", mp->subtype);
 	else
-		strcpy(type, blkdev ? "fuseblk" : "fuse");
+		strcpy(mp->type, mp->blkdev ? "fuseblk" : "fuse");
 
-	if (fsname)
-		strcpy(source, fsname);
+	if (mp->fsname)
+		strcpy(mp->source, mp->fsname);
 	else
-		strcpy(source, subtype ? subtype : dev);
+		strcpy(mp->source, mp->subtype ? mp->subtype : mp->dev);
 
-	res = mount_notrunc(source, mnt, type, flags, optbuf);
-	if (res == -1 && errno == ENODEV && subtype) {
+	return 0;
+
+err:
+	free_mount_params(mp);
+	return -1;
+}
+
+/*
+ * Perform the actual mount operation using prepared parameters.
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+static int perform_mount(const char *mnt, struct mount_params *mp)
+{
+	int res;
+
+	res = mount_notrunc(mp->source, mnt, mp->type, mp->flags, mp->optbuf);
+	if (res == -1 && errno == ENODEV && mp->subtype) {
 		/* Probably missing subtype support */
-		strcpy(type, blkdev ? "fuseblk" : "fuse");
-		if (fsname) {
-			if (!blkdev)
-				sprintf(source, "%s#%s", subtype, fsname);
+		strcpy(mp->type, mp->blkdev ? "fuseblk" : "fuse");
+		if (mp->fsname) {
+			if (!mp->blkdev)
+				sprintf(mp->source, "%s#%s", mp->subtype, mp->fsname);
 		} else {
-			strcpy(source, type);
+			strcpy(mp->source, mp->type);
 		}
 
-		res = mount_notrunc(source, mnt, type, flags, optbuf);
+		res = mount_notrunc(mp->source, mnt, mp->type, mp->flags, mp->optbuf);
 	}
 	if (res == -1 && errno == EINVAL) {
 		/* It could be an old version not supporting group_id */
-		sprintf(d, "fd=%i,rootmode=%o,user_id=%u",
-			fd, rootmode, getuid());
-		res = mount_notrunc(source, mnt, type, flags, optbuf);
+		sprintf(mp->optbuf_end, "fd=%i,rootmode=%o,user_id=%u",
+			mp->fd, mp->rootmode, getuid());
+		res = mount_notrunc(mp->source, mnt, mp->type, mp->flags, mp->optbuf);
 	}
 	if (res == -1) {
 		int errno_save = errno;
-		if (blkdev && errno == ENODEV && !fuse_mnt_check_fuseblk())
+		if (mp->blkdev && errno == ENODEV && !fuse_mnt_check_fuseblk())
 			fprintf(stderr, "%s: 'fuseblk' support missing\n",
 				progname);
 		else
 			fprintf(stderr, "%s: mount failed: %s\n", progname,
 				strerror(errno_save));
-		goto err;
+		return -1;
 	}
-	*sourcep = source;
-	*typep = type;
-	*mnt_optsp = mnt_opts;
-	free(fsname);
-	free(optbuf);
 
 	return 0;
+}
 
-err:
-	free(fsname);
-	free(subtype);
-	free(source);
-	free(type);
-	free(mnt_opts);
-	free(optbuf);
-	return -1;
+static int do_mount(const char *mnt, const char **typep, mode_t rootmode,
+		    int fd, const char *opts, const char *dev, char **sourcep,
+		    char **mnt_optsp)
+{
+	struct mount_params mp = { .fd = fd }; /* implicit zero of other params */
+	int res;
+
+	mp.rootmode = rootmode;
+	mp.dev = dev;
+
+	res = prepare_mount(opts, &mp);
+	if (res == -1)
+		return -1;
+
+	res = perform_mount(mnt, &mp);
+	if (res == -1) {
+		free_mount_params(&mp);
+		return -1;
+	}
+
+	*sourcep = mp.source;
+	*typep = mp.type;
+	*mnt_optsp = mp.mnt_opts;
+
+	/* Free only the intermediate allocations, not the returned ones */
+	free(mp.fsname);
+	free(mp.subtype);
+	free(mp.optbuf);
+
+	return 0;
 }
 
 static int check_perm(const char **mntp, struct stat *stbuf, int *mountpoint_fd)

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 18/25] fusermout: Remove the large read check
  2026-03-26 21:34 [PATCH v2 00/25] libfuse: Add support for synchronous init Bernd Schubert
                   ` (16 preceding siblings ...)
  2026-03-26 21:34 ` [PATCH v2 17/25] Split the fusermount do_mount function Bernd Schubert
@ 2026-03-26 21:34 ` Bernd Schubert
  2026-03-27  3:32   ` Darrick J. Wong
  2026-03-26 21:34 ` [PATCH v2 19/25] fusermount: Refactor extract_x_options Bernd Schubert
                   ` (7 subsequent siblings)
  25 siblings, 1 reply; 56+ messages in thread
From: Bernd Schubert @ 2026-03-26 21:34 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Miklos Szeredi, Joanne Koong, Darrick J. Wong, Kevin Chen,
	Bernd Schubert

<quote Darrick>
Nobody's running 2.4 kernels
anymore, right?
</quote>

Suggested-by: "Darrick J. Wong" <djwong@kernel.org>
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
---
 util/fusermount.c | 37 +------------------------------------
 1 file changed, 1 insertion(+), 36 deletions(-)

diff --git a/util/fusermount.c b/util/fusermount.c
index 8d7598998788f72ea05c2a065a88cb5efd61df35..254f24a98abf935846cfed754693cc1958c8d2ff 100644
--- a/util/fusermount.c
+++ b/util/fusermount.c
@@ -915,33 +915,6 @@ static void free_mount_params(struct mount_params *mp)
 	free(mp->mnt_opts);
 }
 
-/*
- * Check if option is deprecated large_read.
- *
- * Returns true if the option should be skipped (large_read on kernel > 2.4),
- * false otherwise (all other options or large_read on old kernels).
- */
-static bool check_large_read(const char *opt, unsigned int len)
-{
-	struct utsname utsname;
-	unsigned int kmaj, kmin;
-	int res;
-
-	if (!opt_eq(opt, len, "large_read"))
-		return false;
-
-	res = uname(&utsname);
-	if (res == 0 &&
-	    sscanf(utsname.release, "%u.%u", &kmaj, &kmin) == 2 &&
-	    (kmaj > 2 || (kmaj == 2 && kmin > 4))) {
-		fprintf(stderr,
-			"%s: note: 'large_read' mount option is deprecated for %i.%i kernels\n",
-			progname, kmaj, kmin);
-		return true;
-	}
-	return false;
-}
-
 /*
  * Check if user has permission to use allow_other or allow_root options.
  *
@@ -1041,19 +1014,11 @@ static int prepare_mount(const char *opts, struct mount_params *mp)
 			   !begins_with(s, "rootmode=") &&
 			   !begins_with(s, "user_id=") &&
 			   !begins_with(s, "group_id=")) {
-			bool skip;
 
 			if (check_allow_permission(s, len) == -1)
 				goto err;
 
-			skip = check_large_read(s, len);
-
-			/*
-			 * Skip deprecated large_read to avoid passing it to
-			 * kernel which would reject it as unknown option.
-			 */
-			if (!skip)
-				process_generic_option(s, len, &mp->flags, &d);
+			process_generic_option(s, len, &mp->flags, &d);
 		}
 		s += len;
 		if (*s)

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 19/25] fusermount: Refactor extract_x_options
  2026-03-26 21:34 [PATCH v2 00/25] libfuse: Add support for synchronous init Bernd Schubert
                   ` (17 preceding siblings ...)
  2026-03-26 21:34 ` [PATCH v2 18/25] fusermout: Remove the large read check Bernd Schubert
@ 2026-03-26 21:34 ` Bernd Schubert
  2026-03-26 21:34 ` [PATCH v2 20/25] Make fusermount work bidirectional for sync init Bernd Schubert
                   ` (6 subsequent siblings)
  25 siblings, 0 replies; 56+ messages in thread
From: Bernd Schubert @ 2026-03-26 21:34 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Miklos Szeredi, Joanne Koong, Darrick J. Wong, Kevin Chen,
	Bernd Schubert, Bernd Schubert

From: Bernd Schubert <bschubert@ddn.com>

Just to make it better readable.

Also add a NULL check for *regular_opts and *x_prefixed_opts,
there will be multiple callers in an upcoming commit and
it becomes harder to track that no caller has an error.

Signed-off-by: Bernd Schubert <bschubert@ddn.com>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
---
 util/fusermount.c | 58 ++++++++++++++++++++++++++++++++-----------------------
 1 file changed, 34 insertions(+), 24 deletions(-)

diff --git a/util/fusermount.c b/util/fusermount.c
index 254f24a98abf935846cfed754693cc1958c8d2ff..d42e16955f6d26f81e6d2a5acb040b1448e20b51 100644
--- a/util/fusermount.c
+++ b/util/fusermount.c
@@ -112,22 +112,31 @@ static struct mntent *GETMNTENT(FILE *stream)
 
 /*
  * Take a ',' separated option string and extract "x-" options
+ * @original: The original option string
+ * @regular_opts: The regular options
+ * @x_prefixed_opts: The "x-" options
  */
-static int extract_x_options(const char *original, char **non_x_opts,
-			     char **x_opts)
+static int extract_x_options(const char *original, char **regular_opts,
+			     char **x_prefixed_opts)
 {
 	size_t orig_len;
 	const char *opt, *opt_end;
 
 	orig_len = strlen(original) + 1;
 
-	*non_x_opts = calloc(1, orig_len);
-	*x_opts    = calloc(1, orig_len);
+	if (*regular_opts != NULL || *x_prefixed_opts != NULL) {
+		fprintf(stderr, "%s: regular_opts or x_prefixed_opts not NULL\n",
+			__func__);
+		return -EINVAL;
+	}
 
-	size_t non_x_opts_len = orig_len;
-	size_t x_opts_len = orig_len;
+	*regular_opts = calloc(1, orig_len);
+	*x_prefixed_opts    = calloc(1, orig_len);
 
-	if (*non_x_opts == NULL || *x_opts == NULL) {
+	size_t regular_opts_len = orig_len;
+	size_t x_prefixed_opts_len = orig_len;
+
+	if (*regular_opts == NULL || *x_prefixed_opts == NULL) {
 		fprintf(stderr, "%s: Failed to allocate %zuB.\n",
 			__func__, orig_len);
 		return -ENOMEM;
@@ -143,16 +152,16 @@ static int extract_x_options(const char *original, char **non_x_opts,
 		size_t opt_len = opt_end - opt;
 		size_t opt_len_left = orig_len - (opt - original);
 		size_t buf_len;
-		bool is_x_opts;
+		bool is_x_prefixed_opts;
 
 		if (strncmp(opt, "x-", MIN(2, opt_len_left)) == 0) {
-			buf_len = x_opts_len;
-			is_x_opts = true;
-			opt_buf = *x_opts;
+			buf_len = x_prefixed_opts_len;
+			is_x_prefixed_opts = true;
+			opt_buf = *x_prefixed_opts;
 		} else {
-			buf_len = non_x_opts_len;
-			is_x_opts = false;
-			opt_buf = *non_x_opts;
+			buf_len = regular_opts_len;
+			is_x_prefixed_opts = false;
+			opt_buf = *regular_opts;
 		}
 
 		if (buf_len < orig_len) {
@@ -163,7 +172,8 @@ static int extract_x_options(const char *original, char **non_x_opts,
 		/* omits ',' */
 		if ((ssize_t)(buf_len - opt_len) < 0) {
 			/* This would be a bug */
-			fprintf(stderr, "%s: no buf space left in copy, orig='%s'\n",
+			fprintf(stderr,
+				"%s: no buf space left in copy, orig='%s'\n",
 				__func__, original);
 			return -EIO;
 		}
@@ -171,10 +181,10 @@ static int extract_x_options(const char *original, char **non_x_opts,
 		strncat(opt_buf, opt, opt_end - opt);
 		buf_len -= opt_len;
 
-		if (is_x_opts)
-			x_opts_len = buf_len;
+		if (is_x_prefixed_opts)
+			x_prefixed_opts_len = buf_len;
 		else
-			non_x_opts_len = buf_len;
+			regular_opts_len = buf_len;
 	}
 
 	return 0;
@@ -1310,7 +1320,7 @@ static int mount_fuse(const char *mnt, const char *opts, const char **type)
 	const char *real_mnt = mnt;
 	int mountpoint_fd = -1;
 	char *do_mount_opts = NULL;
-	char *x_opts = NULL;
+	char *x_prefixed_opts = NULL;
 
 	fd = open_fuse_device(dev);
 	if (fd == -1)
@@ -1328,7 +1338,7 @@ static int mount_fuse(const char *mnt, const char *opts, const char **type)
 	}
 
 	// Extract any options starting with "x-"
-	res= extract_x_options(opts, &do_mount_opts, &x_opts);
+	res = extract_x_options(opts, &do_mount_opts, &x_prefixed_opts);
 	if (res)
 		goto fail_close_fd;
 
@@ -1351,14 +1361,14 @@ static int mount_fuse(const char *mnt, const char *opts, const char **type)
 	}
 
 	if (geteuid() == 0) {
-		if (x_opts && strlen(x_opts) > 0) {
+		if (x_prefixed_opts && strlen(x_prefixed_opts) > 0) {
 			/*
 			 * Add back the options starting with "x-" to opts from
 			 * do_mount. +2 for ',' and '\0'
 			 */
 			size_t mnt_opts_len = strlen(mnt_opts);
 			size_t x_mnt_opts_len =  mnt_opts_len+
-						 strlen(x_opts) + 2;
+						 strlen(x_prefixed_opts) + 2;
 			char *x_mnt_opts = calloc(1, x_mnt_opts_len);
 
 			if (mnt_opts_len) {
@@ -1366,7 +1376,7 @@ static int mount_fuse(const char *mnt, const char *opts, const char **type)
 				strncat(x_mnt_opts, ",", 2);
 			}
 
-			strncat(x_mnt_opts, x_opts,
+			strncat(x_mnt_opts, x_prefixed_opts,
 				x_mnt_opts_len - mnt_opts_len - 2);
 
 			free(mnt_opts);
@@ -1383,7 +1393,7 @@ static int mount_fuse(const char *mnt, const char *opts, const char **type)
 out_free:
 	free(source);
 	free(mnt_opts);
-	free(x_opts);
+	free(x_prefixed_opts);
 	free(do_mount_opts);
 
 	return fd;

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 20/25] Make fusermount work bidirectional for sync init
  2026-03-26 21:34 [PATCH v2 00/25] libfuse: Add support for synchronous init Bernd Schubert
                   ` (18 preceding siblings ...)
  2026-03-26 21:34 ` [PATCH v2 19/25] fusermount: Refactor extract_x_options Bernd Schubert
@ 2026-03-26 21:34 ` Bernd Schubert
  2026-03-30 19:03   ` Darrick J. Wong
  2026-03-26 21:34 ` [PATCH v2 21/25] New mount API: Filter out "user=" Bernd Schubert
                   ` (5 subsequent siblings)
  25 siblings, 1 reply; 56+ messages in thread
From: Bernd Schubert @ 2026-03-26 21:34 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Miklos Szeredi, Joanne Koong, Darrick J. Wong, Kevin Chen,
	Bernd Schubert, Bernd Schubert

From: Bernd Schubert <bschubert@ddn.com>

Signed-off-by: Bernd Schubert <bschubert@ddn.com>
---
 util/fusermount.c | 302 +++++++++++++++++++++++++++++++++++++++++++++++++++---
 util/meson.build  |   2 +-
 2 files changed, 291 insertions(+), 13 deletions(-)

diff --git a/util/fusermount.c b/util/fusermount.c
index d42e16955f6d26f81e6d2a5acb040b1448e20b51..31bf959024ae0cd2a4e50974589bfab30a100b0a 100644
--- a/util/fusermount.c
+++ b/util/fusermount.c
@@ -11,6 +11,9 @@
 #include "fuse_config.h"
 #include "mount_util.h"
 #include "util.h"
+#if __linux__
+#include "mount_i_linux.h"
+#endif
 
 #include <stdio.h>
 #include <stdlib.h>
@@ -923,6 +926,7 @@ static void free_mount_params(struct mount_params *mp)
 	free(mp->source);
 	free(mp->type);
 	free(mp->mnt_opts);
+	memset(mp, 0, sizeof(*mp));
 }
 
 /*
@@ -1309,6 +1313,171 @@ static int open_fuse_device(const char *dev)
 	return fd;
 }
 
+/*
+ * Context for split mount operation (sync-init mode)
+ */
+struct mount_context {
+	int fd;
+	const char *dev;
+	struct stat stbuf;
+	char *source;
+	char *mnt_opts;
+	char *x_opts;
+	const char *type;
+};
+
+/*
+ * Phase 1: Open device and prepare for mount (sync-init mode)
+ * Returns fd on success, -1 on failure
+ */
+static int mount_fuse_prepare(const char *mnt, const char *opts,
+			      struct mount_context *ctx)
+{
+	int res;
+	int mountpoint_fd = -1;
+	char *do_mount_opts = NULL;
+	const char *real_mnt = mnt;
+
+	memset(ctx, 0, sizeof(*ctx));
+	ctx->dev = getenv(FUSE_KERN_DEVICE_ENV) ?: FUSE_DEV;
+
+	ctx->fd = open_fuse_device(ctx->dev);
+	if (ctx->fd == -1)
+		return -1;
+
+	drop_privs();
+	read_conf();
+
+	if (getuid() != 0 && mount_max != -1) {
+		int mount_count = count_fuse_fs();
+
+		if (mount_count >= mount_max) {
+			fprintf(stderr,
+				"%s: too many FUSE filesystems mounted; mount_max=N can be set in %s\n",
+				progname, FUSE_CONF);
+			goto fail_close_fd;
+		}
+	}
+
+	res = extract_x_options(opts, &do_mount_opts, &ctx->x_opts);
+	if (res)
+		goto fail_close_fd;
+
+	res = check_perm(&real_mnt, &ctx->stbuf, &mountpoint_fd);
+	restore_privs();
+
+	if (mountpoint_fd != -1)
+		close(mountpoint_fd);
+
+	if (res == -1)
+		goto fail_close_fd;
+
+	free(do_mount_opts);
+	return ctx->fd;
+
+fail_close_fd:
+	close(ctx->fd);
+	free(do_mount_opts);
+	free(ctx->x_opts);
+	ctx->fd = -1;
+	return -1;
+}
+
+#ifdef HAVE_NEW_MOUNT_API
+/*
+ * Phase 2: Perform the actual mount using new mount API (sync-init mode)
+ * Returns 0 on success, -1 on failure
+ */
+static int mount_fuse_finish_fsmount(const char *mnt, const char *opts,
+				     struct mount_context *ctx,
+				     const char **type)
+{
+	int res;
+	char *do_mount_opts = NULL;
+	char *x_prefixed_opts = NULL;
+	struct mount_params mp = {
+		.fd = ctx->fd,
+		.rootmode = ctx->stbuf.st_mode & S_IFMT,
+		.dev = ctx->dev,
+	};
+	char *final_mnt_opts = NULL;
+
+	/* Extract x-options */
+	res = extract_x_options(opts, &do_mount_opts, &x_prefixed_opts);
+	if (res)
+		goto fail;
+
+	res = prepare_mount(do_mount_opts, &mp);
+	if (res == -1)
+		goto fail;
+
+	/* Merge x-options if running as root */
+	final_mnt_opts = mp.mnt_opts;
+	if (geteuid() == 0 && ctx->x_opts && strlen(ctx->x_opts) > 0) {
+		size_t mnt_opts_len = strlen(mp.mnt_opts);
+		size_t x_mnt_opts_len = mnt_opts_len + strlen(ctx->x_opts) + 2;
+		char *x_mnt_opts = calloc(1, x_mnt_opts_len);
+
+		if (!x_mnt_opts)
+			goto fail_free_params;
+
+		if (mnt_opts_len) {
+			strcpy(x_mnt_opts, mp.mnt_opts);
+			strncat(x_mnt_opts, ",", 2);
+		}
+		strncat(x_mnt_opts, ctx->x_opts,
+			x_mnt_opts_len - mnt_opts_len - 2);
+
+		final_mnt_opts = x_mnt_opts;
+	}
+
+	/* Use new mount API */
+	res = fuse_kern_fsmount(mnt, mp.flags, mp.blkdev,
+				mp.fsname, mp.subtype, ctx->dev,
+				mp.optbuf, final_mnt_opts);
+	if (res == -1)
+		goto fail_free_merged;
+
+	/* Change to root directory */
+	res = chdir("/");
+	if (res == -1) {
+		fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
+		goto fail_free_merged;
+	}
+
+	/* Store results in context */
+	ctx->source = mp.source;
+	ctx->type = mp.type;
+	ctx->mnt_opts = final_mnt_opts;
+	*type = mp.type;
+
+	res = 0;
+
+	/* Only free what is not assigned to ctx */
+	free(mp.fsname);
+	free(mp.subtype);
+	free(mp.optbuf);
+	if (final_mnt_opts != mp.mnt_opts)
+		free(mp.mnt_opts);
+
+out:
+	free(do_mount_opts);
+	free(x_prefixed_opts);
+
+	return res;
+
+fail_free_merged:
+	if (final_mnt_opts != mp.mnt_opts)
+		free(final_mnt_opts);
+fail_free_params:
+	free_mount_params(&mp);
+fail:
+	res = -1;
+	goto out;
+}
+#endif /* HAVE_NEW_MOUNT_API */
+
+
 static int mount_fuse(const char *mnt, const char *opts, const char **type)
 {
 	int res;
@@ -1404,6 +1573,65 @@ fail_close_fd:
 	goto out_free;
 }
 
+/* Forward declarations for helper functions */
+static int send_fd(int sock_fd, int fd);
+static int wait_for_signal(int sock_fd);
+
+#ifdef HAVE_NEW_MOUNT_API
+/*
+ * Perform sync-init mount using new mount API
+ * Returns 0 on success, -1 on failure
+ */
+static int mount_fuse_sync_init(const char *mnt, const char *opts,
+				int cfd, const char **type)
+{
+	struct mount_context ctx;
+	int fd, res;
+	int32_t status, send_res;
+
+	/* Phase 1: Open device and prepare */
+	fd = mount_fuse_prepare(mnt, opts, &ctx);
+	if (fd == -1)
+		return -1;
+
+	/* Send fd to caller so it can start worker thread */
+	res = send_fd(cfd, fd);
+	if (res != 0) {
+		close(fd);
+		free(ctx.x_opts);
+		return -1;
+	}
+
+	/* Wait for caller to signal that worker thread is ready */
+	res = wait_for_signal(cfd);
+	if (res != 0) {
+		close(fd);
+		free(ctx.x_opts);
+		return -1;
+	}
+
+	/* Phase 2: Perform the actual mount using new API */
+	res = mount_fuse_finish_fsmount(mnt, opts, &ctx, type);
+
+	/* Send mount result back to caller (4-byte error code) */
+	status = (res == 0) ? 0 : -(int32_t)errno;
+	do {
+		send_res = send(cfd, &status, sizeof(status), 0);
+	} while (send_res == -1 && errno == EINTR);
+	if (send_res != sizeof(status)) {
+		fprintf(stderr, "%s: failed to send mount status: %s\n",
+			progname, strerror(errno));
+	}
+
+	close(fd);
+	free(ctx.source);
+	free(ctx.mnt_opts);
+	free(ctx.x_opts);
+
+	return res;
+}
+#endif /* HAVE_NEW_MOUNT_API */
+
 static int send_fd(int sock_fd, int fd)
 {
 	int retval;
@@ -1440,6 +1668,30 @@ static int send_fd(int sock_fd, int fd)
 	return 0;
 }
 
+/*
+ * Wait for a signal byte from the caller.
+ * Returns 0 on success, -1 on error.
+ */
+static int wait_for_signal(int sock_fd)
+{
+	char buf[1];
+	int res;
+
+	do {
+		res = recv(sock_fd, buf, sizeof(buf), 0);
+	} while (res == -1 && errno == EINTR);
+	if (res != 1) {
+		if (res == 0)
+			fprintf(stderr, "%s: connection closed while waiting for signal\n",
+				progname);
+		else
+			fprintf(stderr, "%s: error receiving signal: %s\n",
+				progname, strerror(errno));
+		return -1;
+	}
+	return 0;
+}
+
 /* Helper for should_auto_unmount
  *
  * fusermount typically has the s-bit set - initial open of `mnt` was as root
@@ -1631,6 +1883,7 @@ int main(int argc, char *argv[])
 	const char *opts = "";
 	const char *type = NULL;
 	int setup_auto_unmount_only = 0;
+	int sync_init_mode = 0;
 
 	static const struct option long_opts[] = {
 		{"unmount", no_argument, NULL, 'u'},
@@ -1643,6 +1896,7 @@ int main(int argc, char *argv[])
 		// They'ne meant for internal use by mount.c
 		{"auto-unmount", no_argument, NULL, 'U'},
 		{"comm-fd", required_argument, NULL, 'c'},
+		{"sync-init", no_argument, NULL, 'S'},
 		{0, 0, 0, 0}};
 
 	progname = strdup(argc > 0 ? argv[0] : "fusermount");
@@ -1677,6 +1931,9 @@ int main(int argc, char *argv[])
 		case 'c':
 			commfd = optarg;
 			break;
+		case 'S':
+			sync_init_mode = 1;
+			break;
 		case 'z':
 			lazy = 1;
 			break;
@@ -1754,21 +2011,42 @@ int main(int argc, char *argv[])
 	if (setup_auto_unmount_only)
 		goto wait_for_auto_unmount;
 
-	fd = mount_fuse(mnt, opts, &type);
-	if (fd == -1)
-		goto err_out;
+	if (sync_init_mode) {
+#ifdef HAVE_NEW_MOUNT_API
+		res = mount_fuse_sync_init(mnt, opts, cfd, &type);
+		if (res == -1)
+			goto err_out;
 
-	res = send_fd(cfd, fd);
-	if (res != 0) {
-		umount2(mnt, MNT_DETACH); /* lazy umount */
+		if (!auto_unmount) {
+			free(mnt);
+			free((void *) type);
+			return 0;
+		}
+		/* Continue to auto_unmount handling below */
+#else
+		fprintf(stderr, "%s: sync-init mode requires new mount API support\n",
+			progname);
+		fprintf(stderr, "%s: kernel or headers too old (need fsopen/fsmount)\n",
+			progname);
 		goto err_out;
-	}
-	close(fd);
+#endif
+	} else {
+		fd = mount_fuse(mnt, opts, &type);
+		if (fd == -1)
+			goto err_out;
 
-	if (!auto_unmount) {
-		free(mnt);
-		free((void*) type);
-		return 0;
+		res = send_fd(cfd, fd);
+		if (res != 0) {
+			umount2(mnt, MNT_DETACH); /* lazy umount */
+			goto err_out;
+		}
+		close(fd);
+
+		if (!auto_unmount) {
+			free(mnt);
+			free((void *) type);
+			return 0;
+		}
 	}
 
 wait_for_auto_unmount:
diff --git a/util/meson.build b/util/meson.build
index 0e4b1cce95377e73af7dc45655a7088315497ddb..731ef95488461ac21c21b1972a96d58b1187dc5a 100644
--- a/util/meson.build
+++ b/util/meson.build
@@ -1,6 +1,6 @@
 fuseconf_path = join_paths(get_option('prefix'), get_option('sysconfdir'), 'fuse.conf')
 
-executable('fusermount3', ['fusermount.c', '../lib/mount_util.c', '../lib/util.c'],
+executable('fusermount3', ['fusermount.c', '../lib/mount_util.c', '../lib/mount_fsmount.c', '../lib/util.c'],
            include_directories: include_dirs,
            install: true,
            install_dir: get_option('bindir'),

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 21/25] New mount API: Filter out "user="
  2026-03-26 21:34 [PATCH v2 00/25] libfuse: Add support for synchronous init Bernd Schubert
                   ` (19 preceding siblings ...)
  2026-03-26 21:34 ` [PATCH v2 20/25] Make fusermount work bidirectional for sync init Bernd Schubert
@ 2026-03-26 21:34 ` 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
                   ` (4 subsequent siblings)
  25 siblings, 1 reply; 56+ messages in thread
From: Bernd Schubert @ 2026-03-26 21:34 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Miklos Szeredi, Joanne Koong, Darrick J. Wong, Kevin Chen,
	Bernd Schubert, Bernd Schubert

From: Bernd Schubert <bschubert@ddn.com>

This gets added in the fusermount process and kernel then fails
the mount.

Signed-off-by: Bernd Schubert <bschubert@ddn.com>
---
 lib/mount_fsmount.c | 15 ++++++++++++++-
 1 file changed, 14 insertions(+), 1 deletion(-)

diff --git a/lib/mount_fsmount.c b/lib/mount_fsmount.c
index fbe3647a8923828dba819458b815c21bbd2beaad..f42ae97faedaf2420d97e701a22725d4293e4853 100644
--- a/lib/mount_fsmount.c
+++ b/lib/mount_fsmount.c
@@ -248,6 +248,16 @@ static int is_mount_attr_opt(const char *opt)
 	       strcmp(opt, "symfollow") == 0;
 }
 
+/**
+ * Check if an option is a mount table option (not passed to fsconfig)
+ */
+static int is_mtab_only_opt(const char *opt)
+{
+	/* These options are for /run/mount/utab only, not for the kernel */
+	return strncmp(opt, "user=", 5) == 0 ||
+	       strcmp(opt, "rw") == 0;
+}
+
 /*
  * Parse kernel options string and apply via fsconfig
  * Options are comma-separated key=value pairs
@@ -275,6 +285,7 @@ static int apply_mount_opts(int fsfd, const char *opts)
 		 * not fsconfig().
 		 *
 		 * These string options (nosuid, nodev, etc.) are reconstructed
+		 * Skip mtab-only options - they're for /run/mount/utab, not kernel
 		 * from MS_* flags by get_mnt_flag_opts() in lib/mount.c and
 		 * get_mnt_opts() in util/fusermount.c. Both the library path
 		 * (via fuse_kern_mount_get_base_mnt_opts) and fusermount3 path
@@ -282,8 +293,10 @@ static int apply_mount_opts(int fsfd, const char *opts)
 		 * mnt_opts. They must be filtered here because they are mount
 		 * attributes (passed to fsmount via MOUNT_ATTR_*), not
 		 * filesystem parameters (which would be passed to fsconfig).
+		 *
+		 * Also skip mtab-only options - they're for /run/mount/utab, not kernel
 		 */
-		if (!is_mount_attr_opt(opt)) {
+		if (!is_mount_attr_opt(opt) && !is_mtab_only_opt(opt)) {
 			res = apply_opt_key_value(fsfd, opt);
 			if (res < 0) {
 				free(opts_copy);

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 22/25] Add support for sync-init of unprivileged daemons
  2026-03-26 21:34 [PATCH v2 00/25] libfuse: Add support for synchronous init Bernd Schubert
                   ` (20 preceding siblings ...)
  2026-03-26 21:34 ` [PATCH v2 21/25] New mount API: Filter out "user=" Bernd Schubert
@ 2026-03-26 21:34 ` Bernd Schubert
  2026-03-31  0:54   ` Darrick J. Wong
  2026-03-26 21:34 ` [PATCH v2 23/25] Move fuse_mnt_build_{source,type} to mount_util.c Bernd Schubert
                   ` (3 subsequent siblings)
  25 siblings, 1 reply; 56+ messages in thread
From: Bernd Schubert @ 2026-03-26 21:34 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Miklos Szeredi, Joanne Koong, Darrick J. Wong, Kevin Chen,
	Bernd Schubert, Bernd Schubert

From: Bernd Schubert <bschubert@ddn.com>

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>
---
 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 <assert.h>
 #include <sys/file.h>
 #include <sys/ioctl.h>
+#include <sys/wait.h>
 #include <stdalign.h>
 #include <poll.h>
 
@@ -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


^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 23/25] Move fuse_mnt_build_{source,type} to mount_util.c
  2026-03-26 21:34 [PATCH v2 00/25] libfuse: Add support for synchronous init Bernd Schubert
                   ` (21 preceding siblings ...)
  2026-03-26 21:34 ` [PATCH v2 22/25] Add support for sync-init of unprivileged daemons Bernd Schubert
@ 2026-03-26 21:34 ` 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
                   ` (2 subsequent siblings)
  25 siblings, 1 reply; 56+ messages in thread
From: Bernd Schubert @ 2026-03-26 21:34 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Miklos Szeredi, Joanne Koong, Darrick J. Wong, Kevin Chen,
	Bernd Schubert

Also make it more generic and avoid taking struct mount_opts
as parameter.

mount_fsmount.c and fusermount.c use these functions now.

Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
---
 lib/mount.c         | 37 +++----------------------------------
 lib/mount_fsmount.c | 29 ++++++++++++-----------------
 lib/mount_i_linux.h |  2 --
 lib/mount_util.c    | 31 +++++++++++++++++++++++++++++++
 lib/mount_util.h    |  5 +++++
 util/fusermount.c   | 16 ++--------------
 6 files changed, 53 insertions(+), 67 deletions(-)

diff --git a/lib/mount.c b/lib/mount.c
index f0d12525310a9ad239c0f447af60bd49b105a091..1e82adc8621da150e1ee61e8253b261a33625260 100644
--- a/lib/mount.c
+++ b/lib/mount.c
@@ -651,10 +651,11 @@ int fuse_kern_do_mount(const char *mnt, struct mount_opts *mo,
 	char *source = NULL;
 	char *type = NULL;
 	int res;
+	const char *devname = fuse_mnt_get_devname();
 
 	res = -ENOMEM;
-	source = fuse_mnt_build_source(mo);
-	type = fuse_mnt_build_type(mo);
+	source = fuse_mnt_build_source(mo->fsname, mo->subtype, devname);
+	type = fuse_mnt_build_type(mo->blkdev, mo->subtype);
 	if (!type || !source) {
 		fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate memory\n");
 		goto out_close;
@@ -834,35 +835,3 @@ out:
 	free(mnt_opts);
 	return res;
 }
-
-char *fuse_mnt_build_source(const struct mount_opts *mo)
-{
-	const char *devname = fuse_mnt_get_devname();
-	char *source;
-	int ret;
-
-	ret = asprintf(&source, "%s",
-		       mo->fsname ? mo->fsname :
-				    (mo->subtype ? mo->subtype : devname));
-	if (ret == -1)
-		return NULL;
-
-	return source;
-}
-
-char *fuse_mnt_build_type(const struct mount_opts *mo)
-{
-	char *type;
-	int ret;
-
-	if (mo->subtype)
-		ret = asprintf(&type, "%s.%s", mo->blkdev ? "fuseblk" : "fuse",
-			       mo->subtype);
-	else
-		ret = asprintf(&type, "%s", mo->blkdev ? "fuseblk" : "fuse");
-
-	if (ret == -1)
-		return NULL;
-
-	return type;
-}
diff --git a/lib/mount_fsmount.c b/lib/mount_fsmount.c
index f42ae97faedaf2420d97e701a22725d4293e4853..57355db89f907e0dbd910ead4ea39005ec26c44d 100644
--- a/lib/mount_fsmount.c
+++ b/lib/mount_fsmount.c
@@ -329,15 +329,21 @@ int fuse_kern_fsmount(const char *mnt, unsigned long flags, int blkdev,
 		      const char *source_dev, const char *kernel_opts,
 		      const char *mnt_opts)
 {
-	const char *type;
+	char *type = NULL;
 	char *source = NULL;
 	int fsfd = -1;
 	int mntfd = -1;
 	int err, res;
 	unsigned long mount_attrs;
 
-	/* Determine filesystem type */
-	type = blkdev ? "fuseblk" : "fuse";
+	/* Build type and source strings */
+	type = fuse_mnt_build_type(blkdev, subtype);
+	source = fuse_mnt_build_source(fsname, subtype, source_dev);
+	err = -ENOMEM;
+	if (!type || !source) {
+		fprintf(stderr, "fuse: failed to allocate memory\n");
+		goto out_free;
+	}
 
 	/* Try to open filesystem context */
 	fsfd = fsopen(type, FSOPEN_CLOEXEC);
@@ -345,21 +351,9 @@ int fuse_kern_fsmount(const char *mnt, unsigned long flags, int blkdev,
 		if (errno != EPERM)
 			fprintf(stderr, "fuse: fsopen(%s) failed: %s\n", type,
 				strerror(errno));
-		return -1;
+		goto out_free;
 	}
 
-	/* Build source string */
-	source = malloc((fsname ? strlen(fsname) : 0) +
-			(subtype ? strlen(subtype) : 0) +
-			strlen(source_dev) + 32);
-	err = -ENOMEM;
-	if (!source) {
-		fprintf(stderr, "fuse: failed to allocate memory\n");
-		goto out_close_fsfd;
-	}
-
-	strcpy(source, fsname ? fsname : (subtype ? subtype : source_dev));
-
 	/* Configure source */
 	res = fsconfig(fsfd, FSCONFIG_SET_STRING, "source", source, 0);
 	if (res == -1) {
@@ -439,6 +433,7 @@ int fuse_kern_fsmount(const char *mnt, unsigned long flags, int blkdev,
 
 	close(mntfd);
 	free(source);
+	free(type);
 	return 0;
 
 out_umount:
@@ -458,7 +453,7 @@ out_close_mntfd:
 		close(mntfd);
 out_free:
 	free(source);
-out_close_fsfd:
+	free(type);
 	if (fsfd != -1)
 		close(fsfd);
 	errno = -err;
diff --git a/lib/mount_i_linux.h b/lib/mount_i_linux.h
index ae98296a334a2aa39c4f3e5c3e4e3c136ae38cd3..8fc27b958eab3141ab852a7cb5098febd484e04f 100644
--- a/lib/mount_i_linux.h
+++ b/lib/mount_i_linux.h
@@ -41,8 +41,6 @@ int fuse_kern_fsmount(const char *mnt, unsigned long flags, int blkdev,
 int fuse_kern_fsmount_mo(const char *mnt, 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,
diff --git a/lib/mount_util.c b/lib/mount_util.c
index b6b75e60874ae9b2ec0c96f2a62b4ec1943a2a00..8cb0ce999e0df498c6777daf2bba6f47f12d7eee 100644
--- a/lib/mount_util.c
+++ b/lib/mount_util.c
@@ -461,3 +461,34 @@ const char *fuse_mnt_get_devname(void)
 
 	return devname ? devname : "/dev/fuse";
 }
+
+char *fuse_mnt_build_source(const char *fsname, const char *subtype,
+			     const char *devname)
+{
+	char *source;
+	int ret;
+
+	ret = asprintf(&source, "%s",
+		       fsname ? fsname : (subtype ? subtype : devname));
+	if (ret == -1)
+		return NULL;
+
+	return source;
+}
+
+char *fuse_mnt_build_type(int blkdev, const char *subtype)
+{
+	char *type;
+	int ret;
+
+	if (subtype)
+		ret = asprintf(&type, "%s.%s", blkdev ? "fuseblk" : "fuse",
+			       subtype);
+	else
+		ret = asprintf(&type, "%s", blkdev ? "fuseblk" : "fuse");
+
+	if (ret == -1)
+		return NULL;
+
+	return type;
+}
diff --git a/lib/mount_util.h b/lib/mount_util.h
index b83db556d5fdb4bd8dd5e1750c8d11faf7373d82..5ba428a1f96df0dda21c0aadd936c022c4bb751c 100644
--- a/lib/mount_util.h
+++ b/lib/mount_util.h
@@ -35,4 +35,9 @@ const char *fuse_mnt_get_devname(void);
 int fuse_mnt_add_mount_helper(const char *mnt, const char *source,
 			       const char *type, const char *mnt_opts);
 
+/* Build source and type strings for mounting */
+char *fuse_mnt_build_source(const char *fsname, const char *subtype,
+			     const char *devname);
+char *fuse_mnt_build_type(int blkdev, const char *subtype);
+
 #endif /* FUSE_MOUNT_UTIL_H_ */
diff --git a/util/fusermount.c b/util/fusermount.c
index bf442416c83d6da3f80eca5c9814df29f61bec56..6d3ad4b300ba6266778fc23f35fa6f4fc929ff3f 100644
--- a/util/fusermount.c
+++ b/util/fusermount.c
@@ -1048,25 +1048,13 @@ static int prepare_mount(const char *opts, struct mount_params *mp)
 	sprintf(d, "fd=%i,rootmode=%o,user_id=%u,group_id=%u",
 		mp->fd, mp->rootmode, getuid(), getgid());
 
-	mp->source = malloc((mp->fsname ? strlen(mp->fsname) : 0) +
-			(mp->subtype ? strlen(mp->subtype) : 0) + strlen(mp->dev) + 32);
-
-	mp->type = malloc((mp->subtype ? strlen(mp->subtype) : 0) + 32);
+	mp->source = fuse_mnt_build_source(mp->fsname, mp->subtype, mp->dev);
+	mp->type = fuse_mnt_build_type(mp->blkdev, mp->subtype);
 	if (!mp->type || !mp->source) {
 		fprintf(stderr, "%s: failed to allocate memory\n", progname);
 		goto err;
 	}
 
-	if (mp->subtype)
-		sprintf(mp->type, "%s.%s", mp->blkdev ? "fuseblk" : "fuse", mp->subtype);
-	else
-		strcpy(mp->type, mp->blkdev ? "fuseblk" : "fuse");
-
-	if (mp->fsname)
-		strcpy(mp->source, mp->fsname);
-	else
-		strcpy(mp->source, mp->subtype ? mp->subtype : mp->dev);
-
 	return 0;
 
 err:

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 24/25] Add mount and daemonization README documents
  2026-03-26 21:34 [PATCH v2 00/25] libfuse: Add support for synchronous init Bernd Schubert
                   ` (22 preceding siblings ...)
  2026-03-26 21:34 ` [PATCH v2 23/25] Move fuse_mnt_build_{source,type} to mount_util.c Bernd Schubert
@ 2026-03-26 21:34 ` Bernd Schubert
  2026-03-31  1:17   ` Darrick J. Wong
  2026-03-26 21:34 ` [PATCH v2 25/25] Add a background debug option to passthrough hp Bernd Schubert
  2026-04-07 12:04 ` fuse-devel list on kernel.org Amir Goldstein
  25 siblings, 1 reply; 56+ messages in thread
From: Bernd Schubert @ 2026-03-26 21:34 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Miklos Szeredi, Joanne Koong, Darrick J. Wong, Kevin Chen,
	Bernd Schubert

These are useful to

Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
---
 doc/README.daemonize  | 197 +++++++++++++++++++++++++++
 doc/README.fusermount | 362 ++++++++++++++++++++++++++++++++++++++++++++++++++
 doc/README.mount      |  86 ++++++++++++
 doc/README.sync-init  | 184 +++++++++++++++++++++++++
 4 files changed, 829 insertions(+)

diff --git a/doc/README.daemonize b/doc/README.daemonize
new file mode 100644
index 0000000000000000000000000000000000000000..7b56c33077f29edbd335a2778120d9b6db6022ad
--- /dev/null
+++ b/doc/README.daemonize
@@ -0,0 +1,197 @@
+FUSE Daemonization API
+======================
+
+This document describes the FUSE daemonization APIs, including the legacy
+fuse_daemonize() function and the new fuse_daemonize_start()/signal() API
+introduced in libfuse 3.19.
+
+
+Overview
+--------
+
+FUSE filesystems often need to run as background daemons. Daemonization
+involves forking the process, creating a new session, and redirecting
+standard file descriptors. The challenge is properly reporting initialization
+failures to the parent process.
+
+
+Old API: fuse_daemonize()
+--------------------------
+
+Function signature:
+    int fuse_daemonize(int foreground);
+
+Location: lib/helper.c
+
+This is the legacy daemonization API, primarily used with the high-level
+fuse_main() interface.
+
+Behavior:
+- If foreground=0: forks the process, creates a new Unix session,
+  redirects stdio
+- If foreground=1: only changes directory to "/"
+- Parent waits for a single byte on a pipe before exiting
+- Child writes completion byte immediately after redirecting stdio
+- Always changes directory to "/"
+
+Limitations:
+1. No failure reporting: The parent receives notification immediately after
+   fork/setsid, before any meaningful initialization (like mounting the
+   filesystem or starting threads).
+
+2. Timing constraint: Must be called AFTER fuse_session_mount() in existing
+   examples, because there's no way to report mount failures to the parent.
+
+3. Thread initialization: Cannot report failures from complex initialization
+   steps like:
+   - Starting worker threads
+   - Network connection setup
+   - RDMA memory registration
+   - Resource allocation
+
+4. FUSE_SYNC_INIT incompatibility: With the FUSE_SYNC_INIT feature, FUSE_INIT
+   happens at mount time and may start io_uring threads. This requires
+   daemonization BEFORE mount, which the old API cannot handle properly.
+
+Example usage (old API):
+    fuse = fuse_new(...);
+    fuse_mount(fuse, mountpoint);
+    fuse_daemonize(opts.foreground);  // After mount, can't report mount failure
+    fuse_set_signal_handlers(se);
+    fuse_session_loop(se);
+
+
+New API: fuse_daemonize_start() / fuse_daemonize_success() / fuse_daemonize_fail()
+---------------------------------------------------------------------------------
+
+Functions:
+    int fuse_daemonize_start(unsigned int flags);
+    void fuse_daemonize_success(void);
+    void fuse_daemonize_fail(void);
+    bool fuse_daemonize_active(void);
+
+Location: lib/fuse_daemonize.c, include/fuse_daemonize.h
+Available since: libfuse 3.19
+
+This new API solves the limitations of fuse_daemonize() by splitting
+daemonization into two phases:
+
+1.   fuse_daemonize_start() - Fork and setup, but parent waits
+2.1. fuse_daemonize_fail() - Signal failure to parent
+2.2  fuse_daenmize_success() - Signal startup to success to the parent
+     See below for an important detail.
+
+
+fuse_daemonize_start()
+----------------------
+
+Flags:
+- FUSE_DAEMONIZE_NO_CHDIR: Don't change directory to "/"
+- FUSE_DAEMONIZE_NO_BACKGROUND: Don't fork (foreground mode)
+
+Behavior:
+- Unless NO_BACKGROUND: forks the process
+- Parent waits for status signal from child
+- Child creates new session and continues
+- Unless NO_CHDIR: changes directory to "/"
+- Closes stdin immediately in child
+- Starts a watcher thread to detect parent death
+- Returns 0 in child on success, negative errno on error
+
+Parent death detection:
+- Uses a "death pipe" - parent keeps write end open
+- Child's watcher thread polls the read end
+- When parent dies, pipe gets POLLHUP and child exits
+- Prevents orphaned daemons if parent is killed
+
+
+fuse_daemonize_fail(int err)
+----------------------------
+
+Behavior:
+- Signals the parent about the provided error
+- Parent will exit with that error
+
+fuse_daemonize_success()
+------------------------
+- Signals the parent process with succes
+- On success: redirects stdout/stderr to /dev/null
+- Stops the parent watcher thread
+- Cleans up pipes and internal state
+- Safe to call multiple times
+- Safe to call even if fuse_daemonize_start() failed
+- *Important*: Should be called twice, once from ->init
+  (struct fuse_lowlevel_ops::init or for high level interface
+   struct fuse_operations::init) and after fuse_session_mount().
+  It will internally figure out which of these two calls will
+  actually signal the parent about success. Reason is that
+  sucess must only be signaled when the mount is complete and
+  that depends on if synchronous or asynchronous FUSE_INIT is
+  used. 
+
+
+fuse_daemonize_active()
+-----------------------
+
+Returns true if daemonization is active and waiting for signal.
+
+Used by the high-level fuse_main() to detect if the application already
+called the new API, avoiding double-daemonization.
+
+
+Example usage (new API):
+-------------------------
+
+    // Start daemonization BEFORE mount
+    unsigned int daemon_flags = 0;
+    if (foreground)
+        daemon_flags |= FUSE_DAEMONIZE_NO_BACKGROUND;
+
+    if (fuse_daemonize_start(daemon_flags) != 0)
+        goto error;
+
+    // Complex initialization can fail and be reported
+    if (setup_threads() != 0)
+        goto error_signal;
+
+    if (setup_network() != 0)
+        goto error_signal;
+    // Mount can now fail and be reported to parent
+    if (fuse_session_mount(se, mountpoint) != 0)
+        goto error_signal;
+
+    // Signal success - parent exits with EXIT_SUCCESS
+    // This is typically done in the init() callback after FUSE_INIT
+    fuse_daemonize_signal(FUSE_DAEMONIZE_SUCCESS);
+
+    // Run main loop
+    fuse_session_loop(se);
+
+    return 0;
+
+error_signal:
+    // Signal failure - parent exits with EXIT_FAILURE
+    fuse_daemonize_signal(FUSE_DAEMONIZE_FAILURE);
+error:
+    return 1;
+
+
+When to signal success
+----------------------
+
+The success signal should be sent after all critical initialization is
+complete. For FUSE filesystems, this is typically in the init() callback,
+after FUSE_INIT has been processed successfully.
+
+Example (from passthrough_hp.cc):
+    static void sfs_init(void *userdata, fuse_conn_info *conn) {
+        // ... initialization ...
+        fuse_daemonize_signal(FUSE_DAEMONIZE_SUCCESS);
+    }
+
+This ensures the parent only exits after:
+- Mount succeeded
+- FUSE_INIT completed
+- All threads started
+- Filesystem is ready to serve requests
+
diff --git a/doc/README.fusermount b/doc/README.fusermount
new file mode 100644
index 0000000000000000000000000000000000000000..5b81651c8690aa25fef12db322af977c9cba1d31
--- /dev/null
+++ b/doc/README.fusermount
@@ -0,0 +1,362 @@
+Synchronous FUSE_INIT Protocol
+================================
+
+Overview
+--------
+
+The sync-init feature enables the FUSE library to start worker threads and
+perform initialization ioctl calls BEFORE the actual mount() syscall happens.
+This is required for the kernel's synchronous FUSE_INIT feature, where the
+mount() syscall blocks until the FUSE daemon processes the INIT request.
+
+Without this feature, there would be a deadlock:
+- mount() blocks waiting for INIT response
+- Worker threads can't start because mount() hasn't returned
+- INIT request can't be processed because worker threads aren't running
+
+
+Protocol Flow
+-------------
+
+Traditional mount flow:
+  1. Library calls fusermount3
+  2. fusermount3 opens /dev/fuse
+  3. fusermount3 performs mount() syscall
+  4. fusermount3 sends fd to library
+  5. Library starts worker threads
+  6. Worker threads process FUSE requests
+
+Sync-init mount flow:
+  1. Library calls fusermount3 with --sync-init flag
+  2. fusermount3 opens /dev/fuse
+  3. fusermount3 sends fd to library
+  4. Library receives fd
+  5. Library performs FUSE_DEV_IOC_SYNC_INIT ioctl
+  6. Library starts worker threads
+  7. Library sends "proceed" signal to fusermount3
+  8. fusermount3 performs mount() syscall (blocks until INIT completes)
+  9. Worker threads process INIT request
+  10. mount() syscall completes
+  11. fusermount3 exits
+
+
+Implementation Details
+----------------------
+
+Bidirectional Communication:
+  - Uses the existing unix socket (_FUSE_COMMFD environment variable)
+  - Simple 1-byte protocol for signaling
+  - Library signals fusermount3 when ready to proceed with mount
+
+fusermount3 Changes:
+  - New --sync-init command-line option
+  - Split mount operation into two phases:
+    * mount_fuse_prepare(): Opens device, prepares parameters
+    * mount_fuse_finish_fsmount(): Performs actual mount() syscall
+  - wait_for_signal(): Waits for library to signal readiness
+  - struct mount_context: Preserves state between phases
+
+Library Changes:
+  - fuse_session_mount_new_api(): Uses new protocol when available
+  - Sends "proceed" signal after worker thread is ready
+  - Handles both old and new mount protocols for compatibility
+
+
+Backward Compatibility
+----------------------
+
+The implementation maintains full backward compatibility:
+  - Old library + new fusermount3: Works (uses traditional flow)
+  - New library + old fusermount3: Falls back to traditional flow
+  - New library + new fusermount3: Uses sync-init flow when appropriate
+
+
+Error Handling
+--------------
+
+If any step fails during the sync-init flow:
+  - fusermount3 closes the fd and exits with error
+  - Library detects failure and cleans up
+  - No mount is left in inconsistent state
+
+Connection closure:
+  - If library closes socket before signaling, fusermount3 detects and exits
+  - If fusermount3 crashes, library detects closed socket
+
+
+Security Considerations
+-----------------------
+
+The sync-init protocol does not introduce new security concerns:
+  - Uses the same privilege separation as traditional mount
+  - Socket communication is already established and trusted
+  - No new privileged operations are added
+  - File descriptor passing uses existing SCM_RIGHTS mechanism
+
+
+Performance Impact
+------------------
+
+Minimal performance impact:
+  - One additional recv() call in fusermount3
+  - One additional send() call in library
+  - Total overhead: ~2 context switches
+  - Only affects mount time, not runtime performance
+
+
+Future Enhancements
+-------------------
+
+Potential improvements:
+  - Extended protocol for more complex initialization sequences
+  - Support for multiple worker threads coordination
+  - Enhanced error reporting through the socket
+  - Timeout mechanisms for detecting hung initialization
+
+
+ASCII Workflow Diagrams
+========================
+
+1. Traditional Mount Flow (without --sync-init, async INIT)
+------------------------------------------------------------
+
+Library                   fusermount3              Kernel
+   |                           |                      |
+   |--- spawn fusermount3 ---->|                      |
+   |                           |                      |
+   |                      [open /dev/fuse]            |
+   |                           |------- open -------->|
+   |                           |<------ fd ---------- |
+   |                           |                      |
+   |                      [mount() syscall]           |
+   |                           |------ mount -------->|
+   |                           |<----- success ------ | [mount returns immediately]
+   |                           |                      | [INIT queued in kernel]
+   |                      [send_fd(fd)]               |
+   |<------- fd --------------|                      |
+   |                           |                      |
+   |                      [fusermount3 exits]         |
+   |                                                  |
+   |  [start worker thread]                          |
+   |  [worker reads /dev/fuse]                       |
+   |---------------------------------------- read -->|
+   |<--------------------------------------- INIT ---| [dequeued from kernel]
+   |                                                  |
+   | OK: INIT was queued, worker reads it later      |
+   |     Works fine for async INIT                   |
+
+
+1b. Problem: Synchronous INIT without --sync-init
+--------------------------------------------------
+
+Library                   fusermount3              Kernel
+   |                           |                      |
+   |--- spawn fusermount3 ---->|                      |
+   |                           |                      |
+   |                      [open /dev/fuse]            |
+   |                           |------- open -------->|
+   |                           |<------ fd ---------- |
+   |                           |                      |
+   |                      [mount() syscall]           |
+   |                           |------ mount -------->|
+   |                           |                      | [mount BLOCKS waiting for INIT]
+   |                           | (BLOCKED)            | [needs worker to process INIT]
+   |                           |                      |
+   |  [waiting for fd...]      |                      |
+   |                           |                      |
+   |                           |                      |
+   | DEADLOCK: mount() waits for INIT response       |
+   |           but worker thread not started yet     |
+   |           because we're waiting for fd          |
+
+
+2. Sync-Init Mount Flow (with --sync-init)
+-------------------------------------------
+
+Library                   fusermount3              Kernel
+   |                           |                      |
+   |--- spawn fusermount3 ---->|                      |
+   |    with --sync-init       |                      |
+   |                           |                      |
+   |                      [open /dev/fuse]            |
+   |                           |------- open -------->|
+   |                           |<------ fd ---------- |
+   |                           |                      |
+   |                      [send_fd(fd)]               |
+   |<------- fd --------------|                      |
+   |                           |                      |
+   |                      [wait_for_signal()]         |
+   |                           | (BLOCKED)            |
+   |                           |                      |
+   |  [ioctl SYNC_INIT]        |                      |
+   |---------------------------------------- ioctl -->|
+   |                                                  |
+   |  [start worker thread]                          |
+   |  [worker ready]                                 |
+   |                           |                      |
+   |--- "proceed" signal ----->|                      |
+   |                      [signal received]           |
+   |                           |                      |
+   |                      [mount() syscall]           |
+   |                           |------ mount -------->|
+   |                           |                      | [mount blocks]
+   |                           |                      | [sends INIT]
+   |<------------------------------------------------ |
+   |                           |                      |
+   | [worker processes INIT]   |                      |
+   |------------------------------------------------->|
+   |                           |                      | [mount unblocks]
+   |                           |<----- success ------ |
+   |                           |                      |
+   |                      [fusermount3 exits]         |
+   |                                                  |
+   | SUCCESS: Worker ready before mount()            |
+   |          INIT processed synchronously           |
+
+
+3. Error Scenario: Library Crashes Before Signaling
+----------------------------------------------------
+
+Library                   fusermount3              Kernel
+   |                           |                      |
+   |--- spawn fusermount3 ---->|                      |
+   |    with --sync-init       |                      |
+   |                           |                      |
+   |                      [open /dev/fuse]            |
+   |                           |------- open -------->|
+   |                           |<------ fd ---------- |
+   |                           |                      |
+   |                      [send_fd(fd)]               |
+   |<------- fd --------------|                      |
+   |                           |                      |
+   |                      [wait_for_signal()]         |
+   |                           | (BLOCKED)            |
+   |                           |                      |
+   X  [library crashes]        |                      |
+   |                           |                      |
+   |                      [recv() returns 0]          |
+   |                      [socket closed]             |
+   |                           |                      |
+   |                      [cleanup and exit]          |
+   |                           X                      |
+   |                                                  |
+   | RESULT: Clean failure, no mount performed       |
+
+
+4. Detailed Function Call Flow
+-------------------------------
+
+Library (lib/fuse_lowlevel.c):
+fuse_session_mount_new_api()
+   |
+   +-- fuse_kern_mount_prepare()  [lib/mount.c]
+   |      |
+   |      +-- fuse_mount_fusermount()  [lib/mount_util.c]
+   |             |
+   |             +-- socketpair()  [create comm socket]
+   |             |
+   |             +-- fork()
+   |             |
+   |             +-- [child] execl("fusermount3", "--sync-init", ...)
+   |             |
+   |             +-- [parent] receive_fd()  <--- BLOCKS until fd arrives
+   |                    |
+   |                    +-- recvmsg(SCM_RIGHTS)
+   |                    |
+   |                    +-- return fd
+   |
+   +-- session_start_sync_init()  [lib/fuse_lowlevel.c]
+   |      |
+   |      +-- ioctl(fd, FUSE_DEV_IOC_SYNC_INIT)
+   |      |
+   |      +-- pthread_create(worker_thread)
+   |      |
+   |      +-- return
+   |
+   +-- fuse_fusermount_proceed_mnt(socket)  [lib/mount.c] <--- NEW: Bidirectional handshake
+          |
+          +-- send(socket, "proceed", 1)  <--- Signal fusermount3 to proceed
+          |
+          +-- recv(socket, &status, 1)  <--- BLOCKS until mount result arrives
+          |      |
+          |      +-- [fusermount3 performs mount and sends status byte]
+          |
+          +-- if (status != 0) return -1  <--- Mount failed
+          |
+          +-- return 0  <--- Mount succeeded
+
+
+Utility (util/fusermount.c):
+fusermount3 main() with --sync-init
+   |
+   +-- mount_fuse_sync_init()  [util/fusermount.c]
+          |
+          +-- mount_fuse_prepare()  [util/fusermount.c]
+          |      |
+          |      +-- open("/dev/fuse")
+          |      |
+          |      +-- check_perm()  [util/fusermount.c]
+          |      |
+          |      +-- return fd
+          |
+          +-- send_fd(socket, fd)  [util/fusermount.c]
+          |      |
+          |      +-- sendmsg(SCM_RIGHTS)
+          |
+          +-- wait_for_signal(socket)  [util/fusermount.c] <--- BLOCKS until library signals
+          |      |
+          |      +-- recv(socket, buf, 1)
+          |      |
+          |      +-- return 0
+          |
+          +-- mount_fuse_finish_fsmount()  [util/fusermount.c]
+          |      |
+          |      +-- fuse_kern_fsmount()  [lib/mount_fsmount.c]
+          |      |      |
+          |      |      +-- fsopen("fuse", FSOPEN_CLOEXEC)
+          |      |      |      |
+          |      |      |      +-- [kernel creates filesystem context]
+          |      |      |
+          |      |      +-- fsconfig(fsfd, SET_STRING, "source", ...)
+          |      |      +-- fsconfig(fsfd, SET_STRING, "fd", fd_value, ...)
+          |      |      +-- fsconfig(fsfd, ...) [apply mount options]
+          |      |      +-- fsconfig(fsfd, CMD_CREATE, ...)
+          |      |      |
+          |      |      +-- fsmount(fsfd, FSMOUNT_CLOEXEC, mount_attrs)
+          |      |      |      |
+          |      |      |      +-- [kernel sends FUSE_INIT here]
+          |      |      |      |
+          |      |      |      +-- [worker thread processes INIT]
+          |      |      |      |
+          |      |      |      +-- [fsmount returns mntfd]
+          |      |      |
+          |      |      +-- move_mount(mntfd, "", AT_FDCWD, target, ...)
+          |      |      |      |
+          |      |      |      +-- [attach mount to target directory]
+          |      |      |      |
+          |      |      |      +-- [no blocking - INIT already processed]
+          |      |      |
+          |      |      +-- add_mount()  [lib/mount_fsmount.c - update /etc/mtab]
+          |      |      |
+          |      |      +-- return 0 on success, -1 on failure
+          |      |
+          |      +-- if mount failed: return -1
+          |      +-- if mount succeeded: continue
+          |
+          +-- send_status_byte(socket)  [util/fusermount.c] <--- NEW: Send result to library
+          |      |
+          |      +-- status = (mount_result == 0) ? 0 : 1
+          |      +-- send(socket, &status, 1)
+          |      |
+          |      +-- return
+          |
+          +-- return 0
+
+
+Note: The new mount API (fsopen/fsconfig/fsmount/move_mount) is REQUIRED
+      for sync-init because fsmount() triggers FUSE_INIT before the mount
+      is attached. This allows the worker thread to process INIT before
+      move_mount() completes, preventing deadlock.
+      And also so we don't expose the directory tree to the mountns until we
+      know that FUSE_INIT didn't crash the server.
+
diff --git a/doc/README.mount b/doc/README.mount
new file mode 100644
index 0000000000000000000000000000000000000000..526382ad8a5f6b405a7cb1927b79bacd6c2c2c5c
--- /dev/null
+++ b/doc/README.mount
@@ -0,0 +1,86 @@
+FUSE Mount API Flowcharts
+=========================
+
+Old Mount API
+-------------
+
+fuse_kern_mount()
+  |
+  +-- fuse_mount_sys()
+  |     +-- Try direct mount → mount() syscall
+  |     +-- On EPERM: fuse_mount_fusermount()
+  |           +-- socketpair()
+  |           +-- spawn fusermount3 (no --sync-init)
+  |           +-- fusermount3: open /dev/fuse, mount(), send fd
+  |           +-- receive_fd() → return fd
+  |
+  +-- Worker threads started AFTER mount
+  └─> FUSE_INIT asynchronous (queued in kernel)
+
+
+New Mount API - Privileged Mount
+---------------------------------
+
+fuse_session_mount_new_api()
+  |
+  +-- fuse_kern_mount_prepare() → open /dev/fuse → fd
+  |
+  +-- session_start_sync_init(se, fd)
+  |     +-- ioctl(fd, FUSE_DEV_IOC_SYNC_INIT)
+  |     +-- pthread_create(worker) → ready to process FUSE_INIT
+  |
+  +-- fuse_kern_fsmount_mo()
+  |     +-- fsopen/fsconfig/fsmount (BLOCKS until FUSE_INIT completes)
+  |     +-- Worker processes FUSE_INIT during fsmount()
+  |     +-- move_mount()
+  |
+  +-- session_wait_sync_init_completion(se) → pthread_join
+  └─> return fd
+
+
+New Mount API - EPERM Fallback (fusermount3 with sync-init)
+------------------------------------------------------------
+
+fuse_session_mount_new_api()
+  |
+  +-- fuse_kern_mount_prepare() → open /dev/fuse → fd1
+  |
+  +-- session_start_sync_init(se, fd1)
+  |     +-- ioctl(fd1, FUSE_DEV_IOC_SYNC_INIT)
+  |     +-- pthread_create(worker) → ready with fd1
+  |
+  +-- fuse_kern_fsmount_mo() → EPERM
+  |
+  +-- *** FALLBACK TO FUSERMOUNT3 WITH SYNC-INIT ***
+  |
+  +-- session_wait_sync_init_completion(se)
+  |     +-- pthread_cancel/join → terminate worker with wrong fd1
+  |
+  +-- close(fd1)
+  |
+  +-- fuse_mount_fusermount_sync_init()  [NEW]
+  |     +-- socketpair()
+  |     +-- spawn fusermount3 --sync-init
+  |     +-- fusermount3: open /dev/fuse → fd2, send fd2
+  |     +-- receive_fd() → fd2
+  |     +-- fusermount3 waits for signal
+  |     └─> return fd2, sock
+  |
+  +-- session_start_sync_init(se, fd2)
+  |     +-- ioctl(fd2, FUSE_DEV_IOC_SYNC_INIT)
+  |     +-- pthread_create(worker) → ready with fd2
+  |
+  +-- send_proceed_signal(sock)  [NEW]
+  |     +-- send(sock, "\0", 1) → signal fusermount3
+  |
+  +-- fusermount3: mount() (BLOCKS)
+  |     +-- Kernel sends FUSE_INIT to fd2
+  |     +-- Worker processes FUSE_INIT
+  |     +-- mount() returns
+  |
+  +-- close(sock)
+  |
+  +-- session_wait_sync_init_completion(se) → pthread_join
+  |
+  └─> return fd2
+
diff --git a/doc/README.sync-init b/doc/README.sync-init
new file mode 100644
index 0000000000000000000000000000000000000000..44e47a2eef2c45026abaa19562537eef37f256b9
--- /dev/null
+++ b/doc/README.sync-init
@@ -0,0 +1,184 @@
+FUSE Synchronous vs Asynchronous FUSE_INIT
+============================================
+
+This document explains the difference between asynchronous and synchronous
+FUSE_INIT processing, and when each mode is used.
+
+
+Overview
+--------
+
+FUSE_INIT is the initial handshake between the kernel FUSE module and the
+userspace filesystem daemon. During this handshake, the kernel and daemon
+negotiate capabilities, protocol version, and various feature flags.
+
+Asynchronous FUSE_INIT (Traditional Behavior)
+----------------------------------------------
+
+In the traditional asynchronous mode:
+
+1. mount() syscall completes and returns to caller
+2. Filesystem appears mounted to the system
+3. FUSE daemon starts worker threads
+4. Worker threads process FUSE_INIT request
+5. Filesystem becomes fully operational
+
+Timeline:
+    mount() -----> returns
+                   |
+                   v
+            FUSE_INIT sent
+                   |
+                   v
+            daemon processes FUSE_INIT
+                   |
+                   v
+            filesystem ready
+
+Limitations:
+
+1. **No early requests**: The kernel cannot send requests (like getxattr)
+   during the mount() syscall. This breaks SELinux, which needs to query
+   extended attributes on the root inode immediately upon mounting.
+
+2. **Daemonization timing**: With the old fuse_daemonize() API, the daemon
+   must call it AFTER mount, because there's no way to report mount failures
+   to the parent process if daemonization happens first.
+
+3. **No custom root inode**: The root inode ID is hardcoded to FUSE_ROOT_ID (1)
+   because FUSE_INIT hasn't been processed yet when the mount completes.
+
+4. **Thread startup after mount**: io_uring threads and other worker threads
+   can only start after mount() returns, not before.
+
+
+Synchronous FUSE_INIT (New Behavior)
+-------------------------------------
+
+Kernel support: Linux kernel commit dfb84c330794 (v6.18+)
+libfuse support: libfuse 3.19+
+
+In synchronous mode:
+
+1. FUSE daemon opens /dev/fuse
+2. Daemon calls ioctl(fd, FUSE_DEV_IOC_SYNC_INIT)
+3. Daemon starts worker thread
+4. Daemon calls mount() syscall
+5. Kernel sends FUSE_INIT during mount() - mount() blocks
+6. Worker thread processes FUSE_INIT while mount() is blocked
+7. Worker thread may process additional requests (getxattr, etc.)
+8. mount() syscall completes and returns
+9. Filesystem is fully operational
+
+Timeline:
+    open /dev/fuse
+         |
+         v
+    ioctl(FUSE_DEV_IOC_SYNC_INIT)
+         |
+         v
+    start worker thread
+         |
+         v
+    mount() -----> blocks
+         |         |
+         |         v
+         |    FUSE_INIT sent
+         |         |
+         |         v
+         |    worker processes FUSE_INIT
+         |         |
+         |         v
+         |    (possible getxattr, etc.)
+         |         |
+         +-------> returns
+                   |
+                   v
+            filesystem ready
+
+Advantages:
+
+1. **SELinux support**: The kernel can send getxattr requests during mount()
+   to query security labels on the root inode.
+
+2. **Early daemonization**: The daemon can fork BEFORE mount using the new
+   fuse_daemonize_start()/signal() API, and report mount failures to the
+   parent process.
+
+3. **Custom root inode**: The daemon can specify a custom root inode ID
+   during FUSE_INIT, before mount() completes.
+
+4. **Thread startup before mount**: io_uring threads and worker threads
+   start before mount(), ensuring they're ready to handle requests.
+
+5. **Better error reporting**: Mount failures and initialization errors
+   can be properly reported to the parent process when using the new
+   daemonization API.
+
+
+When Synchronous FUSE_INIT is Used
+-----------------------------------
+
+libfuse automatically enables synchronous FUSE_INIT when:
+
+1. The application calls fuse_session_want_sync_init(), OR
+2. The new daemonization API is used (fuse_daemonize_start() was called)
+
+Synchronous FUSE_INIT requires:
+- Kernel support (commit dfb84c330794 or later)
+- Worker thread started before mount()
+- ioctl(FUSE_DEV_IOC_SYNC_INIT) succeeds
+
+If the kernel doesn't support synchronous FUSE_INIT, libfuse automatically
+falls back to asynchronous mode.
+
+
+Implementation Details
+----------------------
+
+The synchronous FUSE_INIT implementation uses a worker thread:
+
+- **session_sync_init_worker()**: Thread function that polls /dev/fuse
+  and processes FUSE_INIT and any subsequent requests until mount completes.
+
+- **session_start_sync_init()**: Creates the worker thread before mount().
+  Calls ioctl(FUSE_DEV_IOC_SYNC_INIT) to enable kernel support.
+
+- **session_wait_sync_init_completion()**: Waits for the worker thread
+  to complete after mount() returns. Checks for errors.
+
+The worker thread processes requests in a loop until se->terminate_mount_worker
+is set, which happens after mount() completes successfully.
+
+
+Compatibility
+-------------
+
+Synchronous FUSE_INIT is fully backward compatible:
+
+- Old kernels: ioctl returns ENOTTY, libfuse falls back to async mode
+- Old applications: Continue to work with async FUSE_INIT
+- New applications on old kernels: Graceful fallback to async mode
+- New applications on new kernels: Automatic sync mode when appropriate
+
+
+Example: Enabling Synchronous FUSE_INIT
+----------------------------------------
+
+Explicit request:
+    struct fuse_session *se = fuse_session_new(...);
+    fuse_session_want_sync_init(se);
+    fuse_session_mount(se, mountpoint);
+
+Automatic (with new daemonization API):
+    fuse_daemonize_start(0);  // Triggers sync init automatically
+    fuse_session_mount(se, mountpoint);
+
+
+See Also
+--------
+
+- doc/README.daemonize - New daemonization API documentation
+- doc/README.fusermount - Synchronous FUSE_INIT protocol with fusermount3
+- doc/README.mount - Mount implementation details
+

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 25/25] Add a background debug option to passthrough hp
  2026-03-26 21:34 [PATCH v2 00/25] libfuse: Add support for synchronous init Bernd Schubert
                   ` (23 preceding siblings ...)
  2026-03-26 21:34 ` [PATCH v2 24/25] Add mount and daemonization README documents Bernd Schubert
@ 2026-03-26 21:34 ` Bernd Schubert
  2026-03-30 19:04   ` Darrick J. Wong
  2026-04-07 12:04 ` fuse-devel list on kernel.org Amir Goldstein
  25 siblings, 1 reply; 56+ messages in thread
From: Bernd Schubert @ 2026-03-26 21:34 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Miklos Szeredi, Joanne Koong, Darrick J. Wong, Kevin Chen,
	Bernd Schubert

Background debugging is useful when logs should go
to syslog for a daemon running in background.

Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
---
 example/passthrough_hp.cc | 41 +++++++++++++++++++++++++----------------
 1 file changed, 25 insertions(+), 16 deletions(-)

diff --git a/example/passthrough_hp.cc b/example/passthrough_hp.cc
index cca865973ab5e8e9c1faa0fa28eb292a172a4812..fa924b89ab3cc42a88767423674a6a190c0aa520 100644
--- a/example/passthrough_hp.cc
+++ b/example/passthrough_hp.cc
@@ -160,7 +160,8 @@ struct Fs {
 	Inode root;
 	double timeout;
 	bool debug;
-	bool debug_fuse;
+	bool debug_fuse; // foreground fuse debug
+	bool bg_debug_fuse; // background fuse debug
 	bool foreground;
 	std::string source;
 	size_t blocksize;
@@ -1480,21 +1481,22 @@ static cxxopts::ParseResult parse_options(int argc, char **argv)
 {
 	cxxopts::Options opt_parser(argv[0]);
 	std::vector<std::string> mount_options;
-	opt_parser.add_options()("debug", "Enable filesystem debug messages")(
-		"debug-fuse", "Enable libfuse debug messages")(
-		"foreground", "Run in foreground")("help", "Print help")(
-		"nocache", "Disable attribute all caching")(
-		"nosplice", "Do not use splice(2) to transfer data")(
-		"nopassthrough", "Do not use pass-through mode for read/write")(
-		"single", "Run single-threaded")(
-		"o",
-		"Mount options (see mount.fuse(5) - only use if you know what "
-		"you are doing)",
-		cxxopts::value(mount_options))(
-		"num-threads", "Number of libfuse worker threads",
-		cxxopts::value<int>()->default_value(SFS_DEFAULT_THREADS))(
-		"clone-fd", "use separate fuse device fd for each thread")(
-		"direct-io", "enable fuse kernel internal direct-io");
+	opt_parser.add_options()
+		("debug", "Enable filesystem debug messages")
+		("debug-fuse", "Enable libfuse debug messages")
+		("bg-debug-fuse", "Enable libfuse debug messages in background")
+		("foreground", "Run in foreground")("help", "Print help")
+		("nocache", "Disable attribute all caching")
+		("nosplice", "Do not use splice(2) to transfer data")
+		("nopassthrough", "Do not use pass-through mode for read/write")
+		("single", "Run single-threaded")
+		("o", "Mount options (see mount.fuse(5) - only use if you know what "
+		      "you are doing)",
+		cxxopts::value(mount_options))
+		("num-threads", "Number of libfuse worker threads",
+		cxxopts::value<int>()->default_value(SFS_DEFAULT_THREADS))
+		("clone-fd", "use separate fuse device fd for each thread")
+		("direct-io", "enable fuse kernel internal direct-io");
 
 	// FIXME: Find a better way to limit the try clause to just
 	// opt_parser.parse() (cf. https://github.com/jarro2783/cxxopts/issues/146)
@@ -1520,6 +1522,7 @@ static cxxopts::ParseResult parse_options(int argc, char **argv)
 
 	fs.debug = options.count("debug") != 0;
 	fs.debug_fuse = options.count("debug-fuse") != 0;
+	fs.bg_debug_fuse = options.count("bg-debug-fuse") != 0;
 
 	fs.foreground = options.count("foreground") != 0;
 	if (fs.debug || fs.debug_fuse)
@@ -1643,6 +1646,12 @@ int main(int argc, char *argv[])
 
 	fuse_loop_cfg_set_clone_fd(loop_config, fs.clone_fd);
 
+	if (!fs.foreground)
+		fuse_log_enable_syslog("passthrough-hp", LOG_PID | LOG_CONS,
+				       LOG_DAEMON);
+	if (fs.bg_debug_fuse)
+		fuse_session_set_debug(se);
+
 	/* Start daemonization before mount so parent can report mount failure */
 	if (fs.foreground)
 		daemon_flags |= FUSE_DAEMONIZE_NO_BACKGROUND;

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 01/25] ci-build: Add environment logging
  2026-03-26 21:34 ` [PATCH v2 01/25] ci-build: Add environment logging Bernd Schubert
@ 2026-03-27  3:20   ` Darrick J. Wong
  0 siblings, 0 replies; 56+ messages in thread
From: Darrick J. Wong @ 2026-03-27  3:20 UTC (permalink / raw)
  To: Bernd Schubert
  Cc: linux-fsdevel, Miklos Szeredi, Joanne Koong, Kevin Chen,
	Bernd Schubert

On Thu, Mar 26, 2026 at 10:34:34PM +0100, Bernd Schubert wrote:
> From: Bernd Schubert <bschubert@ddn.com>
> 
> It is very hard to see what exactly fails, as everything
> is run from the same script.
> 
> Signed-off-by: Bernd Schubert <bschubert@ddn.com>

This seems reasonable to me...
Acked-by: "Darrick J. Wong" <djwong@kernel.org>

--D

> ---
>  test/ci-build.sh | 16 ++++++++++++++++
>  1 file changed, 16 insertions(+)
> 
> diff --git a/test/ci-build.sh b/test/ci-build.sh
> index 8b019a0b5e52c1af0eee510ede21785d584e8414..799f13b2911fac47b0bbb2d445ec22a6f40f065d 100755
> --- a/test/ci-build.sh
> +++ b/test/ci-build.sh
> @@ -30,6 +30,20 @@ export LSAN_OPTIONS="suppressions=$(pwd)/lsan_suppress.txt"
>  export ASAN_OPTIONS="detect_leaks=1"
>  export CC
>  
> +log_env()
> +{
> +    echo "=== Environment ==="
> +    echo "CC: ${CC}"
> +    echo "CXX: ${CXX}"
> +    echo "LSAN_OPTIONS: ${LSAN_OPTIONS}"
> +    echo "ASAN_OPTIONS: ${ASAN_OPTIONS}"
> +    echo "UBSAN_OPTIONS: ${UBSAN_OPTIONS}"
> +    echo "FUSE_URING_ENABLE: ${FUSE_URING_ENABLE}"
> +    echo "FUSE_URING_QUEUE_DEPTH: ${FUSE_URING_QUEUE_DEPTH}"
> +    echo "Valgrind: ${TEST_WITH_VALGRIND}"
> +    echo "==================="
> +}
> +
>  non_sanitized_build()
>  (
>      echo "Standard build (without sanitizers)"
> @@ -54,6 +68,7 @@ non_sanitized_build()
>              build_opts=''
>          fi
>  
> +        log_env
>          meson setup -Dprefix=${PREFIX_DIR} -D werror=true ${build_opts} "${SOURCE_DIR}" || (cat meson-logs/meson-log.txt; false)
>          ninja
>          sudo env PATH=$PATH ninja install
> @@ -79,6 +94,7 @@ sanitized_build()
>  
>      mkdir build-san; pushd build-san
>  
> +    log_env
>      meson setup -Dprefix=${PREFIX_DIR} -D werror=true\
>             "${SOURCE_DIR}" \
>             || (cat meson-logs/meson-log.txt; false)
> 
> -- 
> 2.43.0
> 
> 

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 07/25] Add FUSE_MOUNT_FALLBACK_NEEDED define for -2 mount errors
  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
  0 siblings, 0 replies; 56+ messages in thread
From: Darrick J. Wong @ 2026-03-27  3:20 UTC (permalink / raw)
  To: Bernd Schubert
  Cc: linux-fsdevel, Miklos Szeredi, Joanne Koong, Kevin Chen,
	Bernd Schubert

On Thu, Mar 26, 2026 at 10:34:40PM +0100, Bernd Schubert wrote:
> From: Bernd Schubert <bschubert@ddn.com>
> 
> Magic numbers in the code are not good - we better use a define.
> 
> Signed-off-by: Bernd Schubert <bschubert@ddn.com>

Looks good now!
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>

--D

> ---
>  lib/fuse_i.h | 3 +++
>  lib/mount.c  | 6 +++---
>  2 files changed, 6 insertions(+), 3 deletions(-)
> 
> diff --git a/lib/fuse_i.h b/lib/fuse_i.h
> index 9e3c5dc5021e210a2778e975a37ab609af324010..a2438a15afdd3b5f62dd35bc3514f9f6853f00c3 100644
> --- a/lib/fuse_i.h
> +++ b/lib/fuse_i.h
> @@ -217,6 +217,9 @@ struct fuse_chan *fuse_chan_get(struct fuse_chan *ch);
>   */
>  void fuse_chan_put(struct fuse_chan *ch);
>  
> +/* Special return value for mount functions to indicate fallback to fusermount3 is needed */
> +#define FUSE_MOUNT_FALLBACK_NEEDED (-2)
> +
>  struct mount_opts *parse_mount_opts(struct fuse_args *args);
>  void destroy_mount_opts(struct mount_opts *mo);
>  void fuse_mount_version(void);
> diff --git a/lib/mount.c b/lib/mount.c
> index 6e404451cc9edc8e35434cc31f25612cfc4edca1..dec9d52274c13536648cacef959789f472c5682c 100644
> --- a/lib/mount.c
> +++ b/lib/mount.c
> @@ -563,7 +563,7 @@ out_close:
>   * @mo: mount options
>   * @mnt_opts: mount options to pass to the kernel
>   *
> - * Returns: 0 on success, -1 on failure, -2 if fusermount should be used
> + * 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)
> @@ -611,7 +611,7 @@ static int fuse_kern_do_mount(const char *mnt, struct mount_opts *mo,
>  		 * case try falling back to fusermount3
>  		 */
>  		if (errno == EPERM) {
> -			res = -2;
> +			res = FUSE_MOUNT_FALLBACK_NEEDED;
>  		} else {
>  			int errno_save = errno;
>  			if (mo->blkdev && errno == ENODEV &&
> @@ -749,7 +749,7 @@ int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo)
>  			umount2(mountpoint, MNT_DETACH); /* lazy umount */
>  			res = -1;
>  		}
> -	} else if (res == -2) {
> +	} else if (res == FUSE_MOUNT_FALLBACK_NEEDED) {
>  		if (mo->fusermount_opts &&
>  		    fuse_opt_add_opt(&mnt_opts, mo->fusermount_opts) == -1)
>  			goto out;
> 
> -- 
> 2.43.0
> 
> 

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 09/25] Use asprintf() for fuse_mnt_build_{source,type}
  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
  0 siblings, 1 reply; 56+ messages in thread
From: Darrick J. Wong @ 2026-03-27  3:24 UTC (permalink / raw)
  To: Bernd Schubert
  Cc: linux-fsdevel, Miklos Szeredi, Joanne Koong, Kevin Chen,
	Bernd Schubert

On Thu, Mar 26, 2026 at 10:34:42PM +0100, Bernd Schubert wrote:
> Simplify the code by using asprintf.
> 
> Suggested-by: "Darrick J. Wong" <djwong@kernel.org>
> Signed-off-by: Bernd Schubert <bschubert@ddn.com>
> ---
>  lib/mount.c | 28 +++++++++++++---------------
>  1 file changed, 13 insertions(+), 15 deletions(-)
> 
> diff --git a/lib/mount.c b/lib/mount.c
> index fe353e2cc4579adb47473cac5db7d1bae2defb2c..68f9219d2b8beee51346b198ce826138b9528e73 100644
> --- a/lib/mount.c
> +++ b/lib/mount.c
> @@ -754,32 +754,30 @@ char *fuse_mnt_build_source(const struct mount_opts *mo)
>  {
>  	const char *devname = fuse_mnt_get_devname();
>  	char *source;
> +	int ret;
>  
> -	source = malloc((mo->fsname ? strlen(mo->fsname) : 0) +
> -			(mo->subtype ? strlen(mo->subtype) : 0) +
> -			strlen(devname) + 32);
> -	if (!source)
> +	ret = asprintf(&source, "%s",
> +		       mo->fsname ? mo->fsname :
> +				    (mo->subtype ? mo->subtype : devname));

That works, though I had been expecting something more like:

	if (mo->fsname)
		ret = asprintf(&source, "%s", mo->fsname);
	else if (mo->subtype)
		ret = asprintf(&source, "%s", mo->subtype);
	else
		ret = asprintf(&source, "%s", devname);
	return ret ? NULL : source;

Eh whatever :)
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>

--D

> +	if (ret == -1)
>  		return NULL;
>  
> -	strcpy(source,
> -	       mo->fsname ? mo->fsname : (mo->subtype ? mo->subtype : devname));
> -
>  	return source;
>  }
>  
>  char *fuse_mnt_build_type(const struct mount_opts *mo)
>  {
>  	char *type;
> +	int ret;
>  
> -	type = malloc((mo->subtype ? strlen(mo->subtype) : 0) + 32);
> -	if (!type)
> +	if (mo->subtype)
> +		ret = asprintf(&type, "%s.%s", mo->blkdev ? "fuseblk" : "fuse",
> +			       mo->subtype);
> +	else
> +		ret = asprintf(&type, "%s", mo->blkdev ? "fuseblk" : "fuse");
> +
> +	if (ret == -1)
>  		return NULL;
>  
> -	strcpy(type, mo->blkdev ? "fuseblk" : "fuse");
> -	if (mo->subtype) {
> -		strcat(type, ".");
> -		strcat(type, mo->subtype);
> -	}
> -
>  	return type;
>  }
> 
> -- 
> 2.43.0
> 
> 

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 10/25] lib/mount.c: Remove some BSD ifdefs
  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
  0 siblings, 0 replies; 56+ messages in thread
From: Darrick J. Wong @ 2026-03-27  3:28 UTC (permalink / raw)
  To: Bernd Schubert
  Cc: linux-fsdevel, Miklos Szeredi, Joanne Koong, Kevin Chen,
	Bernd Schubert

On Thu, Mar 26, 2026 at 10:34:43PM +0100, Bernd Schubert wrote:
> BSD has its own mount_bsd.c, mount.c should be linux only.
> 
> Also remove MS_DIRSYNC defintion, which was introduced a long
> time ago - all use cases of recent libfuse should have picked
> it up.

Yeah, kernel 2.5.19 and glibc 2.12 were quite a while ago...

> Some ifdefs stay, as they will be moved in the next patch.
> 
> Suggested-by: "Darrick J. Wong" <djwong@kernel.org>
> Signed-off-by: Bernd Schubert <bschubert@ddn.com>

Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>

--D

> ---
>  lib/mount.c | 29 +++++++++--------------------
>  1 file changed, 9 insertions(+), 20 deletions(-)
> 
> diff --git a/lib/mount.c b/lib/mount.c
> index 68f9219d2b8beee51346b198ce826138b9528e73..899cd2d0a13a9332a564b10d1c22836fb2cd1710 100644
> --- a/lib/mount.c
> +++ b/lib/mount.c
> @@ -43,27 +43,20 @@
>  #define MS_SYNCHRONOUS	MNT_SYNCHRONOUS
>  #define MS_NOATIME	MNT_NOATIME
>  #define MS_NOSYMFOLLOW	MNT_NOSYMFOLLOW
> -
> -#define umount2(mnt, flags) unmount(mnt, (flags == 2) ? MNT_FORCE : 0)
>  #endif
>  
>  #define FUSERMOUNT_PROG		"fusermount3"
>  #define FUSE_COMMFD_ENV		"_FUSE_COMMFD"
>  #define FUSE_COMMFD2_ENV	"_FUSE_COMMFD2"
>  
> -#ifndef MS_DIRSYNC
> -#define MS_DIRSYNC 128
> -#endif
> -
> -enum {
> -	KEY_KERN_FLAG,
> -	KEY_KERN_OPT,
> -	KEY_FUSERMOUNT_OPT,
> -	KEY_SUBTYPE_OPT,
> -	KEY_MTAB_OPT,
> -	KEY_ALLOW_OTHER,
> -	KEY_RO,
> -};
> +	enum { KEY_KERN_FLAG,
> +	       KEY_KERN_OPT,
> +	       KEY_FUSERMOUNT_OPT,
> +	       KEY_SUBTYPE_OPT,
> +	       KEY_MTAB_OPT,
> +	       KEY_ALLOW_OTHER,
> +	       KEY_RO,
> +	};
>  
>  #define FUSE_MOUNT_OPT(t, p) { t, offsetof(struct mount_opts, p), 1 }
>  
> @@ -177,9 +170,7 @@ static const struct mount_flags mount_flags[] = {
>  	{"nostrictatime",   MS_STRICTATIME,	0},
>  	{"symfollow",	    MS_NOSYMFOLLOW,	0},
>  	{"nosymfollow",	    MS_NOSYMFOLLOW,	1},
> -#ifndef __NetBSD__
> -	{"dirsync", MS_DIRSYNC,	    1},
> -#endif
> +	{"dirsync",	    MS_DIRSYNC,	    1},
>  	{NULL,	    0,		    0}
>  };
>  
> @@ -264,8 +255,6 @@ static int receive_fd(int fd)
>  	msg.msg_namelen = 0;
>  	msg.msg_iov = &iov;
>  	msg.msg_iovlen = 1;
> -	/* old BSD implementations should use msg_accrights instead of
> -	 * msg_control; the interface is different. */
>  	msg.msg_control = ccmsg;
>  	msg.msg_controllen = sizeof(ccmsg);
>  
> 
> -- 
> 2.43.0
> 
> 

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 18/25] fusermout: Remove the large read check
  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
  0 siblings, 1 reply; 56+ messages in thread
From: Darrick J. Wong @ 2026-03-27  3:32 UTC (permalink / raw)
  To: Bernd Schubert; +Cc: linux-fsdevel, Miklos Szeredi, Joanne Koong, Kevin Chen

On Thu, Mar 26, 2026 at 10:34:51PM +0100, Bernd Schubert wrote:
> <quote Darrick>
> Nobody's running 2.4 kernels
> anymore, right?
> </quote>
> 
> Suggested-by: "Darrick J. Wong" <djwong@kernel.org>
> Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
> ---
>  util/fusermount.c | 37 +------------------------------------
>  1 file changed, 1 insertion(+), 36 deletions(-)
> 
> diff --git a/util/fusermount.c b/util/fusermount.c
> index 8d7598998788f72ea05c2a065a88cb5efd61df35..254f24a98abf935846cfed754693cc1958c8d2ff 100644
> --- a/util/fusermount.c
> +++ b/util/fusermount.c
> @@ -915,33 +915,6 @@ static void free_mount_params(struct mount_params *mp)
>  	free(mp->mnt_opts);
>  }
>  
> -/*
> - * Check if option is deprecated large_read.
> - *
> - * Returns true if the option should be skipped (large_read on kernel > 2.4),
> - * false otherwise (all other options or large_read on old kernels).
> - */
> -static bool check_large_read(const char *opt, unsigned int len)
> -{
> -	struct utsname utsname;
> -	unsigned int kmaj, kmin;
> -	int res;
> -
> -	if (!opt_eq(opt, len, "large_read"))
> -		return false;
> -
> -	res = uname(&utsname);
> -	if (res == 0 &&
> -	    sscanf(utsname.release, "%u.%u", &kmaj, &kmin) == 2 &&
> -	    (kmaj > 2 || (kmaj == 2 && kmin > 4))) {
> -		fprintf(stderr,
> -			"%s: note: 'large_read' mount option is deprecated for %i.%i kernels\n",
> -			progname, kmaj, kmin);
> -		return true;
> -	}
> -	return false;
> -}
> -
>  /*
>   * Check if user has permission to use allow_other or allow_root options.
>   *
> @@ -1041,19 +1014,11 @@ static int prepare_mount(const char *opts, struct mount_params *mp)
>  			   !begins_with(s, "rootmode=") &&
>  			   !begins_with(s, "user_id=") &&
>  			   !begins_with(s, "group_id=")) {
> -			bool skip;
>  
>  			if (check_allow_permission(s, len) == -1)
>  				goto err;
>  
> -			skip = check_large_read(s, len);
> -
> -			/*
> -			 * Skip deprecated large_read to avoid passing it to
> -			 * kernel which would reject it as unknown option.
> -			 */
> -			if (!skip)
> -				process_generic_option(s, len, &mp->flags, &d);
> +			process_generic_option(s, len, &mp->flags, &d);

Er... we should still ignore large_read, right?  It's been defunct since
2.4, but there could be some script/fstab/whatever that still contains
the mount option.

--D

>  		}
>  		s += len;
>  		if (*s)
> 
> -- 
> 2.43.0
> 
> 

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 21/25] New mount API: Filter out "user="
  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
  0 siblings, 0 replies; 56+ messages in thread
From: Darrick J. Wong @ 2026-03-27  3:32 UTC (permalink / raw)
  To: Bernd Schubert, OM
  Cc: linux-fsdevel, Miklos Szeredi, Joanne Koong, Kevin Chen,
	Bernd Schubert

On Thu, Mar 26, 2026 at 10:34:54PM +0100, Bernd Schubert wrote:
> From: Bernd Schubert <bschubert@ddn.com>
> 
> This gets added in the fusermount process and kernel then fails
> the mount.
> 
> Signed-off-by: Bernd Schubert <bschubert@ddn.com>

Now that I've wrapped my head around the utab-only options I feel more
confident in saying

Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>

--D

> ---
>  lib/mount_fsmount.c | 15 ++++++++++++++-
>  1 file changed, 14 insertions(+), 1 deletion(-)
> 
> diff --git a/lib/mount_fsmount.c b/lib/mount_fsmount.c
> index fbe3647a8923828dba819458b815c21bbd2beaad..f42ae97faedaf2420d97e701a22725d4293e4853 100644
> --- a/lib/mount_fsmount.c
> +++ b/lib/mount_fsmount.c
> @@ -248,6 +248,16 @@ static int is_mount_attr_opt(const char *opt)
>  	       strcmp(opt, "symfollow") == 0;
>  }
>  
> +/**
> + * Check if an option is a mount table option (not passed to fsconfig)
> + */
> +static int is_mtab_only_opt(const char *opt)
> +{
> +	/* These options are for /run/mount/utab only, not for the kernel */
> +	return strncmp(opt, "user=", 5) == 0 ||
> +	       strcmp(opt, "rw") == 0;
> +}
> +
>  /*
>   * Parse kernel options string and apply via fsconfig
>   * Options are comma-separated key=value pairs
> @@ -275,6 +285,7 @@ static int apply_mount_opts(int fsfd, const char *opts)
>  		 * not fsconfig().
>  		 *
>  		 * These string options (nosuid, nodev, etc.) are reconstructed
> +		 * Skip mtab-only options - they're for /run/mount/utab, not kernel
>  		 * from MS_* flags by get_mnt_flag_opts() in lib/mount.c and
>  		 * get_mnt_opts() in util/fusermount.c. Both the library path
>  		 * (via fuse_kern_mount_get_base_mnt_opts) and fusermount3 path
> @@ -282,8 +293,10 @@ static int apply_mount_opts(int fsfd, const char *opts)
>  		 * mnt_opts. They must be filtered here because they are mount
>  		 * attributes (passed to fsmount via MOUNT_ATTR_*), not
>  		 * filesystem parameters (which would be passed to fsconfig).
> +		 *
> +		 * Also skip mtab-only options - they're for /run/mount/utab, not kernel
>  		 */
> -		if (!is_mount_attr_opt(opt)) {
> +		if (!is_mount_attr_opt(opt) && !is_mtab_only_opt(opt)) {
>  			res = apply_opt_key_value(fsfd, opt);
>  			if (res < 0) {
>  				free(opts_copy);
> 
> -- 
> 2.43.0
> 
> 

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 04/25] Add a new daemonize API
  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-30 17:55   ` Darrick J. Wong
  1 sibling, 1 reply; 56+ messages in thread
From: Darrick J. Wong @ 2026-03-27 22:06 UTC (permalink / raw)
  To: Bernd Schubert; +Cc: linux-fsdevel, Miklos Szeredi, Joanne Koong, Kevin Chen

On Thu, Mar 26, 2026 at 10:34:37PM +0100, Bernd Schubert wrote:
> Existing example/ file systems do the fuse_daemonize() after
> fuse_session_mount() - i.e. after the mount point is already
> established. Though, these example/ daemons do not start
> extra threads and do not need network initialization either.

AFAICT the situation here with the extra threads and async FUSE_INIT is:

a) You can't start them until after fuse_daemonize() because that might
   fork the whole process

b) You don't want to start them until you know that the mount()
   succeeds (maybe?)

c) You need those threads to be active to start serving the fuse
   requests that come after FUSE_INIT

d) libfuse apparently starts even more threads to wait on the iouring
   queues after the fuse server returns from FUSE_INIT.

e) fuse_loop_mt() starts up the request handler threads and waits for
   the session to exit and/or for mt_finish to be sem_post()ed.

Does that sound right?

Looking at fuse4fs, I realize that it need /not/ start its background
threads from the FUSE_INIT handler; all that should be done after
daemonize before calling fuse_session_loop_mt.  The only reason I wrote
it that way was blind patterning after fuse2fs, which doesn't call
daemonize() directly, so FUSE_INIT is the first time any fuse2fs code
gets called after daemonizing.

> fuse_daemonize() also does not allow to return notification
> from the forked child to the parent.
> 
> Complex fuse file system daemons often want the order of
> 1) fork - parent watches, child does the work
> 
> Child:
>     2) start extra threads and system initialization (like network
>        connection and RDMA memory registration) from the fork child.
>     3) Start the fuse session after everything else succeeded
> 
> Parent:
>     Report child initialization success or failure

Under classic async FUSE_INIT, the sequence in most fuse servers is:

1) The parent opens /dev/fuse and mounts the fuse filesystem before even
   daemonizing

2) Mounting the fuse fs causes an async FUSE_INIT to be sent to the
   queues, which sits there because nobody's looking for event yet

3) The parent daemonize()s, and the child proceeds with setting signal
   handlers and starting up the fuse-request processing threads

4) The parent exits, the child continues on to set up the fuse worker
   threads

5) One of the request handler threads finally reads /dev/fuse to find
   the FUSE_INIT request and processes it

6) do_init (in the lowlevel fuse library) starts up the uring workers
   if the kernel acknowledges the uring feature.  The fuse server has
   no means to discover if the fusedev would permit uring before
   calling mount().

Does my understanding make sense?

> A new API is introduced to overcome the limitations of
> fuse_daemonize()
> 
> fuse_daemonize_start() - fork, but foreground process does not
> terminate yet and watches the background.

But with sync FUSE_INIT this is not workable because the child has to
have done (4) before (1) can complete, or it has to set up a temporary
request handler thread to process the FUSE_INIT.  That's partly why
fuse_daemonize_start/success/fail() is created here, right?

And the rest of the reason for the new functions is to enable
communication between the parent and child processes -- if one dies
the other can find out about it; and the child can tell the parent the
outcome of mount()ing the filesystem.

I wonder -- if you know that the kernel supports synchronous FUSE_INIT,
can you start the main event handling threadpool (i.e. the one created
in fuse_loop_mt.c) after opening /dev/fuse (obviously) but before
calling mount()?  That would make a hard requirement of having at least
one event handling thread, but you wouldn't have to create this temp
thread just to handle the FUSE_INIT.

Even better the daemonize() changes reduce to just the pipe between
parent and child and watching either for a return value or the POLLERR
when either program fails unexpectedly.

> fuse_daemonize_success() / fuse_daemonize_fail() - background
> daemon signals to the foreground process success or failure.
> 
> fuse_daemonize_active() - helper function for the high level
> interface, which needs to handle both APIs. fuse_daemonize()
> is called within fuse_main(), which now needs to know if the caller
> actually already used the new API itself.
> 
> The object 'struct fuse_daemonize *' is allocated dynamically
> and stored a global variable in fuse_daemonize.c, because
> - high level fuse_main_real_versioned() needs to know
> if already daemonized
> - high level daemons do not have access to struct fuse_session
> - FUSE_SYNC_INIT in later commits can only be done if the new
> API (or a file system internal) daemonization is used.

I don't know exactly what's required to switch libfuse into uring mode.
It look as though you inject -oio_uring as a mount option, then libfuse
sets up the uring, starts some extra workers to handle the ring(?) and
puts them to sleep.  If the kernel says it supports uring in FUSE_INIT
then do_init wakes them up.  Each uring thread submits SQEs and waits
for fuse requests to appear as CQEs, right?

So after a fuse server negotiates with the kernel about iouring, the
background threads started by fuse_loop_mt just sit there in read()
doing nothing else, while new fuse requests get sent to userspace as a
CQE, right?

--D

> Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
> ---
>  example/passthrough_hp.cc   |  18 ++-
>  include/fuse_daemonize.h    |  74 +++++++++++
>  include/meson.build         |   3 +-
>  lib/fuse_daemonize.c        | 292 ++++++++++++++++++++++++++++++++++++++++++++
>  lib/fuse_i.h                |   4 +-
>  lib/fuse_lowlevel.c         |   3 +
>  lib/fuse_versionscript      |   4 +
>  lib/helper.c                |  13 +-
>  lib/meson.build             |   3 +-
>  test/test_want_conversion.c |   1 +
>  10 files changed, 404 insertions(+), 11 deletions(-)
> 
> diff --git a/example/passthrough_hp.cc b/example/passthrough_hp.cc
> index 9f795c5546ee8312e1393c5b8fcebfd77724fb49..bad435077697e8832cf5a5195c17f2f873f2dfe6 100644
> --- a/example/passthrough_hp.cc
> +++ b/example/passthrough_hp.cc
> @@ -55,6 +55,7 @@
>  #include <errno.h>
>  #include <ftw.h>
>  #include <fuse_lowlevel.h>
> +#include <fuse_daemonize.h>
>  #include <inttypes.h>
>  #include <string.h>
>  #include <sys/file.h>
> @@ -243,6 +244,9 @@ 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 */
> +	fuse_daemonize_success();
>  }
>  
>  static void sfs_getattr(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi)
> @@ -1580,6 +1584,7 @@ int main(int argc, char *argv[])
>  {
>  	struct fuse_loop_config *loop_config = NULL;
>  	void *teardown_watchog = NULL;
> +	unsigned int daemon_flags = 0;
>  
>  	// Parse command line options
>  	auto options{ parse_options(argc, argv) };
> @@ -1638,10 +1643,14 @@ int main(int argc, char *argv[])
>  
>  	fuse_loop_cfg_set_clone_fd(loop_config, fs.clone_fd);
>  
> -	if (fuse_session_mount(se, argv[2]) != 0)
> +	/* Start daemonization before mount so parent can report mount failure */
> +	if (fs.foreground)
> +		daemon_flags |= FUSE_DAEMONIZE_NO_BACKGROUND;
> +	if (fuse_daemonize_start(daemon_flags) != 0)
>  		goto err_out3;
>  
> -	fuse_daemonize(fs.foreground);
> +	if (fuse_session_mount(se, argv[2]) != 0)
> +		goto err_out4;
>  
>  	if (!fs.foreground)
>  		fuse_log_enable_syslog("passthrough-hp", LOG_PID | LOG_CONS,
> @@ -1650,7 +1659,7 @@ int main(int argc, char *argv[])
>  	teardown_watchog = fuse_session_start_teardown_watchdog(
>  		se, fs.root.stop_timeout_secs, NULL, NULL);
>  	if (teardown_watchog == NULL)
> -		goto err_out3;
> +		goto err_out4;
>  
>  	if (options.count("single"))
>  		ret = fuse_session_loop(se);
> @@ -1659,6 +1668,9 @@ int main(int argc, char *argv[])
>  
>  	fuse_session_unmount(se);
>  
> +err_out4:
> +	if (fuse_daemonize_is_active())
> +		fuse_daemonize_fail(ret);
>  err_out3:
>  	fuse_remove_signal_handlers(se);
>  err_out2:
> diff --git a/include/fuse_daemonize.h b/include/fuse_daemonize.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..c35dddd668b399535c53b44ab06c65fc0b3ddefa
> --- /dev/null
> +++ b/include/fuse_daemonize.h
> @@ -0,0 +1,74 @@
> +/*
> + * FUSE: Filesystem in Userspace
> + * Copyright (C) 2026 Bernd Schubert <bsbernd.com>
> + *
> + * This program can be distributed under the terms of the GNU LGPLv2.
> + * See the file COPYING.LIB.
> + *
> + */
> +
> +#ifndef FUSE_DAEMONIZE_H_
> +#define FUSE_DAEMONIZE_H_
> +
> +#include <stdint.h>
> +#include <stdbool.h>
> +
> +#ifdef __cplusplus
> +extern "C" {
> +#endif
> +
> +/**
> + * Flags for fuse_daemonize_start()
> + */
> +#define FUSE_DAEMONIZE_NO_CHDIR      (1 << 0)
> +#define FUSE_DAEMONIZE_NO_BACKGROUND (1 << 1)
> +
> +/**
> + * Start daemonization process.
> + *
> + * Unless FUSE_DAEMONIZE_NO_BACKGROUND is set, this forks the process.
> + * The parent waits for a signal from the child via fuse_daemonize_success()
> + * or fuse_daemonize_fail().
> + * The child returns from this call and continues setup.
> + *
> + * Unless FUSE_DAEMONIZE_NO_CHDIR is set, changes directory to "/".
> + *
> + * Must be called before fuse_session_mount().
> + *
> + * @param flags combination of FUSE_DAEMONIZE_* flags
> + * @return 0 on success, negative errno on error
> + */
> +int fuse_daemonize_start(unsigned int flags);
> +
> +/**
> + * Signal daemonization success to parent and cleanup.
> + */
> +void fuse_daemonize_success(void);
> +
> +/**
> + * Signal daemonization failure to parent and cleanup.
> + *
> + * @param err error code to pass to parent
> + */
> +void fuse_daemonize_fail(int err);
> +
> +/**
> + * Check if daemonization is active and waiting for signal.
> + *
> + * @return true if active, false otherwise
> + */
> +bool fuse_daemonize_is_active(void);
> +
> +/**
> + * Set mounted flag.
> + *
> + * Called from fuse_session_mount().
> + */
> +void fuse_daemonize_set_mounted(void);
> +
> +#ifdef __cplusplus
> +}
> +#endif
> +
> +#endif /* FUSE_DAEMONIZE_H_ */
> +
> diff --git a/include/meson.build b/include/meson.build
> index bf671977a5a6a9142bd67aceabd8a919e3d968d0..cfbaf52ac5d84369e92948c631e2fcfdd04ac2eb 100644
> --- a/include/meson.build
> +++ b/include/meson.build
> @@ -1,4 +1,5 @@
>  libfuse_headers = [ 'fuse.h', 'fuse_common.h', 'fuse_lowlevel.h',
> -	            'fuse_opt.h', 'cuse_lowlevel.h', 'fuse_log.h' ]
> +	            'fuse_opt.h', 'cuse_lowlevel.h', 'fuse_log.h',
> +	            'fuse_daemonize.h' ]
>  
>  install_headers(libfuse_headers, subdir: 'fuse3')
> diff --git a/lib/fuse_daemonize.c b/lib/fuse_daemonize.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..865acad7db56dbe5ed8a1bee52e7353627e89b75
> --- /dev/null
> +++ b/lib/fuse_daemonize.c
> @@ -0,0 +1,292 @@
> +/*
> + * FUSE: Filesystem in Userspace
> + * Copyright (C) 2026 Bernd Schubert <bsbernd.com>
> + *
> + * This program can be distributed under the terms of the GNU LGPLv2.
> + * See the file COPYING.LIB.
> + */
> +
> +#define _GNU_SOURCE
> +
> +#include "fuse_daemonize.h"
> +
> +#include <fcntl.h>
> +#include <poll.h>
> +#include <pthread.h>
> +#include <stdatomic.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <sys/types.h>
> +#include <unistd.h>
> +#include <stdbool.h>
> +#include <errno.h>
> +
> +/**
> + * Status values for fuse_daemonize_success() and fuse_daemonize_fail()
> + */
> +#define FUSE_DAEMONIZE_SUCCESS 0
> +#define FUSE_DAEMONIZE_FAILURE 1
> +
> +/* Private/internal data  */
> +struct fuse_daemonize {
> +	unsigned int flags;
> +	int signal_pipe_wr;	/* write end for signaling parent */
> +	int death_pipe_rd;	/* read end, POLLHUP when parent dies */
> +	int stop_pipe_rd;	/* read end for stop signal */
> +	int stop_pipe_wr;	/* write end for stop signal */
> +	pthread_t watcher;
> +	bool watcher_started;
> +	_Atomic bool active;
> +	_Atomic bool daemonized;
> +	_Atomic bool mounted;
> +};
> +
> +/* Global daemonization object pointer */
> +static struct fuse_daemonize daemonize = {
> +	.signal_pipe_wr = -1,
> +	.death_pipe_rd = -1,
> +	.stop_pipe_rd = -1,
> +	.stop_pipe_wr = -1,
> +};
> +
> +/* Watcher thread: polls for parent death or stop signal */
> +static void *parent_watcher_thread(void *arg)
> +{
> +	struct fuse_daemonize *di = arg;
> +	struct pollfd pfd[2];
> +
> +	pfd[0].fd = di->death_pipe_rd;
> +	pfd[0].events = POLLIN;
> +	pfd[1].fd = di->stop_pipe_rd;
> +	pfd[1].events = POLLIN;
> +
> +	while (1) {
> +		int rc = poll(pfd, 2, -1);
> +
> +		if (rc < 0)
> +			continue;
> +
> +		/* Parent died - death pipe write end closed */
> +		if (pfd[0].revents & (POLLHUP | POLLERR))
> +			_exit(EXIT_FAILURE);
> +
> +		/* Stop signal received */
> +		if (pfd[1].revents & POLLIN)
> +			break;
> +	}
> +	return NULL;
> +}
> +
> +static int start_parent_watcher(struct fuse_daemonize *daemonize)
> +{
> +	int rc;
> +
> +	rc = pthread_create(&daemonize->watcher, NULL, parent_watcher_thread,
> +			    daemonize);
> +	if (rc != 0) {
> +		fprintf(stderr, "fuse_daemonize: pthread_create: %s\n",
> +			strerror(rc));
> +		return -rc;
> +	}
> +	daemonize->watcher_started = true;
> +	return 0;
> +}
> +
> +static void stop_parent_watcher(struct fuse_daemonize *daemonize)
> +{
> +	char byte = 0;
> +
> +	if (daemonize && daemonize->watcher_started) {
> +		/* Signal watcher to stop */
> +		if (write(daemonize->stop_pipe_wr, &byte, 1) != 1)
> +			perror("fuse_daemonize: stop write");
> +		pthread_join(daemonize->watcher, NULL);
> +		daemonize->watcher_started = false;
> +	}
> +}
> +
> +static int daemonize_child(struct fuse_daemonize *daemonize)
> +{
> +	int stop_pipe[2], err = 0;
> +
> +	if (pipe(stop_pipe) == -1) {
> +		err = -errno;
> +		perror("fuse_daemonize_start: stop pipe");
> +		return err;
> +	}
> +	daemonize->stop_pipe_rd = stop_pipe[0];
> +	daemonize->stop_pipe_wr = stop_pipe[1];
> +
> +	if (setsid() == -1) {
> +		err = -errno;
> +		perror("fuse_daemonize_start: setsid");
> +		goto err_close_stop;
> +	}
> +
> +	/* Close stdin immediately */
> +	int nullfd = open("/dev/null", O_RDWR, 0);
> +
> +	if (nullfd != -1) {
> +		(void)dup2(nullfd, 0);
> +		if (nullfd > 0)
> +			close(nullfd);
> +	}
> +
> +	/* Start watcher thread to detect parent death */
> +	err = start_parent_watcher(daemonize);
> +	if (err)
> +		goto err_close_stop;
> +
> +	daemonize->daemonized = true;
> +	return 0;
> +
> +err_close_stop:
> +	close(daemonize->stop_pipe_rd);
> +	close(daemonize->stop_pipe_wr);
> +	return err;
> +}
> +
> +/* Fork and daemonize. Returns 0 in child, never returns in parent. */
> +static int do_daemonize(struct fuse_daemonize *daemonize)
> +{
> +	int signal_pipe[2], death_pipe[2], err;
> +
> +	if (pipe(signal_pipe) == -1) {
> +		err = -errno;
> +		perror("fuse_daemonize_start: signal pipe");
> +		return err;
> +	}
> +
> +	if (pipe(death_pipe) == -1) {
> +		err = -errno;
> +		perror("fuse_daemonize_start: death pipe");
> +		close(signal_pipe[0]);
> +		close(signal_pipe[1]);
> +		return err;
> +	}
> +
> +	switch (fork()) {
> +	case -1:
> +		err = -errno;
> +		perror("fuse_daemonize_start: fork");
> +		close(signal_pipe[0]);
> +		close(signal_pipe[1]);
> +		close(death_pipe[0]);
> +		close(death_pipe[1]);
> +		return err;
> +
> +	case 0:
> +		/* Child: signal write end, death read end */
> +		close(signal_pipe[0]);
> +		close(death_pipe[1]);
> +		daemonize->signal_pipe_wr = signal_pipe[1];
> +		daemonize->death_pipe_rd = death_pipe[0];
> +		return daemonize_child(daemonize);
> +
> +	default: {
> +		/* Parent: signal read end, death write end (kept open) */
> +		int status;
> +		ssize_t res;
> +
> +		close(signal_pipe[1]);
> +		close(death_pipe[0]);
> +
> +		res = read(signal_pipe[0], &status, sizeof(status));
> +		close(signal_pipe[0]);
> +		close(death_pipe[1]);
> +
> +		if (res != sizeof(status))
> +			_exit(EXIT_FAILURE);
> +		_exit(status);
> +	}
> +	}
> +}
> +
> +int fuse_daemonize_start(unsigned int flags)
> +{
> +	struct fuse_daemonize *dm = &daemonize;
> +	int err = 0;
> +
> +	dm->flags = flags;
> +	dm->signal_pipe_wr = -1;
> +	dm->death_pipe_rd = -1;
> +	dm->stop_pipe_rd = -1;
> +	dm->stop_pipe_wr = -1;
> +	dm->active = true;
> +
> +	if (!(flags & FUSE_DAEMONIZE_NO_CHDIR))
> +		(void)chdir("/");
> +
> +	if (!(flags & FUSE_DAEMONIZE_NO_BACKGROUND))
> +		err = do_daemonize(dm);
> +
> +	return err;
> +}
> +
> +static void close_if_valid(int *fd)
> +{
> +	if (*fd != -1) {
> +		close(*fd);
> +		*fd = -1;
> +	}
> +}
> +
> +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");
> +
> +	dm->active = false;
> +
> +	/* Stop watcher before signaling - parent will exit after this */
> +	stop_parent_watcher(dm);
> +
> +	/* Signal status to parent */
> +	if (dm->signal_pipe_wr != -1) {
> +		rc = write(dm->signal_pipe_wr, &status, sizeof(status));
> +		if (rc != sizeof(status))
> +			fprintf(stderr, "%s: write failed\n", __func__);
> +	}
> +
> +	/* Redirect stdout/stderr to /dev/null on success */
> +	if (status == FUSE_DAEMONIZE_SUCCESS && dm->daemonized) {
> +		int nullfd = open("/dev/null", O_RDWR, 0);
> +
> +		if (nullfd != -1) {
> +			(void)dup2(nullfd, 1);
> +			(void)dup2(nullfd, 2);
> +			if (nullfd > 2)
> +				close(nullfd);
> +		}
> +	}
> +
> +	close_if_valid(&dm->signal_pipe_wr);
> +	close_if_valid(&dm->death_pipe_rd);
> +	close_if_valid(&dm->stop_pipe_rd);
> +	close_if_valid(&dm->stop_pipe_wr);
> +}
> +
> +void fuse_daemonize_success(void)
> +{
> +	fuse_daemonize_signal(FUSE_DAEMONIZE_SUCCESS);
> +}
> +
> +void fuse_daemonize_fail(int err)
> +{
> +	fuse_daemonize_signal(err);
> +}
> +
> +bool fuse_daemonize_is_active(void)
> +{
> +	return daemonize.daemonized || daemonize.active;
> +}
> +
> +void fuse_daemonize_set_mounted(void)
> +{
> +	daemonize.mounted = true;
> +}
> diff --git a/lib/fuse_i.h b/lib/fuse_i.h
> index 65d2f68f7f30918a3c3ee4d473796cb013428a8f..9e3c5dc5021e210a2778e975a37ab609af324010 100644
> --- a/lib/fuse_i.h
> +++ b/lib/fuse_i.h
> @@ -17,7 +17,6 @@
>  #include <semaphore.h>
>  #include <stdint.h>
>  #include <stdbool.h>
> -#include <errno.h>
>  #include <stdatomic.h>
>  
>  #define MIN(a, b) \
> @@ -110,6 +109,9 @@ struct fuse_session {
>  	/* true if reading requests from /dev/fuse are handled internally */
>  	bool buf_reallocable;
>  
> +	/* synchronous FUSE_INIT support */
> +	bool want_sync_init;
> +
>  	/* io_uring */
>  	struct fuse_session_uring uring;
>  
> diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c
> index 3234f0ce3b246a4c2c40dc0757177de91b6608b2..ccff6a768f0b8c32469abda9405ff29623f3fff7 100644
> --- a/lib/fuse_lowlevel.c
> +++ b/lib/fuse_lowlevel.c
> @@ -19,6 +19,7 @@
>  #include "mount_util.h"
>  #include "util.h"
>  #include "fuse_uring_i.h"
> +#include "fuse_daemonize.h"
>  
>  #include <pthread.h>
>  #include <stdatomic.h>
> @@ -4451,6 +4452,8 @@ int fuse_session_mount(struct fuse_session *se, const char *_mountpoint)
>  	/* Save mountpoint */
>  	se->mountpoint = mountpoint;
>  
> +	fuse_daemonize_set_mounted();
> +
>  	return 0;
>  
>  error_out:
> diff --git a/lib/fuse_versionscript b/lib/fuse_versionscript
> index cce09610316f4b0b1d6836dd0e63686342b70037..dc6ed0135fb8d82937c756c3fb04a7fcb48fe1f4 100644
> --- a/lib/fuse_versionscript
> +++ b/lib/fuse_versionscript
> @@ -227,6 +227,10 @@ FUSE_3.19 {
>  		fuse_session_start_teardown_watchdog;
>  		fuse_session_stop_teardown_watchdog;
>  		fuse_lowlevel_notify_prune;
> +		fuse_daemonize_start;
> +		fuse_daemonize_success;
> +		fuse_daemonize_fail;
> +		fuse_daemonize_is_active;
>  } FUSE_3.18;
>  
>  # Local Variables:
> diff --git a/lib/helper.c b/lib/helper.c
> index 5c13b93a473181f027eba01e0bfefd78875ede3e..35285be19aa0cc390a432d16701b9eefa16ec12a 100644
> --- a/lib/helper.c
> +++ b/lib/helper.c
> @@ -15,6 +15,7 @@
>  #include "fuse_misc.h"
>  #include "fuse_opt.h"
>  #include "fuse_lowlevel.h"
> +#include "fuse_daemonize.h"
>  #include "mount_util.h"
>  
>  #include <stdio.h>
> @@ -352,17 +353,19 @@ int fuse_main_real_versioned(int argc, char *argv[],
>  		goto out1;
>  	}
>  
> +	struct fuse_session *se = fuse_get_session(fuse);
>  	if (fuse_mount(fuse,opts.mountpoint) != 0) {
>  		res = 4;
>  		goto out2;
>  	}
>  
> -	if (fuse_daemonize(opts.foreground) != 0) {
> -		res = 5;
> -		goto out3;
> +	if (!fuse_daemonize_is_active()) {
> +		/* Avoid daemonizing if we are already daemonized by the newer API */
> +		if (fuse_daemonize(opts.foreground) != 0) {
> +			res = 5;
> +			goto out3;
> +		}
>  	}
> -
> -	struct fuse_session *se = fuse_get_session(fuse);
>  	if (fuse_set_signal_handlers(se) != 0) {
>  		res = 6;
>  		goto out3;
> diff --git a/lib/meson.build b/lib/meson.build
> index fcd95741c9d3748fa01d9ec52b417aca66745f26..5bd449ebffe7c9229df904d647d990c6c47f80b5 100644
> --- a/lib/meson.build
> +++ b/lib/meson.build
> @@ -2,7 +2,8 @@ libfuse_sources = ['fuse.c', 'fuse_i.h', 'fuse_loop.c', 'fuse_loop_mt.c',
>                     'fuse_lowlevel.c', 'fuse_misc.h', 'fuse_opt.c',
>                     'fuse_signals.c', 'buffer.c', 'cuse_lowlevel.c',
>                     'helper.c', 'modules/subdir.c', 'mount_util.c',
> -                   'fuse_log.c', 'compat.c', 'util.c', 'util.h' ]
> +                   'fuse_log.c', 'compat.c', 'util.c', 'util.h',
> +                   'fuse_daemonize.c' ]
>  
>  if host_machine.system().startswith('linux')
>     libfuse_sources += [ 'mount.c' ]
> diff --git a/test/test_want_conversion.c b/test/test_want_conversion.c
> index db731edbfe1be8230ae16b422f798603b4a3bb82..48e6dd2dc6084425a0462bba000563c6083160be 100644
> --- a/test/test_want_conversion.c
> +++ b/test/test_want_conversion.c
> @@ -8,6 +8,7 @@
>  #include <inttypes.h>
>  #include <stdbool.h>
>  #include <err.h>
> +#include <errno.h>
>  
>  static void print_conn_info(const char *prefix, struct fuse_conn_info *conn)
>  {
> 
> -- 
> 2.43.0
> 
> 

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 04/25] Add a new daemonize API
  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
  0 siblings, 2 replies; 56+ messages in thread
From: Bernd Schubert @ 2026-03-27 23:07 UTC (permalink / raw)
  To: Darrick J. Wong; +Cc: linux-fsdevel, Miklos Szeredi, Joanne Koong, Kevin Chen

Hi Darrick,

On 3/27/26 23:06, Darrick J. Wong wrote:
> On Thu, Mar 26, 2026 at 10:34:37PM +0100, Bernd Schubert wrote:
>> Existing example/ file systems do the fuse_daemonize() after
>> fuse_session_mount() - i.e. after the mount point is already
>> established. Though, these example/ daemons do not start
>> extra threads and do not need network initialization either.
> 
> AFAICT the situation here with the extra threads and async FUSE_INIT is:
> 
> a) You can't start them until after fuse_daemonize() because that might
>    fork the whole process

The extra threads are not cloned - they stay attached to the parent.

> 
> b) You don't want to start them until you know that the mount()
>    succeeds (maybe?)

For me the other way around, I want to start them before the mount and
let them do things like network connection/authorization. Not nice if
the mount has already suceeded and then part of the initialization fails
and a stale mount come up.

> 
> c) You need those threads to be active to start serving the fuse
>    requests that come after FUSE_INIT
> 
> d) libfuse apparently starts even more threads to wait on the iouring
>    queues after the fuse server returns from FUSE_INIT.

Actually it starts these threads from FUSE_INIT and before replying
FUSE_INIT success. In order to tell the fuse-client/kernel if
fuse-server wants io-uring.


> 
> e) fuse_loop_mt() starts up the request handler threads and waits for
>    the session to exit and/or for mt_finish to be sem_post()ed.
> 
> Does that sound right?


> 
> Looking at fuse4fs, I realize that it need /not/ start its background
> threads from the FUSE_INIT handler; all that should be done after
> daemonize before calling fuse_session_loop_mt.  The only reason I wrote

So after the mount? What is if starting the threads would fail for some
reason?

> it that way was blind patterning after fuse2fs, which doesn't call
> daemonize() directly, so FUSE_INIT is the first time any fuse2fs code
> gets called after daemonizing.
> 
>> fuse_daemonize() also does not allow to return notification
>> from the forked child to the parent.
>>
>> Complex fuse file system daemons often want the order of
>> 1) fork - parent watches, child does the work
>>
>> Child:
>>     2) start extra threads and system initialization (like network
>>        connection and RDMA memory registration) from the fork child.
>>     3) Start the fuse session after everything else succeeded
>>
>> Parent:
>>     Report child initialization success or failure
> 
> Under classic async FUSE_INIT, the sequence in most fuse servers is:
> 
> 1) The parent opens /dev/fuse and mounts the fuse filesystem before even
>    daemonizing
> 
> 2) Mounting the fuse fs causes an async FUSE_INIT to be sent to the
>    queues, which sits there because nobody's looking for event yet
> 
> 3) The parent daemonize()s, and the child proceeds with setting signal
>    handlers and starting up the fuse-request processing threads
> 
> 4) The parent exits, the child continues on to set up the fuse worker
>    threads
> 
> 5) One of the request handler threads finally reads /dev/fuse to find
>    the FUSE_INIT request and processes it
> 
> 6) do_init (in the lowlevel fuse library) starts up the uring workers
>    if the kernel acknowledges the uring feature.  The fuse server has
>    no means to discover if the fusedev would permit uring before
>    calling mount().

Yeah, initially I wanted to handle that through an ioctl and independent
of FUSE_INIT, Miklos had asked to change that.

> 
> Does my understanding make sense?
> 
>> A new API is introduced to overcome the limitations of
>> fuse_daemonize()
>>
>> fuse_daemonize_start() - fork, but foreground process does not
>> terminate yet and watches the background.
> 
> But with sync FUSE_INIT this is not workable because the child has to
> have done (4) before (1) can complete, or it has to set up a temporary
> request handler thread to process the FUSE_INIT.  That's partly why
> fuse_daemonize_start/success/fail() is created here, right?

I have written two similar APIs for two different daemons at DDN (one in
C and the other in C++), independent of sync FUSE_INIT. For us it is
important to only create the mount point when the network connection
works - that is done by threads not under control from libfuse. With
network you want to see something like "authorization failure" or "host
not reachable" in foreground.

With sync FUSE_INIT the additional issue of
FUSE_INIT-creates-the-io-uring ring thread came up - even the
<libfuse>/example/* file systems all don't work, because now
fuse_session_mount() creates the ring threads, then comes
fuse_daemonize() - parent exits - ring threads gone.

> 
> And the rest of the reason for the new functions is to enable
> communication between the parent and child processes -- if one dies
> the other can find out about it; and the child can tell the parent the
> outcome of mount()ing the filesystem.
> 
> I wonder -- if you know that the kernel supports synchronous FUSE_INIT,
> can you start the main event handling threadpool (i.e. the one created
> in fuse_loop_mt.c) after opening /dev/fuse (obviously) but before
> calling mount()?  That would make a hard requirement of having at least
> one event handling thread, but you wouldn't have to create this temp
> thread just to handle the FUSE_INIT.

That would work and I believe it would be feasible for libfuse examples,
but what about all the other real file systems out there? They would
need to be rewritten to get sync INIT? And how many people understand
the difference between sync and async init? Is this temp thread causing
issues?

> 
> Even better the daemonize() changes reduce to just the pipe between
> parent and child and watching either for a return value or the POLLERR
> when either program fails unexpectedly.

We also need to send a signal to the parent that child has success. How
many pipes we use is an internal detail? If we find a better way later
to reduce the number of pipes, even better.

> 
>> fuse_daemonize_success() / fuse_daemonize_fail() - background
>> daemon signals to the foreground process success or failure.
>>
>> fuse_daemonize_active() - helper function for the high level
>> interface, which needs to handle both APIs. fuse_daemonize()
>> is called within fuse_main(), which now needs to know if the caller
>> actually already used the new API itself.
>>
>> The object 'struct fuse_daemonize *' is allocated dynamically
>> and stored a global variable in fuse_daemonize.c, because
>> - high level fuse_main_real_versioned() needs to know
>> if already daemonized
>> - high level daemons do not have access to struct fuse_session
>> - FUSE_SYNC_INIT in later commits can only be done if the new
>> API (or a file system internal) daemonization is used.
> 
> I don't know exactly what's required to switch libfuse into uring mode.
> It look as though you inject -oio_uring as a mount option, then libfuse
> sets up the uring, starts some extra workers to handle the ring(?) and
> puts them to sleep.  If the kernel says it supports uring in FUSE_INIT
> then do_init wakes them up.  Each uring thread submits SQEs and waits
> for fuse requests to appear as CQEs, right?

Yeah, we can also make that later on a default, without the -oio_uring
option (there is also env for ci test purposes to enforce io-uring
mode). Right now it starts too many threads (got distracted today and
didn't send the new version of ring reduction patches - will try
tomorrow). And I also expected bugs in all that new code, so I didn't
want to make it a default.

> 
> So after a fuse server negotiates with the kernel about iouring, the
> background threads started by fuse_loop_mt just sit there in read()
> doing nothing else, while new fuse requests get sent to userspace as a
> CQE, right?

Exactly.


My question is now, are you ok with the new fuse_daemonize API? It
sounds a bit like you don't like it too much.


Thanks,
Bernd

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 04/25] Add a new daemonize API
  2026-03-27 23:07     ` Bernd Schubert
@ 2026-03-28  4:01       ` Darrick J. Wong
  2026-03-30 17:45       ` Darrick J. Wong
  1 sibling, 0 replies; 56+ messages in thread
From: Darrick J. Wong @ 2026-03-28  4:01 UTC (permalink / raw)
  To: Bernd Schubert; +Cc: linux-fsdevel, Miklos Szeredi, Joanne Koong, Kevin Chen

On Sat, Mar 28, 2026 at 12:07:35AM +0100, Bernd Schubert wrote:
> Hi Darrick,

I'll send a complete reply on Monday, (it's late Friday night now) so
for now <snip>...

> My question is now, are you ok with the new fuse_daemonize API? It
> sounds a bit like you don't like it too much.

...but I do want to clear up any miscommunication: at this point I don't
know enough about the startup process in libfuse to have any strong
feelings about the new daemonize API.  In thinking about the new API, I
had to check my understanding of how things work currently and the
requirements of synchronous FUSE_INIT, in order to play around with your
proposed solution and to figure out if there were other ways to solve
it.

I asked a lot of questions because of my own unfamiliarity with the
subject, and I'm sorry that it came off more aggressively than I meant.
No hard feelings, I hope! :)

--D

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 18/25] fusermout: Remove the large read check
  2026-03-27  3:32   ` Darrick J. Wong
@ 2026-03-30 15:26     ` Bernd Schubert
  2026-03-30 17:57       ` Darrick J. Wong
  0 siblings, 1 reply; 56+ messages in thread
From: Bernd Schubert @ 2026-03-30 15:26 UTC (permalink / raw)
  To: Darrick J. Wong; +Cc: linux-fsdevel, Miklos Szeredi, Joanne Koong, Kevin Chen



On 3/27/26 04:32, Darrick J. Wong wrote:
> On Thu, Mar 26, 2026 at 10:34:51PM +0100, Bernd Schubert wrote:
>> <quote Darrick>
>> Nobody's running 2.4 kernels
>> anymore, right?
>> </quote>
>>
>> Suggested-by: "Darrick J. Wong" <djwong@kernel.org>
>> Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
>> ---
>>  util/fusermount.c | 37 +------------------------------------
>>  1 file changed, 1 insertion(+), 36 deletions(-)
>>
>> diff --git a/util/fusermount.c b/util/fusermount.c
>> index 8d7598998788f72ea05c2a065a88cb5efd61df35..254f24a98abf935846cfed754693cc1958c8d2ff 100644
>> --- a/util/fusermount.c
>> +++ b/util/fusermount.c
>> @@ -915,33 +915,6 @@ static void free_mount_params(struct mount_params *mp)
>>  	free(mp->mnt_opts);
>>  }
>>  
>> -/*
>> - * Check if option is deprecated large_read.
>> - *
>> - * Returns true if the option should be skipped (large_read on kernel > 2.4),
>> - * false otherwise (all other options or large_read on old kernels).
>> - */
>> -static bool check_large_read(const char *opt, unsigned int len)
>> -{
>> -	struct utsname utsname;
>> -	unsigned int kmaj, kmin;
>> -	int res;
>> -
>> -	if (!opt_eq(opt, len, "large_read"))
>> -		return false;
>> -
>> -	res = uname(&utsname);
>> -	if (res == 0 &&
>> -	    sscanf(utsname.release, "%u.%u", &kmaj, &kmin) == 2 &&
>> -	    (kmaj > 2 || (kmaj == 2 && kmin > 4))) {
>> -		fprintf(stderr,
>> -			"%s: note: 'large_read' mount option is deprecated for %i.%i kernels\n",
>> -			progname, kmaj, kmin);
>> -		return true;
>> -	}
>> -	return false;
>> -}
>> -
>>  /*
>>   * Check if user has permission to use allow_other or allow_root options.
>>   *
>> @@ -1041,19 +1014,11 @@ static int prepare_mount(const char *opts, struct mount_params *mp)
>>  			   !begins_with(s, "rootmode=") &&
>>  			   !begins_with(s, "user_id=") &&
>>  			   !begins_with(s, "group_id=")) {
>> -			bool skip;
>>  
>>  			if (check_allow_permission(s, len) == -1)
>>  				goto err;
>>  
>> -			skip = check_large_read(s, len);
>> -
>> -			/*
>> -			 * Skip deprecated large_read to avoid passing it to
>> -			 * kernel which would reject it as unknown option.
>> -			 */
>> -			if (!skip)
>> -				process_generic_option(s, len, &mp->flags, &d);
>> +			process_generic_option(s, len, &mp->flags, &d);
> 
> Er... we should still ignore large_read, right?  It's been defunct since
> 2.4, but there could be some script/fstab/whatever that still contains
> the mount option.

Dunno, I'm more tempted to remove it and to filter it out if someone
ever complains. 2.4 was a long time ago.


Thanks,
Bernd

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 09/25] Use asprintf() for fuse_mnt_build_{source,type}
  2026-03-27  3:24   ` Darrick J. Wong
@ 2026-03-30 15:34     ` Bernd Schubert
  0 siblings, 0 replies; 56+ messages in thread
From: Bernd Schubert @ 2026-03-30 15:34 UTC (permalink / raw)
  To: Darrick J. Wong
  Cc: linux-fsdevel, Miklos Szeredi, Joanne Koong, Kevin Chen,
	Bernd Schubert



On 3/27/26 04:24, Darrick J. Wong wrote:
> On Thu, Mar 26, 2026 at 10:34:42PM +0100, Bernd Schubert wrote:
>> Simplify the code by using asprintf.
>>
>> Suggested-by: "Darrick J. Wong" <djwong@kernel.org>
>> Signed-off-by: Bernd Schubert <bschubert@ddn.com>
>> ---
>>  lib/mount.c | 28 +++++++++++++---------------
>>  1 file changed, 13 insertions(+), 15 deletions(-)
>>
>> diff --git a/lib/mount.c b/lib/mount.c
>> index fe353e2cc4579adb47473cac5db7d1bae2defb2c..68f9219d2b8beee51346b198ce826138b9528e73 100644
>> --- a/lib/mount.c
>> +++ b/lib/mount.c
>> @@ -754,32 +754,30 @@ char *fuse_mnt_build_source(const struct mount_opts *mo)
>>  {
>>  	const char *devname = fuse_mnt_get_devname();
>>  	char *source;
>> +	int ret;
>>  
>> -	source = malloc((mo->fsname ? strlen(mo->fsname) : 0) +
>> -			(mo->subtype ? strlen(mo->subtype) : 0) +
>> -			strlen(devname) + 32);
>> -	if (!source)
>> +	ret = asprintf(&source, "%s",
>> +		       mo->fsname ? mo->fsname :
>> +				    (mo->subtype ? mo->subtype : devname));
> 
> That works, though I had been expecting something more like:
> 
> 	if (mo->fsname)
> 		ret = asprintf(&source, "%s", mo->fsname);
> 	else if (mo->subtype)
> 		ret = asprintf(&source, "%s", mo->subtype);
> 	else
> 		ret = asprintf(&source, "%s", devname);
> 	return ret ? NULL : source;

Also fine with me, I changed to that version and added your Reviewed-by.


Thanks,
Bernd

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 04/25] Add a new daemonize API
  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
  1 sibling, 1 reply; 56+ messages in thread
From: Darrick J. Wong @ 2026-03-30 17:45 UTC (permalink / raw)
  To: Bernd Schubert; +Cc: linux-fsdevel, Miklos Szeredi, Joanne Koong, Kevin Chen

On Sat, Mar 28, 2026 at 12:07:35AM +0100, Bernd Schubert wrote:
> Hi Darrick,
> 
> On 3/27/26 23:06, Darrick J. Wong wrote:
> > On Thu, Mar 26, 2026 at 10:34:37PM +0100, Bernd Schubert wrote:
> >> Existing example/ file systems do the fuse_daemonize() after
> >> fuse_session_mount() - i.e. after the mount point is already
> >> established. Though, these example/ daemons do not start
> >> extra threads and do not need network initialization either.
> > 
> > AFAICT the situation here with the extra threads and async FUSE_INIT is:
> > 
> > a) You can't start them until after fuse_daemonize() because that might
> >    fork the whole process
> 
> The extra threads are not cloned - they stay attached to the parent.

<nod>  I guess I should have said that explicitly -- "...because that
might fork the whole process, and all threads remain attached to the
parent."

> > 
> > b) You don't want to start them until you know that the mount()
> >    succeeds (maybe?)
> 
> For me the other way around, I want to start them before the mount and
> let them do things like network connection/authorization. Not nice if
> the mount has already suceeded and then part of the initialization fails
> and a stale mount come up.

<nod> That makes sense, you want to acquire resources and start the
threads (or fail) before calling mount().

> > c) You need those threads to be active to start serving the fuse
> >    requests that come after FUSE_INIT
> > 
> > d) libfuse apparently starts even more threads to wait on the iouring
> >    queues after the fuse server returns from FUSE_INIT.
> 
> Actually it starts these threads from FUSE_INIT and before replying
> FUSE_INIT success. In order to tell the fuse-client/kernel if
> fuse-server wants io-uring.

Got it.  Now I see the "XXX: Add an option to make non-available
io-uring fatal" code 20 lines up.

> > e) fuse_loop_mt() starts up the request handler threads and waits for
> >    the session to exit and/or for mt_finish to be sem_post()ed.
> > 
> > Does that sound right?
> 
> 
> > 
> > Looking at fuse4fs, I realize that it need /not/ start its background
> > threads from the FUSE_INIT handler; all that should be done after
> > daemonize before calling fuse_session_loop_mt.  The only reason I wrote
> 
> So after the mount? What is if starting the threads would fail for some
> reason?

Right now they're optional features (monitor memory PSI file and flush
cache) so it's no big deal if they don't initialize.  But that *does*
make the case for adding the parent/child pipelines so that you can
daemonize earlier and report failures to the parent process.

Hrm.  Thinking about this more, the new fuse_daemonize_XXX calls make it
possible for any fuse server to do pre-mount() initialization in the
child and report the outcome to the parent.  Even if that fuse server
doesn't know, care, or try to enable sync FUSE_INIT.

So this new API really is separate from FUSE_SYNC_INIT.  The latter
depends on the former, but the reverse is not true.

> > it that way was blind patterning after fuse2fs, which doesn't call
> > daemonize() directly, so FUSE_INIT is the first time any fuse2fs code
> > gets called after daemonizing.
> > 
> >> fuse_daemonize() also does not allow to return notification
> >> from the forked child to the parent.
> >>
> >> Complex fuse file system daemons often want the order of
> >> 1) fork - parent watches, child does the work
> >>
> >> Child:
> >>     2) start extra threads and system initialization (like network
> >>        connection and RDMA memory registration) from the fork child.
> >>     3) Start the fuse session after everything else succeeded
> >>
> >> Parent:
> >>     Report child initialization success or failure
> > 
> > Under classic async FUSE_INIT, the sequence in most fuse servers is:
> > 
> > 1) The parent opens /dev/fuse and mounts the fuse filesystem before even
> >    daemonizing
> > 
> > 2) Mounting the fuse fs causes an async FUSE_INIT to be sent to the
> >    queues, which sits there because nobody's looking for event yet
> > 
> > 3) The parent daemonize()s, and the child proceeds with setting signal
> >    handlers and starting up the fuse-request processing threads
> > 
> > 4) The parent exits, the child continues on to set up the fuse worker
> >    threads
> > 
> > 5) One of the request handler threads finally reads /dev/fuse to find
> >    the FUSE_INIT request and processes it
> > 
> > 6) do_init (in the lowlevel fuse library) starts up the uring workers
> >    if the kernel acknowledges the uring feature.  The fuse server has
> >    no means to discover if the fusedev would permit uring before
> >    calling mount().
> 
> Yeah, initially I wanted to handle that through an ioctl and independent
> of FUSE_INIT, Miklos had asked to change that.

<nod>

> > Does my understanding make sense?
> > 
> >> A new API is introduced to overcome the limitations of
> >> fuse_daemonize()
> >>
> >> fuse_daemonize_start() - fork, but foreground process does not
> >> terminate yet and watches the background.
> > 
> > But with sync FUSE_INIT this is not workable because the child has to
> > have done (4) before (1) can complete, or it has to set up a temporary
> > request handler thread to process the FUSE_INIT.  That's partly why
> > fuse_daemonize_start/success/fail() is created here, right?
> 
> I have written two similar APIs for two different daemons at DDN (one in
> C and the other in C++), independent of sync FUSE_INIT. For us it is
> important to only create the mount point when the network connection
> works - that is done by threads not under control from libfuse. With
> network you want to see something like "authorization failure" or "host
> not reachable" in foreground.
> 
> With sync FUSE_INIT the additional issue of
> FUSE_INIT-creates-the-io-uring ring thread came up - even the
> <libfuse>/example/* file systems all don't work, because now
> fuse_session_mount() creates the ring threads, then comes
> fuse_daemonize() - parent exits - ring threads gone.

<nod>

> > And the rest of the reason for the new functions is to enable
> > communication between the parent and child processes -- if one dies
> > the other can find out about it; and the child can tell the parent the
> > outcome of mount()ing the filesystem.
> > 
> > I wonder -- if you know that the kernel supports synchronous FUSE_INIT,
> > can you start the main event handling threadpool (i.e. the one created
> > in fuse_loop_mt.c) after opening /dev/fuse (obviously) but before
> > calling mount()?  That would make a hard requirement of having at least
> > one event handling thread, but you wouldn't have to create this temp
> > thread just to handle the FUSE_INIT.
> 
> That would work and I believe it would be feasible for libfuse examples,
> but what about all the other real file systems out there? They would
> need to be rewritten to get sync INIT? And how many people understand
> the difference between sync and async init? Is this temp thread causing
> issues?

Don't fuse server authors already need to adapt their codebases to the
new fuse_daemonize_* calls if either they want to start threads or want
sync INIT?

The problem I have with the temp thread is that it adds a fourth(?)
piece of code that handles fuse requests (the existing ones being
single-thread read, multi-thread read, iouring) and I think "err, more
code that everyone has to understand".

Granted, any server that wants sync FUSE_INIT will basically have to be
a multithreaded server.

Shifting to fuse servers, adding a background thread does have some side
effects -- if you want to use pthread_[gs]etspecific from fuse operation
handlers, you have to know that FUSE_INIT will run in a different thread
from the ones set up by fuse_session_loop_mt.  If the fuse server needs
to preallocate the thread-specific variables, it has to know to add an
extra preallocation for the FUSE_INIT thread.

(Or you can't use them from FUSE_INIT)

> > Even better the daemonize() changes reduce to just the pipe between
> > parent and child and watching either for a return value or the POLLERR
> > when either program fails unexpectedly.
> 
> We also need to send a signal to the parent that child has success. How
> many pipes we use is an internal detail? If we find a better way later
> to reduce the number of pipes, even better.

<nod> Between the parent and child of a fork() operation that's fine, we
can update the interface at any time.

> >> fuse_daemonize_success() / fuse_daemonize_fail() - background
> >> daemon signals to the foreground process success or failure.
> >>
> >> fuse_daemonize_active() - helper function for the high level
> >> interface, which needs to handle both APIs. fuse_daemonize()
> >> is called within fuse_main(), which now needs to know if the caller
> >> actually already used the new API itself.
> >>
> >> The object 'struct fuse_daemonize *' is allocated dynamically
> >> and stored a global variable in fuse_daemonize.c, because
> >> - high level fuse_main_real_versioned() needs to know
> >> if already daemonized
> >> - high level daemons do not have access to struct fuse_session
> >> - FUSE_SYNC_INIT in later commits can only be done if the new
> >> API (or a file system internal) daemonization is used.
> > 
> > I don't know exactly what's required to switch libfuse into uring mode.
> > It look as though you inject -oio_uring as a mount option, then libfuse
> > sets up the uring, starts some extra workers to handle the ring(?) and
> > puts them to sleep.  If the kernel says it supports uring in FUSE_INIT
> > then do_init wakes them up.  Each uring thread submits SQEs and waits
> > for fuse requests to appear as CQEs, right?
> 
> Yeah, we can also make that later on a default, without the -oio_uring
> option (there is also env for ci test purposes to enforce io-uring
> mode). Right now it starts too many threads (got distracted today and
> didn't send the new version of ring reduction patches - will try
> tomorrow). And I also expected bugs in all that new code, so I didn't
> want to make it a default.

<nod>

> > 
> > So after a fuse server negotiates with the kernel about iouring, the
> > background threads started by fuse_loop_mt just sit there in read()
> > doing nothing else, while new fuse requests get sent to userspace as a
> > CQE, right?
> 
> Exactly.

Any reason not to cancel those threads?

> My question is now, are you ok with the new fuse_daemonize API? It
> sounds a bit like you don't like it too much.

Now that I've come back with a fresher mind, I think I'm ok with the
daemonize API itself.  I've a few minor nits to pick, so I'll reply to
the patch itself.

--D

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 04/25] Add a new daemonize API
  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-30 17:55   ` Darrick J. Wong
  1 sibling, 0 replies; 56+ messages in thread
From: Darrick J. Wong @ 2026-03-30 17:55 UTC (permalink / raw)
  To: Bernd Schubert; +Cc: linux-fsdevel, Miklos Szeredi, Joanne Koong, Kevin Chen

On Thu, Mar 26, 2026 at 10:34:37PM +0100, Bernd Schubert wrote:
> Existing example/ file systems do the fuse_daemonize() after
> fuse_session_mount() - i.e. after the mount point is already
> established. Though, these example/ daemons do not start
> extra threads and do not need network initialization either.
> 
> fuse_daemonize() also does not allow to return notification
> from the forked child to the parent.
> 
> Complex fuse file system daemons often want the order of
> 1) fork - parent watches, child does the work
> 
> Child:
>     2) start extra threads and system initialization (like network
>        connection and RDMA memory registration) from the fork child.
>     3) Start the fuse session after everything else succeeded
> 
> Parent:
>     Report child initialization success or failure
> 
> A new API is introduced to overcome the limitations of
> fuse_daemonize()
> 
> fuse_daemonize_start() - fork, but foreground process does not
> terminate yet and watches the background.
> 
> fuse_daemonize_success() / fuse_daemonize_fail() - background
> daemon signals to the foreground process success or failure.
> 
> fuse_daemonize_active() - helper function for the high level
> interface, which needs to handle both APIs. fuse_daemonize()
> is called within fuse_main(), which now needs to know if the caller
> actually already used the new API itself.
> 
> The object 'struct fuse_daemonize *' is allocated dynamically
> and stored a global variable in fuse_daemonize.c, because
> - high level fuse_main_real_versioned() needs to know
> if already daemonized
> - high level daemons do not have access to struct fuse_session
> - FUSE_SYNC_INIT in later commits can only be done if the new
> API (or a file system internal) daemonization is used.
> 
> Signed-off-by: Bernd Schubert <bernd@bsbernd.com>

Only a couple nitpicks now...

> ---
>  example/passthrough_hp.cc   |  18 ++-
>  include/fuse_daemonize.h    |  74 +++++++++++
>  include/meson.build         |   3 +-
>  lib/fuse_daemonize.c        | 292 ++++++++++++++++++++++++++++++++++++++++++++
>  lib/fuse_i.h                |   4 +-
>  lib/fuse_lowlevel.c         |   3 +
>  lib/fuse_versionscript      |   4 +
>  lib/helper.c                |  13 +-
>  lib/meson.build             |   3 +-
>  test/test_want_conversion.c |   1 +
>  10 files changed, 404 insertions(+), 11 deletions(-)
> 
> diff --git a/example/passthrough_hp.cc b/example/passthrough_hp.cc
> index 9f795c5546ee8312e1393c5b8fcebfd77724fb49..bad435077697e8832cf5a5195c17f2f873f2dfe6 100644
> --- a/example/passthrough_hp.cc
> +++ b/example/passthrough_hp.cc
> @@ -55,6 +55,7 @@
>  #include <errno.h>
>  #include <ftw.h>
>  #include <fuse_lowlevel.h>
> +#include <fuse_daemonize.h>
>  #include <inttypes.h>
>  #include <string.h>
>  #include <sys/file.h>
> @@ -243,6 +244,9 @@ 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 */
> +	fuse_daemonize_success();
>  }
>  
>  static void sfs_getattr(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi)
> @@ -1580,6 +1584,7 @@ int main(int argc, char *argv[])
>  {
>  	struct fuse_loop_config *loop_config = NULL;
>  	void *teardown_watchog = NULL;
> +	unsigned int daemon_flags = 0;
>  
>  	// Parse command line options
>  	auto options{ parse_options(argc, argv) };
> @@ -1638,10 +1643,14 @@ int main(int argc, char *argv[])
>  
>  	fuse_loop_cfg_set_clone_fd(loop_config, fs.clone_fd);
>  
> -	if (fuse_session_mount(se, argv[2]) != 0)
> +	/* Start daemonization before mount so parent can report mount failure */
> +	if (fs.foreground)
> +		daemon_flags |= FUSE_DAEMONIZE_NO_BACKGROUND;
> +	if (fuse_daemonize_start(daemon_flags) != 0)
>  		goto err_out3;
>  
> -	fuse_daemonize(fs.foreground);

Should the old fuse_daemonize() no-op (or complain) if the client has
already called fuse_daemonize_start()?  You've done something like that
in fuse_main_real_versioned, so maybe it should be automatic.

> +	if (fuse_session_mount(se, argv[2]) != 0)
> +		goto err_out4;
>  
>  	if (!fs.foreground)
>  		fuse_log_enable_syslog("passthrough-hp", LOG_PID | LOG_CONS,
> @@ -1650,7 +1659,7 @@ int main(int argc, char *argv[])
>  	teardown_watchog = fuse_session_start_teardown_watchdog(
>  		se, fs.root.stop_timeout_secs, NULL, NULL);
>  	if (teardown_watchog == NULL)
> -		goto err_out3;
> +		goto err_out4;
>  
>  	if (options.count("single"))
>  		ret = fuse_session_loop(se);
> @@ -1659,6 +1668,9 @@ int main(int argc, char *argv[])
>  
>  	fuse_session_unmount(se);
>  
> +err_out4:
> +	if (fuse_daemonize_is_active())
> +		fuse_daemonize_fail(ret);
>  err_out3:
>  	fuse_remove_signal_handlers(se);
>  err_out2:
> diff --git a/include/fuse_daemonize.h b/include/fuse_daemonize.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..c35dddd668b399535c53b44ab06c65fc0b3ddefa
> --- /dev/null
> +++ b/include/fuse_daemonize.h
> @@ -0,0 +1,74 @@
> +/*
> + * FUSE: Filesystem in Userspace
> + * Copyright (C) 2026 Bernd Schubert <bsbernd.com>
> + *
> + * This program can be distributed under the terms of the GNU LGPLv2.
> + * See the file COPYING.LIB.
> + *
> + */
> +
> +#ifndef FUSE_DAEMONIZE_H_
> +#define FUSE_DAEMONIZE_H_
> +
> +#include <stdint.h>
> +#include <stdbool.h>
> +
> +#ifdef __cplusplus
> +extern "C" {
> +#endif
> +
> +/**
> + * Flags for fuse_daemonize_start()
> + */
> +#define FUSE_DAEMONIZE_NO_CHDIR      (1 << 0)
> +#define FUSE_DAEMONIZE_NO_BACKGROUND (1 << 1)
> +
> +/**
> + * Start daemonization process.
> + *
> + * Unless FUSE_DAEMONIZE_NO_BACKGROUND is set, this forks the process.
> + * The parent waits for a signal from the child via fuse_daemonize_success()
> + * or fuse_daemonize_fail().
> + * The child returns from this call and continues setup.
> + *
> + * Unless FUSE_DAEMONIZE_NO_CHDIR is set, changes directory to "/".
> + *
> + * Must be called before fuse_session_mount().
> + *
> + * @param flags combination of FUSE_DAEMONIZE_* flags
> + * @return 0 on success, negative errno on error
> + */
> +int fuse_daemonize_start(unsigned int flags);
> +
> +/**
> + * Signal daemonization success to parent and cleanup.
> + */
> +void fuse_daemonize_success(void);
> +
> +/**
> + * Signal daemonization failure to parent and cleanup.
> + *
> + * @param err error code to pass to parent

I think this should say explicitly that @err will become the exit code
of the parent process.

It might also be useful to state that except for fuse_daemonize_start,
all the other APIs must only be called from the child process.

--D

> + */
> +void fuse_daemonize_fail(int err);
> +
> +/**
> + * Check if daemonization is active and waiting for signal.
> + *
> + * @return true if active, false otherwise
> + */
> +bool fuse_daemonize_is_active(void);
> +
> +/**
> + * Set mounted flag.
> + *
> + * Called from fuse_session_mount().
> + */
> +void fuse_daemonize_set_mounted(void);
> +
> +#ifdef __cplusplus
> +}
> +#endif
> +
> +#endif /* FUSE_DAEMONIZE_H_ */
> +
> diff --git a/include/meson.build b/include/meson.build
> index bf671977a5a6a9142bd67aceabd8a919e3d968d0..cfbaf52ac5d84369e92948c631e2fcfdd04ac2eb 100644
> --- a/include/meson.build
> +++ b/include/meson.build
> @@ -1,4 +1,5 @@
>  libfuse_headers = [ 'fuse.h', 'fuse_common.h', 'fuse_lowlevel.h',
> -	            'fuse_opt.h', 'cuse_lowlevel.h', 'fuse_log.h' ]
> +	            'fuse_opt.h', 'cuse_lowlevel.h', 'fuse_log.h',
> +	            'fuse_daemonize.h' ]
>  
>  install_headers(libfuse_headers, subdir: 'fuse3')
> diff --git a/lib/fuse_daemonize.c b/lib/fuse_daemonize.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..865acad7db56dbe5ed8a1bee52e7353627e89b75
> --- /dev/null
> +++ b/lib/fuse_daemonize.c
> @@ -0,0 +1,292 @@
> +/*
> + * FUSE: Filesystem in Userspace
> + * Copyright (C) 2026 Bernd Schubert <bsbernd.com>
> + *
> + * This program can be distributed under the terms of the GNU LGPLv2.
> + * See the file COPYING.LIB.
> + */
> +
> +#define _GNU_SOURCE
> +
> +#include "fuse_daemonize.h"
> +
> +#include <fcntl.h>
> +#include <poll.h>
> +#include <pthread.h>
> +#include <stdatomic.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <sys/types.h>
> +#include <unistd.h>
> +#include <stdbool.h>
> +#include <errno.h>
> +
> +/**
> + * Status values for fuse_daemonize_success() and fuse_daemonize_fail()
> + */
> +#define FUSE_DAEMONIZE_SUCCESS 0
> +#define FUSE_DAEMONIZE_FAILURE 1
> +
> +/* Private/internal data  */
> +struct fuse_daemonize {
> +	unsigned int flags;
> +	int signal_pipe_wr;	/* write end for signaling parent */
> +	int death_pipe_rd;	/* read end, POLLHUP when parent dies */
> +	int stop_pipe_rd;	/* read end for stop signal */
> +	int stop_pipe_wr;	/* write end for stop signal */
> +	pthread_t watcher;
> +	bool watcher_started;
> +	_Atomic bool active;
> +	_Atomic bool daemonized;
> +	_Atomic bool mounted;
> +};
> +
> +/* Global daemonization object pointer */
> +static struct fuse_daemonize daemonize = {
> +	.signal_pipe_wr = -1,
> +	.death_pipe_rd = -1,
> +	.stop_pipe_rd = -1,
> +	.stop_pipe_wr = -1,
> +};
> +
> +/* Watcher thread: polls for parent death or stop signal */
> +static void *parent_watcher_thread(void *arg)
> +{
> +	struct fuse_daemonize *di = arg;
> +	struct pollfd pfd[2];
> +
> +	pfd[0].fd = di->death_pipe_rd;
> +	pfd[0].events = POLLIN;
> +	pfd[1].fd = di->stop_pipe_rd;
> +	pfd[1].events = POLLIN;
> +
> +	while (1) {
> +		int rc = poll(pfd, 2, -1);
> +
> +		if (rc < 0)
> +			continue;
> +
> +		/* Parent died - death pipe write end closed */
> +		if (pfd[0].revents & (POLLHUP | POLLERR))
> +			_exit(EXIT_FAILURE);
> +
> +		/* Stop signal received */
> +		if (pfd[1].revents & POLLIN)
> +			break;
> +	}
> +	return NULL;
> +}
> +
> +static int start_parent_watcher(struct fuse_daemonize *daemonize)
> +{
> +	int rc;
> +
> +	rc = pthread_create(&daemonize->watcher, NULL, parent_watcher_thread,
> +			    daemonize);
> +	if (rc != 0) {
> +		fprintf(stderr, "fuse_daemonize: pthread_create: %s\n",
> +			strerror(rc));
> +		return -rc;
> +	}
> +	daemonize->watcher_started = true;
> +	return 0;
> +}
> +
> +static void stop_parent_watcher(struct fuse_daemonize *daemonize)
> +{
> +	char byte = 0;
> +
> +	if (daemonize && daemonize->watcher_started) {
> +		/* Signal watcher to stop */
> +		if (write(daemonize->stop_pipe_wr, &byte, 1) != 1)
> +			perror("fuse_daemonize: stop write");
> +		pthread_join(daemonize->watcher, NULL);
> +		daemonize->watcher_started = false;
> +	}
> +}
> +
> +static int daemonize_child(struct fuse_daemonize *daemonize)
> +{
> +	int stop_pipe[2], err = 0;
> +
> +	if (pipe(stop_pipe) == -1) {
> +		err = -errno;
> +		perror("fuse_daemonize_start: stop pipe");
> +		return err;
> +	}
> +	daemonize->stop_pipe_rd = stop_pipe[0];
> +	daemonize->stop_pipe_wr = stop_pipe[1];
> +
> +	if (setsid() == -1) {
> +		err = -errno;
> +		perror("fuse_daemonize_start: setsid");
> +		goto err_close_stop;
> +	}
> +
> +	/* Close stdin immediately */
> +	int nullfd = open("/dev/null", O_RDWR, 0);
> +
> +	if (nullfd != -1) {
> +		(void)dup2(nullfd, 0);
> +		if (nullfd > 0)
> +			close(nullfd);
> +	}
> +
> +	/* Start watcher thread to detect parent death */
> +	err = start_parent_watcher(daemonize);
> +	if (err)
> +		goto err_close_stop;
> +
> +	daemonize->daemonized = true;
> +	return 0;
> +
> +err_close_stop:
> +	close(daemonize->stop_pipe_rd);
> +	close(daemonize->stop_pipe_wr);
> +	return err;
> +}
> +
> +/* Fork and daemonize. Returns 0 in child, never returns in parent. */
> +static int do_daemonize(struct fuse_daemonize *daemonize)
> +{
> +	int signal_pipe[2], death_pipe[2], err;
> +
> +	if (pipe(signal_pipe) == -1) {
> +		err = -errno;
> +		perror("fuse_daemonize_start: signal pipe");
> +		return err;
> +	}
> +
> +	if (pipe(death_pipe) == -1) {
> +		err = -errno;
> +		perror("fuse_daemonize_start: death pipe");
> +		close(signal_pipe[0]);
> +		close(signal_pipe[1]);
> +		return err;
> +	}
> +
> +	switch (fork()) {
> +	case -1:
> +		err = -errno;
> +		perror("fuse_daemonize_start: fork");
> +		close(signal_pipe[0]);
> +		close(signal_pipe[1]);
> +		close(death_pipe[0]);
> +		close(death_pipe[1]);
> +		return err;
> +
> +	case 0:
> +		/* Child: signal write end, death read end */
> +		close(signal_pipe[0]);
> +		close(death_pipe[1]);
> +		daemonize->signal_pipe_wr = signal_pipe[1];
> +		daemonize->death_pipe_rd = death_pipe[0];
> +		return daemonize_child(daemonize);
> +
> +	default: {
> +		/* Parent: signal read end, death write end (kept open) */
> +		int status;
> +		ssize_t res;
> +
> +		close(signal_pipe[1]);
> +		close(death_pipe[0]);
> +
> +		res = read(signal_pipe[0], &status, sizeof(status));
> +		close(signal_pipe[0]);
> +		close(death_pipe[1]);
> +
> +		if (res != sizeof(status))
> +			_exit(EXIT_FAILURE);
> +		_exit(status);
> +	}
> +	}
> +}
> +
> +int fuse_daemonize_start(unsigned int flags)
> +{
> +	struct fuse_daemonize *dm = &daemonize;
> +	int err = 0;
> +
> +	dm->flags = flags;
> +	dm->signal_pipe_wr = -1;
> +	dm->death_pipe_rd = -1;
> +	dm->stop_pipe_rd = -1;
> +	dm->stop_pipe_wr = -1;
> +	dm->active = true;
> +
> +	if (!(flags & FUSE_DAEMONIZE_NO_CHDIR))
> +		(void)chdir("/");
> +
> +	if (!(flags & FUSE_DAEMONIZE_NO_BACKGROUND))
> +		err = do_daemonize(dm);
> +
> +	return err;
> +}
> +
> +static void close_if_valid(int *fd)
> +{
> +	if (*fd != -1) {
> +		close(*fd);
> +		*fd = -1;
> +	}
> +}
> +
> +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");
> +
> +	dm->active = false;
> +
> +	/* Stop watcher before signaling - parent will exit after this */
> +	stop_parent_watcher(dm);
> +
> +	/* Signal status to parent */
> +	if (dm->signal_pipe_wr != -1) {
> +		rc = write(dm->signal_pipe_wr, &status, sizeof(status));
> +		if (rc != sizeof(status))
> +			fprintf(stderr, "%s: write failed\n", __func__);
> +	}
> +
> +	/* Redirect stdout/stderr to /dev/null on success */
> +	if (status == FUSE_DAEMONIZE_SUCCESS && dm->daemonized) {
> +		int nullfd = open("/dev/null", O_RDWR, 0);
> +
> +		if (nullfd != -1) {
> +			(void)dup2(nullfd, 1);
> +			(void)dup2(nullfd, 2);
> +			if (nullfd > 2)
> +				close(nullfd);
> +		}
> +	}
> +
> +	close_if_valid(&dm->signal_pipe_wr);
> +	close_if_valid(&dm->death_pipe_rd);
> +	close_if_valid(&dm->stop_pipe_rd);
> +	close_if_valid(&dm->stop_pipe_wr);
> +}
> +
> +void fuse_daemonize_success(void)
> +{
> +	fuse_daemonize_signal(FUSE_DAEMONIZE_SUCCESS);
> +}
> +
> +void fuse_daemonize_fail(int err)
> +{
> +	fuse_daemonize_signal(err);
> +}
> +
> +bool fuse_daemonize_is_active(void)
> +{
> +	return daemonize.daemonized || daemonize.active;
> +}
> +
> +void fuse_daemonize_set_mounted(void)
> +{
> +	daemonize.mounted = true;
> +}
> diff --git a/lib/fuse_i.h b/lib/fuse_i.h
> index 65d2f68f7f30918a3c3ee4d473796cb013428a8f..9e3c5dc5021e210a2778e975a37ab609af324010 100644
> --- a/lib/fuse_i.h
> +++ b/lib/fuse_i.h
> @@ -17,7 +17,6 @@
>  #include <semaphore.h>
>  #include <stdint.h>
>  #include <stdbool.h>
> -#include <errno.h>
>  #include <stdatomic.h>
>  
>  #define MIN(a, b) \
> @@ -110,6 +109,9 @@ struct fuse_session {
>  	/* true if reading requests from /dev/fuse are handled internally */
>  	bool buf_reallocable;
>  
> +	/* synchronous FUSE_INIT support */
> +	bool want_sync_init;
> +
>  	/* io_uring */
>  	struct fuse_session_uring uring;
>  
> diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c
> index 3234f0ce3b246a4c2c40dc0757177de91b6608b2..ccff6a768f0b8c32469abda9405ff29623f3fff7 100644
> --- a/lib/fuse_lowlevel.c
> +++ b/lib/fuse_lowlevel.c
> @@ -19,6 +19,7 @@
>  #include "mount_util.h"
>  #include "util.h"
>  #include "fuse_uring_i.h"
> +#include "fuse_daemonize.h"
>  
>  #include <pthread.h>
>  #include <stdatomic.h>
> @@ -4451,6 +4452,8 @@ int fuse_session_mount(struct fuse_session *se, const char *_mountpoint)
>  	/* Save mountpoint */
>  	se->mountpoint = mountpoint;
>  
> +	fuse_daemonize_set_mounted();
> +
>  	return 0;
>  
>  error_out:
> diff --git a/lib/fuse_versionscript b/lib/fuse_versionscript
> index cce09610316f4b0b1d6836dd0e63686342b70037..dc6ed0135fb8d82937c756c3fb04a7fcb48fe1f4 100644
> --- a/lib/fuse_versionscript
> +++ b/lib/fuse_versionscript
> @@ -227,6 +227,10 @@ FUSE_3.19 {
>  		fuse_session_start_teardown_watchdog;
>  		fuse_session_stop_teardown_watchdog;
>  		fuse_lowlevel_notify_prune;
> +		fuse_daemonize_start;
> +		fuse_daemonize_success;
> +		fuse_daemonize_fail;
> +		fuse_daemonize_is_active;
>  } FUSE_3.18;
>  
>  # Local Variables:
> diff --git a/lib/helper.c b/lib/helper.c
> index 5c13b93a473181f027eba01e0bfefd78875ede3e..35285be19aa0cc390a432d16701b9eefa16ec12a 100644
> --- a/lib/helper.c
> +++ b/lib/helper.c
> @@ -15,6 +15,7 @@
>  #include "fuse_misc.h"
>  #include "fuse_opt.h"
>  #include "fuse_lowlevel.h"
> +#include "fuse_daemonize.h"
>  #include "mount_util.h"
>  
>  #include <stdio.h>
> @@ -352,17 +353,19 @@ int fuse_main_real_versioned(int argc, char *argv[],
>  		goto out1;
>  	}
>  
> +	struct fuse_session *se = fuse_get_session(fuse);
>  	if (fuse_mount(fuse,opts.mountpoint) != 0) {
>  		res = 4;
>  		goto out2;
>  	}
>  
> -	if (fuse_daemonize(opts.foreground) != 0) {
> -		res = 5;
> -		goto out3;
> +	if (!fuse_daemonize_is_active()) {
> +		/* Avoid daemonizing if we are already daemonized by the newer API */
> +		if (fuse_daemonize(opts.foreground) != 0) {
> +			res = 5;
> +			goto out3;
> +		}
>  	}
> -
> -	struct fuse_session *se = fuse_get_session(fuse);
>  	if (fuse_set_signal_handlers(se) != 0) {
>  		res = 6;
>  		goto out3;
> diff --git a/lib/meson.build b/lib/meson.build
> index fcd95741c9d3748fa01d9ec52b417aca66745f26..5bd449ebffe7c9229df904d647d990c6c47f80b5 100644
> --- a/lib/meson.build
> +++ b/lib/meson.build
> @@ -2,7 +2,8 @@ libfuse_sources = ['fuse.c', 'fuse_i.h', 'fuse_loop.c', 'fuse_loop_mt.c',
>                     'fuse_lowlevel.c', 'fuse_misc.h', 'fuse_opt.c',
>                     'fuse_signals.c', 'buffer.c', 'cuse_lowlevel.c',
>                     'helper.c', 'modules/subdir.c', 'mount_util.c',
> -                   'fuse_log.c', 'compat.c', 'util.c', 'util.h' ]
> +                   'fuse_log.c', 'compat.c', 'util.c', 'util.h',
> +                   'fuse_daemonize.c' ]
>  
>  if host_machine.system().startswith('linux')
>     libfuse_sources += [ 'mount.c' ]
> diff --git a/test/test_want_conversion.c b/test/test_want_conversion.c
> index db731edbfe1be8230ae16b422f798603b4a3bb82..48e6dd2dc6084425a0462bba000563c6083160be 100644
> --- a/test/test_want_conversion.c
> +++ b/test/test_want_conversion.c
> @@ -8,6 +8,7 @@
>  #include <inttypes.h>
>  #include <stdbool.h>
>  #include <err.h>
> +#include <errno.h>
>  
>  static void print_conn_info(const char *prefix, struct fuse_conn_info *conn)
>  {
> 
> -- 
> 2.43.0
> 
> 

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 18/25] fusermout: Remove the large read check
  2026-03-30 15:26     ` Bernd Schubert
@ 2026-03-30 17:57       ` Darrick J. Wong
  0 siblings, 0 replies; 56+ messages in thread
From: Darrick J. Wong @ 2026-03-30 17:57 UTC (permalink / raw)
  To: Bernd Schubert; +Cc: linux-fsdevel, Miklos Szeredi, Joanne Koong, Kevin Chen

On Mon, Mar 30, 2026 at 05:26:15PM +0200, Bernd Schubert wrote:
> 
> 
> On 3/27/26 04:32, Darrick J. Wong wrote:
> > On Thu, Mar 26, 2026 at 10:34:51PM +0100, Bernd Schubert wrote:
> >> <quote Darrick>
> >> Nobody's running 2.4 kernels
> >> anymore, right?
> >> </quote>
> >>
> >> Suggested-by: "Darrick J. Wong" <djwong@kernel.org>
> >> Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
> >> ---
> >>  util/fusermount.c | 37 +------------------------------------
> >>  1 file changed, 1 insertion(+), 36 deletions(-)
> >>
> >> diff --git a/util/fusermount.c b/util/fusermount.c
> >> index 8d7598998788f72ea05c2a065a88cb5efd61df35..254f24a98abf935846cfed754693cc1958c8d2ff 100644
> >> --- a/util/fusermount.c
> >> +++ b/util/fusermount.c
> >> @@ -915,33 +915,6 @@ static void free_mount_params(struct mount_params *mp)
> >>  	free(mp->mnt_opts);
> >>  }
> >>  
> >> -/*
> >> - * Check if option is deprecated large_read.
> >> - *
> >> - * Returns true if the option should be skipped (large_read on kernel > 2.4),
> >> - * false otherwise (all other options or large_read on old kernels).
> >> - */
> >> -static bool check_large_read(const char *opt, unsigned int len)
> >> -{
> >> -	struct utsname utsname;
> >> -	unsigned int kmaj, kmin;
> >> -	int res;
> >> -
> >> -	if (!opt_eq(opt, len, "large_read"))
> >> -		return false;
> >> -
> >> -	res = uname(&utsname);
> >> -	if (res == 0 &&
> >> -	    sscanf(utsname.release, "%u.%u", &kmaj, &kmin) == 2 &&
> >> -	    (kmaj > 2 || (kmaj == 2 && kmin > 4))) {
> >> -		fprintf(stderr,
> >> -			"%s: note: 'large_read' mount option is deprecated for %i.%i kernels\n",
> >> -			progname, kmaj, kmin);
> >> -		return true;
> >> -	}
> >> -	return false;
> >> -}
> >> -
> >>  /*
> >>   * Check if user has permission to use allow_other or allow_root options.
> >>   *
> >> @@ -1041,19 +1014,11 @@ static int prepare_mount(const char *opts, struct mount_params *mp)
> >>  			   !begins_with(s, "rootmode=") &&
> >>  			   !begins_with(s, "user_id=") &&
> >>  			   !begins_with(s, "group_id=")) {
> >> -			bool skip;
> >>  
> >>  			if (check_allow_permission(s, len) == -1)
> >>  				goto err;
> >>  
> >> -			skip = check_large_read(s, len);
> >> -
> >> -			/*
> >> -			 * Skip deprecated large_read to avoid passing it to
> >> -			 * kernel which would reject it as unknown option.
> >> -			 */
> >> -			if (!skip)
> >> -				process_generic_option(s, len, &mp->flags, &d);
> >> +			process_generic_option(s, len, &mp->flags, &d);
> > 
> > Er... we should still ignore large_read, right?  It's been defunct since
> > 2.4, but there could be some script/fstab/whatever that still contains
> > the mount option.
> 
> Dunno, I'm more tempted to remove it and to filter it out if someone
> ever complains. 2.4 was a long time ago.

Ok by me then :)
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>

(who recently got flames whilst trying to remove defunct mount options
from xfs and is probably now overcautious about that)

--D

> 
> 
> Thanks,
> Bernd
> 

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 11/25] Move 'struct mount_flags' to util.h
  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
  0 siblings, 0 replies; 56+ messages in thread
From: Darrick J. Wong @ 2026-03-30 18:11 UTC (permalink / raw)
  To: Bernd Schubert; +Cc: linux-fsdevel, Miklos Szeredi, Joanne Koong, Kevin Chen

On Thu, Mar 26, 2026 at 10:34:44PM +0100, Bernd Schubert wrote:
> We actually need these in fusermount.c and for the
> new mount API, which goes into its own file.
> 
> Also extend the struct with the extra safe field
> used by fusermount.c
> 
> Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
> ---
>  include/fuse_mount_compat.h |  5 +++++
>  lib/mount.c                 | 39 --------------------------------
>  lib/mount_bsd.c             |  1 +
>  lib/mount_i_linux.h         |  4 ++--
>  lib/mount_util.c            | 54 ++++++++++++++++++++++++++++++++++++++++++++-
>  lib/mount_util.h            |  9 ++++++++
>  util/fusermount.c           | 34 ----------------------------
>  7 files changed, 70 insertions(+), 76 deletions(-)
> 
> diff --git a/include/fuse_mount_compat.h b/include/fuse_mount_compat.h
> index be8d576cf959fba7acb25440fafd43f7e973964e..1b0eb00c3eb79ad2d58a9e86ac2836d3f7d3b2fb 100644
> --- a/include/fuse_mount_compat.h
> +++ b/include/fuse_mount_compat.h
> @@ -11,6 +11,9 @@
>  #ifndef FUSE_MOUNT_COMPAT_H_
>  #define FUSE_MOUNT_COMPAT_H_
>  
> +#if !defined(__NetBSD__) && !defined(__FreeBSD__) && !defined(__DragonFly__) && \
> +	!defined(__FreeBSD_kernel__)
> +
>  #include <sys/mount.h>
>  
>  /* Some libc don't define MS_*, so define them manually
> @@ -46,4 +49,6 @@
>  #define UMOUNT_UNUSED	0x80000000	/* Flag guaranteed to be unused */
>  #endif
>  
> +#endif /* BSD */
> +
>  #endif /* FUSE_MOUNT_COMPAT_H_ */
> diff --git a/lib/mount.c b/lib/mount.c
> index 899cd2d0a13a9332a564b10d1c22836fb2cd1710..43920a06ac03747595bde95441252e9cac77ce88 100644
> --- a/lib/mount.c
> +++ b/lib/mount.c
> @@ -33,18 +33,6 @@
>  
>  #include "fuse_mount_compat.h"
>  
> -#ifdef __NetBSD__
> -#include <perfuse.h>
> -
> -#define MS_RDONLY	MNT_RDONLY
> -#define MS_NOSUID	MNT_NOSUID
> -#define MS_NODEV	MNT_NODEV
> -#define MS_NOEXEC	MNT_NOEXEC
> -#define MS_SYNCHRONOUS	MNT_SYNCHRONOUS
> -#define MS_NOATIME	MNT_NOATIME
> -#define MS_NOSYMFOLLOW	MNT_NOSYMFOLLOW
> -#endif
> -
>  #define FUSERMOUNT_PROG		"fusermount3"
>  #define FUSE_COMMFD_ENV		"_FUSE_COMMFD"
>  #define FUSE_COMMFD2_ENV	"_FUSE_COMMFD2"
> @@ -147,33 +135,6 @@ void fuse_mount_version(void)
>  			 FUSERMOUNT_PROG);
>  }
>  
> -struct mount_flags {
> -	const char *opt;
> -	unsigned long flag;
> -	int on;
> -};
> -
> -static const struct mount_flags mount_flags[] = {
> -	{"rw",	    MS_RDONLY,	    0},
> -	{"ro",	    MS_RDONLY,	    1},
> -	{"suid",    MS_NOSUID,	    0},
> -	{"nosuid",  MS_NOSUID,	    1},
> -	{"dev",	    MS_NODEV,	    0},
> -	{"nodev",   MS_NODEV,	    1},
> -	{"exec",    MS_NOEXEC,	    0},
> -	{"noexec",  MS_NOEXEC,	    1},
> -	{"async",   MS_SYNCHRONOUS, 0},
> -	{"sync",    MS_SYNCHRONOUS, 1},
> -	{"noatime", MS_NOATIME,	    1},
> -	{"nodiratime",	    MS_NODIRATIME,	1},
> -	{"norelatime",	    MS_RELATIME,	0},
> -	{"nostrictatime",   MS_STRICTATIME,	0},
> -	{"symfollow",	    MS_NOSYMFOLLOW,	0},
> -	{"nosymfollow",	    MS_NOSYMFOLLOW,	1},
> -	{"dirsync",	    MS_DIRSYNC,	    1},
> -	{NULL,	    0,		    0}
> -};
> -
>  unsigned int get_max_read(struct mount_opts *o)
>  {
>  	return o->max_read;
> diff --git a/lib/mount_bsd.c b/lib/mount_bsd.c
> index c5b831160f826c0e4620626a72c4b93427d18bab..09631932eff443d21743dd55dcd5345dd581227f 100644
> --- a/lib/mount_bsd.c
> +++ b/lib/mount_bsd.c
> @@ -16,6 +16,7 @@
>  #include "util.h"
>  
>  #include <sys/param.h>
> +#include <sys/mount.h>
>  #include "fuse_mount_compat.h"
>  
>  #include <sys/wait.h>
> diff --git a/lib/mount_i_linux.h b/lib/mount_i_linux.h
> index 254b75151f35578ce22197776e6fa4aceb04150e..1bfc78b32dc4fd434870e4fff1a37e0c340d207e 100644
> --- a/lib/mount_i_linux.h
> +++ b/lib/mount_i_linux.h
> @@ -10,7 +10,8 @@
>  #ifndef FUSE_MOUNT_I_LINUX_H_
>  #define FUSE_MOUNT_I_LINUX_H_
>  
> -/* Forward declaration for fuse_args */
> +#include <sys/mount.h>
> +
>  struct fuse_args;
>  
>  /* Mount options structure */
> @@ -28,5 +29,4 @@ struct mount_opts {
>  	unsigned int max_read;
>  };
>  
> -
>  #endif /* FUSE_MOUNT_I_LINUX_H_ */
> diff --git a/lib/mount_util.c b/lib/mount_util.c
> index a42a02a0b92f98778abb2c491cdc7a01260f56ee..9ff711023fcb41fe24f1de21aaf811cb1c12d25e 100644
> --- a/lib/mount_util.c
> +++ b/lib/mount_util.c
> @@ -34,9 +34,61 @@
>  #include <sys/param.h>
>  
>  #if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__)
> -#define umount2(mnt, flags) unmount(mnt, ((flags) == 2) ? MNT_FORCE : 0)
> +#include <sys/mount.h>
> +#ifdef __NetBSD__
> +#include <perfuse.h>
>  #endif
>  
> +#define umount2(mnt, flags) unmount(mnt, ((flags) == 2) ? MNT_FORCE : 0)
> +
> +#define MS_RDONLY	MNT_RDONLY
> +#define MS_NOSUID	MNT_NOSUID
> +#ifdef MNT_NODEV
> +#define MS_NODEV	MNT_NODEV
> +#else
> +#define MS_NODEV	0
> +#endif
> +#define MS_NOEXEC	MNT_NOEXEC
> +#define MS_SYNCHRONOUS	MNT_SYNCHRONOUS
> +#define MS_NOATIME	MNT_NOATIME
> +#define MS_NOSYMFOLLOW	0
> +#define MS_DIRSYNC	0
> +
> +/* BSD doesn't have these, define as 0 */
> +#ifndef MS_NODIRATIME
> +#define MS_NODIRATIME 0
> +#endif
> +#ifndef MS_RELATIME
> +#define MS_RELATIME 0
> +#endif
> +#ifndef MS_STRICTATIME
> +#define MS_STRICTATIME 0
> +#endif
> +
> +#endif /* BSD */
> +
> +/* XXX This table is duplicated in util/fusermount.c */

Doesn't this patch remove this from fusermount.c?  I think the comment
is wrong and can be deleted.

--D

> +const struct mount_flags mount_flags[] = {
> +	{"rw",	    MS_RDONLY,	    0, 1},
> +	{"ro",	    MS_RDONLY,	    1, 1},
> +	{"suid",    MS_NOSUID,	    0, 0},
> +	{"nosuid",  MS_NOSUID,	    1, 1},
> +	{"dev",	    MS_NODEV,	    0, 1},
> +	{"nodev",   MS_NODEV,	    1, 1},
> +	{"exec",    MS_NOEXEC,	    0, 1},
> +	{"noexec",  MS_NOEXEC,	    1, 1},
> +	{"async",   MS_SYNCHRONOUS, 0, 1},
> +	{"sync",    MS_SYNCHRONOUS, 1, 1},
> +	{"noatime", MS_NOATIME,	    1, 1},
> +	{"nodiratime",	    MS_NODIRATIME,	1, 1},
> +	{"norelatime",	    MS_RELATIME,	0, 1},
> +	{"nostrictatime",   MS_STRICTATIME,	0, 1},
> +	{"symfollow",	    MS_NOSYMFOLLOW,	0, 1},
> +	{"nosymfollow",	    MS_NOSYMFOLLOW,	1, 1},
> +	{"dirsync",	    MS_DIRSYNC,		1, 1},
> +	{NULL,	    0,		    0, 0}
> +};
> +
>  #ifdef IGNORE_MTAB
>  #define mtab_needs_update(mnt) 0
>  #else
> diff --git a/lib/mount_util.h b/lib/mount_util.h
> index 4688d00091f001b3ccd36b1ef3b7e799031ed773..e62052b626875abd6118b845de162d6c5c41857a 100644
> --- a/lib/mount_util.h
> +++ b/lib/mount_util.h
> @@ -9,6 +9,15 @@
>  #include <sys/types.h>
>  #include "mount_common_i.h" // IWYU pragma: keep
>  
> +/* Mount flags mapping structure */
> +struct mount_flags {
> +	const char *opt;
> +	unsigned long flag;
> +	int on;
> +	int safe; /* used by fusermount */
> +};
> +extern const struct mount_flags mount_flags[];
> +
>  int fuse_mnt_add_mount(const char *progname, const char *fsname,
>  		       const char *mnt, const char *type, const char *opts);
>  int fuse_mnt_remove_mount(const char *progname, const char *mnt);
> diff --git a/util/fusermount.c b/util/fusermount.c
> index f17b44f51142c682b339d0ce2287f7c00d644454..f66c4e8e0e809351384ec10cf50ba4d3d3a831b0 100644
> --- a/util/fusermount.c
> +++ b/util/fusermount.c
> @@ -759,40 +759,6 @@ static int begins_with(const char *s, const char *beg)
>  		return 0;
>  }
>  
> -struct mount_flags {
> -	const char *opt;
> -	unsigned long flag;
> -	int on;
> -	int safe;
> -};
> -
> -static struct mount_flags mount_flags[] = {
> -	{"rw",	    MS_RDONLY,	    0, 1},
> -	{"ro",	    MS_RDONLY,	    1, 1},
> -	{"suid",    MS_NOSUID,	    0, 0},
> -	{"nosuid",  MS_NOSUID,	    1, 1},
> -	{"dev",	    MS_NODEV,	    0, 0},
> -	{"nodev",   MS_NODEV,	    1, 1},
> -	{"exec",    MS_NOEXEC,	    0, 1},
> -	{"noexec",  MS_NOEXEC,	    1, 1},
> -	{"async",   MS_SYNCHRONOUS, 0, 1},
> -	{"sync",    MS_SYNCHRONOUS, 1, 1},
> -	{"atime",   MS_NOATIME,	    0, 1},
> -	{"noatime", MS_NOATIME,	    1, 1},
> -	{"diratime",        MS_NODIRATIME,  0, 1},
> -	{"nodiratime",      MS_NODIRATIME,  1, 1},
> -	{"lazytime",        MS_LAZYTIME,    1, 1},
> -	{"nolazytime",      MS_LAZYTIME,    0, 1},
> -	{"relatime",        MS_RELATIME,    1, 1},
> -	{"norelatime",      MS_RELATIME,    0, 1},
> -	{"strictatime",     MS_STRICTATIME, 1, 1},
> -	{"nostrictatime",   MS_STRICTATIME, 0, 1},
> -	{"dirsync", MS_DIRSYNC,	    1, 1},
> -	{"symfollow",       MS_NOSYMFOLLOW, 0, 1},
> -	{"nosymfollow",     MS_NOSYMFOLLOW, 1, 1},
> -	{NULL,	    0,		    0, 0}
> -};
> -
>  static int find_mount_flag(const char *s, unsigned len, int *on, int *flag)
>  {
>  	int i;
> 
> -- 
> 2.43.0
> 
> 

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 12/25] conftest.py: Add more valgrind filter patterns
  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
  0 siblings, 0 replies; 56+ messages in thread
From: Darrick J. Wong @ 2026-03-30 18:16 UTC (permalink / raw)
  To: Bernd Schubert
  Cc: linux-fsdevel, Miklos Szeredi, Joanne Koong, Kevin Chen,
	Bernd Schubert

On Thu, Mar 26, 2026 at 10:34:45PM +0100, Bernd Schubert wrote:
> From: Bernd Schubert <bschubert@ddn.com>
> 
> Valgrind complains that it does not know the fsmount syscall,
> pytest checks for warnings, find that and fails the test
> 
> --14936-- WARNING: unhandled amd64-linux syscall: 430
> --14936-- You may be able to write your own handler.
> --14936-- Read the file README_MISSING_SYSCALL_OR_IOCTL.
> --14936-- Nevertheless we consider this a bug.  Please report
> --14936-- it at http://valgrind.org/support/bug_reports.html.

Perhaps it's time to upgrade the CI system?

Oh.  valgrind didn't add fsmount until April 2025 even though the
syscalls have existed since 2018.  Even Debian 13 doesn't know:

--1794-- WARNING: unhandled amd64-linux syscall: 430
--1794-- You may be able to write your own handler.
--1794-- Read the file README_MISSING_SYSCALL_OR_IOCTL.
--1794-- Nevertheless we consider this a bug.  Please report
--1794-- it at http://valgrind.org/support/bug_reports.html.

Well I'm not surprised, the manpages didn't get merged until last
December.

<sigh>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>

--D

> fuse: fsopen(fuse) failed: Function not implemented
> =========================== short test summary info
> 
> Obviously we want to filter our valgrind warnings.
> 
> Signed-off-by: Bernd Schubert <bschubert@ddn.com>
> ---
>  test/conftest.py | 7 ++++++-
>  1 file changed, 6 insertions(+), 1 deletion(-)
> 
> diff --git a/test/conftest.py b/test/conftest.py
> index 291c9199b9860d664600638213382bd8cc182104..45e9672a92e687980ba93cb25ef711ad1c2e3633 100644
> --- a/test/conftest.py
> +++ b/test/conftest.py
> @@ -67,11 +67,16 @@ class OutputChecker:
>              cp = re.compile(pattern, flags)
>              (buf, cnt) = cp.subn('', buf, count=count)
>  
> +        # Filter out Valgrind output lines before checking for suspicious words
> +        # ==PID== prefix: Valgrind standard messages (errors, info)
> +        # --PID-- prefix: Valgrind warnings (e.g., unhandled syscalls)
> +        buf = re.sub(r'^==[0-9]+== .*$', '', buf, flags=re.MULTILINE)
> +        buf = re.sub(r'^--[0-9]+-- .*$', '', buf, flags=re.MULTILINE)
> +
>          patterns = [ r'\b{}\b'.format(x) for x in
>                       ('exception', 'error', 'warning', 'fatal', 'traceback',
>                          'fault', 'crash(?:ed)?', 'abort(?:ed)',
>                          'uninitiali[zs]ed') ]
> -        patterns += ['^==[0-9]+== ']
>  
>          for pattern in patterns:
>              cp = re.compile(pattern, re.IGNORECASE | re.MULTILINE)
> 
> -- 
> 2.43.0
> 
> 

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 04/25] Add a new daemonize API
  2026-03-30 17:45       ` Darrick J. Wong
@ 2026-03-30 18:26         ` Bernd Schubert
  2026-03-30 21:25           ` Darrick J. Wong
  0 siblings, 1 reply; 56+ messages in thread
From: Bernd Schubert @ 2026-03-30 18:26 UTC (permalink / raw)
  To: Darrick J. Wong; +Cc: linux-fsdevel, Miklos Szeredi, Joanne Koong, Kevin Chen



On 3/30/26 19:45, Darrick J. Wong wrote:
> On Sat, Mar 28, 2026 at 12:07:35AM +0100, Bernd Schubert wrote:
>> Hi Darrick,
>>
>> On 3/27/26 23:06, Darrick J. Wong wrote:
>>> On Thu, Mar 26, 2026 at 10:34:37PM +0100, Bernd Schubert wrote:
>>>> Existing example/ file systems do the fuse_daemonize() after
>>>> fuse_session_mount() - i.e. after the mount point is already
>>>> established. Though, these example/ daemons do not start
>>>> extra threads and do not need network initialization either.
>>>
>>> AFAICT the situation here with the extra threads and async FUSE_INIT is:
>>>
>>> a) You can't start them until after fuse_daemonize() because that might
>>>    fork the whole process
>>
>> The extra threads are not cloned - they stay attached to the parent.
> 
> <nod>  I guess I should have said that explicitly -- "...because that
> might fork the whole process, and all threads remain attached to the
> parent."
> 
>>>
>>> b) You don't want to start them until you know that the mount()
>>>    succeeds (maybe?)
>>
>> For me the other way around, I want to start them before the mount and
>> let them do things like network connection/authorization. Not nice if
>> the mount has already suceeded and then part of the initialization fails
>> and a stale mount come up.
> 
> <nod> That makes sense, you want to acquire resources and start the
> threads (or fail) before calling mount().
> 
>>> c) You need those threads to be active to start serving the fuse
>>>    requests that come after FUSE_INIT
>>>
>>> d) libfuse apparently starts even more threads to wait on the iouring
>>>    queues after the fuse server returns from FUSE_INIT.
>>
>> Actually it starts these threads from FUSE_INIT and before replying
>> FUSE_INIT success. In order to tell the fuse-client/kernel if
>> fuse-server wants io-uring.
> 
> Got it.  Now I see the "XXX: Add an option to make non-available
> io-uring fatal" code 20 lines up.
> 
>>> e) fuse_loop_mt() starts up the request handler threads and waits for
>>>    the session to exit and/or for mt_finish to be sem_post()ed.
>>>
>>> Does that sound right?
>>
>>
>>>
>>> Looking at fuse4fs, I realize that it need /not/ start its background
>>> threads from the FUSE_INIT handler; all that should be done after
>>> daemonize before calling fuse_session_loop_mt.  The only reason I wrote
>>
>> So after the mount? What is if starting the threads would fail for some
>> reason?
> 
> Right now they're optional features (monitor memory PSI file and flush
> cache) so it's no big deal if they don't initialize.  But that *does*
> make the case for adding the parent/child pipelines so that you can
> daemonize earlier and report failures to the parent process.
> 
> Hrm.  Thinking about this more, the new fuse_daemonize_XXX calls make it
> possible for any fuse server to do pre-mount() initialization in the
> child and report the outcome to the parent.  Even if that fuse server
> doesn't know, care, or try to enable sync FUSE_INIT.
> 
> So this new API really is separate from FUSE_SYNC_INIT.  The latter
> depends on the former, but the reverse is not true.

Absolutely, as I wrote somewhere, this is basically my 3rd implementation of the same thing.

> 
>>> it that way was blind patterning after fuse2fs, which doesn't call
>>> daemonize() directly, so FUSE_INIT is the first time any fuse2fs code
>>> gets called after daemonizing.
>>>
>>>> fuse_daemonize() also does not allow to return notification
>>>> from the forked child to the parent.
>>>>
>>>> Complex fuse file system daemons often want the order of
>>>> 1) fork - parent watches, child does the work
>>>>
>>>> Child:
>>>>     2) start extra threads and system initialization (like network
>>>>        connection and RDMA memory registration) from the fork child.
>>>>     3) Start the fuse session after everything else succeeded
>>>>
>>>> Parent:
>>>>     Report child initialization success or failure
>>>
>>> Under classic async FUSE_INIT, the sequence in most fuse servers is:
>>>
>>> 1) The parent opens /dev/fuse and mounts the fuse filesystem before even
>>>    daemonizing
>>>
>>> 2) Mounting the fuse fs causes an async FUSE_INIT to be sent to the
>>>    queues, which sits there because nobody's looking for event yet
>>>
>>> 3) The parent daemonize()s, and the child proceeds with setting signal
>>>    handlers and starting up the fuse-request processing threads
>>>
>>> 4) The parent exits, the child continues on to set up the fuse worker
>>>    threads
>>>
>>> 5) One of the request handler threads finally reads /dev/fuse to find
>>>    the FUSE_INIT request and processes it
>>>
>>> 6) do_init (in the lowlevel fuse library) starts up the uring workers
>>>    if the kernel acknowledges the uring feature.  The fuse server has
>>>    no means to discover if the fusedev would permit uring before
>>>    calling mount().
>>
>> Yeah, initially I wanted to handle that through an ioctl and independent
>> of FUSE_INIT, Miklos had asked to change that.
> 
> <nod>
> 
>>> Does my understanding make sense?
>>>
>>>> A new API is introduced to overcome the limitations of
>>>> fuse_daemonize()
>>>>
>>>> fuse_daemonize_start() - fork, but foreground process does not
>>>> terminate yet and watches the background.
>>>
>>> But with sync FUSE_INIT this is not workable because the child has to
>>> have done (4) before (1) can complete, or it has to set up a temporary
>>> request handler thread to process the FUSE_INIT.  That's partly why
>>> fuse_daemonize_start/success/fail() is created here, right?
>>
>> I have written two similar APIs for two different daemons at DDN (one in
>> C and the other in C++), independent of sync FUSE_INIT. For us it is
>> important to only create the mount point when the network connection
>> works - that is done by threads not under control from libfuse. With
>> network you want to see something like "authorization failure" or "host
>> not reachable" in foreground.
>>
>> With sync FUSE_INIT the additional issue of
>> FUSE_INIT-creates-the-io-uring ring thread came up - even the
>> <libfuse>/example/* file systems all don't work, because now
>> fuse_session_mount() creates the ring threads, then comes
>> fuse_daemonize() - parent exits - ring threads gone.
> 
> <nod>
> 
>>> And the rest of the reason for the new functions is to enable
>>> communication between the parent and child processes -- if one dies
>>> the other can find out about it; and the child can tell the parent the
>>> outcome of mount()ing the filesystem.
>>>
>>> I wonder -- if you know that the kernel supports synchronous FUSE_INIT,
>>> can you start the main event handling threadpool (i.e. the one created
>>> in fuse_loop_mt.c) after opening /dev/fuse (obviously) but before
>>> calling mount()?  That would make a hard requirement of having at least
>>> one event handling thread, but you wouldn't have to create this temp
>>> thread just to handle the FUSE_INIT.
>>
>> That would work and I believe it would be feasible for libfuse examples,
>> but what about all the other real file systems out there? They would
>> need to be rewritten to get sync INIT? And how many people understand
>> the difference between sync and async init? Is this temp thread causing
>> issues?
> 
> Don't fuse server authors already need to adapt their codebases to the
> new fuse_daemonize_* calls if either they want to start threads or want
> sync INIT?

If run with io-uring yes, otherwise I guess not, the current condition is

	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;
	}


want_sync_init needs to be set if you had your own daemonization process
(as we have at DDN). 

> 
> The problem I have with the temp thread is that it adds a fourth(?)
> piece of code that handles fuse requests (the existing ones being
> single-thread read, multi-thread read, iouring) and I think "err, more
> code that everyone has to understand".

We can actually switch single threaded to fuse_loop_mt.c and remove 
fuse_loop.c altogether. I.e. just run a single worker thread with 
fuse_loop_mt.c

> 
> Granted, any server that wants sync FUSE_INIT will basically have to be
> a multithreaded server.

Does it?

> 
> Shifting to fuse servers, adding a background thread does have some side
> effects -- if you want to use pthread_[gs]etspecific from fuse operation
> handlers, you have to know that FUSE_INIT will run in a different thread
> from the ones set up by fuse_session_loop_mt.  If the fuse server needs
> to preallocate the thread-specific variables, it has to know to add an
> extra preallocation for the FUSE_INIT thread.
> 
> (Or you can't use them from FUSE_INIT)

Hmm, I was hoping these would libfuse internal threads and you never would
need to care about such things.

> 
>>> Even better the daemonize() changes reduce to just the pipe between
>>> parent and child and watching either for a return value or the POLLERR
>>> when either program fails unexpectedly.
>>
>> We also need to send a signal to the parent that child has success. How
>> many pipes we use is an internal detail? If we find a better way later
>> to reduce the number of pipes, even better.
> 
> <nod> Between the parent and child of a fork() operation that's fine, we
> can update the interface at any time.
> 
>>>> fuse_daemonize_success() / fuse_daemonize_fail() - background
>>>> daemon signals to the foreground process success or failure.
>>>>
>>>> fuse_daemonize_active() - helper function for the high level
>>>> interface, which needs to handle both APIs. fuse_daemonize()
>>>> is called within fuse_main(), which now needs to know if the caller
>>>> actually already used the new API itself.
>>>>
>>>> The object 'struct fuse_daemonize *' is allocated dynamically
>>>> and stored a global variable in fuse_daemonize.c, because
>>>> - high level fuse_main_real_versioned() needs to know
>>>> if already daemonized
>>>> - high level daemons do not have access to struct fuse_session
>>>> - FUSE_SYNC_INIT in later commits can only be done if the new
>>>> API (or a file system internal) daemonization is used.
>>>
>>> I don't know exactly what's required to switch libfuse into uring mode.
>>> It look as though you inject -oio_uring as a mount option, then libfuse
>>> sets up the uring, starts some extra workers to handle the ring(?) and
>>> puts them to sleep.  If the kernel says it supports uring in FUSE_INIT
>>> then do_init wakes them up.  Each uring thread submits SQEs and waits
>>> for fuse requests to appear as CQEs, right?
>>
>> Yeah, we can also make that later on a default, without the -oio_uring
>> option (there is also env for ci test purposes to enforce io-uring
>> mode). Right now it starts too many threads (got distracted today and
>> didn't send the new version of ring reduction patches - will try
>> tomorrow). And I also expected bugs in all that new code, so I didn't
>> want to make it a default.
> 
> <nod>
> 
>>>
>>> So after a fuse server negotiates with the kernel about iouring, the
>>> background threads started by fuse_loop_mt just sit there in read()
>>> doing nothing else, while new fuse requests get sent to userspace as a
>>> CQE, right?
>>
>> Exactly.
> 
> Any reason not to cancel those threads?

When and why do you want to cancel them? After at fork time and recreate
them in the fork child? I'm not sure how much liburing likes that. At
lease with IORING_SETUP_SINGLE_ISSUER and/or IORING_SETUP_COOP_TASKRUN.

> 
>> My question is now, are you ok with the new fuse_daemonize API? It
>> sounds a bit like you don't like it too much.
> 
> Now that I've come back with a fresher mind, I think I'm ok with the
> daemonize API itself.  I've a few minor nits to pick, so I'll reply to
> the patch itself.


So now it is about the temporary thread ;)


Thanks for all your reviews!


Bernd

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 13/25] Add support for the new linux mount API
  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
  0 siblings, 0 replies; 56+ messages in thread
From: Darrick J. Wong @ 2026-03-30 18:27 UTC (permalink / raw)
  To: Bernd Schubert
  Cc: linux-fsdevel, Miklos Szeredi, Joanne Koong, Kevin Chen,
	Bernd Schubert

On Thu, Mar 26, 2026 at 10:34:46PM +0100, Bernd Schubert wrote:
> From: Bernd Schubert <bschubert@ddn.com>
> 
> So far only supported for fuse_session_mount(), which is called
> from high and low level API, but not yet supported for
> fuse_open_channel(), which used for privilege drop through
> mount.fuse. Main goal for the new API is support for synchronous
> FUSE_INIT and I don't think that is going to work with
> fuse_open_channel(). At least not with io-uring support as long
> as it is started from FUSE_INIT.
> 
> Signed-off-by: Bernd Schubert <bschubert@ddn.com>
> ---
>  lib/fuse_lowlevel.c |  74 ++++++++-
>  lib/meson.build     |   3 +
>  lib/mount.c         |  27 +++-
>  lib/mount_fsmount.c | 454 ++++++++++++++++++++++++++++++++++++++++++++++++++++
>  lib/mount_i_linux.h |  14 ++
>  meson.build         |  19 ++-
>  6 files changed, 583 insertions(+), 8 deletions(-)
> 
> diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c
> index ccff6a768f0b8c32469abda9405ff29623f3fff7..a7be40cbb012361ad664a9ced3d38042ba52c681 100644
> --- a/lib/fuse_lowlevel.c
> +++ b/lib/fuse_lowlevel.c
> @@ -20,6 +20,9 @@
>  #include "util.h"
>  #include "fuse_uring_i.h"
>  #include "fuse_daemonize.h"
> +#if defined(__linux__)
> +#include "mount_i_linux.h"
> +#endif
>  
>  #include <pthread.h>
>  #include <stdatomic.h>
> @@ -4398,6 +4401,63 @@ int fuse_session_custom_io_30(struct fuse_session *se,
>  			offsetof(struct fuse_custom_io, clone_fd), fd);
>  }
>  
> +#if defined(HAVE_NEW_MOUNT_API)
> +static int fuse_session_mount_new_api(struct fuse_session *se,
> +				      const char *mountpoint)
> +{
> +	int fd = -1;
> +	int res, err;
> +	char *mnt_opts = NULL;
> +	char *mnt_opts_with_fd = NULL;
> +	char fd_opt[32];
> +
> +	res = fuse_kern_mount_get_base_mnt_opts(se->mo, &mnt_opts);
> +	err = -EIO;
> +	if (res == -1) {
> +		fuse_log(FUSE_LOG_ERR,
> +			 "fuse: failed to get base mount options\n");
> +		goto err;
> +	}
> +
> +	fd = fuse_kern_mount_prepare(mountpoint, se->mo);
> +	if (fd == -1) {
> +		fuse_log(FUSE_LOG_ERR, "Mount preparation failed.\n");
> +		err = -EIO;
> +		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) {
> +		err = -ENOMEM;
> +		goto err;
> +	}
> +
> +	err = fuse_kern_fsmount_mo(mountpoint, se->mo, mnt_opts_with_fd);
> +err:
> +	if (err) {
> +		if (fd >= 0)
> +			close(fd);
> +		fd = -1;
> +		se->fd = -1;
> +		se->error = -errno;
> +	}
> +
> +	free(mnt_opts);
> +	free(mnt_opts_with_fd);
> +	return fd;
> +}
> +#else
> +static int fuse_session_mount_new_api(struct fuse_session *se,
> +				      const char *mountpoint)
> +{
> +	(void)se;
> +	(void)mountpoint;
> +
> +	return -1;
> +}
> +#endif
> +
>  int fuse_session_mount(struct fuse_session *se, const char *_mountpoint)
>  {
>  	int fd;
> @@ -4425,6 +4485,8 @@ int fuse_session_mount(struct fuse_session *se, const char *_mountpoint)
>  			close(fd);
>  	} while (fd >= 0 && fd <= 2);
>  
> +	/* Open channel */
> +
>  	/*
>  	 * To allow FUSE daemons to run without privileges, the caller may open
>  	 * /dev/fuse before launching the file system and pass on the file
> @@ -4443,10 +4505,18 @@ int fuse_session_mount(struct fuse_session *se, const char *_mountpoint)
>  		return 0;
>  	}
>  
> -	/* Open channel */
> +	/* new linux mount api */
> +	fd = fuse_session_mount_new_api(se, mountpoint);
> +	if (fd >= 0)
> +		goto out;
> +
> +	/* fall back to old API */
> +	se->error = 0; /* reset error of new api */
>  	fd = fuse_kern_mount(mountpoint, se->mo);
> -	if (fd == -1)
> +	if (fd < 0)
>  		goto error_out;
> +
> +out:
>  	se->fd = fd;
>  
>  	/* Save mountpoint */
> diff --git a/lib/meson.build b/lib/meson.build
> index 5bd449ebffe7c9229df904d647d990c6c47f80b5..5fd738a589c5aba97a738d5eedbf0f9962e4adfc 100644
> --- a/lib/meson.build
> +++ b/lib/meson.build
> @@ -7,6 +7,9 @@ libfuse_sources = ['fuse.c', 'fuse_i.h', 'fuse_loop.c', 'fuse_loop_mt.c',
>  
>  if host_machine.system().startswith('linux')
>     libfuse_sources += [ 'mount.c' ]
> +   if private_cfg.get('HAVE_NEW_MOUNT_API', false)
> +      libfuse_sources += [ 'mount_fsmount.c' ]
> +   endif
>  else
>     libfuse_sources += [ 'mount_bsd.c' ]
>  endif
> diff --git a/lib/mount.c b/lib/mount.c
> index 43920a06ac03747595bde95441252e9cac77ce88..e8c65363d36a56f483f82434f642e785da4d0341 100644
> --- a/lib/mount.c
> +++ b/lib/mount.c
> @@ -442,8 +442,8 @@ static int fuse_mount_fusermount(const char *mountpoint, struct mount_opts *mo,
>  #define O_CLOEXEC 0
>  #endif
>  
> -static int fuse_kern_mount_prepare(const char *mnt,
> -				   struct mount_opts *mo)
> +int fuse_kern_mount_prepare(const char *mnt,
> +			    struct mount_opts *mo)
>  {
>  	char tmp[128];
>  	const char *devname = fuse_mnt_get_devname();
> @@ -493,6 +493,26 @@ out_close:
>  	return -1;
>  }
>  
> +#if defined(HAVE_NEW_MOUNT_API)
> +/**
> + * Wrapper for fuse_kern_fsmount that accepts struct mount_opts
> + * @mnt: mountpoint
> + * @mo: mount options
> + * @mnt_opts: mount options to pass to the kernel
> + *
> + * Returns: 0 on success, -1 on failure with errno set
> + */
> +int fuse_kern_fsmount_mo(const char *mnt, struct mount_opts *mo,
> +			 const char *mnt_opts)
> +{
> +	const char *devname = fuse_mnt_get_devname();
> +
> +	return fuse_kern_fsmount(mnt, mo->flags, mo->blkdev, mo->fsname,
> +				 mo->subtype, devname, mo->kernel_opts,
> +				 mnt_opts);
> +}
> +#endif
> +
>  /**
>   * Complete the mount operation with an already-opened fd
>   * @mnt: mountpoint
> @@ -636,8 +656,7 @@ void destroy_mount_opts(struct mount_opts *mo)
>  	free(mo);
>  }
>  
> -static int fuse_kern_mount_get_base_mnt_opts(struct mount_opts *mo,
> -					     char **mnt_optsp)
> +int fuse_kern_mount_get_base_mnt_opts(struct mount_opts *mo, char **mnt_optsp)
>  {
>  	if (get_mnt_flag_opts(mnt_optsp, mo->flags) == -1)
>  		return -1;
> diff --git a/lib/mount_fsmount.c b/lib/mount_fsmount.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..fbe3647a8923828dba819458b815c21bbd2beaad
> --- /dev/null
> +++ b/lib/mount_fsmount.c
> @@ -0,0 +1,454 @@
> +/*
> + *  FUSE: Filesystem in Userspace
> + *  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
> + *                2026 Bernd Schubert <bernd@bsbernd.com>
> + *
> + * New Linux mount API (fsopen/fsconfig/fsmount/move_mount) support.
> + *
> + *  This program can be distributed under the terms of the GNU LGPLv2.
> + *  See the file LGPL2.txt.
> + */
> +
> +#define _GNU_SOURCE
> +
> +#include "fuse_config.h"
> +#include "fuse_misc.h"
> +#include "mount_util.h"
> +#include "mount_i_linux.h"
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <unistd.h>
> +#include <string.h>
> +#include <fcntl.h>
> +#include <errno.h>
> +#include <sys/mount.h>
> +#include <sys/syscall.h>
> +
> +/*
> + * Mount attribute flags for fsmount() - from linux/mount.h
> + * This file is only compiled conditionally when support for the new
> + * mount API is detected - only flags that were not in the initial linux
> + * commit introducing that API are defined here.
> + */
> +#ifndef MOUNT_ATTR_NOSYMFOLLOW
> +#define MOUNT_ATTR_NOSYMFOLLOW  0x00200000
> +#endif
> +
> +/*
> + * Convert MS_* mount flags to MOUNT_ATTR_* mount attributes.
> + * These flags are passed to fsmount(), not fsconfig().
> + * Mount attributes control mount-point level behavior.
> + */
> +static unsigned long ms_flags_to_mount_attrs(unsigned long flags)
> +{
> +	unsigned long attrs = 0;
> +
> +	if (flags & MS_NOSUID)
> +		attrs |= MOUNT_ATTR_NOSUID;
> +	if (flags & MS_NODEV)
> +		attrs |= MOUNT_ATTR_NODEV;
> +	if (flags & MS_NOEXEC)
> +		attrs |= MOUNT_ATTR_NOEXEC;
> +	if (flags & MS_NOATIME)
> +		attrs |= MOUNT_ATTR_NOATIME;
> +	else if (flags & MS_RELATIME)
> +		attrs |= MOUNT_ATTR_RELATIME;
> +	else if (flags & MS_STRICTATIME)
> +		attrs |= MOUNT_ATTR_STRICTATIME;
> +	if (flags & MS_NODIRATIME)
> +		attrs |= MOUNT_ATTR_NODIRATIME;
> +	if (flags & MS_NOSYMFOLLOW)
> +		attrs |= MOUNT_ATTR_NOSYMFOLLOW;
> +
> +	return attrs;
> +}
> +
> +/*
> + * Read and print kernel error messages from fsopen fd.
> + * The kernel can provide detailed error/warning/info messages via the
> + * filesystem context fd that are more informative than strerror(errno).
> + */
> +static void log_fsconfig_kmsg(int fd)
> +{
> +	char buf[4096];
> +	int err, sz = 0;
> +
> +	err = errno;
> +
> +	while ((sz = read(fd, buf, sizeof(buf) - 1)) != -1) {
> +		if (sz <= 0)
> +			continue;
> +		if (buf[sz - 1] == '\n')
> +			buf[--sz] = '\0';
> +		else
> +			buf[sz] = '\0';
> +
> +		if (!*buf)
> +			continue;
> +
> +		switch (buf[0]) {
> +		case 'e':
> +			fprintf(stderr, " Error: %s\n", buf + 2);
> +			break;
> +		case 'w':
> +			fprintf(stderr, " Warning: %s\n", buf + 2);
> +			break;
> +		case 'i':
> +			fprintf(stderr, " Info: %s\n", buf + 2);
> +			break;
> +		default:
> +			fprintf(stderr, " %s\n", buf);
> +			break;
> +		}
> +	}
> +
> +	errno = err;
> +}
> +
> +/*
> + * Apply VFS superblock flags to the filesystem context.
> + * Only handles flags that are filesystem parameters (ro, sync, dirsync).
> + * Mount attributes (nosuid, nodev, etc.) are handled separately via fsmount().
> + */
> +static int apply_mount_flags(int fsfd, unsigned long flags)
> +{
> +	int res, save_errno;
> +
> +	/* Handle read-only flag */
> +	if (flags & MS_RDONLY) {
> +		const char *flag = "ro";
> +
> +		res = fsconfig(fsfd, FSCONFIG_SET_FLAG, flag, NULL, 0);
> +		if (res == -1) {
> +			save_errno = errno;
> +			fprintf(stderr, "fuse: fsconfig SET_FLAG %s failed:", flag);
> +			log_fsconfig_kmsg(fsfd);
> +			return -save_errno;
> +		}
> +	}
> +
> +	/* Handle sync flag */
> +	if (flags & MS_SYNCHRONOUS) {
> +		const char *flag = "sync";
> +
> +		res = fsconfig(fsfd, FSCONFIG_SET_FLAG, flag, NULL, 0);
> +		if (res == -1) {
> +			save_errno = errno;
> +			fprintf(stderr, "fuse: fsconfig SET_FLAG %s failed:", flag);
> +			log_fsconfig_kmsg(fsfd);
> +			return -save_errno;
> +		}
> +	}
> +
> +#ifndef __NetBSD__

This is always true, right?  Only Linux has fsmount.

> +	/* Handle dirsync flag */
> +	if (flags & MS_DIRSYNC) {
> +		const char *flag = "dirsync";
> +
> +		res = fsconfig(fsfd, FSCONFIG_SET_FLAG, flag, NULL, 0);
> +		if (res == -1) {
> +			save_errno = errno;
> +			fprintf(stderr, "fuse: fsconfig SET_FLAG %s failed:", flag);
> +			log_fsconfig_kmsg(fsfd);
> +			return -save_errno;
> +		}
> +	}
> +#endif
> +
> +	return 0;

I did this a bit differently:


static const struct ms_to_str_map strflags[] = {
	{ MS_SYNCHRONOUS,	"sync" },
	{ MS_DIRSYNC,		"dirsync" },
	{ MS_LAZYTIME,		"lazytime" },
	{ 0, 0 },
};

static int set_ms_flags(struct mount_service *mo, unsigned long ms_flags)
{
	const struct ms_to_str_map *i;
	int ret;

	for (i = strflags; i->ms_flag != 0; i++) {
		if (!(ms_flags & i->ms_flag))
			continue;

		ret = fsconfig(mo->fsopenfd, FSCONFIG_SET_FLAG, i->string,
			       NULL, 0);
		if (ret) {
			int error = errno;

			fprintf(stderr, "%s: set %s option: %s\n",
				mo->msgtag, i->string, strerror(error));
			emit_fsconfig_messages(mo);

			errno = error;
			return -1;
		}
		ms_flags &= ~i->ms_flag;
	}

	/*
	 * We can't translate all the supplied MS_ flags into MOUNT_ATTR_ flags
	 * or string flags!  Return a magic code so the caller will fall back
	 * to regular mount(2).
	 */
	return ms_flags ? -2 : 0;
}

That way you can always fall back to classic mount() instead of quietly
dropping an MS_ flag here if one ever gets added without a corresponding
MOUNT_ATTR_ flag.  AFAICT there aren't any that *would* get dropped and
you can verify via code inspection, but why not avoid the logic bomb?

> +}
> +
> +static int apply_opt_fd(int fsfd, const char *value)
> +{
> +	int res, save_errno;
> +
> +	/* The fd parameter is a u32 value, not a file descriptor to pass */
> +	res = fsconfig(fsfd, FSCONFIG_SET_STRING, "fd", value, 0);
> +	if (res == -1) {
> +		save_errno = errno;
> +		fprintf(stderr, "fuse: fsconfig SET_STRING fd=%s failed:",
> +			value);
> +		log_fsconfig_kmsg(fsfd);
> +		return -save_errno;
> +	}
> +	return 0;
> +}
> +
> +static int apply_opt_string(int fsfd, const char *key, const char *value)
> +{
> +	int res, save_errno;
> +
> +	res = fsconfig(fsfd, FSCONFIG_SET_STRING, key, value, 0);
> +	save_errno = errno;
> +	if (res == -1) {
> +		log_fsconfig_kmsg(fsfd);
> +		fprintf(stderr, "fuse: fsconfig SET_STRING %s=%s failed: ",
> +			key, value);
> +		return -save_errno;

I think this function should call fprintf and log_fsconfig_kmsg in the
same order as the other two apply_opt_* functions.

> +	}
> +	return 0;
> +}
> +
> +static int apply_opt_flag(int fsfd, const char *opt)
> +{
> +	int res, save_errno;
> +
> +	res = fsconfig(fsfd, FSCONFIG_SET_FLAG, opt, NULL, 0);
> +	if (res == -1) {
> +		save_errno = errno;
> +		fprintf(stderr, "fuse: fsconfig SET_FLAG %s failed:", opt);
> +		log_fsconfig_kmsg(fsfd);
> +		return -save_errno;
> +	}
> +	return 0;
> +}
> +
> +static int apply_opt_key_value(int fsfd, char *opt)
> +{
> +	char *eq;
> +	const char *key;
> +	const char *value;
> +
> +	eq = strchr(opt, '=');
> +	if (!eq)
> +		return apply_opt_flag(fsfd, opt);
> +
> +	*eq = '\0';
> +	key = opt;
> +	value = eq + 1;
> +
> +	if (strcmp(key, "fd") == 0)
> +		return apply_opt_fd(fsfd, value);
> +
> +	return apply_opt_string(fsfd, key, value);
> +}
> +
> +/**
> + * Check if an option is a mount attribute (handled by fsmount, not fsconfig)
> + */
> +static int is_mount_attr_opt(const char *opt)
> +{
> +	/* These options are mount attributes passed to fsmount(), not fsconfig() */
> +	return strcmp(opt, "nosuid") == 0 ||
> +	       strcmp(opt, "suid") == 0 ||
> +	       strcmp(opt, "nodev") == 0 ||
> +	       strcmp(opt, "dev") == 0 ||
> +	       strcmp(opt, "noexec") == 0 ||
> +	       strcmp(opt, "exec") == 0 ||
> +	       strcmp(opt, "noatime") == 0 ||
> +	       strcmp(opt, "atime") == 0 ||
> +	       strcmp(opt, "nodiratime") == 0 ||
> +	       strcmp(opt, "diratime") == 0 ||
> +	       strcmp(opt, "relatime") == 0 ||
> +	       strcmp(opt, "norelatime") == 0 ||
> +	       strcmp(opt, "strictatime") == 0 ||
> +	       strcmp(opt, "nostrictatime") == 0 ||
> +	       strcmp(opt, "nosymfollow") == 0 ||
> +	       strcmp(opt, "symfollow") == 0;

/me wonders if this should be walking the mount_flags array instead of
opencoding the strings here?

--D

> +}
> +
> +/*
> + * Parse kernel options string and apply via fsconfig
> + * Options are comma-separated key=value pairs
> + */
> +static int apply_mount_opts(int fsfd, const char *opts)
> +{
> +	char *opts_copy;
> +	char *opt;
> +	char *saveptr;
> +	int res;
> +
> +	if (!opts || !*opts)
> +		return 0;
> +
> +	opts_copy = strdup(opts);
> +	if (!opts_copy) {
> +		fprintf(stderr, "fuse: failed to allocate memory\n");
> +		return -ENOMEM;
> +	}
> +
> +	opt = strtok_r(opts_copy, ",", &saveptr);
> +	while (opt) {
> +		/*
> +		 * Skip mount attributes, they're handled by fsmount()
> +		 * not fsconfig().
> +		 *
> +		 * These string options (nosuid, nodev, etc.) are reconstructed
> +		 * from MS_* flags by get_mnt_flag_opts() in lib/mount.c and
> +		 * get_mnt_opts() in util/fusermount.c. Both the library path
> +		 * (via fuse_kern_mount_get_base_mnt_opts) and fusermount3 path
> +		 * rebuild these strings from the flags bitmask and pass them in
> +		 * mnt_opts. They must be filtered here because they are mount
> +		 * attributes (passed to fsmount via MOUNT_ATTR_*), not
> +		 * filesystem parameters (which would be passed to fsconfig).
> +		 */
> +		if (!is_mount_attr_opt(opt)) {
> +			res = apply_opt_key_value(fsfd, opt);
> +			if (res < 0) {
> +				free(opts_copy);
> +				return res;
> +			}
> +		}
> +		opt = strtok_r(NULL, ",", &saveptr);
> +	}
> +
> +	free(opts_copy);
> +	return 0;
> +}
> +
> +
> +/**
> + * Mount using the new Linux mount API (fsopen/fsconfig/fsmount/move_mount)
> + * @mnt: mountpoint
> + * @flags: mount flags (MS_NOSUID, MS_NODEV, etc.)
> + * @blkdev: 1 for fuseblk, 0 for fuse
> + * @fsname: filesystem name (or NULL)
> + * @subtype: filesystem subtype (or NULL)
> + * @source_dev: device name for building source string
> + * @kernel_opts: kernel mount options string
> + * @mnt_opts: additional mount options to pass to the kernel
> + *
> + * Returns: 0 on success, -1 on failure with errno set
> + */
> +int fuse_kern_fsmount(const char *mnt, unsigned long flags, int blkdev,
> +		      const char *fsname, const char *subtype,
> +		      const char *source_dev, const char *kernel_opts,
> +		      const char *mnt_opts)
> +{
> +	const char *type;
> +	char *source = NULL;
> +	int fsfd = -1;
> +	int mntfd = -1;
> +	int err, res;
> +	unsigned long mount_attrs;
> +
> +	/* Determine filesystem type */
> +	type = blkdev ? "fuseblk" : "fuse";
> +
> +	/* Try to open filesystem context */
> +	fsfd = fsopen(type, FSOPEN_CLOEXEC);
> +	if (fsfd == -1) {
> +		if (errno != EPERM)
> +			fprintf(stderr, "fuse: fsopen(%s) failed: %s\n", type,
> +				strerror(errno));
> +		return -1;
> +	}
> +
> +	/* Build source string */
> +	source = malloc((fsname ? strlen(fsname) : 0) +
> +			(subtype ? strlen(subtype) : 0) +
> +			strlen(source_dev) + 32);
> +	err = -ENOMEM;
> +	if (!source) {
> +		fprintf(stderr, "fuse: failed to allocate memory\n");
> +		goto out_close_fsfd;
> +	}
> +
> +	strcpy(source, fsname ? fsname : (subtype ? subtype : source_dev));
> +
> +	/* Configure source */
> +	res = fsconfig(fsfd, FSCONFIG_SET_STRING, "source", source, 0);
> +	if (res == -1) {
> +		err = -errno;
> +		log_fsconfig_kmsg(fsfd);
> +		fprintf(stderr, "fuse: fsconfig source failed: %s\n",
> +			strerror(errno));
> +		goto out_free;
> +	}
> +
> +	/* Apply VFS superblock flags (ro, sync, dirsync) */
> +	err = apply_mount_flags(fsfd, flags);
> +	if (err < 0) {
> +		log_fsconfig_kmsg(fsfd);
> +		fprintf(stderr, "fuse: failed to apply mount flags\n");
> +		goto out_free;
> +	}
> +
> +	/* Apply kernel options */
> +	err = apply_mount_opts(fsfd, kernel_opts);
> +	if (err < 0) {
> +		log_fsconfig_kmsg(fsfd);
> +		fprintf(stderr,
> +			"fuse: failed to apply kernel options '%s'\n",
> +			kernel_opts);
> +		goto out_free;
> +	}
> +
> +	/* Apply additional mount options */
> +	err = apply_mount_opts(fsfd, mnt_opts);
> +	if (err < 0) {
> +		log_fsconfig_kmsg(fsfd);
> +		fprintf(stderr,
> +			"fuse: failed to apply additional mount options '%s'\n",
> +			mnt_opts);
> +		goto out_free;
> +	}
> +
> +	/* Create the filesystem instance */
> +	res = fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0);
> +	if (res == -1) {
> +		err = -errno;
> +		log_fsconfig_kmsg(fsfd);
> +		fprintf(stderr, "fuse: fsconfig CREATE failed: %s\n",
> +			strerror(errno));
> +		goto out_free;
> +	}
> +
> +	/* Convert MS_* flags to MOUNT_ATTR_* for fsmount() */
> +	mount_attrs = ms_flags_to_mount_attrs(flags);
> +
> +	/* Create mount object with mount attributes */
> +	mntfd = fsmount(fsfd, FSMOUNT_CLOEXEC, mount_attrs);
> +	if (mntfd == -1) {
> +		err = -errno;
> +		log_fsconfig_kmsg(fsfd);
> +		fprintf(stderr, "fuse: fsmount failed: %s\n",
> +			strerror(errno));
> +		goto out_free;
> +	}
> +
> +	close(fsfd);
> +	fsfd = -1;
> +
> +	/* Attach to mount point */
> +	if (move_mount(mntfd, "", AT_FDCWD, mnt, MOVE_MOUNT_F_EMPTY_PATH) ==
> +	    -1) {
> +		err = -errno;
> +		fprintf(stderr, "fuse: move_mount failed: %s\n",
> +			strerror(errno));
> +		goto out_close_mntfd;
> +	}
> +
> +	err = fuse_mnt_add_mount_helper(mnt, source, type, mnt_opts);
> +	if (err == -1)
> +		goto out_umount;
> +
> +	close(mntfd);
> +	free(source);
> +	return 0;
> +
> +out_umount:
> +	{
> +		/* race free umount */
> +		char fd_path[64];
> +
> +		snprintf(fd_path, sizeof(fd_path), "/proc/self/fd/%d", mntfd);
> +		if (umount2(fd_path, MNT_DETACH) == -1 && errno != EINVAL) {
> +			fprintf(stderr,
> +				"fuse: cleanup umount failed: %s\n",
> +				strerror(errno));
> +		}
> +	}
> +out_close_mntfd:
> +	if (mntfd != -1)
> +		close(mntfd);
> +out_free:
> +	free(source);
> +out_close_fsfd:
> +	if (fsfd != -1)
> +		close(fsfd);
> +	errno = -err;
> +	return -1;
> +}
> +
> diff --git a/lib/mount_i_linux.h b/lib/mount_i_linux.h
> index 1bfc78b32dc4fd434870e4fff1a37e0c340d207e..34c7386715155e8640cac68c218a1e1062990ce6 100644
> --- a/lib/mount_i_linux.h
> +++ b/lib/mount_i_linux.h
> @@ -29,4 +29,18 @@ struct mount_opts {
>  	unsigned int max_read;
>  };
>  
> +int fuse_kern_mount_prepare(const char *mnt, struct mount_opts *mo);
> +
> +int fuse_kern_mount_get_base_mnt_opts(struct mount_opts *mo, char **mnt_optsp);
> +
> +int fuse_kern_fsmount(const char *mnt, unsigned long flags, int blkdev,
> +		      const char *fsname, const char *subtype,
> +		      const char *source_dev, const char *kernel_opts,
> +		      const char *mnt_opts);
> +
> +int fuse_kern_fsmount_mo(const char *mnt, struct mount_opts *mo,
> +			 const char *mnt_opts);
> +
> +int fuse_kern_fsmount_mo(const char *mnt, struct mount_opts *mo,
> +			 const char *mnt_opts);
>  #endif /* FUSE_MOUNT_I_LINUX_H_ */
> diff --git a/meson.build b/meson.build
> index 80c5f1dc0bd3565c11ad3084dac08e28dab611dc..465a7bbfd14fe9c45897f197ef37db76079d20ee 100644
> --- a/meson.build
> +++ b/meson.build
> @@ -30,7 +30,7 @@ if platform == 'darwin'
>          'https://www.fuse-t.org/ instead')
>  elif platform == 'cygwin' or platform == 'windows'
>    error('libfuse does not support Windows.\n' +
> -        'Take a look at http://www.secfs.net/winfsp/ instead')       
> +        'Take a look at http://www.secfs.net/winfsp/ instead')
>  endif
>  
>  cc = meson.get_compiler('c')
> @@ -118,6 +118,21 @@ special_funcs = {
>  	    return -1;
>  	  }
>  	}
> +    ''',
> +    'new_mount_api': '''
> +	#define _GNU_SOURCE
> +	#include <sys/mount.h>
> +	#include <linux/mount.h>
> +	#include <unistd.h>
> +	#include <fcntl.h>
> +
> +	int main(void) {
> +	    int fsfd = fsopen("fuse", FSOPEN_CLOEXEC);
> +	    int res = fsconfig(fsfd, FSCONFIG_SET_STRING, "source", "test", 0);
> +	    int mntfd = fsmount(fsfd, FSMOUNT_CLOEXEC, 0);
> +	    res = move_mount(mntfd, "", AT_FDCWD, "/mnt", MOVE_MOUNT_F_EMPTY_PATH);
> +	    return 0;
> +	}
>      '''
>  }
>  
> @@ -308,7 +323,7 @@ configure_file(output: 'libfuse_config.h',
>  include_dirs = include_directories('include', 'lib', '.')
>  
>  # Common dependencies
> -thread_dep = dependency('threads') 
> +thread_dep = dependency('threads')
>  
>  #
>  # Read build files from sub-directories
> 
> -- 
> 2.43.0
> 
> 

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 14/25] fuse mount: Support synchronous FUSE_INIT (privileged daemon)
  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
  0 siblings, 0 replies; 56+ messages in thread
From: Darrick J. Wong @ 2026-03-30 18:44 UTC (permalink / raw)
  To: Bernd Schubert
  Cc: linux-fsdevel, Miklos Szeredi, Joanne Koong, Kevin Chen,
	Bernd Schubert

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?

> +
>  #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?

>  
>  	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?

--D

>  
>  	return -1;
>  }
> @@ -4826,3 +5003,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 e8c65363d36a56f483f82434f642e785da4d0341..f19817e2675713e988bb91fc658c52b36468462b 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"
>  
> @@ -522,8 +523,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;
> 
> -- 
> 2.43.0
> 
> 

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 16/25] Move more generic mount code to mount_util.{c,h}
  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
  0 siblings, 0 replies; 56+ messages in thread
From: Darrick J. Wong @ 2026-03-30 18:47 UTC (permalink / raw)
  To: Bernd Schubert
  Cc: linux-fsdevel, Miklos Szeredi, Joanne Koong, Kevin Chen,
	Bernd Schubert

On Thu, Mar 26, 2026 at 10:34:49PM +0100, Bernd Schubert wrote:
> From: Bernd Schubert <bschubert@ddn.com>
> 
> This is to allow fusermount to use the code from mount_fsmount.c.
> I.e. avoid code dup and add just re-use the new linux api mount
> functions from that file for fusermount.
> 
> Signed-off-by: Bernd Schubert <bschubert@ddn.com>

Looks good!
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>

--D

> ---
>  lib/mount.c          |  7 -------
>  lib/mount_common_i.h |  2 --
>  lib/mount_i_linux.h  |  4 ++--
>  lib/mount_util.c     | 10 ++++++++++
>  lib/mount_util.h     |  5 +++++
>  5 files changed, 17 insertions(+), 11 deletions(-)
> 
> diff --git a/lib/mount.c b/lib/mount.c
> index f19817e2675713e988bb91fc658c52b36468462b..408e9d36896048fc167e264c95b6f6e31d86679f 100644
> --- a/lib/mount.c
> +++ b/lib/mount.c
> @@ -713,13 +713,6 @@ out:
>  	return res;
>  }
>  
> -const char *fuse_mnt_get_devname(void)
> -{
> -	const char *devname = getenv(FUSE_KERN_DEVICE_ENV);
> -
> -	return devname ? devname : "/dev/fuse";
> -}
> -
>  char *fuse_mnt_build_source(const struct mount_opts *mo)
>  {
>  	const char *devname = fuse_mnt_get_devname();
> diff --git a/lib/mount_common_i.h b/lib/mount_common_i.h
> index 34f0f9893918a14c83a57a3212f80cfc96ed2e6d..fc9ab443cc0cf6cf8438dd2ca486f6e37c8704e6 100644
> --- a/lib/mount_common_i.h
> +++ b/lib/mount_common_i.h
> @@ -24,8 +24,6 @@ struct mount_opts;
>  struct mount_opts *parse_mount_opts(struct fuse_args *args);
>  void destroy_mount_opts(struct mount_opts *mo);
>  unsigned int get_max_read(struct mount_opts *o);
> -char *fuse_mnt_build_source(const struct mount_opts *mo);
> -char *fuse_mnt_build_type(const struct mount_opts *mo);
>  
>  
>  #endif /* FUSE_MOUNT_COMMON_I_H_ */
> diff --git a/lib/mount_i_linux.h b/lib/mount_i_linux.h
> index 34c7386715155e8640cac68c218a1e1062990ce6..597b380076fccc1d38fd4d0b9108fc92a1adfa62 100644
> --- a/lib/mount_i_linux.h
> +++ b/lib/mount_i_linux.h
> @@ -41,6 +41,6 @@ int fuse_kern_fsmount(const char *mnt, unsigned long flags, int blkdev,
>  int fuse_kern_fsmount_mo(const char *mnt, struct mount_opts *mo,
>  			 const char *mnt_opts);
>  
> -int fuse_kern_fsmount_mo(const char *mnt, 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);
>  #endif /* FUSE_MOUNT_I_LINUX_H_ */
> diff --git a/lib/mount_util.c b/lib/mount_util.c
> index 9ff711023fcb41fe24f1de21aaf811cb1c12d25e..b6b75e60874ae9b2ec0c96f2a62b4ec1943a2a00 100644
> --- a/lib/mount_util.c
> +++ b/lib/mount_util.c
> @@ -10,6 +10,9 @@
>  
>  #include "fuse_config.h"
>  #include "mount_util.h"
> +#ifdef __linux__
> +#include "mount_i_linux.h"
> +#endif
>  
>  #include <stdio.h>
>  #include <unistd.h>
> @@ -451,3 +454,10 @@ int fuse_mnt_add_mount_helper(const char *mnt, const char *source,
>  	(void)mnt_opts;
>  	return 0;
>  }
> +
> +const char *fuse_mnt_get_devname(void)
> +{
> +	const char *devname = getenv(FUSE_KERN_DEVICE_ENV);
> +
> +	return devname ? devname : "/dev/fuse";
> +}
> diff --git a/lib/mount_util.h b/lib/mount_util.h
> index e62052b626875abd6118b845de162d6c5c41857a..b83db556d5fdb4bd8dd5e1750c8d11faf7373d82 100644
> --- a/lib/mount_util.h
> +++ b/lib/mount_util.h
> @@ -6,6 +6,9 @@
>    See the file LGPL2.txt.
>  */
>  
> +#ifndef FUSE_MOUNT_UTIL_H_
> +#define FUSE_MOUNT_UTIL_H_
> +
>  #include <sys/types.h>
>  #include "mount_common_i.h" // IWYU pragma: keep
>  
> @@ -31,3 +34,5 @@ int fuse_mnt_parse_fuse_fd(const char *mountpoint);
>  const char *fuse_mnt_get_devname(void);
>  int fuse_mnt_add_mount_helper(const char *mnt, const char *source,
>  			       const char *type, const char *mnt_opts);
> +
> +#endif /* FUSE_MOUNT_UTIL_H_ */
> 
> -- 
> 2.43.0
> 
> 

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 17/25] Split the fusermount do_mount function
  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
  0 siblings, 0 replies; 56+ messages in thread
From: Darrick J. Wong @ 2026-03-30 18:48 UTC (permalink / raw)
  To: Bernd Schubert
  Cc: linux-fsdevel, Miklos Szeredi, Joanne Koong, Kevin Chen,
	Bernd Schubert

On Thu, Mar 26, 2026 at 10:34:50PM +0100, Bernd Schubert wrote:
> From: Bernd Schubert <bschubert@ddn.com>
> 
> We will need new API and old API and need to pass the options to
> two different functions - factor out the option parsing.
> 
> Signed-off-by: Bernd Schubert <bschubert@ddn.com>

I like this splitting a loooong function into smaller pieces that are
easier to understand.

Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>

--D

> ---
>  util/fusermount.c | 298 +++++++++++++++++++++++++++++++++++++-----------------
>  1 file changed, 205 insertions(+), 93 deletions(-)
> 
> diff --git a/util/fusermount.c b/util/fusermount.c
> index f66c4e8e0e809351384ec10cf50ba4d3d3a831b0..8d7598998788f72ea05c2a065a88cb5efd61df35 100644
> --- a/util/fusermount.c
> +++ b/util/fusermount.c
> @@ -883,30 +883,132 @@ static int mount_notrunc(const char *source, const char *target,
>  	return mount(source, target, filesystemtype, mountflags, data);
>  }
>  
> +struct mount_params {
> +	/* Input parameters */
> +	int fd;                  /* /dev/fuse file descriptor */
> +	mode_t rootmode;         /* Root mode from stat */
> +	const char *dev;         /* Device path (/dev/fuse) */
>  
> -static int do_mount(const char *mnt, const char **typep, mode_t rootmode,
> -		    int fd, const char *opts, const char *dev, char **sourcep,
> -		    char **mnt_optsp)
> +	/* Parsed mount options */
> +	unsigned long flags;     /* Mount flags (MS_NOSUID, etc.) */
> +	char *optbuf;           /* Kernel mount options buffer */
> +	char *fsname;           /* Filesystem name from options */
> +	char *subtype;          /* Subtype from options */
> +	int blkdev;             /* Block device flag */
> +
> +	/* Generated mount parameters */
> +	char *source;           /* Mount source string */
> +	char *type;             /* Filesystem type string */
> +	char *mnt_opts;         /* Mount table options */
> +
> +	/* Pointer for optbuf manipulation */
> +	char *optbuf_end;       /* Points to end of optbuf for sprintf */
> +};
> +
> +static void free_mount_params(struct mount_params *mp)
> +{
> +	free(mp->optbuf);
> +	free(mp->fsname);
> +	free(mp->subtype);
> +	free(mp->source);
> +	free(mp->type);
> +	free(mp->mnt_opts);
> +}
> +
> +/*
> + * Check if option is deprecated large_read.
> + *
> + * Returns true if the option should be skipped (large_read on kernel > 2.4),
> + * false otherwise (all other options or large_read on old kernels).
> + */
> +static bool check_large_read(const char *opt, unsigned int len)
> +{
> +	struct utsname utsname;
> +	unsigned int kmaj, kmin;
> +	int res;
> +
> +	if (!opt_eq(opt, len, "large_read"))
> +		return false;
> +
> +	res = uname(&utsname);
> +	if (res == 0 &&
> +	    sscanf(utsname.release, "%u.%u", &kmaj, &kmin) == 2 &&
> +	    (kmaj > 2 || (kmaj == 2 && kmin > 4))) {
> +		fprintf(stderr,
> +			"%s: note: 'large_read' mount option is deprecated for %i.%i kernels\n",
> +			progname, kmaj, kmin);
> +		return true;
> +	}
> +	return false;
> +}
> +
> +/*
> + * Check if user has permission to use allow_other or allow_root options.
> + *
> + * Returns -1 if permission denied, 0 if allowed or option is not
> + * allow_other/allow_root.
> + */
> +static int check_allow_permission(const char *opt, unsigned int len)
> +{
> +	if (getuid() != 0 && !user_allow_other &&
> +	    (opt_eq(opt, len, "allow_other") || opt_eq(opt, len, "allow_root"))) {
> +		fprintf(stderr, "%s: option %.*s only allowed if 'user_allow_other' is set in %s\n",
> +			progname, len, opt, FUSE_CONF);
> +		return -1;
> +	}
> +	return 0;
> +}
> +
> +/*
> + * Process generic mount option.
> + *
> + * Handles mount flags (ro, rw, suid, etc.), kernel options
> + * (default_permissions, allow_other, max_read, blksize), or exits on
> + * unknown options.
> + */
> +static int process_generic_option(const char *opt, unsigned int len,
> +				   unsigned long *flags, char **dest)
> +{
> +	int on;
> +	int flag;
> +
> +	if (find_mount_flag(opt, len, &on, &flag)) {
> +		if (on)
> +			*flags |= flag;
> +		else
> +			*flags &= ~flag;
> +		return 0;
> +	}
> +
> +	if (opt_eq(opt, len, "default_permissions") ||
> +	    opt_eq(opt, len, "allow_other") ||
> +	    begins_with(opt, "max_read=") ||
> +	    begins_with(opt, "blksize=")) {
> +		memcpy(*dest, opt, len);
> +		*dest += len;
> +		**dest = ',';
> +		(*dest)++;
> +		return 0;
> +	}
> +
> +	fprintf(stderr, "%s: unknown option '%.*s'\n", progname, len, opt);
> +	exit(1);
> +}
> +
> +static int prepare_mount(const char *opts, struct mount_params *mp)
>  {
>  	int res;
> -	int flags = MS_NOSUID | MS_NODEV;
> -	char *optbuf;
> -	char *mnt_opts = NULL;
>  	const char *s;
>  	char *d;
> -	char *fsname = NULL;
> -	char *subtype = NULL;
> -	char *source = NULL;
> -	char *type = NULL;
> -	int blkdev = 0;
>  
> -	optbuf = (char *) malloc(strlen(opts) + 128);
> -	if (!optbuf) {
> +	mp->flags = MS_NOSUID | MS_NODEV;
> +	mp->optbuf = (char *) malloc(strlen(opts) + 128);
> +	if (!mp->optbuf) {
>  		fprintf(stderr, "%s: failed to allocate memory\n", progname);
>  		return -1;
>  	}
>  
> -	for (s = opts, d = optbuf; *s;) {
> +	for (s = opts, d = mp->optbuf; *s;) {
>  		unsigned len;
>  		const char *fsname_str = "fsname=";
>  		const char *subtype_str = "subtype=";
> @@ -919,10 +1021,10 @@ static int do_mount(const char *mnt, const char **typep, mode_t rootmode,
>  				break;
>  		}
>  		if (begins_with(s, fsname_str)) {
> -			if (!get_string_opt(s, len, fsname_str, &fsname))
> +			if (!get_string_opt(s, len, fsname_str, &mp->fsname))
>  				goto err;
>  		} else if (begins_with(s, subtype_str)) {
> -			if (!get_string_opt(s, len, subtype_str, &subtype))
> +			if (!get_string_opt(s, len, subtype_str, &mp->subtype))
>  				goto err;
>  		} else if (opt_eq(s, len, "blkdev")) {
>  			if (getuid() != 0) {
> @@ -931,7 +1033,7 @@ static int do_mount(const char *mnt, const char **typep, mode_t rootmode,
>  					progname);
>  				goto err;
>  			}
> -			blkdev = 1;
> +			mp->blkdev = 1;
>  		} else if (opt_eq(s, len, "auto_unmount")) {
>  			auto_unmount = 1;
>  		} else if (!opt_eq(s, len, "nonempty") &&
> @@ -939,122 +1041,132 @@ static int do_mount(const char *mnt, const char **typep, mode_t rootmode,
>  			   !begins_with(s, "rootmode=") &&
>  			   !begins_with(s, "user_id=") &&
>  			   !begins_with(s, "group_id=")) {
> -			int on;
> -			int flag;
> -			int skip_option = 0;
> -			if (opt_eq(s, len, "large_read")) {
> -				struct utsname utsname;
> -				unsigned kmaj, kmin;
> -				res = uname(&utsname);
> -				if (res == 0 &&
> -				    sscanf(utsname.release, "%u.%u",
> -					   &kmaj, &kmin) == 2 &&
> -				    (kmaj > 2 || (kmaj == 2 && kmin > 4))) {
> -					fprintf(stderr, "%s: note: 'large_read' mount option is deprecated for %i.%i kernels\n", progname, kmaj, kmin);
> -					skip_option = 1;
> -				}
> -			}
> -			if (getuid() != 0 && !user_allow_other &&
> -			    (opt_eq(s, len, "allow_other") ||
> -			     opt_eq(s, len, "allow_root"))) {
> -				fprintf(stderr, "%s: option %.*s only allowed if 'user_allow_other' is set in %s\n", progname, len, s, FUSE_CONF);
> +			bool skip;
> +
> +			if (check_allow_permission(s, len) == -1)
>  				goto err;
> -			}
> -			if (!skip_option) {
> -				if (find_mount_flag(s, len, &on, &flag)) {
> -					if (on)
> -						flags |= flag;
> -					else
> -						flags  &= ~flag;
> -				} else if (opt_eq(s, len, "default_permissions") ||
> -					   opt_eq(s, len, "allow_other") ||
> -					   begins_with(s, "max_read=") ||
> -					   begins_with(s, "blksize=")) {
> -					memcpy(d, s, len);
> -					d += len;
> -					*d++ = ',';
> -				} else {
> -					fprintf(stderr, "%s: unknown option '%.*s'\n", progname, len, s);
> -					exit(1);
> -				}
> -			}
> +
> +			skip = check_large_read(s, len);
> +
> +			/*
> +			 * Skip deprecated large_read to avoid passing it to
> +			 * kernel which would reject it as unknown option.
> +			 */
> +			if (!skip)
> +				process_generic_option(s, len, &mp->flags, &d);
>  		}
>  		s += len;
>  		if (*s)
>  			s++;
>  	}
>  	*d = '\0';
> -	res = get_mnt_opts(flags, optbuf, &mnt_opts);
> +	res = get_mnt_opts(mp->flags, mp->optbuf, &mp->mnt_opts);
>  	if (res == -1)
>  		goto err;
>  
> +	mp->optbuf_end = d;
> +
>  	sprintf(d, "fd=%i,rootmode=%o,user_id=%u,group_id=%u",
> -		fd, rootmode, getuid(), getgid());
> +		mp->fd, mp->rootmode, getuid(), getgid());
>  
> -	source = malloc((fsname ? strlen(fsname) : 0) +
> -			(subtype ? strlen(subtype) : 0) + strlen(dev) + 32);
> +	mp->source = malloc((mp->fsname ? strlen(mp->fsname) : 0) +
> +			(mp->subtype ? strlen(mp->subtype) : 0) + strlen(mp->dev) + 32);
>  
> -	type = malloc((subtype ? strlen(subtype) : 0) + 32);
> -	if (!type || !source) {
> +	mp->type = malloc((mp->subtype ? strlen(mp->subtype) : 0) + 32);
> +	if (!mp->type || !mp->source) {
>  		fprintf(stderr, "%s: failed to allocate memory\n", progname);
>  		goto err;
>  	}
>  
> -	if (subtype)
> -		sprintf(type, "%s.%s", blkdev ? "fuseblk" : "fuse", subtype);
> +	if (mp->subtype)
> +		sprintf(mp->type, "%s.%s", mp->blkdev ? "fuseblk" : "fuse", mp->subtype);
>  	else
> -		strcpy(type, blkdev ? "fuseblk" : "fuse");
> +		strcpy(mp->type, mp->blkdev ? "fuseblk" : "fuse");
>  
> -	if (fsname)
> -		strcpy(source, fsname);
> +	if (mp->fsname)
> +		strcpy(mp->source, mp->fsname);
>  	else
> -		strcpy(source, subtype ? subtype : dev);
> +		strcpy(mp->source, mp->subtype ? mp->subtype : mp->dev);
>  
> -	res = mount_notrunc(source, mnt, type, flags, optbuf);
> -	if (res == -1 && errno == ENODEV && subtype) {
> +	return 0;
> +
> +err:
> +	free_mount_params(mp);
> +	return -1;
> +}
> +
> +/*
> + * Perform the actual mount operation using prepared parameters.
> + *
> + * Returns 0 on success, -1 on failure.
> + */
> +static int perform_mount(const char *mnt, struct mount_params *mp)
> +{
> +	int res;
> +
> +	res = mount_notrunc(mp->source, mnt, mp->type, mp->flags, mp->optbuf);
> +	if (res == -1 && errno == ENODEV && mp->subtype) {
>  		/* Probably missing subtype support */
> -		strcpy(type, blkdev ? "fuseblk" : "fuse");
> -		if (fsname) {
> -			if (!blkdev)
> -				sprintf(source, "%s#%s", subtype, fsname);
> +		strcpy(mp->type, mp->blkdev ? "fuseblk" : "fuse");
> +		if (mp->fsname) {
> +			if (!mp->blkdev)
> +				sprintf(mp->source, "%s#%s", mp->subtype, mp->fsname);
>  		} else {
> -			strcpy(source, type);
> +			strcpy(mp->source, mp->type);
>  		}
>  
> -		res = mount_notrunc(source, mnt, type, flags, optbuf);
> +		res = mount_notrunc(mp->source, mnt, mp->type, mp->flags, mp->optbuf);
>  	}
>  	if (res == -1 && errno == EINVAL) {
>  		/* It could be an old version not supporting group_id */
> -		sprintf(d, "fd=%i,rootmode=%o,user_id=%u",
> -			fd, rootmode, getuid());
> -		res = mount_notrunc(source, mnt, type, flags, optbuf);
> +		sprintf(mp->optbuf_end, "fd=%i,rootmode=%o,user_id=%u",
> +			mp->fd, mp->rootmode, getuid());
> +		res = mount_notrunc(mp->source, mnt, mp->type, mp->flags, mp->optbuf);
>  	}
>  	if (res == -1) {
>  		int errno_save = errno;
> -		if (blkdev && errno == ENODEV && !fuse_mnt_check_fuseblk())
> +		if (mp->blkdev && errno == ENODEV && !fuse_mnt_check_fuseblk())
>  			fprintf(stderr, "%s: 'fuseblk' support missing\n",
>  				progname);
>  		else
>  			fprintf(stderr, "%s: mount failed: %s\n", progname,
>  				strerror(errno_save));
> -		goto err;
> +		return -1;
>  	}
> -	*sourcep = source;
> -	*typep = type;
> -	*mnt_optsp = mnt_opts;
> -	free(fsname);
> -	free(optbuf);
>  
>  	return 0;
> +}
>  
> -err:
> -	free(fsname);
> -	free(subtype);
> -	free(source);
> -	free(type);
> -	free(mnt_opts);
> -	free(optbuf);
> -	return -1;
> +static int do_mount(const char *mnt, const char **typep, mode_t rootmode,
> +		    int fd, const char *opts, const char *dev, char **sourcep,
> +		    char **mnt_optsp)
> +{
> +	struct mount_params mp = { .fd = fd }; /* implicit zero of other params */
> +	int res;
> +
> +	mp.rootmode = rootmode;
> +	mp.dev = dev;
> +
> +	res = prepare_mount(opts, &mp);
> +	if (res == -1)
> +		return -1;
> +
> +	res = perform_mount(mnt, &mp);
> +	if (res == -1) {
> +		free_mount_params(&mp);
> +		return -1;
> +	}
> +
> +	*sourcep = mp.source;
> +	*typep = mp.type;
> +	*mnt_optsp = mp.mnt_opts;
> +
> +	/* Free only the intermediate allocations, not the returned ones */
> +	free(mp.fsname);
> +	free(mp.subtype);
> +	free(mp.optbuf);
> +
> +	return 0;
>  }
>  
>  static int check_perm(const char **mntp, struct stat *stbuf, int *mountpoint_fd)
> 
> -- 
> 2.43.0
> 
> 

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 20/25] Make fusermount work bidirectional for sync init
  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
  0 siblings, 0 replies; 56+ messages in thread
From: Darrick J. Wong @ 2026-03-30 19:03 UTC (permalink / raw)
  To: Bernd Schubert
  Cc: linux-fsdevel, Miklos Szeredi, Joanne Koong, Kevin Chen,
	Bernd Schubert

On Thu, Mar 26, 2026 at 10:34:53PM +0100, Bernd Schubert wrote:
> From: Bernd Schubert <bschubert@ddn.com>
> 
> Signed-off-by: Bernd Schubert <bschubert@ddn.com>
> ---
>  util/fusermount.c | 302 +++++++++++++++++++++++++++++++++++++++++++++++++++---
>  util/meson.build  |   2 +-
>  2 files changed, 291 insertions(+), 13 deletions(-)
> 
> diff --git a/util/fusermount.c b/util/fusermount.c
> index d42e16955f6d26f81e6d2a5acb040b1448e20b51..31bf959024ae0cd2a4e50974589bfab30a100b0a 100644
> --- a/util/fusermount.c
> +++ b/util/fusermount.c
> @@ -11,6 +11,9 @@
>  #include "fuse_config.h"
>  #include "mount_util.h"
>  #include "util.h"
> +#if __linux__
> +#include "mount_i_linux.h"
> +#endif
>  
>  #include <stdio.h>
>  #include <stdlib.h>
> @@ -923,6 +926,7 @@ static void free_mount_params(struct mount_params *mp)
>  	free(mp->source);
>  	free(mp->type);
>  	free(mp->mnt_opts);
> +	memset(mp, 0, sizeof(*mp));
>  }
>  
>  /*
> @@ -1309,6 +1313,171 @@ static int open_fuse_device(const char *dev)
>  	return fd;
>  }
>  
> +/*
> + * Context for split mount operation (sync-init mode)
> + */
> +struct mount_context {
> +	int fd;
> +	const char *dev;
> +	struct stat stbuf;
> +	char *source;
> +	char *mnt_opts;
> +	char *x_opts;
> +	const char *type;
> +};
> +
> +/*
> + * Phase 1: Open device and prepare for mount (sync-init mode)
> + * Returns fd on success, -1 on failure
> + */
> +static int mount_fuse_prepare(const char *mnt, const char *opts,
> +			      struct mount_context *ctx)
> +{
> +	int res;
> +	int mountpoint_fd = -1;
> +	char *do_mount_opts = NULL;
> +	const char *real_mnt = mnt;
> +
> +	memset(ctx, 0, sizeof(*ctx));
> +	ctx->dev = getenv(FUSE_KERN_DEVICE_ENV) ?: FUSE_DEV;

Should this use the helper to figure out the fuse device path?

> +
> +	ctx->fd = open_fuse_device(ctx->dev);
> +	if (ctx->fd == -1)
> +		return -1;
> +
> +	drop_privs();
> +	read_conf();
> +
> +	if (getuid() != 0 && mount_max != -1) {
> +		int mount_count = count_fuse_fs();
> +
> +		if (mount_count >= mount_max) {
> +			fprintf(stderr,
> +				"%s: too many FUSE filesystems mounted; mount_max=N can be set in %s\n",
> +				progname, FUSE_CONF);
> +			goto fail_close_fd;
> +		}
> +	}
> +
> +	res = extract_x_options(opts, &do_mount_opts, &ctx->x_opts);
> +	if (res)
> +		goto fail_close_fd;
> +
> +	res = check_perm(&real_mnt, &ctx->stbuf, &mountpoint_fd);
> +	restore_privs();
> +
> +	if (mountpoint_fd != -1)
> +		close(mountpoint_fd);
> +
> +	if (res == -1)
> +		goto fail_close_fd;
> +
> +	free(do_mount_opts);
> +	return ctx->fd;
> +
> +fail_close_fd:
> +	close(ctx->fd);
> +	free(do_mount_opts);
> +	free(ctx->x_opts);
> +	ctx->fd = -1;
> +	return -1;
> +}
> +
> +#ifdef HAVE_NEW_MOUNT_API
> +/*
> + * Phase 2: Perform the actual mount using new mount API (sync-init mode)
> + * Returns 0 on success, -1 on failure
> + */
> +static int mount_fuse_finish_fsmount(const char *mnt, const char *opts,
> +				     struct mount_context *ctx,
> +				     const char **type)
> +{
> +	int res;
> +	char *do_mount_opts = NULL;
> +	char *x_prefixed_opts = NULL;
> +	struct mount_params mp = {
> +		.fd = ctx->fd,
> +		.rootmode = ctx->stbuf.st_mode & S_IFMT,
> +		.dev = ctx->dev,
> +	};
> +	char *final_mnt_opts = NULL;
> +
> +	/* Extract x-options */
> +	res = extract_x_options(opts, &do_mount_opts, &x_prefixed_opts);

Didn't we already extract_x_options() in phase 1?  Can we save
do_mount_opts in ctx instead of calling this twice?

> +	if (res)
> +		goto fail;
> +
> +	res = prepare_mount(do_mount_opts, &mp);
> +	if (res == -1)
> +		goto fail;
> +
> +	/* Merge x-options if running as root */
> +	final_mnt_opts = mp.mnt_opts;
> +	if (geteuid() == 0 && ctx->x_opts && strlen(ctx->x_opts) > 0) {
> +		size_t mnt_opts_len = strlen(mp.mnt_opts);
> +		size_t x_mnt_opts_len = mnt_opts_len + strlen(ctx->x_opts) + 2;
> +		char *x_mnt_opts = calloc(1, x_mnt_opts_len);
> +
> +		if (!x_mnt_opts)
> +			goto fail_free_params;
> +
> +		if (mnt_opts_len) {
> +			strcpy(x_mnt_opts, mp.mnt_opts);
> +			strncat(x_mnt_opts, ",", 2);
> +		}
> +		strncat(x_mnt_opts, ctx->x_opts,
> +			x_mnt_opts_len - mnt_opts_len - 2);

asprintf?

--D

> +
> +		final_mnt_opts = x_mnt_opts;
> +	}
> +
> +	/* Use new mount API */
> +	res = fuse_kern_fsmount(mnt, mp.flags, mp.blkdev,
> +				mp.fsname, mp.subtype, ctx->dev,
> +				mp.optbuf, final_mnt_opts);
> +	if (res == -1)
> +		goto fail_free_merged;
> +
> +	/* Change to root directory */
> +	res = chdir("/");
> +	if (res == -1) {
> +		fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
> +		goto fail_free_merged;
> +	}
> +
> +	/* Store results in context */
> +	ctx->source = mp.source;
> +	ctx->type = mp.type;
> +	ctx->mnt_opts = final_mnt_opts;
> +	*type = mp.type;
> +
> +	res = 0;
> +
> +	/* Only free what is not assigned to ctx */
> +	free(mp.fsname);
> +	free(mp.subtype);
> +	free(mp.optbuf);
> +	if (final_mnt_opts != mp.mnt_opts)
> +		free(mp.mnt_opts);
> +
> +out:
> +	free(do_mount_opts);
> +	free(x_prefixed_opts);
> +
> +	return res;
> +
> +fail_free_merged:
> +	if (final_mnt_opts != mp.mnt_opts)
> +		free(final_mnt_opts);
> +fail_free_params:
> +	free_mount_params(&mp);
> +fail:
> +	res = -1;
> +	goto out;
> +}
> +#endif /* HAVE_NEW_MOUNT_API */
> +
> +
>  static int mount_fuse(const char *mnt, const char *opts, const char **type)
>  {
>  	int res;
> @@ -1404,6 +1573,65 @@ fail_close_fd:
>  	goto out_free;
>  }
>  
> +/* Forward declarations for helper functions */
> +static int send_fd(int sock_fd, int fd);
> +static int wait_for_signal(int sock_fd);
> +
> +#ifdef HAVE_NEW_MOUNT_API
> +/*
> + * Perform sync-init mount using new mount API
> + * Returns 0 on success, -1 on failure
> + */
> +static int mount_fuse_sync_init(const char *mnt, const char *opts,
> +				int cfd, const char **type)
> +{
> +	struct mount_context ctx;
> +	int fd, res;
> +	int32_t status, send_res;
> +
> +	/* Phase 1: Open device and prepare */
> +	fd = mount_fuse_prepare(mnt, opts, &ctx);
> +	if (fd == -1)
> +		return -1;
> +
> +	/* Send fd to caller so it can start worker thread */
> +	res = send_fd(cfd, fd);
> +	if (res != 0) {
> +		close(fd);
> +		free(ctx.x_opts);
> +		return -1;
> +	}
> +
> +	/* Wait for caller to signal that worker thread is ready */
> +	res = wait_for_signal(cfd);
> +	if (res != 0) {
> +		close(fd);
> +		free(ctx.x_opts);
> +		return -1;
> +	}
> +
> +	/* Phase 2: Perform the actual mount using new API */
> +	res = mount_fuse_finish_fsmount(mnt, opts, &ctx, type);
> +
> +	/* Send mount result back to caller (4-byte error code) */
> +	status = (res == 0) ? 0 : -(int32_t)errno;
> +	do {
> +		send_res = send(cfd, &status, sizeof(status), 0);
> +	} while (send_res == -1 && errno == EINTR);
> +	if (send_res != sizeof(status)) {
> +		fprintf(stderr, "%s: failed to send mount status: %s\n",
> +			progname, strerror(errno));
> +	}
> +
> +	close(fd);
> +	free(ctx.source);
> +	free(ctx.mnt_opts);
> +	free(ctx.x_opts);
> +
> +	return res;
> +}
> +#endif /* HAVE_NEW_MOUNT_API */
> +
>  static int send_fd(int sock_fd, int fd)
>  {
>  	int retval;
> @@ -1440,6 +1668,30 @@ static int send_fd(int sock_fd, int fd)
>  	return 0;
>  }
>  
> +/*
> + * Wait for a signal byte from the caller.
> + * Returns 0 on success, -1 on error.
> + */
> +static int wait_for_signal(int sock_fd)
> +{
> +	char buf[1];
> +	int res;
> +
> +	do {
> +		res = recv(sock_fd, buf, sizeof(buf), 0);
> +	} while (res == -1 && errno == EINTR);
> +	if (res != 1) {
> +		if (res == 0)
> +			fprintf(stderr, "%s: connection closed while waiting for signal\n",
> +				progname);
> +		else
> +			fprintf(stderr, "%s: error receiving signal: %s\n",
> +				progname, strerror(errno));
> +		return -1;
> +	}
> +	return 0;
> +}
> +
>  /* Helper for should_auto_unmount
>   *
>   * fusermount typically has the s-bit set - initial open of `mnt` was as root
> @@ -1631,6 +1883,7 @@ int main(int argc, char *argv[])
>  	const char *opts = "";
>  	const char *type = NULL;
>  	int setup_auto_unmount_only = 0;
> +	int sync_init_mode = 0;
>  
>  	static const struct option long_opts[] = {
>  		{"unmount", no_argument, NULL, 'u'},
> @@ -1643,6 +1896,7 @@ int main(int argc, char *argv[])
>  		// They'ne meant for internal use by mount.c
>  		{"auto-unmount", no_argument, NULL, 'U'},
>  		{"comm-fd", required_argument, NULL, 'c'},
> +		{"sync-init", no_argument, NULL, 'S'},
>  		{0, 0, 0, 0}};
>  
>  	progname = strdup(argc > 0 ? argv[0] : "fusermount");
> @@ -1677,6 +1931,9 @@ int main(int argc, char *argv[])
>  		case 'c':
>  			commfd = optarg;
>  			break;
> +		case 'S':
> +			sync_init_mode = 1;
> +			break;
>  		case 'z':
>  			lazy = 1;
>  			break;
> @@ -1754,21 +2011,42 @@ int main(int argc, char *argv[])
>  	if (setup_auto_unmount_only)
>  		goto wait_for_auto_unmount;
>  
> -	fd = mount_fuse(mnt, opts, &type);
> -	if (fd == -1)
> -		goto err_out;
> +	if (sync_init_mode) {
> +#ifdef HAVE_NEW_MOUNT_API
> +		res = mount_fuse_sync_init(mnt, opts, cfd, &type);
> +		if (res == -1)
> +			goto err_out;
>  
> -	res = send_fd(cfd, fd);
> -	if (res != 0) {
> -		umount2(mnt, MNT_DETACH); /* lazy umount */
> +		if (!auto_unmount) {
> +			free(mnt);
> +			free((void *) type);
> +			return 0;
> +		}
> +		/* Continue to auto_unmount handling below */
> +#else
> +		fprintf(stderr, "%s: sync-init mode requires new mount API support\n",
> +			progname);
> +		fprintf(stderr, "%s: kernel or headers too old (need fsopen/fsmount)\n",
> +			progname);
>  		goto err_out;
> -	}
> -	close(fd);
> +#endif
> +	} else {
> +		fd = mount_fuse(mnt, opts, &type);
> +		if (fd == -1)
> +			goto err_out;
>  
> -	if (!auto_unmount) {
> -		free(mnt);
> -		free((void*) type);
> -		return 0;
> +		res = send_fd(cfd, fd);
> +		if (res != 0) {
> +			umount2(mnt, MNT_DETACH); /* lazy umount */
> +			goto err_out;
> +		}
> +		close(fd);
> +
> +		if (!auto_unmount) {
> +			free(mnt);
> +			free((void *) type);
> +			return 0;
> +		}
>  	}
>  
>  wait_for_auto_unmount:
> diff --git a/util/meson.build b/util/meson.build
> index 0e4b1cce95377e73af7dc45655a7088315497ddb..731ef95488461ac21c21b1972a96d58b1187dc5a 100644
> --- a/util/meson.build
> +++ b/util/meson.build
> @@ -1,6 +1,6 @@
>  fuseconf_path = join_paths(get_option('prefix'), get_option('sysconfdir'), 'fuse.conf')
>  
> -executable('fusermount3', ['fusermount.c', '../lib/mount_util.c', '../lib/util.c'],
> +executable('fusermount3', ['fusermount.c', '../lib/mount_util.c', '../lib/mount_fsmount.c', '../lib/util.c'],
>             include_directories: include_dirs,
>             install: true,
>             install_dir: get_option('bindir'),
> 
> -- 
> 2.43.0
> 
> 

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 23/25] Move fuse_mnt_build_{source,type} to mount_util.c
  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
  0 siblings, 0 replies; 56+ messages in thread
From: Darrick J. Wong @ 2026-03-30 19:04 UTC (permalink / raw)
  To: Bernd Schubert; +Cc: linux-fsdevel, Miklos Szeredi, Joanne Koong, Kevin Chen

On Thu, Mar 26, 2026 at 10:34:56PM +0100, Bernd Schubert wrote:
> Also make it more generic and avoid taking struct mount_opts
> as parameter.
> 
> mount_fsmount.c and fusermount.c use these functions now.
> 
> Signed-off-by: Bernd Schubert <bernd@bsbernd.com>

Woot!
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>

--D

> ---
>  lib/mount.c         | 37 +++----------------------------------
>  lib/mount_fsmount.c | 29 ++++++++++++-----------------
>  lib/mount_i_linux.h |  2 --
>  lib/mount_util.c    | 31 +++++++++++++++++++++++++++++++
>  lib/mount_util.h    |  5 +++++
>  util/fusermount.c   | 16 ++--------------
>  6 files changed, 53 insertions(+), 67 deletions(-)
> 
> diff --git a/lib/mount.c b/lib/mount.c
> index f0d12525310a9ad239c0f447af60bd49b105a091..1e82adc8621da150e1ee61e8253b261a33625260 100644
> --- a/lib/mount.c
> +++ b/lib/mount.c
> @@ -651,10 +651,11 @@ int fuse_kern_do_mount(const char *mnt, struct mount_opts *mo,
>  	char *source = NULL;
>  	char *type = NULL;
>  	int res;
> +	const char *devname = fuse_mnt_get_devname();
>  
>  	res = -ENOMEM;
> -	source = fuse_mnt_build_source(mo);
> -	type = fuse_mnt_build_type(mo);
> +	source = fuse_mnt_build_source(mo->fsname, mo->subtype, devname);
> +	type = fuse_mnt_build_type(mo->blkdev, mo->subtype);
>  	if (!type || !source) {
>  		fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate memory\n");
>  		goto out_close;
> @@ -834,35 +835,3 @@ out:
>  	free(mnt_opts);
>  	return res;
>  }
> -
> -char *fuse_mnt_build_source(const struct mount_opts *mo)
> -{
> -	const char *devname = fuse_mnt_get_devname();
> -	char *source;
> -	int ret;
> -
> -	ret = asprintf(&source, "%s",
> -		       mo->fsname ? mo->fsname :
> -				    (mo->subtype ? mo->subtype : devname));
> -	if (ret == -1)
> -		return NULL;
> -
> -	return source;
> -}
> -
> -char *fuse_mnt_build_type(const struct mount_opts *mo)
> -{
> -	char *type;
> -	int ret;
> -
> -	if (mo->subtype)
> -		ret = asprintf(&type, "%s.%s", mo->blkdev ? "fuseblk" : "fuse",
> -			       mo->subtype);
> -	else
> -		ret = asprintf(&type, "%s", mo->blkdev ? "fuseblk" : "fuse");
> -
> -	if (ret == -1)
> -		return NULL;
> -
> -	return type;
> -}
> diff --git a/lib/mount_fsmount.c b/lib/mount_fsmount.c
> index f42ae97faedaf2420d97e701a22725d4293e4853..57355db89f907e0dbd910ead4ea39005ec26c44d 100644
> --- a/lib/mount_fsmount.c
> +++ b/lib/mount_fsmount.c
> @@ -329,15 +329,21 @@ int fuse_kern_fsmount(const char *mnt, unsigned long flags, int blkdev,
>  		      const char *source_dev, const char *kernel_opts,
>  		      const char *mnt_opts)
>  {
> -	const char *type;
> +	char *type = NULL;
>  	char *source = NULL;
>  	int fsfd = -1;
>  	int mntfd = -1;
>  	int err, res;
>  	unsigned long mount_attrs;
>  
> -	/* Determine filesystem type */
> -	type = blkdev ? "fuseblk" : "fuse";
> +	/* Build type and source strings */
> +	type = fuse_mnt_build_type(blkdev, subtype);
> +	source = fuse_mnt_build_source(fsname, subtype, source_dev);
> +	err = -ENOMEM;
> +	if (!type || !source) {
> +		fprintf(stderr, "fuse: failed to allocate memory\n");
> +		goto out_free;
> +	}
>  
>  	/* Try to open filesystem context */
>  	fsfd = fsopen(type, FSOPEN_CLOEXEC);
> @@ -345,21 +351,9 @@ int fuse_kern_fsmount(const char *mnt, unsigned long flags, int blkdev,
>  		if (errno != EPERM)
>  			fprintf(stderr, "fuse: fsopen(%s) failed: %s\n", type,
>  				strerror(errno));
> -		return -1;
> +		goto out_free;
>  	}
>  
> -	/* Build source string */
> -	source = malloc((fsname ? strlen(fsname) : 0) +
> -			(subtype ? strlen(subtype) : 0) +
> -			strlen(source_dev) + 32);
> -	err = -ENOMEM;
> -	if (!source) {
> -		fprintf(stderr, "fuse: failed to allocate memory\n");
> -		goto out_close_fsfd;
> -	}
> -
> -	strcpy(source, fsname ? fsname : (subtype ? subtype : source_dev));
> -
>  	/* Configure source */
>  	res = fsconfig(fsfd, FSCONFIG_SET_STRING, "source", source, 0);
>  	if (res == -1) {
> @@ -439,6 +433,7 @@ int fuse_kern_fsmount(const char *mnt, unsigned long flags, int blkdev,
>  
>  	close(mntfd);
>  	free(source);
> +	free(type);
>  	return 0;
>  
>  out_umount:
> @@ -458,7 +453,7 @@ out_close_mntfd:
>  		close(mntfd);
>  out_free:
>  	free(source);
> -out_close_fsfd:
> +	free(type);
>  	if (fsfd != -1)
>  		close(fsfd);
>  	errno = -err;
> diff --git a/lib/mount_i_linux.h b/lib/mount_i_linux.h
> index ae98296a334a2aa39c4f3e5c3e4e3c136ae38cd3..8fc27b958eab3141ab852a7cb5098febd484e04f 100644
> --- a/lib/mount_i_linux.h
> +++ b/lib/mount_i_linux.h
> @@ -41,8 +41,6 @@ int fuse_kern_fsmount(const char *mnt, unsigned long flags, int blkdev,
>  int fuse_kern_fsmount_mo(const char *mnt, 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,
> diff --git a/lib/mount_util.c b/lib/mount_util.c
> index b6b75e60874ae9b2ec0c96f2a62b4ec1943a2a00..8cb0ce999e0df498c6777daf2bba6f47f12d7eee 100644
> --- a/lib/mount_util.c
> +++ b/lib/mount_util.c
> @@ -461,3 +461,34 @@ const char *fuse_mnt_get_devname(void)
>  
>  	return devname ? devname : "/dev/fuse";
>  }
> +
> +char *fuse_mnt_build_source(const char *fsname, const char *subtype,
> +			     const char *devname)
> +{
> +	char *source;
> +	int ret;
> +
> +	ret = asprintf(&source, "%s",
> +		       fsname ? fsname : (subtype ? subtype : devname));
> +	if (ret == -1)
> +		return NULL;
> +
> +	return source;
> +}
> +
> +char *fuse_mnt_build_type(int blkdev, const char *subtype)
> +{
> +	char *type;
> +	int ret;
> +
> +	if (subtype)
> +		ret = asprintf(&type, "%s.%s", blkdev ? "fuseblk" : "fuse",
> +			       subtype);
> +	else
> +		ret = asprintf(&type, "%s", blkdev ? "fuseblk" : "fuse");
> +
> +	if (ret == -1)
> +		return NULL;
> +
> +	return type;
> +}
> diff --git a/lib/mount_util.h b/lib/mount_util.h
> index b83db556d5fdb4bd8dd5e1750c8d11faf7373d82..5ba428a1f96df0dda21c0aadd936c022c4bb751c 100644
> --- a/lib/mount_util.h
> +++ b/lib/mount_util.h
> @@ -35,4 +35,9 @@ const char *fuse_mnt_get_devname(void);
>  int fuse_mnt_add_mount_helper(const char *mnt, const char *source,
>  			       const char *type, const char *mnt_opts);
>  
> +/* Build source and type strings for mounting */
> +char *fuse_mnt_build_source(const char *fsname, const char *subtype,
> +			     const char *devname);
> +char *fuse_mnt_build_type(int blkdev, const char *subtype);
> +
>  #endif /* FUSE_MOUNT_UTIL_H_ */
> diff --git a/util/fusermount.c b/util/fusermount.c
> index bf442416c83d6da3f80eca5c9814df29f61bec56..6d3ad4b300ba6266778fc23f35fa6f4fc929ff3f 100644
> --- a/util/fusermount.c
> +++ b/util/fusermount.c
> @@ -1048,25 +1048,13 @@ static int prepare_mount(const char *opts, struct mount_params *mp)
>  	sprintf(d, "fd=%i,rootmode=%o,user_id=%u,group_id=%u",
>  		mp->fd, mp->rootmode, getuid(), getgid());
>  
> -	mp->source = malloc((mp->fsname ? strlen(mp->fsname) : 0) +
> -			(mp->subtype ? strlen(mp->subtype) : 0) + strlen(mp->dev) + 32);
> -
> -	mp->type = malloc((mp->subtype ? strlen(mp->subtype) : 0) + 32);
> +	mp->source = fuse_mnt_build_source(mp->fsname, mp->subtype, mp->dev);
> +	mp->type = fuse_mnt_build_type(mp->blkdev, mp->subtype);
>  	if (!mp->type || !mp->source) {
>  		fprintf(stderr, "%s: failed to allocate memory\n", progname);
>  		goto err;
>  	}
>  
> -	if (mp->subtype)
> -		sprintf(mp->type, "%s.%s", mp->blkdev ? "fuseblk" : "fuse", mp->subtype);
> -	else
> -		strcpy(mp->type, mp->blkdev ? "fuseblk" : "fuse");
> -
> -	if (mp->fsname)
> -		strcpy(mp->source, mp->fsname);
> -	else
> -		strcpy(mp->source, mp->subtype ? mp->subtype : mp->dev);
> -
>  	return 0;
>  
>  err:
> 
> -- 
> 2.43.0
> 
> 

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 25/25] Add a background debug option to passthrough hp
  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
  0 siblings, 0 replies; 56+ messages in thread
From: Darrick J. Wong @ 2026-03-30 19:04 UTC (permalink / raw)
  To: Bernd Schubert; +Cc: linux-fsdevel, Miklos Szeredi, Joanne Koong, Kevin Chen

On Thu, Mar 26, 2026 at 10:34:58PM +0100, Bernd Schubert wrote:
> Background debugging is useful when logs should go
> to syslog for a daemon running in background.
> 
> Signed-off-by: Bernd Schubert <bernd@bsbernd.com>

Looks good to me,
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>

--D

> ---
>  example/passthrough_hp.cc | 41 +++++++++++++++++++++++++----------------
>  1 file changed, 25 insertions(+), 16 deletions(-)
> 
> diff --git a/example/passthrough_hp.cc b/example/passthrough_hp.cc
> index cca865973ab5e8e9c1faa0fa28eb292a172a4812..fa924b89ab3cc42a88767423674a6a190c0aa520 100644
> --- a/example/passthrough_hp.cc
> +++ b/example/passthrough_hp.cc
> @@ -160,7 +160,8 @@ struct Fs {
>  	Inode root;
>  	double timeout;
>  	bool debug;
> -	bool debug_fuse;
> +	bool debug_fuse; // foreground fuse debug
> +	bool bg_debug_fuse; // background fuse debug
>  	bool foreground;
>  	std::string source;
>  	size_t blocksize;
> @@ -1480,21 +1481,22 @@ static cxxopts::ParseResult parse_options(int argc, char **argv)
>  {
>  	cxxopts::Options opt_parser(argv[0]);
>  	std::vector<std::string> mount_options;
> -	opt_parser.add_options()("debug", "Enable filesystem debug messages")(
> -		"debug-fuse", "Enable libfuse debug messages")(
> -		"foreground", "Run in foreground")("help", "Print help")(
> -		"nocache", "Disable attribute all caching")(
> -		"nosplice", "Do not use splice(2) to transfer data")(
> -		"nopassthrough", "Do not use pass-through mode for read/write")(
> -		"single", "Run single-threaded")(
> -		"o",
> -		"Mount options (see mount.fuse(5) - only use if you know what "
> -		"you are doing)",
> -		cxxopts::value(mount_options))(
> -		"num-threads", "Number of libfuse worker threads",
> -		cxxopts::value<int>()->default_value(SFS_DEFAULT_THREADS))(
> -		"clone-fd", "use separate fuse device fd for each thread")(
> -		"direct-io", "enable fuse kernel internal direct-io");
> +	opt_parser.add_options()
> +		("debug", "Enable filesystem debug messages")
> +		("debug-fuse", "Enable libfuse debug messages")
> +		("bg-debug-fuse", "Enable libfuse debug messages in background")
> +		("foreground", "Run in foreground")("help", "Print help")
> +		("nocache", "Disable attribute all caching")
> +		("nosplice", "Do not use splice(2) to transfer data")
> +		("nopassthrough", "Do not use pass-through mode for read/write")
> +		("single", "Run single-threaded")
> +		("o", "Mount options (see mount.fuse(5) - only use if you know what "
> +		      "you are doing)",
> +		cxxopts::value(mount_options))
> +		("num-threads", "Number of libfuse worker threads",
> +		cxxopts::value<int>()->default_value(SFS_DEFAULT_THREADS))
> +		("clone-fd", "use separate fuse device fd for each thread")
> +		("direct-io", "enable fuse kernel internal direct-io");
>  
>  	// FIXME: Find a better way to limit the try clause to just
>  	// opt_parser.parse() (cf. https://github.com/jarro2783/cxxopts/issues/146)
> @@ -1520,6 +1522,7 @@ static cxxopts::ParseResult parse_options(int argc, char **argv)
>  
>  	fs.debug = options.count("debug") != 0;
>  	fs.debug_fuse = options.count("debug-fuse") != 0;
> +	fs.bg_debug_fuse = options.count("bg-debug-fuse") != 0;
>  
>  	fs.foreground = options.count("foreground") != 0;
>  	if (fs.debug || fs.debug_fuse)
> @@ -1643,6 +1646,12 @@ int main(int argc, char *argv[])
>  
>  	fuse_loop_cfg_set_clone_fd(loop_config, fs.clone_fd);
>  
> +	if (!fs.foreground)
> +		fuse_log_enable_syslog("passthrough-hp", LOG_PID | LOG_CONS,
> +				       LOG_DAEMON);
> +	if (fs.bg_debug_fuse)
> +		fuse_session_set_debug(se);
> +
>  	/* Start daemonization before mount so parent can report mount failure */
>  	if (fs.foreground)
>  		daemon_flags |= FUSE_DAEMONIZE_NO_BACKGROUND;
> 
> -- 
> 2.43.0
> 
> 

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 04/25] Add a new daemonize API
  2026-03-30 18:26         ` Bernd Schubert
@ 2026-03-30 21:25           ` Darrick J. Wong
  0 siblings, 0 replies; 56+ messages in thread
From: Darrick J. Wong @ 2026-03-30 21:25 UTC (permalink / raw)
  To: Bernd Schubert; +Cc: linux-fsdevel, Miklos Szeredi, Joanne Koong, Kevin Chen

On Mon, Mar 30, 2026 at 08:26:58PM +0200, Bernd Schubert wrote:
> 
> 
> On 3/30/26 19:45, Darrick J. Wong wrote:
> > On Sat, Mar 28, 2026 at 12:07:35AM +0100, Bernd Schubert wrote:
> >> Hi Darrick,
> >>
> >> On 3/27/26 23:06, Darrick J. Wong wrote:
> >>> On Thu, Mar 26, 2026 at 10:34:37PM +0100, Bernd Schubert wrote:
> >>>> Existing example/ file systems do the fuse_daemonize() after
> >>>> fuse_session_mount() - i.e. after the mount point is already
> >>>> established. Though, these example/ daemons do not start
> >>>> extra threads and do not need network initialization either.
> >>>
> >>> AFAICT the situation here with the extra threads and async FUSE_INIT is:
> >>>
> >>> a) You can't start them until after fuse_daemonize() because that might
> >>>    fork the whole process
> >>
> >> The extra threads are not cloned - they stay attached to the parent.
> > 
> > <nod>  I guess I should have said that explicitly -- "...because that
> > might fork the whole process, and all threads remain attached to the
> > parent."
> > 
> >>>
> >>> b) You don't want to start them until you know that the mount()
> >>>    succeeds (maybe?)
> >>
> >> For me the other way around, I want to start them before the mount and
> >> let them do things like network connection/authorization. Not nice if
> >> the mount has already suceeded and then part of the initialization fails
> >> and a stale mount come up.
> > 
> > <nod> That makes sense, you want to acquire resources and start the
> > threads (or fail) before calling mount().
> > 
> >>> c) You need those threads to be active to start serving the fuse
> >>>    requests that come after FUSE_INIT
> >>>
> >>> d) libfuse apparently starts even more threads to wait on the iouring
> >>>    queues after the fuse server returns from FUSE_INIT.
> >>
> >> Actually it starts these threads from FUSE_INIT and before replying
> >> FUSE_INIT success. In order to tell the fuse-client/kernel if
> >> fuse-server wants io-uring.
> > 
> > Got it.  Now I see the "XXX: Add an option to make non-available
> > io-uring fatal" code 20 lines up.
> > 
> >>> e) fuse_loop_mt() starts up the request handler threads and waits for
> >>>    the session to exit and/or for mt_finish to be sem_post()ed.
> >>>
> >>> Does that sound right?
> >>
> >>
> >>>
> >>> Looking at fuse4fs, I realize that it need /not/ start its background
> >>> threads from the FUSE_INIT handler; all that should be done after
> >>> daemonize before calling fuse_session_loop_mt.  The only reason I wrote
> >>
> >> So after the mount? What is if starting the threads would fail for some
> >> reason?
> > 
> > Right now they're optional features (monitor memory PSI file and flush
> > cache) so it's no big deal if they don't initialize.  But that *does*
> > make the case for adding the parent/child pipelines so that you can
> > daemonize earlier and report failures to the parent process.
> > 
> > Hrm.  Thinking about this more, the new fuse_daemonize_XXX calls make it
> > possible for any fuse server to do pre-mount() initialization in the
> > child and report the outcome to the parent.  Even if that fuse server
> > doesn't know, care, or try to enable sync FUSE_INIT.
> > 
> > So this new API really is separate from FUSE_SYNC_INIT.  The latter
> > depends on the former, but the reverse is not true.
> 
> Absolutely, as I wrote somewhere, this is basically my 3rd implementation of the same thing.

<nod>  

> > 
> >>> it that way was blind patterning after fuse2fs, which doesn't call
> >>> daemonize() directly, so FUSE_INIT is the first time any fuse2fs code
> >>> gets called after daemonizing.
> >>>
> >>>> fuse_daemonize() also does not allow to return notification
> >>>> from the forked child to the parent.
> >>>>
> >>>> Complex fuse file system daemons often want the order of
> >>>> 1) fork - parent watches, child does the work
> >>>>
> >>>> Child:
> >>>>     2) start extra threads and system initialization (like network
> >>>>        connection and RDMA memory registration) from the fork child.
> >>>>     3) Start the fuse session after everything else succeeded
> >>>>
> >>>> Parent:
> >>>>     Report child initialization success or failure
> >>>
> >>> Under classic async FUSE_INIT, the sequence in most fuse servers is:
> >>>
> >>> 1) The parent opens /dev/fuse and mounts the fuse filesystem before even
> >>>    daemonizing
> >>>
> >>> 2) Mounting the fuse fs causes an async FUSE_INIT to be sent to the
> >>>    queues, which sits there because nobody's looking for event yet
> >>>
> >>> 3) The parent daemonize()s, and the child proceeds with setting signal
> >>>    handlers and starting up the fuse-request processing threads
> >>>
> >>> 4) The parent exits, the child continues on to set up the fuse worker
> >>>    threads
> >>>
> >>> 5) One of the request handler threads finally reads /dev/fuse to find
> >>>    the FUSE_INIT request and processes it
> >>>
> >>> 6) do_init (in the lowlevel fuse library) starts up the uring workers
> >>>    if the kernel acknowledges the uring feature.  The fuse server has
> >>>    no means to discover if the fusedev would permit uring before
> >>>    calling mount().
> >>
> >> Yeah, initially I wanted to handle that through an ioctl and independent
> >> of FUSE_INIT, Miklos had asked to change that.
> > 
> > <nod>
> > 
> >>> Does my understanding make sense?
> >>>
> >>>> A new API is introduced to overcome the limitations of
> >>>> fuse_daemonize()
> >>>>
> >>>> fuse_daemonize_start() - fork, but foreground process does not
> >>>> terminate yet and watches the background.
> >>>
> >>> But with sync FUSE_INIT this is not workable because the child has to
> >>> have done (4) before (1) can complete, or it has to set up a temporary
> >>> request handler thread to process the FUSE_INIT.  That's partly why
> >>> fuse_daemonize_start/success/fail() is created here, right?
> >>
> >> I have written two similar APIs for two different daemons at DDN (one in
> >> C and the other in C++), independent of sync FUSE_INIT. For us it is
> >> important to only create the mount point when the network connection
> >> works - that is done by threads not under control from libfuse. With
> >> network you want to see something like "authorization failure" or "host
> >> not reachable" in foreground.
> >>
> >> With sync FUSE_INIT the additional issue of
> >> FUSE_INIT-creates-the-io-uring ring thread came up - even the
> >> <libfuse>/example/* file systems all don't work, because now
> >> fuse_session_mount() creates the ring threads, then comes
> >> fuse_daemonize() - parent exits - ring threads gone.
> > 
> > <nod>
> > 
> >>> And the rest of the reason for the new functions is to enable
> >>> communication between the parent and child processes -- if one dies
> >>> the other can find out about it; and the child can tell the parent the
> >>> outcome of mount()ing the filesystem.
> >>>
> >>> I wonder -- if you know that the kernel supports synchronous FUSE_INIT,
> >>> can you start the main event handling threadpool (i.e. the one created
> >>> in fuse_loop_mt.c) after opening /dev/fuse (obviously) but before
> >>> calling mount()?  That would make a hard requirement of having at least
> >>> one event handling thread, but you wouldn't have to create this temp
> >>> thread just to handle the FUSE_INIT.
> >>
> >> That would work and I believe it would be feasible for libfuse examples,
> >> but what about all the other real file systems out there? They would
> >> need to be rewritten to get sync INIT? And how many people understand
> >> the difference between sync and async init? Is this temp thread causing
> >> issues?
> > 
> > Don't fuse server authors already need to adapt their codebases to the
> > new fuse_daemonize_* calls if either they want to start threads or want
> > sync INIT?
> 
> If run with io-uring yes, otherwise I guess not, the current condition is
> 
> 	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;
> 	}
> 
> 
> want_sync_init needs to be set if you had your own daemonization process
> (as we have at DDN). 
> 
> > 
> > The problem I have with the temp thread is that it adds a fourth(?)
> > piece of code that handles fuse requests (the existing ones being
> > single-thread read, multi-thread read, iouring) and I think "err, more
> > code that everyone has to understand".
> 
> We can actually switch single threaded to fuse_loop_mt.c and remove 
> fuse_loop.c altogether. I.e. just run a single worker thread with 
> fuse_loop_mt.c

<nod> At this point fuse3.pc always links in -lpthread so I concede that
there aren't really any "single threaded" (aka pthreadless) fuse servers
anymore.

> > Granted, any server that wants sync FUSE_INIT will basically have to be
> > a multithreaded server.
> 
> Does it?

Well libfuse starts one background thread to handle the FUSE_INIT, which
(in my mind) means the fuse server must be careful about thread
synchronization.  But these days there aren't many complex storage
programs that don't pull in pthreads.

> > 
> > Shifting to fuse servers, adding a background thread does have some side
> > effects -- if you want to use pthread_[gs]etspecific from fuse operation
> > handlers, you have to know that FUSE_INIT will run in a different thread
> > from the ones set up by fuse_session_loop_mt.  If the fuse server needs
> > to preallocate the thread-specific variables, it has to know to add an
> > extra preallocation for the FUSE_INIT thread.
> > 
> > (Or you can't use them from FUSE_INIT)
> 
> Hmm, I was hoping these would libfuse internal threads and you never would
> need to care about such things.

Libraries love to leak things, I'm afraid. :(

Granted if you /did/ want to use pthread_setspecific you'd also have to
be aware of (or at least disable) libfuse's ability to scale the fuse
worker pool size up and down.

> >>> Even better the daemonize() changes reduce to just the pipe between
> >>> parent and child and watching either for a return value or the POLLERR
> >>> when either program fails unexpectedly.
> >>
> >> We also need to send a signal to the parent that child has success. How
> >> many pipes we use is an internal detail? If we find a better way later
> >> to reduce the number of pipes, even better.
> > 
> > <nod> Between the parent and child of a fork() operation that's fine, we
> > can update the interface at any time.
> > 
> >>>> fuse_daemonize_success() / fuse_daemonize_fail() - background
> >>>> daemon signals to the foreground process success or failure.
> >>>>
> >>>> fuse_daemonize_active() - helper function for the high level
> >>>> interface, which needs to handle both APIs. fuse_daemonize()
> >>>> is called within fuse_main(), which now needs to know if the caller
> >>>> actually already used the new API itself.
> >>>>
> >>>> The object 'struct fuse_daemonize *' is allocated dynamically
> >>>> and stored a global variable in fuse_daemonize.c, because
> >>>> - high level fuse_main_real_versioned() needs to know
> >>>> if already daemonized
> >>>> - high level daemons do not have access to struct fuse_session
> >>>> - FUSE_SYNC_INIT in later commits can only be done if the new
> >>>> API (or a file system internal) daemonization is used.
> >>>
> >>> I don't know exactly what's required to switch libfuse into uring mode.
> >>> It look as though you inject -oio_uring as a mount option, then libfuse
> >>> sets up the uring, starts some extra workers to handle the ring(?) and
> >>> puts them to sleep.  If the kernel says it supports uring in FUSE_INIT
> >>> then do_init wakes them up.  Each uring thread submits SQEs and waits
> >>> for fuse requests to appear as CQEs, right?
> >>
> >> Yeah, we can also make that later on a default, without the -oio_uring
> >> option (there is also env for ci test purposes to enforce io-uring
> >> mode). Right now it starts too many threads (got distracted today and
> >> didn't send the new version of ring reduction patches - will try
> >> tomorrow). And I also expected bugs in all that new code, so I didn't
> >> want to make it a default.
> > 
> > <nod>
> > 
> >>>
> >>> So after a fuse server negotiates with the kernel about iouring, the
> >>> background threads started by fuse_loop_mt just sit there in read()
> >>> doing nothing else, while new fuse requests get sent to userspace as a
> >>> CQE, right?
> >>
> >> Exactly.
> > 
> > Any reason not to cancel those threads?
> 
> When and why do you want to cancel them? After at fork time and recreate
> them in the fork child? I'm not sure how much liburing likes that. At
> lease with IORING_SETUP_SINGLE_ISSUER and/or IORING_SETUP_COOP_TASKRUN.

Eh, let's leave it alone then :)

> > 
> >> My question is now, are you ok with the new fuse_daemonize API? It
> >> sounds a bit like you don't like it too much.
> > 
> > Now that I've come back with a fresher mind, I think I'm ok with the
> > daemonize API itself.  I've a few minor nits to pick, so I'll reply to
> > the patch itself.
> 
> 
> So now it is about the temporary thread ;)

Yep.  FWIW even now I don't hate it so much as to nak it :P

> Thanks for all your reviews!

Likewise!

--D

> 
> Bernd
> 

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 22/25] Add support for sync-init of unprivileged daemons
  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
  0 siblings, 0 replies; 56+ messages in thread
From: Darrick J. Wong @ 2026-03-31  0:54 UTC (permalink / raw)
  To: Bernd Schubert
  Cc: linux-fsdevel, Miklos Szeredi, Joanne Koong, Kevin Chen,
	Bernd Schubert

On Thu, Mar 26, 2026 at 10:34:55PM +0100, Bernd Schubert wrote:
> From: Bernd Schubert <bschubert@ddn.com>
> 
> 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>
> ---
>  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);

I wonder, could libfuse itself call fuse_daemonize_success from do_init
after the ->init function returns if !se->is_sync_init; or from the
end of fuse_session_mount otherwise?  In other words, hide this
particular detail from the fuse server?

Or are there things that a fuse server might want to do prior to calling
fuse_daemonize_success/fail?

> @@ -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 <assert.h>
>  #include <sys/file.h>
>  #include <sys/ioctl.h>
> +#include <sys/wait.h>
>  #include <stdalign.h>
>  #include <poll.h>
>  
> @@ -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;
> +	}

I wonder if you could reduce the indent levels with

	if (err == 0 || (err < 0 && errno == EPERM))
		goto err;

	/* all the fallback code */

It's really a pity that C makes us do all this boilerplate.  Though it's
also a little funny to have a library that prints error messages.

Eh, that's a stylistic preference.  The logic looks correct. :)

> +
> +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

What fd is in the return value?  The /dev/fuse that fusermount opened,
right?

> + * 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);

Huh.  Would have been nice to have known about posix_spawn when I was
writing xfs_healer.

> +
> +	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);

Hrmm, I probably need to wrap the sendmsg/recvmsg calls in my own
patches to do this loop, don't I?

--D

> +
> +	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
> 
> 

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 24/25] Add mount and daemonization README documents
  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
  0 siblings, 0 replies; 56+ messages in thread
From: Darrick J. Wong @ 2026-03-31  1:17 UTC (permalink / raw)
  To: Bernd Schubert; +Cc: linux-fsdevel, Miklos Szeredi, Joanne Koong, Kevin Chen

On Thu, Mar 26, 2026 at 10:34:57PM +0100, Bernd Schubert wrote:
> These are useful to
> 
> Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
> ---
>  doc/README.daemonize  | 197 +++++++++++++++++++++++++++
>  doc/README.fusermount | 362 ++++++++++++++++++++++++++++++++++++++++++++++++++
>  doc/README.mount      |  86 ++++++++++++
>  doc/README.sync-init  | 184 +++++++++++++++++++++++++
>  4 files changed, 829 insertions(+)
> 
> diff --git a/doc/README.daemonize b/doc/README.daemonize
> new file mode 100644
> index 0000000000000000000000000000000000000000..7b56c33077f29edbd335a2778120d9b6db6022ad
> --- /dev/null
> +++ b/doc/README.daemonize
> @@ -0,0 +1,197 @@
> +FUSE Daemonization API
> +======================
> +
> +This document describes the FUSE daemonization APIs, including the legacy
> +fuse_daemonize() function and the new fuse_daemonize_start()/signal() API
> +introduced in libfuse 3.19.
> +
> +
> +Overview
> +--------
> +
> +FUSE filesystems often need to run as background daemons. Daemonization
> +involves forking the process, creating a new session, and redirecting
> +standard file descriptors. The challenge is properly reporting initialization
> +failures to the parent process.

But why is it important to report init failures to the parent?

"The parent can, in turn, pass those errors up to whichever process
started the fuse server."?

> +
> +
> +Old API: fuse_daemonize()
> +--------------------------
> +
> +Function signature:
> +    int fuse_daemonize(int foreground);
> +
> +Location: lib/helper.c
> +
> +This is the legacy daemonization API, primarily used with the high-level
> +fuse_main() interface.
> +
> +Behavior:
> +- If foreground=0: forks the process, creates a new Unix session,
> +  redirects stdio
> +- If foreground=1: only changes directory to "/"
> +- Parent waits for a single byte on a pipe before exiting
> +- Child writes completion byte immediately after redirecting stdio
> +- Always changes directory to "/"
> +
> +Limitations:
> +1. No failure reporting: The parent receives notification immediately after
> +   fork/setsid, before any meaningful initialization (like mounting the
> +   filesystem or starting threads).
> +
> +2. Timing constraint: Must be called AFTER fuse_session_mount() in existing
> +   examples, because there's no way to report mount failures to the parent.

"...to report mount() failures...", specifically.

> +
> +3. Thread initialization: Cannot report failures from complex initialization
> +   steps like:
> +   - Starting worker threads
> +   - Network connection setup
> +   - RDMA memory registration
> +   - Resource allocation
> +
> +4. FUSE_SYNC_INIT incompatibility: With the FUSE_SYNC_INIT feature, FUSE_INIT
> +   happens at mount time and may start io_uring threads. This requires
> +   daemonization BEFORE mount, which the old API cannot handle properly.
> +
> +Example usage (old API):
> +    fuse = fuse_new(...);
> +    fuse_mount(fuse, mountpoint);
> +    fuse_daemonize(opts.foreground);  // After mount, can't report mount failure
> +    fuse_set_signal_handlers(se);
> +    fuse_session_loop(se);
> +
> +
> +New API: fuse_daemonize_start() / fuse_daemonize_success() / fuse_daemonize_fail()
> +---------------------------------------------------------------------------------
> +
> +Functions:
> +    int fuse_daemonize_start(unsigned int flags);
> +    void fuse_daemonize_success(void);
> +    void fuse_daemonize_fail(void);
> +    bool fuse_daemonize_active(void);
> +
> +Location: lib/fuse_daemonize.c, include/fuse_daemonize.h
> +Available since: libfuse 3.19
> +
> +This new API solves the limitations of fuse_daemonize() by splitting
> +daemonization into two phases:
> +
> +1.   fuse_daemonize_start() - Fork and setup, but parent waits
> +2.1. fuse_daemonize_fail() - Signal failure to parent
> +2.2  fuse_daenmize_success() - Signal startup to success to the parent

fuse_daemonize_success()

> +     See below for an important detail.
> +
> +
> +fuse_daemonize_start()
> +----------------------
> +
> +Flags:
> +- FUSE_DAEMONIZE_NO_CHDIR: Don't change directory to "/"
> +- FUSE_DAEMONIZE_NO_BACKGROUND: Don't fork (foreground mode)
> +
> +Behavior:
> +- Unless NO_BACKGROUND: forks the process
> +- Parent waits for status signal from child
> +- Child creates new session and continues
> +- Unless NO_CHDIR: changes directory to "/"
> +- Closes stdin immediately in child
> +- Starts a watcher thread to detect parent death
> +- Returns 0 in child on success, negative errno on error
> +
> +Parent death detection:
> +- Uses a "death pipe" - parent keeps write end open
> +- Child's watcher thread polls the read end
> +- When parent dies, pipe gets POLLHUP and child exits

"If parent dies"?  Using the word "when" make it sound like the parent
is supposed to die before the child.

> +- Prevents orphaned daemons if parent is killed

If the child dies unexpectedly, the read(signal_pipe[0], ...) call
returns zero, so the parent will also exit, right?  I think that should
be mentioned here.

> +
> +
> +fuse_daemonize_fail(int err)
> +----------------------------
> +
> +Behavior:
> +- Signals the parent about the provided error
> +- Parent will exit with that error
> +
> +fuse_daemonize_success()
> +------------------------
> +- Signals the parent process with succes
> +- On success: redirects stdout/stderr to /dev/null

For systemd service mode, it'd be nice to have a third flag that
disables stdout/stderr redirection too, since it's expected in systemd
land that stdout goes to journald/syslog.  I can add that in my own
patchset.

> +- Stops the parent watcher thread
> +- Cleans up pipes and internal state
> +- Safe to call multiple times
> +- Safe to call even if fuse_daemonize_start() failed
> +- *Important*: Should be called twice, once from ->init
> +  (struct fuse_lowlevel_ops::init or for high level interface
> +   struct fuse_operations::init) and after fuse_session_mount().
> +  It will internally figure out which of these two calls will
> +  actually signal the parent about success. Reason is that
> +  sucess must only be signaled when the mount is complete and

success ^^^

> +  that depends on if synchronous or asynchronous FUSE_INIT is
> +  used. 
> +
> +
> +fuse_daemonize_active()
> +-----------------------
> +
> +Returns true if daemonization is active and waiting for signal.
> +
> +Used by the high-level fuse_main() to detect if the application already
> +called the new API, avoiding double-daemonization.
> +
> +
> +Example usage (new API):
> +-------------------------
> +
> +    // Start daemonization BEFORE mount
> +    unsigned int daemon_flags = 0;
> +    if (foreground)
> +        daemon_flags |= FUSE_DAEMONIZE_NO_BACKGROUND;
> +
> +    if (fuse_daemonize_start(daemon_flags) != 0)
> +        goto error;
> +
> +    // Complex initialization can fail and be reported
> +    if (setup_threads() != 0)
> +        goto error_signal;
> +
> +    if (setup_network() != 0)
> +        goto error_signal;
> +    // Mount can now fail and be reported to parent
> +    if (fuse_session_mount(se, mountpoint) != 0)
> +        goto error_signal;
> +
> +    // Signal success - parent exits with EXIT_SUCCESS
> +    // This is typically done in the init() callback after FUSE_INIT
> +    fuse_daemonize_signal(FUSE_DAEMONIZE_SUCCESS);
> +
> +    // Run main loop
> +    fuse_session_loop(se);
> +
> +    return 0;
> +
> +error_signal:
> +    // Signal failure - parent exits with EXIT_FAILURE
> +    fuse_daemonize_signal(FUSE_DAEMONIZE_FAILURE);
> +error:
> +    return 1;
> +
> +
> +When to signal success
> +----------------------
> +
> +The success signal should be sent after all critical initialization is
> +complete. For FUSE filesystems, this is typically in the init() callback,
> +after FUSE_INIT has been processed successfully.
> +
> +Example (from passthrough_hp.cc):
> +    static void sfs_init(void *userdata, fuse_conn_info *conn) {
> +        // ... initialization ...
> +        fuse_daemonize_signal(FUSE_DAEMONIZE_SUCCESS);
> +    }
> +
> +This ensures the parent only exits after:
> +- Mount succeeded
> +- FUSE_INIT completed
> +- All threads started
> +- Filesystem is ready to serve requests
> +
> diff --git a/doc/README.fusermount b/doc/README.fusermount
> new file mode 100644
> index 0000000000000000000000000000000000000000..5b81651c8690aa25fef12db322af977c9cba1d31
> --- /dev/null
> +++ b/doc/README.fusermount
> @@ -0,0 +1,362 @@
> +Synchronous FUSE_INIT Protocol
> +================================

Hm.  I thought I just got done reading a documnet about how synchronous
FUSE_INIT works?  Looking at the copious references to fusermount3 under
"Protocol Flow", is this the doc for how it works when fusermount is
involved?

> +
> +Overview
> +--------
> +
> +The sync-init feature enables the FUSE library to start worker threads and
> +perform initialization ioctl calls BEFORE the actual mount() syscall happens.
> +This is required for the kernel's synchronous FUSE_INIT feature, where the
> +mount() syscall blocks until the FUSE daemon processes the INIT request.
> +
> +Without this feature, there would be a deadlock:
> +- mount() blocks waiting for INIT response
> +- Worker threads can't start because mount() hasn't returned
> +- INIT request can't be processed because worker threads aren't running
> +
> +
> +Protocol Flow
> +-------------
> +
> +Traditional mount flow:
> +  1. Library calls fusermount3
> +  2. fusermount3 opens /dev/fuse
> +  3. fusermount3 performs mount() syscall
> +  4. fusermount3 sends fd to library
> +  5. Library starts worker threads
> +  6. Worker threads process FUSE requests
> +
> +Sync-init mount flow:
> +  1. Library calls fusermount3 with --sync-init flag
> +  2. fusermount3 opens /dev/fuse
> +  3. fusermount3 sends fd to library
> +  4. Library receives fd
> +  5. Library performs FUSE_DEV_IOC_SYNC_INIT ioctl
> +  6. Library starts worker threads
> +  7. Library sends "proceed" signal to fusermount3
> +  8. fusermount3 performs mount() syscall (blocks until INIT completes)
> +  9. Worker threads process INIT request
> +  10. mount() syscall completes
> +  11. fusermount3 exits
> +
> +
> +Implementation Details
> +----------------------
> +
> +Bidirectional Communication:
> +  - Uses the existing unix socket (_FUSE_COMMFD environment variable)
> +  - Simple 1-byte protocol for signaling
> +  - Library signals fusermount3 when ready to proceed with mount
> +
> +fusermount3 Changes:
> +  - New --sync-init command-line option
> +  - Split mount operation into two phases:
> +    * mount_fuse_prepare(): Opens device, prepares parameters
> +    * mount_fuse_finish_fsmount(): Performs actual mount() syscall
> +  - wait_for_signal(): Waits for library to signal readiness
> +  - struct mount_context: Preserves state between phases
> +
> +Library Changes:
> +  - fuse_session_mount_new_api(): Uses new protocol when available
> +  - Sends "proceed" signal after worker thread is ready
> +  - Handles both old and new mount protocols for compatibility
> +
> +
> +Backward Compatibility
> +----------------------
> +
> +The implementation maintains full backward compatibility:
> +  - Old library + new fusermount3: Works (uses traditional flow)
> +  - New library + old fusermount3: Falls back to traditional flow

"traditional", as in async FUSE_INIT?

> +  - New library + new fusermount3: Uses sync-init flow when appropriate
> +
> +
> +Error Handling
> +--------------
> +
> +If any step fails during the sync-init flow:
> +  - fusermount3 closes the fd and exits with error
> +  - Library detects failure and cleans up
> +  - No mount is left in inconsistent state
> +
> +Connection closure:
> +  - If library closes socket before signaling, fusermount3 detects and exits
> +  - If fusermount3 crashes, library detects closed socket
> +
> +
> +Security Considerations
> +-----------------------
> +
> +The sync-init protocol does not introduce new security concerns:
> +  - Uses the same privilege separation as traditional mount
> +  - Socket communication is already established and trusted
> +  - No new privileged operations are added
> +  - File descriptor passing uses existing SCM_RIGHTS mechanism
> +
> +
> +Performance Impact
> +------------------
> +
> +Minimal performance impact:
> +  - One additional recv() call in fusermount3
> +  - One additional send() call in library
> +  - Total overhead: ~2 context switches
> +  - Only affects mount time, not runtime performance
> +
> +
> +Future Enhancements
> +-------------------
> +
> +Potential improvements:
> +  - Extended protocol for more complex initialization sequences
> +  - Support for multiple worker threads coordination
> +  - Enhanced error reporting through the socket
> +  - Timeout mechanisms for detecting hung initialization

Is that for libfuse detecting that a fusermount has hung?  Or for
fusermount detecting that the fuse server hasn't responded to FUSE_INIT
and giving up?  I suspect the former.

> +
> +
> +ASCII Workflow Diagrams
> +========================
> +
> +1. Traditional Mount Flow (without --sync-init, async INIT)
> +------------------------------------------------------------
> +
> +Library                   fusermount3              Kernel
> +   |                           |                      |
> +   |--- spawn fusermount3 ---->|                      |
> +   |                           |                      |
> +   |                      [open /dev/fuse]            |
> +   |                           |------- open -------->|
> +   |                           |<------ fd ---------- |
> +   |                           |                      |
> +   |                      [mount() syscall]           |
> +   |                           |------ mount -------->|
> +   |                           |<----- success ------ | [mount returns immediately]
> +   |                           |                      | [INIT queued in kernel]
> +   |                      [send_fd(fd)]               |
> +   |<------- fd --------------|                      |
> +   |                           |                      |
> +   |                      [fusermount3 exits]         |
> +   |                                                  |
> +   |  [start worker thread]                          |
> +   |  [worker reads /dev/fuse]                       |
> +   |---------------------------------------- read -->|
> +   |<--------------------------------------- INIT ---| [dequeued from kernel]
> +   |                                                  |
> +   | OK: INIT was queued, worker reads it later      |
> +   |     Works fine for async INIT                   |
> +
> +
> +1b. Problem: Synchronous INIT without --sync-init
> +--------------------------------------------------
> +
> +Library                   fusermount3              Kernel
> +   |                           |                      |
> +   |--- spawn fusermount3 ---->|                      |
> +   |                           |                      |
> +   |                      [open /dev/fuse]            |
> +   |                           |------- open -------->|
> +   |                           |<------ fd ---------- |
> +   |                           |                      |
> +   |                      [mount() syscall]           |
> +   |                           |------ mount -------->|
> +   |                           |                      | [mount BLOCKS waiting for INIT]
> +   |                           | (BLOCKED)            | [needs worker to process INIT]
> +   |                           |                      |
> +   |  [waiting for fd...]      |                      |
> +   |                           |                      |
> +   |                           |                      |
> +   | DEADLOCK: mount() waits for INIT response       |
> +   |           but worker thread not started yet     |
> +   |           because we're waiting for fd          |
> +
> +
> +2. Sync-Init Mount Flow (with --sync-init)
> +-------------------------------------------
> +
> +Library                   fusermount3              Kernel
> +   |                           |                      |
> +   |--- spawn fusermount3 ---->|                      |
> +   |    with --sync-init       |                      |
> +   |                           |                      |
> +   |                      [open /dev/fuse]            |
> +   |                           |------- open -------->|
> +   |                           |<------ fd ---------- |
> +   |                           |                      |
> +   |                      [send_fd(fd)]               |
> +   |<------- fd --------------|                      |
> +   |                           |                      |
> +   |                      [wait_for_signal()]         |
> +   |                           | (BLOCKED)            |
> +   |                           |                      |
> +   |  [ioctl SYNC_INIT]        |                      |
> +   |---------------------------------------- ioctl -->|
> +   |                                                  |
> +   |  [start worker thread]                          |
> +   |  [worker ready]                                 |
> +   |                           |                      |
> +   |--- "proceed" signal ----->|                      |
> +   |                      [signal received]           |
> +   |                           |                      |
> +   |                      [mount() syscall]           |
> +   |                           |------ mount -------->|
> +   |                           |                      | [mount blocks]
> +   |                           |                      | [sends INIT]
> +   |<------------------------------------------------ |
> +   |                           |                      |
> +   | [worker processes INIT]   |                      |
> +   |------------------------------------------------->|
> +   |                           |                      | [mount unblocks]
> +   |                           |<----- success ------ |
> +   |                           |                      |
> +   |                      [fusermount3 exits]         |
> +   |                                                  |
> +   | SUCCESS: Worker ready before mount()            |
> +   |          INIT processed synchronously           |
> +
> +
> +3. Error Scenario: Library Crashes Before Signaling
> +----------------------------------------------------
> +
> +Library                   fusermount3              Kernel
> +   |                           |                      |
> +   |--- spawn fusermount3 ---->|                      |
> +   |    with --sync-init       |                      |
> +   |                           |                      |
> +   |                      [open /dev/fuse]            |
> +   |                           |------- open -------->|
> +   |                           |<------ fd ---------- |
> +   |                           |                      |
> +   |                      [send_fd(fd)]               |
> +   |<------- fd --------------|                      |
> +   |                           |                      |
> +   |                      [wait_for_signal()]         |
> +   |                           | (BLOCKED)            |
> +   |                           |                      |
> +   X  [library crashes]        |                      |
> +   |                           |                      |
> +   |                      [recv() returns 0]          |
> +   |                      [socket closed]             |
> +   |                           |                      |
> +   |                      [cleanup and exit]          |
> +   |                           X                      |
> +   |                                                  |
> +   | RESULT: Clean failure, no mount performed       |
> +
> +
> +4. Detailed Function Call Flow
> +-------------------------------
> +
> +Library (lib/fuse_lowlevel.c):
> +fuse_session_mount_new_api()
> +   |
> +   +-- fuse_kern_mount_prepare()  [lib/mount.c]
> +   |      |
> +   |      +-- fuse_mount_fusermount()  [lib/mount_util.c]
> +   |             |
> +   |             +-- socketpair()  [create comm socket]
> +   |             |
> +   |             +-- fork()
> +   |             |
> +   |             +-- [child] execl("fusermount3", "--sync-init", ...)
> +   |             |
> +   |             +-- [parent] receive_fd()  <--- BLOCKS until fd arrives
> +   |                    |
> +   |                    +-- recvmsg(SCM_RIGHTS)
> +   |                    |
> +   |                    +-- return fd
> +   |
> +   +-- session_start_sync_init()  [lib/fuse_lowlevel.c]
> +   |      |
> +   |      +-- ioctl(fd, FUSE_DEV_IOC_SYNC_INIT)
> +   |      |
> +   |      +-- pthread_create(worker_thread)
> +   |      |
> +   |      +-- return
> +   |
> +   +-- fuse_fusermount_proceed_mnt(socket)  [lib/mount.c] <--- NEW: Bidirectional handshake
> +          |
> +          +-- send(socket, "proceed", 1)  <--- Signal fusermount3 to proceed
> +          |
> +          +-- recv(socket, &status, 1)  <--- BLOCKS until mount result arrives
> +          |      |
> +          |      +-- [fusermount3 performs mount and sends status byte]
> +          |
> +          +-- if (status != 0) return -1  <--- Mount failed
> +          |
> +          +-- return 0  <--- Mount succeeded
> +
> +
> +Utility (util/fusermount.c):
> +fusermount3 main() with --sync-init
> +   |
> +   +-- mount_fuse_sync_init()  [util/fusermount.c]
> +          |
> +          +-- mount_fuse_prepare()  [util/fusermount.c]
> +          |      |
> +          |      +-- open("/dev/fuse")
> +          |      |
> +          |      +-- check_perm()  [util/fusermount.c]
> +          |      |
> +          |      +-- return fd
> +          |
> +          +-- send_fd(socket, fd)  [util/fusermount.c]
> +          |      |
> +          |      +-- sendmsg(SCM_RIGHTS)
> +          |
> +          +-- wait_for_signal(socket)  [util/fusermount.c] <--- BLOCKS until library signals
> +          |      |
> +          |      +-- recv(socket, buf, 1)
> +          |      |
> +          |      +-- return 0
> +          |
> +          +-- mount_fuse_finish_fsmount()  [util/fusermount.c]
> +          |      |
> +          |      +-- fuse_kern_fsmount()  [lib/mount_fsmount.c]
> +          |      |      |
> +          |      |      +-- fsopen("fuse", FSOPEN_CLOEXEC)
> +          |      |      |      |
> +          |      |      |      +-- [kernel creates filesystem context]
> +          |      |      |
> +          |      |      +-- fsconfig(fsfd, SET_STRING, "source", ...)
> +          |      |      +-- fsconfig(fsfd, SET_STRING, "fd", fd_value, ...)
> +          |      |      +-- fsconfig(fsfd, ...) [apply mount options]
> +          |      |      +-- fsconfig(fsfd, CMD_CREATE, ...)
> +          |      |      |
> +          |      |      +-- fsmount(fsfd, FSMOUNT_CLOEXEC, mount_attrs)
> +          |      |      |      |
> +          |      |      |      +-- [kernel sends FUSE_INIT here]
> +          |      |      |      |
> +          |      |      |      +-- [worker thread processes INIT]
> +          |      |      |      |
> +          |      |      |      +-- [fsmount returns mntfd]
> +          |      |      |
> +          |      |      +-- move_mount(mntfd, "", AT_FDCWD, target, ...)
> +          |      |      |      |
> +          |      |      |      +-- [attach mount to target directory]
> +          |      |      |      |
> +          |      |      |      +-- [no blocking - INIT already processed]
> +          |      |      |
> +          |      |      +-- add_mount()  [lib/mount_fsmount.c - update /etc/mtab]
> +          |      |      |
> +          |      |      +-- return 0 on success, -1 on failure
> +          |      |
> +          |      +-- if mount failed: return -1
> +          |      +-- if mount succeeded: continue
> +          |
> +          +-- send_status_byte(socket)  [util/fusermount.c] <--- NEW: Send result to library
> +          |      |
> +          |      +-- status = (mount_result == 0) ? 0 : 1
> +          |      +-- send(socket, &status, 1)
> +          |      |
> +          |      +-- return
> +          |
> +          +-- return 0
> +
> +
> +Note: The new mount API (fsopen/fsconfig/fsmount/move_mount) is REQUIRED
> +      for sync-init because fsmount() triggers FUSE_INIT before the mount
> +      is attached. This allows the worker thread to process INIT before
> +      move_mount() completes, preventing deadlock.
> +      And also so we don't expose the directory tree to the mountns until we
> +      know that FUSE_INIT didn't crash the server.

Question: Can libfuse call fuse_reply_err in response to an ->init call
if the fuse server wants to fail a synchronous mount?  Right now it
looks as if it always calls fuse_reply_ok, but I'm curious because that
might be a more graceful way for a fuse server to fail than just
terminating.

(Granted the kernel still has to handle the fuse server blowing up
unintentionally.)

> +
> diff --git a/doc/README.mount b/doc/README.mount
> new file mode 100644
> index 0000000000000000000000000000000000000000..526382ad8a5f6b405a7cb1927b79bacd6c2c2c5c
> --- /dev/null
> +++ b/doc/README.mount
> @@ -0,0 +1,86 @@
> +FUSE Mount API Flowcharts
> +=========================
> +
> +Old Mount API
> +-------------
> +
> +fuse_kern_mount()
> +  |
> +  +-- fuse_mount_sys()
> +  |     +-- Try direct mount → mount() syscall
> +  |     +-- On EPERM: fuse_mount_fusermount()
> +  |           +-- socketpair()
> +  |           +-- spawn fusermount3 (no --sync-init)
> +  |           +-- fusermount3: open /dev/fuse, mount(), send fd
> +  |           +-- receive_fd() → return fd
> +  |
> +  +-- Worker threads started AFTER mount
> +  └─> FUSE_INIT asynchronous (queued in kernel)
> +
> +
> +New Mount API - Privileged Mount
> +---------------------------------
> +
> +fuse_session_mount_new_api()
> +  |
> +  +-- fuse_kern_mount_prepare() → open /dev/fuse → fd
> +  |
> +  +-- session_start_sync_init(se, fd)
> +  |     +-- ioctl(fd, FUSE_DEV_IOC_SYNC_INIT)
> +  |     +-- pthread_create(worker) → ready to process FUSE_INIT
> +  |
> +  +-- fuse_kern_fsmount_mo()
> +  |     +-- fsopen/fsconfig/fsmount (BLOCKS until FUSE_INIT completes)
> +  |     +-- Worker processes FUSE_INIT during fsmount()
> +  |     +-- move_mount()
> +  |
> +  +-- session_wait_sync_init_completion(se) → pthread_join
> +  └─> return fd
> +
> +
> +New Mount API - EPERM Fallback (fusermount3 with sync-init)
> +------------------------------------------------------------
> +
> +fuse_session_mount_new_api()
> +  |
> +  +-- fuse_kern_mount_prepare() → open /dev/fuse → fd1
> +  |
> +  +-- session_start_sync_init(se, fd1)
> +  |     +-- ioctl(fd1, FUSE_DEV_IOC_SYNC_INIT)
> +  |     +-- pthread_create(worker) → ready with fd1
> +  |
> +  +-- fuse_kern_fsmount_mo() → EPERM
> +  |
> +  +-- *** FALLBACK TO FUSERMOUNT3 WITH SYNC-INIT ***
> +  |
> +  +-- session_wait_sync_init_completion(se)
> +  |     +-- pthread_cancel/join → terminate worker with wrong fd1
> +  |
> +  +-- close(fd1)
> +  |
> +  +-- fuse_mount_fusermount_sync_init()  [NEW]
> +  |     +-- socketpair()
> +  |     +-- spawn fusermount3 --sync-init
> +  |     +-- fusermount3: open /dev/fuse → fd2, send fd2
> +  |     +-- receive_fd() → fd2
> +  |     +-- fusermount3 waits for signal
> +  |     └─> return fd2, sock
> +  |
> +  +-- session_start_sync_init(se, fd2)
> +  |     +-- ioctl(fd2, FUSE_DEV_IOC_SYNC_INIT)
> +  |     +-- pthread_create(worker) → ready with fd2
> +  |
> +  +-- send_proceed_signal(sock)  [NEW]
> +  |     +-- send(sock, "\0", 1) → signal fusermount3
> +  |
> +  +-- fusermount3: mount() (BLOCKS)
> +  |     +-- Kernel sends FUSE_INIT to fd2
> +  |     +-- Worker processes FUSE_INIT
> +  |     +-- mount() returns
> +  |
> +  +-- close(sock)
> +  |
> +  +-- session_wait_sync_init_completion(se) → pthread_join
> +  |
> +  └─> return fd2

I'm not sure how useful it is to mention what's inside these functions,
since one could read the code and I worry that the code will diverge
very quickly.

> +
> diff --git a/doc/README.sync-init b/doc/README.sync-init
> new file mode 100644
> index 0000000000000000000000000000000000000000..44e47a2eef2c45026abaa19562537eef37f256b9
> --- /dev/null
> +++ b/doc/README.sync-init
> @@ -0,0 +1,184 @@
> +FUSE Synchronous vs Asynchronous FUSE_INIT
> +============================================
> +
> +This document explains the difference between asynchronous and synchronous
> +FUSE_INIT processing, and when each mode is used.
> +
> +
> +Overview
> +--------
> +
> +FUSE_INIT is the initial handshake between the kernel FUSE module and the
> +userspace filesystem daemon. During this handshake, the kernel and daemon
> +negotiate capabilities, protocol version, and various feature flags.
> +
> +Asynchronous FUSE_INIT (Traditional Behavior)
> +----------------------------------------------
> +
> +In the traditional asynchronous mode:
> +
> +1. mount() syscall completes and returns to caller
> +2. Filesystem appears mounted to the system
> +3. FUSE daemon starts worker threads
> +4. Worker threads process FUSE_INIT request
> +5. Filesystem becomes fully operational
> +
> +Timeline:
> +    mount() -----> returns
> +                   |
> +                   v
> +            FUSE_INIT sent
> +                   |
> +                   v
> +            daemon processes FUSE_INIT
> +                   |
> +                   v
> +            filesystem ready
> +
> +Limitations:
> +
> +1. **No early requests**: The kernel cannot send requests (like getxattr)
> +   during the mount() syscall. This breaks SELinux, which needs to query
> +   extended attributes on the root inode immediately upon mounting.
> +
> +2. **Daemonization timing**: With the old fuse_daemonize() API, the daemon
> +   must call it AFTER mount, because there's no way to report mount failures
> +   to the parent process if daemonization happens first.
> +
> +3. **No custom root inode**: The root inode ID is hardcoded to FUSE_ROOT_ID (1)
> +   because FUSE_INIT hasn't been processed yet when the mount completes.
> +
> +4. **Thread startup after mount**: io_uring threads and other worker threads
> +   can only start after mount() returns, not before.
> +
> +
> +Synchronous FUSE_INIT (New Behavior)
> +-------------------------------------
> +
> +Kernel support: Linux kernel commit dfb84c330794 (v6.18+)
> +libfuse support: libfuse 3.19+
> +
> +In synchronous mode:
> +
> +1. FUSE daemon opens /dev/fuse
> +2. Daemon calls ioctl(fd, FUSE_DEV_IOC_SYNC_INIT)
> +3. Daemon starts worker thread
> +4. Daemon calls mount() syscall
> +5. Kernel sends FUSE_INIT during mount() - mount() blocks
> +6. Worker thread processes FUSE_INIT while mount() is blocked
> +7. Worker thread may process additional requests (getxattr, etc.)
> +8. mount() syscall completes and returns
> +9. Filesystem is fully operational
> +
> +Timeline:
> +    open /dev/fuse
> +         |
> +         v
> +    ioctl(FUSE_DEV_IOC_SYNC_INIT)
> +         |
> +         v
> +    start worker thread
> +         |
> +         v
> +    mount() -----> blocks
> +         |         |
> +         |         v
> +         |    FUSE_INIT sent
> +         |         |
> +         |         v
> +         |    worker processes FUSE_INIT
> +         |         |
> +         |         v
> +         |    (possible getxattr, etc.)
> +         |         |
> +         +-------> returns
> +                   |
> +                   v
> +            filesystem ready
> +
> +Advantages:
> +
> +1. **SELinux support**: The kernel can send getxattr requests during mount()
> +   to query security labels on the root inode.
> +
> +2. **Early daemonization**: The daemon can fork BEFORE mount using the new
> +   fuse_daemonize_start()/signal() API, and report mount failures to the
> +   parent process.
> +
> +3. **Custom root inode**: The daemon can specify a custom root inode ID
> +   during FUSE_INIT, before mount() completes.

Where?  Or are you talking about this?
https://lore.kernel.org/linux-fsdevel/177188735166.3936993.12658858435281080344.stgit@frogsfrogsfrogs/

but perhaps the root nodeid can be supplied in the reply from the
synchronous FUSE_INIT?

> +
> +4. **Thread startup before mount**: io_uring threads and worker threads
> +   start before mount(), ensuring they're ready to handle requests.
> +
> +5. **Better error reporting**: Mount failures and initialization errors
> +   can be properly reported to the parent process when using the new
> +   daemonization API.
> +
> +
> +When Synchronous FUSE_INIT is Used
> +-----------------------------------
> +
> +libfuse automatically enables synchronous FUSE_INIT when:
> +
> +1. The application calls fuse_session_want_sync_init(), OR
> +2. The new daemonization API is used (fuse_daemonize_start() was called)
> +
> +Synchronous FUSE_INIT requires:
> +- Kernel support (commit dfb84c330794 or later)
> +- Worker thread started before mount()
> +- ioctl(FUSE_DEV_IOC_SYNC_INIT) succeeds
> +
> +If the kernel doesn't support synchronous FUSE_INIT, libfuse automatically
> +falls back to asynchronous mode.
> +
> +
> +Implementation Details
> +----------------------
> +
> +The synchronous FUSE_INIT implementation uses a worker thread:
> +
> +- **session_sync_init_worker()**: Thread function that polls /dev/fuse
> +  and processes FUSE_INIT and any subsequent requests until mount completes.
> +
> +- **session_start_sync_init()**: Creates the worker thread before mount().
> +  Calls ioctl(FUSE_DEV_IOC_SYNC_INIT) to enable kernel support.
> +
> +- **session_wait_sync_init_completion()**: Waits for the worker thread
> +  to complete after mount() returns. Checks for errors.
> +
> +The worker thread processes requests in a loop until se->terminate_mount_worker
> +is set, which happens after mount() completes successfully.

Now that I've looked through this more carefully, I think it's all right
to do this.

> +
> +
> +Compatibility
> +-------------
> +
> +Synchronous FUSE_INIT is fully backward compatible:
> +
> +- Old kernels: ioctl returns ENOTTY, libfuse falls back to async mode
> +- Old applications: Continue to work with async FUSE_INIT
> +- New applications on old kernels: Graceful fallback to async mode
> +- New applications on new kernels: Automatic sync mode when appropriate
> +
> +
> +Example: Enabling Synchronous FUSE_INIT
> +----------------------------------------
> +
> +Explicit request:
> +    struct fuse_session *se = fuse_session_new(...);
> +    fuse_session_want_sync_init(se);
> +    fuse_session_mount(se, mountpoint);
> +
> +Automatic (with new daemonization API):
> +    fuse_daemonize_start(0);  // Triggers sync init automatically
> +    fuse_session_mount(se, mountpoint);
> +
> +
> +See Also
> +--------
> +
> +- doc/README.daemonize - New daemonization API documentation
> +- doc/README.fusermount - Synchronous FUSE_INIT protocol with fusermount3
> +- doc/README.mount - Mount implementation details

Thanks for splitting out the documentation updates.

--D

> +
> 
> -- 
> 2.43.0
> 
> 

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: fuse-devel list on kernel.org
  2026-03-26 21:34 [PATCH v2 00/25] libfuse: Add support for synchronous init Bernd Schubert
                   ` (24 preceding siblings ...)
  2026-03-26 21:34 ` [PATCH v2 25/25] Add a background debug option to passthrough hp Bernd Schubert
@ 2026-04-07 12:04 ` Amir Goldstein
  2026-04-07 12:25   ` Bernd Schubert
  25 siblings, 1 reply; 56+ messages in thread
From: Amir Goldstein @ 2026-04-07 12:04 UTC (permalink / raw)
  To: Bernd Schubert
  Cc: linux-fsdevel, Miklos Szeredi, Joanne Koong, Darrick J. Wong,
	Kevin Chen, Bernd Schubert

On Thu, Mar 26, 2026 at 10:34:33PM +0100, Bernd Schubert wrote:
> I'm taking Darricks example here and posting API changing libfuse
> changes to linux-fsdevel. We should consider to create a fuse
> specific list if that is too much. The existing
> fuse-devel@lists.sourceforge.net is rather hopeless due to lack
> of spam filtering.
> 

Let's do this already!

https://korg.docs.kernel.org/lore-archives.html

Do you want me to send the email to kernel.org?

I suggest that we ask for fuse-deve@lists.linux.dev
and then we can migrated the old sourceforge archives

As far as I can understand getting the archives from sourceforge
required the admin password:

https://sourceforge.net/p/forge/documentation/Mailing%20List%20Archives/

Mikos,

Will you be able to handle the archive migration?

Thanks,
Amir.

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: fuse-devel list on kernel.org
  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
  0 siblings, 1 reply; 56+ messages in thread
From: Bernd Schubert @ 2026-04-07 12:25 UTC (permalink / raw)
  To: Amir Goldstein
  Cc: linux-fsdevel, Miklos Szeredi, Joanne Koong, Darrick J. Wong,
	Kevin Chen, Bernd Schubert



On 4/7/26 14:04, Amir Goldstein wrote:
> On Thu, Mar 26, 2026 at 10:34:33PM +0100, Bernd Schubert wrote:
>> I'm taking Darricks example here and posting API changing libfuse
>> changes to linux-fsdevel. We should consider to create a fuse
>> specific list if that is too much. The existing
>> fuse-devel@lists.sourceforge.net is rather hopeless due to lack
>> of spam filtering.
>>
> 
> Let's do this already!
> 
> https://korg.docs.kernel.org/lore-archives.html
> 
> Do you want me to send the email to kernel.org?
> 
> I suggest that we ask for fuse-deve@lists.linux.dev
> and then we can migrated the old sourceforge archives

I'm all for a new fuse mailing list.

> 
> As far as I can understand getting the archives from sourceforge
> required the admin password:
> 
> https://sourceforge.net/p/forge/documentation/Mailing%20List%20Archives/
> 
> Mikos,
> 
> Will you be able to handle the archive migration?

Have that password from Nikolaus.


Thanks,
Bernd

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: fuse-devel list on kernel.org
  2026-04-07 12:25   ` Bernd Schubert
@ 2026-04-07 12:29     ` Amir Goldstein
  0 siblings, 0 replies; 56+ messages in thread
From: Amir Goldstein @ 2026-04-07 12:29 UTC (permalink / raw)
  To: Bernd Schubert
  Cc: linux-fsdevel, Miklos Szeredi, Joanne Koong, Darrick J. Wong,
	Kevin Chen, Bernd Schubert

On Tue, Apr 7, 2026 at 2:25 PM Bernd Schubert <bernd@bsbernd.com> wrote:
>
>
>
> On 4/7/26 14:04, Amir Goldstein wrote:
> > On Thu, Mar 26, 2026 at 10:34:33PM +0100, Bernd Schubert wrote:
> >> I'm taking Darricks example here and posting API changing libfuse
> >> changes to linux-fsdevel. We should consider to create a fuse
> >> specific list if that is too much. The existing
> >> fuse-devel@lists.sourceforge.net is rather hopeless due to lack
> >> of spam filtering.
> >>
> >
> > Let's do this already!
> >
> > https://korg.docs.kernel.org/lore-archives.html
> >
> > Do you want me to send the email to kernel.org?
> >
> > I suggest that we ask for fuse-deve@lists.linux.dev

Just don't copy paste my fuse-deve typo in the request ;)

> > and then we can migrated the old sourceforge archives
>
> I'm all for a new fuse mailing list.
>
> >
> > As far as I can understand getting the archives from sourceforge
> > required the admin password:
> >
> > https://sourceforge.net/p/forge/documentation/Mailing%20List%20Archives/
> >
> > Mikos,
> >
> > Will you be able to handle the archive migration?
>
> Have that password from Nikolaus.
>

Excellent, so will you be sending that email to helpdesk@kernel.org?

Thanks,
Amir.

^ permalink raw reply	[flat|nested] 56+ messages in thread

end of thread, other threads:[~2026-04-07 12:29 UTC | newest]

Thread overview: 56+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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-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-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-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-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-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-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

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox