From: "Darrick J. Wong" <djwong@kernel.org>
To: Bernd Schubert <bernd@bsbernd.com>
Cc: linux-fsdevel@vger.kernel.org, Miklos Szeredi <miklos@szeredi.hu>,
Joanne Koong <joannelkoong@gmail.com>
Subject: Re: [PATCH 04/19] Add a new daemonize API
Date: Mon, 23 Mar 2026 15:28:39 -0700 [thread overview]
Message-ID: <20260323222839.GG6202@frogsfrogsfrogs> (raw)
In-Reply-To: <20260323-fuse-init-before-mount-v1-4-a52d3040af69@bsbernd.com>
On Mon, Mar 23, 2026 at 06:44:59PM +0100, Bernd Schubert wrote:
> In complex fuse file systems one often wants
> a) fork
> b) start extra threads and system initialization (like network
> connection and RDMA memory registration).
> c) Start the fuse session
>
> With fuse_daemonize() there is no way to return a notification
> if step b) or c) failed. Therefore exising examples do
> the fuse_daemonize() after fuse_session_mount().
> Step, i.e. starting extra threads and possible failures are
> do not exist in these examples - unhandled use case.
Er, this sentence is a little garbled. Is this supposed to say "The
example servers do not cover step b. This is an un-demonstrated use
case"?
> With the FUSE_SYNC_INIT feature, which does FUSE_INIT at mount time
> and not after the mount anymore, it becomes even more complex
> as FUSE_INIT triggers startup of fuse_io_uring threads. That
> means for FUSE_SYNC_INIT forking/daemonization has to be done
> _before_ the fuse_session_mount().
Hey, that /is/ a neat trick!
> 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_signal() - 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>
> ---
> doc/README.daemonize | 186 +++++++++++++++++++++++++++++
> example/passthrough_hp.cc | 18 ++-
> include/fuse_daemonize.h | 71 +++++++++++
> include/meson.build | 3 +-
> lib/fuse_daemonize.c | 284 ++++++++++++++++++++++++++++++++++++++++++++
> lib/fuse_i.h | 4 +-
> lib/fuse_lowlevel.c | 1 +
> lib/fuse_versionscript | 3 +
> lib/helper.c | 13 +-
> lib/meson.build | 3 +-
> test/test_want_conversion.c | 1 +
> 11 files changed, 576 insertions(+), 11 deletions(-)
>
> diff --git a/doc/README.daemonize b/doc/README.daemonize
> new file mode 100644
> index 0000000000000000000000000000000000000000..f7473361b3a39369291e9401dfc5f7928ff4160b
> --- /dev/null
> +++ b/doc/README.daemonize
> @@ -0,0 +1,186 @@
> +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.
Yeah, I found this part pretty murky when writing fuse[24]fs -- the
internal library failures are fuse_log()'d, but then what if the fuse
server has its own logging library? libext2fs has its own, and there's
rather a lot of shenanigans that go on to make sure that libfuse and
libext2fs print to the same streams.
If you want to run as a systemd service, stdout/stderr are usually
routed to journald so there's no need to mess with stdout/stderr. But
then if you're running as a system service you don't want daemonize
anyway.
Ok enough rambling about my thing. I appreciate you documenting the old
fuse_daemonize in detail:
> +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 new session, redirects stdio
It creates a new session? I see that a pipe gets created between parent
and child, but I don't see a new fuse_session or ... oh, you meant a new
*Unix* session via setsid(). Can you say "creates new Unix session"?
> +- 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).
<nod> and I gather that's what fuse_daemonize_signal() is for.
> +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
Where do those errors go? It looks like by default they go to stderr,
which is /dev/null after daemonization, right?
> + - 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.
(For anyone following at home, fuse-containers cannot use synchronous
init because the new fuservicemount helper opens /dev/fuse and calls
mount() at the behest of the fuse server, so I'm mostly interested in
this as an exercise in getting to know libfuse.)
> +
> +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_signal()
> +----------------------------------------------------------
> +
> +Functions:
> + int fuse_daemonize_start(unsigned int flags);
> + void fuse_daemonize_signal(int status);
> + 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. fuse_daemonize_signal() - Signal success/failure to parent
> +
> +
> +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_signal()
> +-----------------------
> +
> +Status values:
> +- FUSE_DAEMONIZE_SUCCESS (0): Initialization succeeded
> +- FUSE_DAEMONIZE_FAILURE (1): Initialization failed
> +
> +Behavior:
> +- Signals the parent process with success or failure status
Does this get written back through the death pipe? Or does it use
process signals?
> +- Parent exits with EXIT_SUCCESS or EXIT_FAILURE accordingly
> +- On success: redirects stdout/stderr to /dev/null
> +- Stops the parent watcher thread
> +- Cleans up pipes and internal state
I think these might be better called fuse_daemonize_success() and
fuse_daemonize_fail() since one of them does more than just signal the
parent process.
> +- Safe to call multiple times
> +- Safe to call even if fuse_daemonize_start() failed
> +
> +
> +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;
> +
> + // Mount can now fail and be reported to parent
> + if (fuse_session_mount(se, mountpoint) != 0)
> + goto error_signal;
> +
> + // Complex initialization can fail and be reported
> + if (setup_threads() != 0)
> + goto error_signal;
> +
> + if (setup_network() != 0)
> + goto error_signal;
Hrmm, interesting, this solves the problem of fuse2fs having to wait
until FUSE_INIT to create background threads.
> +
> + // 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
Very nice! Does this new daemonization work for async-init servers?
> diff --git a/example/passthrough_hp.cc b/example/passthrough_hp.cc
> index 9f795c5546ee8312e1393c5b8fcebfd77724fb49..a0da5c7e6ab07f921df65db9c700e7de77fe1598 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_signal(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_active())
> + fuse_daemonize_signal(FUSE_DAEMONIZE_FAILURE);
> 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..f49f6aa580a93547198e7dabe65498708e65d024
> --- /dev/null
> +++ b/include/fuse_daemonize.h
> @@ -0,0 +1,71 @@
> +/*
> + * 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)
> +
> +/**
> + * Status values for fuse_daemonize_signal()
> + */
> +#define FUSE_DAEMONIZE_SUCCESS 0
> +#define FUSE_DAEMONIZE_FAILURE 1
What if fuse_daemonize_signal() took an exitcode and passed it directly
to the parent process via the death pipe, and the parent process can
pass that to exit()? IOWs, what if fuse_daemonize_signal writes @status
into signal_pipe_wr instead of flattening it to EXIT_SUCCESS/FAILURE?
Or, if you want to constrain the values to binary, then why not use
#define FUSE_DAEMONIZE_SUCCESS (EXIT_SUCCESS) ?
> +
> +/**
> + * 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_signal().
> + * 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 status to parent and cleanup.
> + *
> + * The child calls this after setup is complete (or failed).
> + * The parent receives the status and exits with it.
> + * Safe to call multiple times or if start failed.
> + *
> + * @param status FUSE_DAEMONIZE_SUCCESS or FUSE_DAEMONIZE_FAILURE
> + */
> +void fuse_daemonize_signal(int status);
> +
> +/**
> + * Check if daemonization is active and waiting for signal.
> + *
> + * @return true if active, false otherwise
> + */
> +bool fuse_daemonize_active(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..5d191e7d737d04876d02ef6bd526061c003d2ab7
> --- /dev/null
> +++ b/lib/fuse_daemonize.c
> @@ -0,0 +1,284 @@
> +/*
> + * 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>
> +
> +/* Private/internal data */
> +struct fuse_daemonize {
> + unsigned int flags;
> + int signal_pipe_wr; /* write end for signaling parent */
Hrm. Ok, so signal_pipe[2] is the pipe through which the child writes
an int back to the parent, and then the parent can convey that outcome
to whatever started the parent.
> + int death_pipe_rd; /* read end, POLLHUP when parent dies */
death_pipe[2] is a different pipe. The parent closes its end of the
pipe and the child treats POLLHUP as a message that the parent died.
> + int stop_pipe_rd; /* read end for stop signal */
> + int stop_pipe_wr; /* write end for stop signal */
and this third pipe exist so that the child can wake its own "parent
watcher" thread and have it abort.
> + pthread_t watcher;
> + int watcher_started;
Hm. So watcher_started is initialized to 0 in the parent, gets set to 1
in the child's copy of memory when it starts the parent-watcher thread,
and later becomes zero when the child shuts down the parent-watcher
thread.
> + _Atomic bool active;
active is set in the parent process, copied to the child process, and
cleared in fuse_daemonize_signal in the child.
> + _Atomic bool daemonized;
and daemonized is set in the child upon creation of the child; and
read by fuse_daemonize_signal. The parent never accesses its copy of
the variable.
> +};
> +
> +/* Global daemonization object pointer */
> +static _Atomic(struct fuse_daemonize *) daemonize;
> +
> +/* 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) {
> + perror("fuse_daemonize: pthread_create");
pthread functions return positive error numbers and do not set errno, so
you can't use perror().
> + return -1;
> + }
> + daemonize->watcher_started = 1;
Isn't this a bool value?
FWIW the rest of the logic below looks correct, though I think the
daemonize object itself would need a pthread_mutex_t to coordinate
access if it's possible or desirable for multiple threads to access it.
I think that's not the case, and any fuse server that did need that
could implement the locking on its own.
--D
> + 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 = 0;
> + }
> +}
> +
> +static int daemonize_child(struct fuse_daemonize *daemonize)
> +{
> + int stop_pipe[2];
> +
> + if (pipe(stop_pipe) == -1) {
> + perror("fuse_daemonize_start: stop pipe");
> + return -1;
> + }
> + daemonize->stop_pipe_rd = stop_pipe[0];
> + daemonize->stop_pipe_wr = stop_pipe[1];
> +
> + if (setsid() == -1) {
> + 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 */
> + if (start_parent_watcher(daemonize) != 0)
> + goto err_close_stop;
> +
> + daemonize->daemonized = true;
> + return 0;
> +
> +err_close_stop:
> + close(daemonize->stop_pipe_rd);
> + close(daemonize->stop_pipe_wr);
> + return -1;
> +}
> +
> +/* Fork and daemonize. Returns 0 in child, never returns in parent. */
> +static int do_daemonize(struct fuse_daemonize *daemonize)
> +{
> + int signal_pipe[2];
> + int death_pipe[2];
> +
> + if (pipe(signal_pipe) == -1) {
> + perror("fuse_daemonize_start: signal pipe");
> + return -1;
> + }
> +
> + if (pipe(death_pipe) == -1) {
> + perror("fuse_daemonize_start: death pipe");
> + close(signal_pipe[0]);
> + close(signal_pipe[1]);
> + return -1;
> + }
> +
> + switch (fork()) {
> + case -1:
> + perror("fuse_daemonize_start: fork");
> + close(signal_pipe[0]);
> + close(signal_pipe[1]);
> + close(death_pipe[0]);
> + close(death_pipe[1]);
> + return -1;
> +
> + 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) */
> + unsigned char 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;
> + struct fuse_daemonize *expected = NULL;
> +
> + dm = calloc(1, sizeof(*dm));
> + if (dm == NULL) {
> + fprintf(stderr, "%s: calloc failed\n", __func__);
> + return -ENOMEM;
> + }
> +
> + 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)) {
> + if (do_daemonize(dm) != 0) {
> + free(dm);
> + return -errno;
> + }
> + }
> +
> + /* Set global pointer using CAS - fail if already set */
> + if (!atomic_compare_exchange_strong(&daemonize, &expected, dm)) {
> + fprintf(stderr, "%s: already active\n", __func__);
> + free(dm);
> + return -EEXIST;
> + }
> +
> + return 0;
> +}
> +
> +static void close_if_valid(int *fd)
> +{
> + if (*fd != -1) {
> + close(*fd);
> + *fd = -1;
> + }
> +}
> +
> +void fuse_daemonize_signal(int status)
> +{
> + struct fuse_daemonize *dm;
> + unsigned char st;
> +
> + dm = atomic_load(&daemonize);
> + if (dm == NULL || !dm->active)
> + return;
> +
> + 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) {
> + st = (status != 0) ? EXIT_FAILURE : EXIT_SUCCESS;
> + if (write(dm->signal_pipe_wr, &st, sizeof(st)) != sizeof(st))
> + fprintf(stderr, "%s: write failed\n", __func__);
> + }
> +
> + /* Redirect stdout/stderr to /dev/null on success */
> + if (status == 0 && 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);
> +
> + /* Clear global pointer using CAS and free */
> + if (atomic_compare_exchange_strong(&daemonize, &dm, NULL))
> + free(dm);
> +}
> +
> +bool fuse_daemonize_active(void)
> +{
> + struct fuse_daemonize *dm = atomic_load(&daemonize);
> +
> + return dm != NULL && (dm->daemonized || dm->active);
> +}
> 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..4a1c7c4c02a05706e077a7ea89fab3c81bfe22cd 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>
> diff --git a/lib/fuse_versionscript b/lib/fuse_versionscript
> index cce09610316f4b0b1d6836dd0e63686342b70037..f1765d39e13bc9b1f53e625b9a091c5fa53f5afd 100644
> --- a/lib/fuse_versionscript
> +++ b/lib/fuse_versionscript
> @@ -227,6 +227,9 @@ FUSE_3.19 {
> fuse_session_start_teardown_watchdog;
> fuse_session_stop_teardown_watchdog;
> fuse_lowlevel_notify_prune;
> + fuse_daemonize_start;
> + fuse_daemonize_signal;
> + fuse_daemonize_active;
> } FUSE_3.18;
>
> # Local Variables:
> diff --git a/lib/helper.c b/lib/helper.c
> index 5c13b93a473181f027eba01e0bfefd78875ede3e..e6ec74364d000a6e091e0596fc74954b11cc51ab 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_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
>
>
next prev parent reply other threads:[~2026-03-23 22:28 UTC|newest]
Thread overview: 61+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-03-23 17:44 [PATCH 00/19] libfuse: Add support for synchronous init Bernd Schubert
2026-03-23 17:44 ` [PATCH 01/19] ci-build: Add environment logging Bernd Schubert
2026-03-23 17:44 ` [PATCH 02/19] Add 'STRCPY' to the checkpatch ignore option Bernd Schubert
2026-03-23 21:03 ` Darrick J. Wong
2026-03-23 17:44 ` [PATCH 03/19] checkpatch.pl: Add _Atomic to $Attribute patttern Bernd Schubert
2026-03-23 21:09 ` Darrick J. Wong
2026-03-23 17:44 ` [PATCH 04/19] Add a new daemonize API Bernd Schubert
2026-03-23 22:28 ` Darrick J. Wong [this message]
2026-03-24 17:36 ` Bernd Schubert
2026-03-24 22:20 ` Darrick J. Wong
2026-03-23 17:45 ` [PATCH 05/19] Sync fuse_kernel.h with linux-6.18 Bernd Schubert
2026-03-23 21:16 ` Darrick J. Wong
2026-03-23 17:45 ` [PATCH 06/19] mount.c: Split fuse_mount_sys to prepare privileged sync FUSE_INIT Bernd Schubert
2026-03-23 22:34 ` Darrick J. Wong
2026-03-23 17:45 ` [PATCH 07/19] Add FUSE_MOUNT_FALLBACK_NEEDED define for -2 mount errors Bernd Schubert
2026-03-23 22:36 ` Darrick J. Wong
2026-03-24 18:03 ` Bernd Schubert
2026-03-23 17:45 ` [PATCH 08/19] Refactor mount code / move common functions to mount_util.c Bernd Schubert
2026-03-23 22:40 ` Darrick J. Wong
2026-03-23 17:45 ` [PATCH 09/19] Move mount flags to mount_i.h Bernd Schubert
2026-03-23 22:45 ` Darrick J. Wong
2026-03-24 18:40 ` Bernd Schubert
2026-03-23 17:45 ` [PATCH 10/19] conftest.py: Add more valgrind filter patterns Bernd Schubert
2026-03-23 17:45 ` [PATCH 11/19] Add support for the new linux mount API Bernd Schubert
2026-03-23 23:42 ` Darrick J. Wong
2026-03-24 20:16 ` Bernd Schubert
2026-03-24 22:46 ` Darrick J. Wong
2026-03-23 17:45 ` [PATCH 12/19] fuse mount: Support synchronous FUSE_INIT (privileged daemon) Bernd Schubert
2026-03-24 0:03 ` Darrick J. Wong
2026-03-24 20:42 ` Bernd Schubert
2026-03-24 22:50 ` Darrick J. Wong
2026-03-25 7:52 ` Bernd Schubert
2026-03-25 16:42 ` Darrick J. Wong
2026-03-26 19:32 ` Bernd Schubert
2026-03-26 22:33 ` Darrick J. Wong
2026-03-23 17:45 ` [PATCH 13/19] Add fuse_session_set_debug() to enable debug output without foreground Bernd Schubert
2026-03-24 0:04 ` Darrick J. Wong
2026-03-23 17:45 ` [PATCH 14/19] Move more generic mount code to mount_util.{c,h} Bernd Schubert
2026-03-24 0:06 ` Darrick J. Wong
2026-03-24 20:57 ` Bernd Schubert
2026-03-23 17:45 ` [PATCH 15/19] Split the fusermount do_mount function Bernd Schubert
2026-03-24 0:14 ` Darrick J. Wong
2026-03-24 21:05 ` Bernd Schubert
2026-03-24 22:53 ` Darrick J. Wong
2026-03-23 17:45 ` [PATCH 16/19] fusermount: Refactor extract_x_options Bernd Schubert
2026-03-24 0:18 ` Darrick J. Wong
2026-03-23 17:45 ` [PATCH 17/19] Make fusermount work bidirectional for sync init Bernd Schubert
2026-03-24 19:35 ` Darrick J. Wong
2026-03-24 21:24 ` Bernd Schubert
2026-03-24 22:59 ` Darrick J. Wong
2026-03-25 19:48 ` Bernd Schubert
2026-03-25 22:03 ` Darrick J. Wong
2026-03-23 17:45 ` [PATCH 18/19] New mount API: Filter out "user=" Bernd Schubert
2026-03-24 19:51 ` Darrick J. Wong
2026-03-24 20:01 ` Bernd Schubert
2026-03-24 23:02 ` Darrick J. Wong
2026-03-23 17:45 ` [PATCH 19/19] Add support for sync-init of unprivileged daemons Bernd Schubert
2026-03-24 20:21 ` Darrick J. Wong
2026-03-24 21:53 ` Bernd Schubert
2026-03-24 23:13 ` Darrick J. Wong
2026-03-24 0:19 ` [PATCH 00/19] libfuse: Add support for synchronous init Darrick J. Wong
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260323222839.GG6202@frogsfrogsfrogs \
--to=djwong@kernel.org \
--cc=bernd@bsbernd.com \
--cc=joannelkoong@gmail.com \
--cc=linux-fsdevel@vger.kernel.org \
--cc=miklos@szeredi.hu \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox