| /* |
| * 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 "fuse_daemonize_i.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> |
| #include <err.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; /* daemonization in progress */ |
| _Atomic bool daemonized; /* daemonized successfully */ |
| _Atomic bool mounted; /* fuse_session_mount() completed */ |
| _Atomic bool got_init; /* got FUSE_INIT */ |
| }; |
| |
| /* 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) |
| { |
| const 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 *dm) |
| { |
| int rc; |
| |
| rc = pthread_create(&dm->watcher, NULL, parent_watcher_thread, |
| dm); |
| if (rc != 0) { |
| fprintf(stderr, "fuse_daemonize: pthread_create: %s\n", |
| strerror(rc)); |
| return -rc; |
| } |
| dm->watcher_started = true; |
| return 0; |
| } |
| |
| static void stop_parent_watcher(struct fuse_daemonize *dm) |
| { |
| char byte = 0; |
| |
| if (dm && dm->watcher_started) { |
| /* Signal watcher to stop */ |
| if (write(dm->stop_pipe_wr, &byte, 1) != 1) |
| perror("fuse_daemonize: stop write"); |
| pthread_join(dm->watcher, NULL); |
| dm->watcher_started = false; |
| } |
| } |
| |
| static int daemonize_child(struct fuse_daemonize *dm) |
| { |
| int stop_pipe[2], err = 0; |
| |
| if (pipe(stop_pipe) == -1) { |
| err = -errno; |
| perror("fuse_daemonize_start: stop pipe"); |
| return err; |
| } |
| dm->stop_pipe_rd = stop_pipe[0]; |
| dm->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(dm); |
| if (err) |
| goto err_close_stop; |
| |
| dm->daemonized = true; |
| return 0; |
| |
| err_close_stop: |
| close(dm->stop_pipe_rd); |
| close(dm->stop_pipe_wr); |
| return err; |
| } |
| |
| /* Fork and daemonize. Returns 0 in child, never returns in parent. */ |
| static int do_daemonize(struct fuse_daemonize *dm) |
| { |
| 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]); |
| dm->signal_pipe_wr = signal_pipe[1]; |
| dm->death_pipe_rd = death_pipe[0]; |
| return daemonize_child(dm); |
| |
| 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_early_start(unsigned int flags) |
| { |
| struct fuse_daemonize *dm = &daemonize; |
| int err = 0; |
| |
| if (dm->active) |
| return 0; /* already called, no need for another fork */ |
| |
| dm->flags = flags; |
| dm->signal_pipe_wr = -1; |
| dm->death_pipe_rd = -1; |
| dm->stop_pipe_rd = -1; |
| dm->stop_pipe_wr = -1; |
| |
| if (!(flags & FUSE_DAEMONIZE_NO_CHDIR)) |
| (void)chdir("/"); |
| |
| if (!(flags & FUSE_DAEMONIZE_NO_BACKGROUND)) { |
| dm->active = true; |
| err = do_daemonize(dm); |
| } |
| |
| return err; |
| } |
| |
| static void close_if_valid(int *fd) |
| { |
| if (*fd != -1) { |
| close(*fd); |
| *fd = -1; |
| } |
| } |
| |
| static void fuse_daemonize_early_signal(int status) |
| { |
| struct fuse_daemonize *dm = &daemonize; |
| |
| if (!dm->active) |
| errx(EINVAL, "%s: not active and cannot signal status", __func__); |
| |
| /* 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; |
| |
| /* Stop watcher before signaling - parent will exit after this */ |
| stop_parent_watcher(dm); |
| |
| /* Signal status to parent */ |
| if (dm->signal_pipe_wr != -1) { |
| int 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_early_success(void) |
| { |
| /* |
| * Needs to be gracefully handled as automatically called libfuse |
| * internal from FUSE_INIT handler |
| */ |
| if (!fuse_daemonize_early_is_used()) |
| return; |
| |
| fuse_daemonize_early_signal(FUSE_DAEMONIZE_SUCCESS); |
| } |
| |
| void fuse_daemonize_early_fail(int err) |
| { |
| fuse_daemonize_early_signal(err); |
| } |
| |
| bool fuse_daemonize_early_is_used(void) |
| { |
| return daemonize.daemonized || daemonize.active; |
| } |
| |
| bool fuse_daemonize_early_is_active(void) |
| { |
| return daemonize.active; |
| } |
| |
| void fuse_daemonize_early_set_mounted(void) |
| { |
| daemonize.mounted = true; |
| } |
| |
| void fuse_daemonize_set_got_init(void) |
| { |
| daemonize.got_init = true; |
| } |
| |
| /* |
| * defined in fuse_common.h, but fuse_common.h is outside of the scope |
| * of this file - duplicated definition is better here. |
| */ |
| int fuse_daemonize(int foreground); |
| |
| int fuse_daemonize(int foreground) |
| { |
| /* Check if the NEW API is used */ |
| if (fuse_daemonize_early_is_used()) { |
| perror("Newer API fuse_daemonize_start() already used\n"); |
| return -1; |
| } |
| |
| if (!foreground) { |
| int nullfd; |
| int waiter[2]; |
| char completed; |
| |
| if (pipe(waiter)) { |
| err(errno, "%s: pipe\n", __func__); |
| return -1; |
| } |
| |
| /* |
| * demonize current process by forking it and killing the |
| * parent. This makes current process as a child of 'init'. |
| */ |
| switch (fork()) { |
| case -1: |
| err(errno, "%s: fork\n", __func__); |
| close(waiter[0]); |
| close(waiter[1]); |
| return -1; |
| case 0: |
| /* child */ |
| close(waiter[0]); |
| waiter[0] = -1; |
| break; |
| default: |
| /* parent */ |
| (void)read(waiter[0], &completed, sizeof(completed)); |
| close(waiter[0]); |
| close(waiter[1]); |
| _exit(0); |
| } |
| |
| if (setsid() == -1) { |
| err(errno, "%s: setsid", __func__); |
| close(waiter[1]); |
| return -1; |
| } |
| |
| (void)chdir("/"); |
| |
| nullfd = open("/dev/null", O_RDWR, 0); |
| if (nullfd != -1) { |
| (void)dup2(nullfd, 0); |
| (void)dup2(nullfd, 1); |
| (void)dup2(nullfd, 2); |
| if (nullfd > 2) |
| close(nullfd); |
| } |
| |
| /* Propagate completion of daemon initialization */ |
| completed = 1; |
| (void)write(waiter[1], &completed, sizeof(completed)); |
| close(waiter[1]); |
| waiter[1] = -1; |
| } else { |
| (void)chdir("/"); |
| } |
| |
| return 0; |
| } |
| |
| |