Download raw body.
fuse: change termination behaviour
This patch addresses incompatibilities in the way that FUSE handles
terminating a FUSE session.
The primary change is:
The Linux libfuse implementation of fuse_loop(3) terminates either when
the kernel sends FUSE_DESTROY or, if fuse_set_signal_handlers(3) has
been called, when it catches one of SIGINT, SIGHUP, or SIGTERM.
The OpenBSD implementation behaves similarly when the file system is
unmounted with umount(8). However, it tries to unmount the file system
automatically when one of the above signals is caught. It should
instead just terminate and rely on fuse_unmount(3) being called later by
the FUSE file system daemon as part of its termination.
Additional changes:
- The FUSE file system daemon's destroy handler is the last operation
called after the file system is unmounted. Before, it was incorrectly
being called when FBT_DESTROY is received by fuse_loop(3). The destroy
handler is now called in fuse_destroy(3).
- The file system is no longer unmounted when the device is closed.
fuse_unmount(3) now closes the FUSE device before unmount(2) is called to
prevent deadlocks due to the kernel trying to send FBT_DESTROY when
fuse_loop(3) is no longer active and there listening for messages from
the kernel.
The side effect of this change is that if a FUSE file system daemon
crashes, the FUSE device is automatically closed but the file system is not
unmounted. It must be unmounted manually with umount(8). This will
likely raise some eyebrows, but's it how it works on Linux and FreeBSD
(I haven't looked at other platforms).
- Man page updates to reflect this change and correct a few other minor
errors.
OK?
Index: lib/libfuse/fuse.c
===================================================================
RCS file: /cvs/src/lib/libfuse/fuse.c,v
diff -u -p -r1.54 fuse.c
--- lib/libfuse/fuse.c 6 Sep 2025 06:15:52 -0000 1.54
+++ lib/libfuse/fuse.c 10 Sep 2025 18:06:05 -0000
@@ -113,42 +113,6 @@ static struct fuse_opt fuse_mount_opts[]
FUSE_OPT_END
};
-static void
-ifuse_try_unmount(struct fuse *f)
-{
- pid_t child;
-
- /* unmount in another thread so fuse_loop() doesn't deadlock */
- child = fork();
-
- if (child == -1) {
- DPERROR(__func__);
- return;
- }
-
- if (child == 0) {
- fuse_remove_signal_handlers(fuse_get_session(f));
- errno = 0;
- fuse_unmount(f->fc->dir, f->fc);
- _exit(errno);
- }
-}
-
-static void
-ifuse_child_exit(const struct fuse *f)
-{
- int status;
-
- if (waitpid(WAIT_ANY, &status, WNOHANG) == -1)
- fprintf(stderr, "fuse: %s\n", strerror(errno));
-
- if (WIFEXITED(status) && (WEXITSTATUS(status) != 0))
- fprintf(stderr, "fuse: %s: %s\n",
- f->fc->dir, strerror(WEXITSTATUS(status)));
-
- return;
-}
-
int
fuse_loop(struct fuse *fuse)
{
@@ -159,7 +123,7 @@ fuse_loop(struct fuse *fuse)
struct iovec iov[2];
size_t fb_dat_size = FUSEBUFMAXSIZE;
ssize_t n;
- int ret;
+ int ret, intr;
if (fuse == NULL)
return (-1);
@@ -189,7 +153,8 @@ fuse_loop(struct fuse *fuse)
iov[0].iov_len = sizeof(fbuf.fb_hdr) + sizeof(fbuf.FD);
iov[1].iov_base = fbuf.fb_dat;
- while (!fuse->fc->dead) {
+ intr = 0;
+ while (!intr && !fuse->fc->dead) {
ret = kevent(fuse->fc->kq, &event[0], 5, &ev, 1, NULL);
if (ret == -1) {
if (errno != EINTR)
@@ -197,23 +162,21 @@ fuse_loop(struct fuse *fuse)
} else if (ret > 0 && ev.filter == EVFILT_SIGNAL) {
int signum = ev.ident;
switch (signum) {
- case SIGCHLD:
- ifuse_child_exit(fuse);
- break;
case SIGHUP:
case SIGINT:
case SIGTERM:
- ifuse_try_unmount(fuse);
+ DPRINTF("%s: %s\n", __func__,
+ strsignal(signum));
+ intr = 1;
break;
default:
fprintf(stderr, "%s: %s\n", __func__,
- strsignal(signum));
+ strsignal(signum));
}
} else if (ret > 0) {
iov[1].iov_len = fb_dat_size;
n = readv(fuse->fc->fd, iov, 2);
if (n == -1) {
- perror("fuse_loop");
fprintf(stderr, "%s: bad fusebuf read: %s\n",
__func__, strerror(errno));
free(fbuf.fb_dat);
@@ -251,7 +214,6 @@ fuse_loop(struct fuse *fuse)
ctx.umask = fbuf.fb_umask;
ctx.private_data = fuse->private_data;
ictx = &ctx;
-
ret = ifuse_exec_opcode(fuse, &fbuf);
if (ret) {
ictx = NULL;
@@ -353,11 +315,19 @@ DEF(fuse_mount);
void
fuse_unmount(const char *dir, struct fuse_chan *ch)
{
- if (ch == NULL || ch->dead)
+ if (ch == NULL)
return;
- if (unmount(dir, MNT_UPDATE) == -1)
+ /*
+ * Close the device before unmounting to prevent deadlocks with
+ * FBT_DESTROY if fuse_loop() has already terminated.
+ */
+ if (close(ch->fd) == -1)
DPERROR(__func__);
+
+ if (!ch->dead)
+ if (unmount(dir, MNT_FORCE) == -1)
+ DPERROR(__func__);
}
DEF(fuse_unmount);
@@ -463,20 +433,32 @@ fuse_daemonize(int foreground)
DEF(fuse_daemonize);
void
-fuse_destroy(struct fuse *f)
+fuse_destroy(struct fuse *fuse)
{
- if (f == NULL)
+ struct fuse_context ctx;
+
+ if (fuse == NULL)
return;
+ if (fuse->fc->init && fuse->op.destroy) {
+ /* setup a basic fuse context for the callback */
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.fuse = fuse;
+ ctx.private_data = fuse->private_data;
+ ictx = &ctx;
+
+ fuse->op.destroy(fuse->private_data);
+
+ ictx = NULL;
+ }
+
/*
* Even though these were allocated in fuse_mount(), we can't free them
- * in fuse_unmount() since fuse_loop() will not have terminated yet so
- * we free them here.
+ * in fuse_unmount() since they are still needed, so we free them here.
*/
- close(f->fc->fd);
- free(f->fc->dir);
- free(f->fc);
- free(f);
+ free(fuse->fc->dir);
+ free(fuse->fc);
+ free(fuse);
}
DEF(fuse_destroy);
@@ -532,11 +514,6 @@ fuse_set_signal_handlers(unused struct f
if (old_sa.sa_handler == SIG_DFL)
signal(SIGPIPE, SIG_IGN);
- if (sigaction(SIGCHLD, NULL, &old_sa) == -1)
- return (-1);
- if (old_sa.sa_handler == SIG_DFL)
- signal(SIGCHLD, SIG_IGN);
-
return (0);
}
@@ -723,10 +700,15 @@ int
fuse_main(int argc, char **argv, const struct fuse_operations *ops, void *data)
{
struct fuse *fuse;
+ char *mp;
+ int ret;
- fuse = fuse_setup(argc, argv, ops, sizeof(*ops), NULL, NULL, data);
+ fuse = fuse_setup(argc, argv, ops, sizeof(*ops), &mp, NULL, data);
if (fuse == NULL)
return (-1);
- return (fuse_loop(fuse));
+ ret = fuse_loop(fuse);
+ fuse_teardown(fuse, mp);
+
+ return (ret == -1 ? 1 : 0);
}
Index: lib/libfuse/fuse_destroy.3
===================================================================
RCS file: /cvs/src/lib/libfuse/fuse_destroy.3,v
diff -u -p -r1.3 fuse_destroy.3
--- lib/libfuse/fuse_destroy.3 10 Jun 2025 12:55:33 -0000 1.3
+++ lib/libfuse/fuse_destroy.3 10 Sep 2025 18:06:05 -0000
@@ -27,9 +27,12 @@
.Fn fuse_destroy "struct fuse *f"
.Sh DESCRIPTION
.Fn fuse_destroy
-closes the FUSE device and frees memory associated with the FUSE channel
-and FUSE handle specified by
+Frees memory associated with the FUSE channel and FUSE handle specified by
.Fa f .
+The file system's destroy operation will be called if
+.Xr fuse_loop 3
+did not receive the FBT_DESTROY message.
+Usually due to terminating from a signal.
.Pp
This function does not unmount the file system, which should be done
with
Index: lib/libfuse/fuse_loop.3
===================================================================
RCS file: /cvs/src/lib/libfuse/fuse_loop.3,v
diff -u -p -r1.3 fuse_loop.3
--- lib/libfuse/fuse_loop.3 10 Jun 2025 12:55:33 -0000 1.3
+++ lib/libfuse/fuse_loop.3 10 Sep 2025 18:06:05 -0000
@@ -47,7 +47,7 @@ the file system is being unmounted.
If FUSE signaler handlers have been installed and either SIGHUP, SIGINT
or SIGTERM is received then
.Fn fuse_loop
-will attempt to unmount the file system.
+will terminate.
See
.Xr fuse_set_signal_handlers 3 .
.Pp
Index: lib/libfuse/fuse_main.3
===================================================================
RCS file: /cvs/src/lib/libfuse/fuse_main.3,v
diff -u -p -r1.8 fuse_main.3
--- lib/libfuse/fuse_main.3 10 Jun 2025 12:55:33 -0000 1.8
+++ lib/libfuse/fuse_main.3 10 Sep 2025 18:06:05 -0000
@@ -137,6 +137,7 @@ main(int argc, char **argv)
.Xr fuse_new 3 ,
.Xr fuse_parse_cmdline 3 ,
.Xr fuse_setup 3 ,
+.Xr fuse_teardown 3 ,
.Xr fuse 4
.Sh STANDARDS
The
Index: lib/libfuse/fuse_mount.3
===================================================================
RCS file: /cvs/src/lib/libfuse/fuse_mount.3,v
diff -u -p -r1.3 fuse_mount.3
--- lib/libfuse/fuse_mount.3 10 Jun 2025 12:55:33 -0000 1.3
+++ lib/libfuse/fuse_mount.3 10 Sep 2025 18:06:05 -0000
@@ -77,15 +77,13 @@ Can also be specified by itself with
.El
.Pp
.Fn fuse_unmount
-will attempt to unmount the file system mounted at
+will close the FUSE device and attempt to unmount the file system mounted at
.Fa dir
by calling the
.Xr unmount 2
system call.
-If this is successful, the kernel will send the
-FBT_DESTROY message to the file system, causing
-.Xr fuse_loop 3
-to terminate.
+To avoid a deadlock, the kernel will not send the
+FBT_DESTROY message to the file system.
There is no way to determine whether this call was successful.
.Pp
Only the super user can mount and unmount FUSE file systems.
Index: lib/libfuse/fuse_new.3
===================================================================
RCS file: /cvs/src/lib/libfuse/fuse_new.3,v
diff -u -p -r1.9 fuse_new.3
--- lib/libfuse/fuse_new.3 9 Sep 2025 16:46:55 -0000 1.9
+++ lib/libfuse/fuse_new.3 10 Sep 2025 18:06:05 -0000
@@ -43,8 +43,8 @@ FUSE will return ENOSYS if any operation
fsyncdir is not implemented.
.Pp
The first parameter to each of these operations (except for init and
-terminate) is a NULL terminated string representing the full path to
-the file or directory, relative to the root of this file system, that
+destroy) is a NULL terminated string representing the full path to
+the file or directory, relative to the root of the file system, that
is being operated on.
.Bd -literal
struct fuse_operations {
Index: lib/libfuse/fuse_ops.c
===================================================================
RCS file: /cvs/src/lib/libfuse/fuse_ops.c,v
diff -u -p -r1.39 fuse_ops.c
--- lib/libfuse/fuse_ops.c 9 Sep 2025 16:46:55 -0000 1.39
+++ lib/libfuse/fuse_ops.c 10 Sep 2025 18:06:05 -0000
@@ -74,6 +74,9 @@ ifuse_ops_init(struct fuse *f)
f->op.init(&fci);
}
+
+ f->fc->init = 1;
+
return (0);
}
@@ -969,15 +972,7 @@ ifuse_ops_rename(struct fuse *f, struct
static int
ifuse_ops_destroy(struct fuse *f)
{
- struct fuse_context *ctx;
-
DPRINTF("Opcode: destroy\n");
-
- if (f->op.destroy) {
- ctx = fuse_get_context();
-
- f->op.destroy((ctx)?ctx->private_data:NULL);
- }
f->fc->dead = 1;
Index: lib/libfuse/fuse_private.h
===================================================================
RCS file: /cvs/src/lib/libfuse/fuse_private.h,v
diff -u -p -r1.23 fuse_private.h
--- lib/libfuse/fuse_private.h 20 Sep 2024 02:00:46 -0000 1.23
+++ lib/libfuse/fuse_private.h 10 Sep 2025 18:06:05 -0000
@@ -65,6 +65,7 @@ struct fuse_chan {
struct fuse_args *args;
int fd;
+ int init;
int dead;
/* kqueue stuff */
Index: sys/miscfs/fuse/fuse_device.c
===================================================================
RCS file: /cvs/src/sys/miscfs/fuse/fuse_device.c,v
diff -u -p -r1.45 fuse_device.c
--- sys/miscfs/fuse/fuse_device.c 9 Sep 2025 16:46:55 -0000 1.45
+++ sys/miscfs/fuse/fuse_device.c 10 Sep 2025 18:06:19 -0000
@@ -165,6 +165,7 @@ fuse_device_cleanup(dev_t dev)
wakeup(f);
lprev = f;
}
+ knote_locked(&fd->fd_rklist, 0);
rw_exit_write(&fd->fd_lock);
/* clear FIFO WAIT*/
@@ -247,26 +248,22 @@ int
fuseclose(dev_t dev, int flags, int fmt, struct proc *p)
{
struct fuse_d *fd;
- int error;
fd = fuse_lookup(minor(dev));
if (fd == NULL)
return (EINVAL);
- if (fd->fd_fmp) {
- printf("fuse: device close without umount\n");
+ fuse_device_cleanup(dev);
+
+ /*
+ * Let fusefs_unmount know the device is closed so it doesn't try and
+ * send FBT_DESTROY to a dead file system daemon.
+ */
+ if (fd->fd_fmp)
fd->fd_fmp->sess_init = 0;
- fuse_device_cleanup(dev);
- if ((vfs_busy(fd->fd_fmp->mp, VB_WRITE | VB_NOWAIT)) != 0)
- goto end;
- error = dounmount(fd->fd_fmp->mp, MNT_FORCE, p);
- if (error)
- printf("fuse: unmount failed with error %d\n", error);
- fd->fd_fmp = NULL;
- }
-end:
LIST_REMOVE(fd, fd_list);
+
free(fd, M_DEVBUF, sizeof(*fd));
stat_opened_fusedev--;
return (0);
fuse: change termination behaviour