Linux filesystem development
 help / color / mirror / Atom feed
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
> 
> 

  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