tech-userlevel archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

Request for review: librefuse overhaul for supporting more filesystems



Hello,

I've been working on an overhaul of librefuse to support wider range of API versions while also retaining ABI compatibility. In fact it now supports all the API variants from the oldest FUSE 1.1 to the latest FUSE 3.10. It *should* be now easier to support future API versions as well.

I tested my patches against all of pkgsrc filesystems/fuse-* and confirmed that they actually work, or at least they compile without #ifdef's they currently have. (Some of the packages had patches to accommodate to the past broken state of librefuse and I had to conditionalize them, which is mostly my fault because it's me who sorta broke librefuse.)

So the question is, can anyone review this... huge mess? Here are the patches that can damage your brain and sanity, also attached to this mail:
https://github.com/NetBSD/src/compare/trunk...depressed-pho:refuse-overhaul

<rant>It's a huge mess because... let's just say FUSE API isn't very carefully designed. Every time they add something to the API, they always do it with the least possible extensibility, and then later fix it by introducing a breaking change to the API, again with the least possible... Ugh.</rant>
From 59ba89075911abd4cf057943e81cba4ddcf60073 Mon Sep 17 00:00:00 2001
From: PHO <pho%cielonegro.org@localhost>
Date: Wed, 12 Jan 2022 15:46:43 +0900
Subject: [PATCH 01/17] Allow calling puffs_mount(3) before puffs_daemon(3)

puffs_daemon(3) creates a pipe before forking, and the parent process
waits for the child to either complete puffs_mount(3) or fail. If a
user calls puffs_daemon(3) after puffs_mount(3), the function
deadlocks. While this error-reporting functionality is really a nice
thing to have, deadlocking is not great. If the filesystem has already
been mounted, puffs_mount(3) should just daemonize the process and
return.

This became an issue because fuse_daemonize(3) in FUSE API had no such
requirement and some FUSE filesystems in the wild suffered deadlocks.
---
 lib/libpuffs/Makefile     |  1 -
 lib/libpuffs/puffs.3      |  6 ++++--
 lib/libpuffs/puffs.c      | 40 +++++++++++++++++++++++----------------
 lib/libpuffs/puffs_priv.h |  1 +
 4 files changed, 29 insertions(+), 19 deletions(-)

diff --git a/lib/libpuffs/Makefile b/lib/libpuffs/Makefile
index 4720dca61..92865628c 100644
--- a/lib/libpuffs/Makefile
+++ b/lib/libpuffs/Makefile
@@ -16,7 +16,6 @@ MAN=		puffs.3 puffs_cc.3 puffs_cred.3 puffs_flush.3		\
 		puffs_framebuf.3 puffs_node.3 puffs_ops.3 puffs_path.3
 INCS=		puffs.h puffsdump.h
 INCSDIR=	/usr/include
-CPPFLAGS+=	-D_KERNTYPES
 LINTFLAGS+=-S -w
 
 .include <bsd.lib.mk>
diff --git a/lib/libpuffs/puffs.3 b/lib/libpuffs/puffs.3
index 3ab4171b4..d17387e3c 100644
--- a/lib/libpuffs/puffs.3
+++ b/lib/libpuffs/puffs.3
@@ -455,11 +455,13 @@ is called
 .It Fn puffs_daemon pu nochdir noclose
 Detach from the console like
 .Fn daemon 3 .
-This call synchronizes with
+If it is called before
+.Fn puffs_mount ,
+this call synchronizes with
 .Fn puffs_mount
 and the foreground process does not exit before the file system mount
 call has returned from the kernel.
-Since this routine internally calls fork, it has to be called
+Since this routine internally calls fork, it is highly recommended to call it
 .Em before
 .Fn puffs_mount .
 .It Fn puffs_mainloop pu flags
diff --git a/lib/libpuffs/puffs.c b/lib/libpuffs/puffs.c
index 377e55f59..c6d438a87 100644
--- a/lib/libpuffs/puffs.c
+++ b/lib/libpuffs/puffs.c
@@ -440,9 +440,12 @@ puffs_daemon(struct puffs_usermount *pu, int nochdir, int noclose)
 {
 	long int n;
 	int parent, value, fd;
+	bool is_beforemount;
 
-	if (pipe(pu->pu_dpipe) == -1)
-		return -1;
+	is_beforemount = (puffs_getstate(pu) < PUFFS_STATE_RUNNING);
+	if (is_beforemount)
+		if (pipe(pu->pu_dpipe) == -1)
+			return -1;
 
 	switch (fork()) {
 	case -1:
@@ -454,18 +457,21 @@ puffs_daemon(struct puffs_usermount *pu, int nochdir, int noclose)
 		parent = 1;
 		break;
 	}
-	pu->pu_state |= PU_PUFFSDAEMON;
+	if (is_beforemount)
+		PU_SETSFLAG(pu, PU_PUFFSDAEMON);
 
 	if (parent) {
-		close(pu->pu_dpipe[1]);
-		n = read(pu->pu_dpipe[0], &value, sizeof(int));
-		if (n == -1)
-			err(1, "puffs_daemon");
-		if (n != sizeof(value))
-			errx(1, "puffs_daemon got %ld bytes", n);
-		if (value) {
-			errno = value;
-			err(1, "puffs_daemon");
+		if (is_beforemount) {
+			close(pu->pu_dpipe[1]);
+			n = read(pu->pu_dpipe[0], &value, sizeof(int));
+			if (n == -1)
+				err(1, "puffs_daemon");
+			if (n != sizeof(value))
+				errx(1, "puffs_daemon got %ld bytes", n);
+			if (value) {
+				errno = value;
+				err(1, "puffs_daemon");
+			}
 		}
 		exit(0);
 	} else {
@@ -489,8 +495,10 @@ puffs_daemon(struct puffs_usermount *pu, int nochdir, int noclose)
 	}
 
  fail:
-	n = write(pu->pu_dpipe[1], &errno, sizeof(int));
-	assert(n == 4);
+	if (is_beforemount) {
+		n = write(pu->pu_dpipe[1], &errno, sizeof(int));
+		assert(n == 4);
+	}
 	return -1;
 }
 
@@ -614,7 +622,7 @@ do {									\
 	free(pu->pu_kargp);
 	pu->pu_kargp = NULL;
 
-	if (pu->pu_state & PU_PUFFSDAEMON)
+	if (PU_GETSFLAG(pu, PU_PUFFSDAEMON))
 		shutdaemon(pu, sverrno);
 
 	errno = sverrno;
@@ -706,8 +714,8 @@ puffs_init(struct puffs_ops *pops, const char *mntfromname,
 void
 puffs_cancel(struct puffs_usermount *pu, int error)
 {
-
 	assert(puffs_getstate(pu) < PUFFS_STATE_RUNNING);
+	assert(PU_GETSFLAG(pu, PU_PUFFSDAEMON));
 	shutdaemon(pu, error);
 	free(pu);
 }
diff --git a/lib/libpuffs/puffs_priv.h b/lib/libpuffs/puffs_priv.h
index 289bd5eb9..cbd1ec82f 100644
--- a/lib/libpuffs/puffs_priv.h
+++ b/lib/libpuffs/puffs_priv.h
@@ -113,6 +113,7 @@ struct puffs_usermount {
 #define PU_DONEXIT	0x2000
 #define PU_SETSTATE(pu, s) (pu->pu_state = (s) | (pu->pu_state & ~PU_STATEMASK))
 #define PU_SETSFLAG(pu, s) (pu->pu_state |= (s))
+#define PU_GETSFLAG(pu, s) (pu->pu_state & (s))
 #define PU_CLRSFLAG(pu, s) \
     (pu->pu_state = ((pu->pu_state & ~(s)) | (pu->pu_state & PU_STATEMASK)))
 	int			pu_dpipe[2];
-- 
2.34.0


From e62a1b783dbde46fcd8880c96111e8fe6251e5a4 Mon Sep 17 00:00:00 2001
From: PHO <pho%cielonegro.org@localhost>
Date: Wed, 12 Jan 2022 15:57:24 +0900
Subject: [PATCH 02/17] Implement a logging API appeared on FUSE 3.7

---
 distrib/sets/lists/comp/mi |   1 +
 lib/librefuse/Makefile     |   5 +-
 lib/librefuse/fuse_log.h   |  63 +++++++++++++++++++++++
 lib/librefuse/refuse_log.c | 100 +++++++++++++++++++++++++++++++++++++
 4 files changed, 167 insertions(+), 2 deletions(-)
 create mode 100644 lib/librefuse/fuse_log.h
 create mode 100644 lib/librefuse/refuse_log.c

diff --git a/distrib/sets/lists/comp/mi b/distrib/sets/lists/comp/mi
index 64fd17122..142a3210d 100644
--- a/distrib/sets/lists/comp/mi
+++ b/distrib/sets/lists/comp/mi
@@ -992,6 +992,7 @@
 ./usr/include/fts.h				comp-c-include
 ./usr/include/ftw.h				comp-c-include
 ./usr/include/fuse.h				comp-refuse-include
+./usr/include/fuse_log.h			comp-refuse-include
 ./usr/include/fuse_lowlevel.h			comp-refuse-include
 ./usr/include/fuse_opt.h			comp-refuse-include
 ./usr/include/g++/ACG.h				comp-obsolete		obsolete
diff --git a/lib/librefuse/Makefile b/lib/librefuse/Makefile
index 6c13742d9..d2230cd5a 100644
--- a/lib/librefuse/Makefile
+++ b/lib/librefuse/Makefile
@@ -12,10 +12,11 @@ FUSE_OPT_DEBUG_FLAGS=	-g -DFUSE_OPT_DEBUG
 
 CFLAGS+=	${FUSE_OPT_DEBUG_FLAGS}
 CPPFLAGS+=	-I${.CURDIR}
-SRCS=		refuse.c refuse_opt.c refuse_lowlevel.c
+SRCS=		refuse.c refuse_log.c refuse_lowlevel.c
+SRCS+=		refuse_opt.c
 MAN=		refuse.3
 WARNS?=		5
-INCS=           fuse.h fuse_opt.h fuse_lowlevel.h
+INCS=           fuse.h fuse_opt.h fuse_log.h fuse_lowlevel.h
 INCSDIR=        /usr/include
 
 .include <bsd.lib.mk>
diff --git a/lib/librefuse/fuse_log.h b/lib/librefuse/fuse_log.h
new file mode 100644
index 000000000..93bca0c67
--- /dev/null
+++ b/lib/librefuse/fuse_log.h
@@ -0,0 +1,63 @@
+/* $NetBSD$ */
+
+/*
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior written
+ *    permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#if !defined(_FUSE_LOG_H_)
+#define _FUSE_LOG_H_
+
+/* FUSE logging API, appeared on FUSE 3.7. */
+
+#include <stdarg.h>
+#include <sys/cdefs.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum fuse_log_level {
+	FUSE_LOG_EMERG,
+	FUSE_LOG_ALERT,
+	FUSE_LOG_CRIT,
+	FUSE_LOG_ERR,
+	FUSE_LOG_WARNING,
+	FUSE_LOG_NOTICE,
+	FUSE_LOG_INFO,
+	FUSE_LOG_DEBUG
+};
+
+typedef void (*fuse_log_func_t)(enum fuse_log_level level, const char *fmt, va_list ap);
+
+void fuse_set_log_func(fuse_log_func_t func);
+void fuse_log(enum fuse_log_level level, const char *fmt, ...) __printflike(2, 3);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/librefuse/refuse_log.c b/lib/librefuse/refuse_log.c
new file mode 100644
index 000000000..6245cdd93
--- /dev/null
+++ b/lib/librefuse/refuse_log.c
@@ -0,0 +1,100 @@
+/* $NetBSD$ */
+
+/*
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior written
+ *    permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#if !defined(lint)
+__RCSID("$NetBSD$");
+#endif /* !lint */
+
+#include <assert.h>
+#include <fuse_log.h>
+#if defined(MULTITHREADED_REFUSE)
+#	include <pthread.h>
+#endif
+#include <stdio.h>
+
+static void
+default_log_func(enum fuse_log_level level __attribute__((__unused__)),
+                 const char *fmt, va_list ap) {
+    /* This function needs to be thread-safe. Calling vfprintf(3)
+     * should be okay because POSIX mandates locking FILE* objects
+     * internally. */
+    vfprintf(stderr, fmt, ap);
+}
+
+#if defined(MULTITHREADED_REFUSE)
+static pthread_mutex_t	log_func_mutex = PTHREAD_MUTEX_INITIALIZER;
+#endif
+static fuse_log_func_t	log_func = default_log_func;
+
+void
+fuse_set_log_func(fuse_log_func_t func) {
+#if defined(MULTITHREADED_REFUSE)
+    /* What we really need here is merely a memory barrier, but
+     * locking a mutex is the easiest way to achieve that. */
+    int rv;
+
+    rv = pthread_mutex_lock(&log_func_mutex);
+    assert(rv == 0);
+#endif
+
+    if (func)
+        log_func = func;
+    else
+        log_func = default_log_func;
+
+#if defined(MULTITHREADED_REFUSE)
+    rv = pthread_mutex_unlock(&log_func_mutex);
+    assert(rv == 0);
+#endif
+}
+
+void
+fuse_log(enum fuse_log_level level, const char *fmt, ...) {
+    va_list ap;
+#if defined(MULTITHREADED_REFUSE)
+    /* What we really need here is merely a memory barrier, but
+     * locking a mutex is the easiest way to achieve that. */
+    int rv;
+
+    rv = pthread_mutex_lock(&log_func_mutex);
+    assert(rv == 0);
+#endif
+
+    va_start(ap, fmt);
+    log_func(level, fmt, ap);
+    va_end(ap);
+
+#if defined(MULTITHREADED_REFUSE)
+    rv = pthread_mutex_unlock(&log_func_mutex);
+    assert(rv == 0);
+#endif
+}
-- 
2.34.0


From 3693ed0b9420c1ad1c1526f39600d391cfff14a5 Mon Sep 17 00:00:00 2001
From: PHO <pho%cielonegro.org@localhost>
Date: Wed, 12 Jan 2022 16:30:27 +0900
Subject: [PATCH 03/17] Implement FUSE session API and its signal handling
 functionality

---
 distrib/sets/lists/base/mi                    |   1 +
 distrib/sets/lists/comp/mi                    |   1 +
 etc/mtree/NetBSD.dist.base                    |   1 +
 lib/librefuse/Makefile                        |   4 +-
 lib/librefuse/fuse.h                          |   6 +-
 lib/librefuse/fuse_internal.h                 |  15 +
 lib/librefuse/refuse.c                        |   2 +
 lib/librefuse/refuse/Makefile.inc             |   7 +
 .../{fuse_internal.h => refuse/session.c}     |  50 ++-
 .../{fuse_internal.h => refuse/session.h}     |  45 ++-
 lib/librefuse/refuse_signals.c                | 331 ++++++++++++++++++
 11 files changed, 441 insertions(+), 22 deletions(-)
 create mode 100644 lib/librefuse/refuse/Makefile.inc
 copy lib/librefuse/{fuse_internal.h => refuse/session.c} (55%)
 copy lib/librefuse/{fuse_internal.h => refuse/session.h} (62%)
 create mode 100644 lib/librefuse/refuse_signals.c

diff --git a/distrib/sets/lists/base/mi b/distrib/sets/lists/base/mi
index 4fffcd06f..4611afe32 100644
--- a/distrib/sets/lists/base/mi
+++ b/distrib/sets/lists/base/mi
@@ -1147,6 +1147,7 @@
 ./usr/include/protocols				base-c-usr
 ./usr/include/quota				base-obsolete		obsolete
 ./usr/include/readline				base-c-usr
+./usr/include/refuse				base-refuse-usr
 ./usr/include/rpc				base-c-usr
 ./usr/include/rpcsvc				base-c-usr
 ./usr/include/rump				base-c-usr
diff --git a/distrib/sets/lists/comp/mi b/distrib/sets/lists/comp/mi
index 142a3210d..046d005e3 100644
--- a/distrib/sets/lists/comp/mi
+++ b/distrib/sets/lists/comp/mi
@@ -3092,6 +3092,7 @@
 ./usr/include/readline.h			comp-obsolete		obsolete
 ./usr/include/readline/history.h		comp-c-include
 ./usr/include/readline/readline.h		comp-c-include
+./usr/include/refuse/session.h			comp-refuse-include
 ./usr/include/regex.h				comp-c-include
 ./usr/include/regexp.h				comp-c-include
 ./usr/include/res_update.h			comp-c-include
diff --git a/etc/mtree/NetBSD.dist.base b/etc/mtree/NetBSD.dist.base
index 3357b3ce2..0a0af77ac 100644
--- a/etc/mtree/NetBSD.dist.base
+++ b/etc/mtree/NetBSD.dist.base
@@ -326,6 +326,7 @@
 ./usr/include/prop
 ./usr/include/protocols
 ./usr/include/readline
+./usr/include/refuse
 ./usr/include/rpc
 ./usr/include/rpcsvc
 ./usr/include/rump
diff --git a/lib/librefuse/Makefile b/lib/librefuse/Makefile
index d2230cd5a..e86f96368 100644
--- a/lib/librefuse/Makefile
+++ b/lib/librefuse/Makefile
@@ -13,10 +13,12 @@ FUSE_OPT_DEBUG_FLAGS=	-g -DFUSE_OPT_DEBUG
 CFLAGS+=	${FUSE_OPT_DEBUG_FLAGS}
 CPPFLAGS+=	-I${.CURDIR}
 SRCS=		refuse.c refuse_log.c refuse_lowlevel.c
-SRCS+=		refuse_opt.c
+SRCS+=		refuse_opt.c refuse_signals.c
 MAN=		refuse.3
 WARNS?=		5
 INCS=           fuse.h fuse_opt.h fuse_log.h fuse_lowlevel.h
 INCSDIR=        /usr/include
 
+.include "${.CURDIR}/refuse/Makefile.inc"
+
 .include <bsd.lib.mk>
diff --git a/lib/librefuse/fuse.h b/lib/librefuse/fuse.h
index 5d1aba02e..c52597c31 100644
--- a/lib/librefuse/fuse.h
+++ b/lib/librefuse/fuse.h
@@ -30,9 +30,11 @@
 #ifndef FUSE_H_
 #define FUSE_H_	20211204
 
+#include <refuse/session.h>
+#include <sys/cdefs.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
 #include <sys/types.h>
-
-#include <puffs.h>
 #include <utime.h>
 
 /* The latest version of FUSE API currently provided by refuse. */
diff --git a/lib/librefuse/fuse_internal.h b/lib/librefuse/fuse_internal.h
index 7bdc832e3..72eee00c9 100644
--- a/lib/librefuse/fuse_internal.h
+++ b/lib/librefuse/fuse_internal.h
@@ -38,5 +38,20 @@
 
 #include <fuse.h>
 #include <fuse_lowlevel.h>
+#include <sys/cdefs.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Internal functions, hidden from users */
+__BEGIN_HIDDEN_DECLS
+int __fuse_set_signal_handlers(struct fuse* fuse);
+int __fuse_remove_signal_handlers(struct fuse* fuse);
+__END_HIDDEN_DECLS
+
+#ifdef __cplusplus
+}
+#endif
 
 #endif
diff --git a/lib/librefuse/refuse.c b/lib/librefuse/refuse.c
index 0daba6647..87d1bc3b7 100644
--- a/lib/librefuse/refuse.c
+++ b/lib/librefuse/refuse.c
@@ -47,6 +47,8 @@ __RCSID("$NetBSD: refuse.c,v 1.103 2021/12/04 06:42:39 pho Exp $");
 #include <fuse_internal.h>
 #include <fuse_opt.h>
 #include <paths.h>
+#include <puffs.h>
+#include <stdbool.h>
 #include <stddef.h>
 #include <stdio.h>
 #include <stdlib.h>
diff --git a/lib/librefuse/refuse/Makefile.inc b/lib/librefuse/refuse/Makefile.inc
new file mode 100644
index 000000000..a9c0d9e45
--- /dev/null
+++ b/lib/librefuse/refuse/Makefile.inc
@@ -0,0 +1,7 @@
+# $NetBSD$
+
+.PATH: ${.CURDIR}/refuse
+
+SRCS+=	session.c
+
+INCS+=	refuse/session.h
diff --git a/lib/librefuse/fuse_internal.h b/lib/librefuse/refuse/session.c
similarity index 55%
copy from lib/librefuse/fuse_internal.h
copy to lib/librefuse/refuse/session.c
index 7bdc832e3..aebe5ba48 100644
--- a/lib/librefuse/fuse_internal.h
+++ b/lib/librefuse/refuse/session.c
@@ -1,4 +1,4 @@
-/* $NetBSD: fuse_internal.h,v 1.1 2021/12/04 06:42:39 pho Exp $ */
+/* $NetBSD$ */
 
 /*
  * Copyright (c) 2021 The NetBSD Foundation, Inc.
@@ -28,15 +28,45 @@
  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
-#if !defined(FUSE_INTERNAL_H)
-#define FUSE_INTERNAL_H
 
-/* We emit a compiler warning for anyone including <fuse.h> without
- * defining FUSE_USE_VERSION. Define it here, or otherwise we'll be
- * warned too. */
-#define FUSE_USE_VERSION	FUSE_VERSION
+#include <sys/cdefs.h>
+#if !defined(lint)
+__RCSID("$NetBSD$");
+#endif /* !lint */
 
-#include <fuse.h>
-#include <fuse_lowlevel.h>
+#include <err.h>
+#include <fuse_internal.h>
+#include <puffs.h>
 
-#endif
+/* The documentation for FUSE is not clear as to what "struct
+ * fuse_session" is, why it exists, or how it's different from "struct
+ * fuse". For now we define it as an empty struct and treat "struct
+ * fuse_session *" as being identical to "struct fuse *". */
+struct fuse_session {};
+
+struct fuse_session *
+fuse_get_session(struct fuse *f) {
+    return (struct fuse_session*)f;
+}
+
+int
+fuse_session_fd(struct fuse_session *se) {
+    struct fuse* fuse = (struct fuse*)se;
+
+    /* We don't want to expose this to users, but filesystems in the
+     * wild often wants to set FD_CLOEXEC on it. Hope they don't
+     * assume it's the real /dev/fuse, because it's actually
+     * /dev/puffs in our implementation. */
+    return puffs_getselectable(fuse->pu);
+}
+
+int
+fuse_set_signal_handlers(struct fuse_session *se) {
+    return __fuse_set_signal_handlers((struct fuse*)se);
+}
+
+void
+fuse_remove_signal_handlers(struct fuse_session *se) {
+    if (__fuse_remove_signal_handlers((struct fuse*)se) == -1)
+        warn("%s: failed to remove signal handlers", __func__);
+}
diff --git a/lib/librefuse/fuse_internal.h b/lib/librefuse/refuse/session.h
similarity index 62%
copy from lib/librefuse/fuse_internal.h
copy to lib/librefuse/refuse/session.h
index 7bdc832e3..d3d955360 100644
--- a/lib/librefuse/fuse_internal.h
+++ b/lib/librefuse/refuse/session.h
@@ -1,4 +1,4 @@
-/* $NetBSD: fuse_internal.h,v 1.1 2021/12/04 06:42:39 pho Exp $ */
+/* $NetBSD$ */
 
 /*
  * Copyright (c) 2021 The NetBSD Foundation, Inc.
@@ -28,15 +28,42 @@
  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
-#if !defined(FUSE_INTERNAL_H)
-#define FUSE_INTERNAL_H
+#if !defined(_FUSE_SESSION_H_)
+#define _FUSE_SESSION_H_
 
-/* We emit a compiler warning for anyone including <fuse.h> without
- * defining FUSE_USE_VERSION. Define it here, or otherwise we'll be
- * warned too. */
-#define FUSE_USE_VERSION	FUSE_VERSION
+/* FUSE session API
+ */
 
-#include <fuse.h>
-#include <fuse_lowlevel.h>
+#if !defined(FUSE_H_)
+#  error Do not include this header directly. Include <fuse.h> instead.
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Forward declarations */
+struct fuse;
+
+/* A private structure appeared on FUSE 2.4. */
+struct fuse_session;
+
+/* Get a session from a fuse object. Appeared on FUSE 2.6. */
+struct fuse_session *fuse_get_session(struct fuse *f);
+
+/* Get the file descriptor for communicaiton with kernel. Appeared on
+ * FUSE 3.0. */
+int fuse_session_fd(struct fuse_session *se);
+
+/* Exit a session on SIGHUP, SIGTERM, and SIGINT and ignore
+ * SIGPIPE. Appeared on FUSE 2.5. */
+int fuse_set_signal_handlers(struct fuse_session *se);
+
+/* Restore default signal handlers. Appeared on FUSE 2.5. */
+void fuse_remove_signal_handlers(struct fuse_session *se);
+
+#ifdef __cplusplus
+}
+#endif
 
 #endif
diff --git a/lib/librefuse/refuse_signals.c b/lib/librefuse/refuse_signals.c
new file mode 100644
index 000000000..d4480c8d4
--- /dev/null
+++ b/lib/librefuse/refuse_signals.c
@@ -0,0 +1,331 @@
+/* $NetBSD$ */
+
+/*
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior written
+ *    permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#if !defined(lint)
+__RCSID("$NetBSD$");
+#endif /* !lint */
+
+#include <assert.h>
+#include <fuse_internal.h>
+#if defined(MULTITHREADED_REFUSE)
+#  include <pthread.h>
+#endif
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Signal handling routines
+ *
+ * FUSE only supports running a single filesystem per process. ReFUSE
+ * is going to allow a process to run a filesystem per thread. In
+ * order to support this, our implementation of
+ * fuse_set_signal_handlers() installs a set of signal handlers which,
+ * when invoked, terminates all the filesystems that called the
+ * function. This means our fuse_remove_signal_handlers() must not
+ * actually remove the signal handlers until the last thread calls the
+ * function.
+ *
+ * FUSE installs a signal handler for a signal only if its sa_handler
+ * is set to SIG_DFL. This obviously has a bad consequence: if the
+ * caller process already has a non-default signal handler for SIGINT,
+ * Ctrl-C will not stop the main loop of FUSE. See
+ * https://stackoverflow.com/q/5044375/3571336
+ *
+ * Maybe we should do the same knowing it's bad, but it's probably
+ * better to call our handler along with the old one. We may change
+ * this behavior if this turns out to cause serious compatibility
+ * issues.
+ *
+ * Also note that it is tempting to use puffs_unmountonsignal(3) but
+ * we can't, because there is no way to revert its effect.
+ */
+
+#if defined(MULTITHREADED_REFUSE)
+/* A mutex to protect the global state regarding signal handlers. When
+ * a thread is going to lock this, it must block all the signals (with
+ * pthread_sigmask(3)) that we install a handler for, or otherwise it
+ * may deadlock for trying to acquire a lock that is already held by
+ * itself. */
+static pthread_mutex_t signal_mutex = PTHREAD_MUTEX_INITIALIZER;
+#endif
+
+/* Saved sigaction for each signal before we modify them. */
+static struct sigaction* saved_actions[NSIG];
+
+/* A linked list of "struct fuse*" which should be terminated upon
+ * receiving a signal. */
+struct refuse_obj_elem {
+    struct fuse* fuse;
+    struct refuse_obj_elem* next;
+};
+static struct refuse_obj_elem* fuse_head;
+
+#if defined(MULTITHREADED_REFUSE)
+static int
+block_signals(sigset_t* oset) {
+    sigset_t set;
+
+    if (sigemptyset(&set) != 0)
+        return -1;
+
+    if (sigaddset(&set, SIGHUP) != 0)
+        return -1;
+
+    if (sigaddset(&set, SIGINT) != 0)
+        return -1;
+
+    if (sigaddset(&set, SIGTERM) != 0)
+        return -1;
+
+    return pthread_sigmask(SIG_BLOCK, &set, oset);
+}
+
+static int
+unblock_signals(const sigset_t* oset) {
+    return pthread_sigmask(SIG_SETMASK, oset, NULL);
+}
+#endif /* defined(MULTITHREADED_REFUSE) */
+
+/* handler == NULL means the signal should be ignored. */
+static int
+set_signal_handler(int sig, void (*handler)(int, siginfo_t*, void*)) {
+    struct sigaction* saved;
+    struct sigaction act;
+
+    saved = malloc(sizeof(*saved));
+    if (!saved)
+        return -1;
+
+    if (sigaction(sig, NULL, saved) != 0) {
+        free(saved);
+        return -1;
+    }
+
+    saved_actions[sig] = saved;
+
+    memset(&act, 0, sizeof(act));
+    if (handler) {
+        act.sa_sigaction = handler;
+        act.sa_flags = SA_SIGINFO;
+    }
+    else {
+        /* Ignore the signal only if the signal doesn't have a
+         * handler. */
+        if (!(saved->sa_flags & SA_SIGINFO) && saved->sa_handler == SIG_DFL)
+            act.sa_handler = SIG_IGN;
+        else
+            return 0;
+    }
+
+    if (sigemptyset(&act.sa_mask) != 0) {
+        free(saved);
+        saved_actions[sig] = NULL;
+        return -1;
+    }
+
+    return sigaction(sig, &act, NULL);
+}
+
+static int
+restore_signal_handler(int sig, void (*handler)(int, siginfo_t*, void*)) {
+    struct sigaction oact;
+    struct sigaction* saved;
+
+    saved = saved_actions[sig];
+    assert(saved != NULL);
+
+    if (sigaction(sig, NULL, &oact) != 0)
+        return -1;
+
+    /* Has the sigaction changed since we installed our handler? Do
+     * nothing if so. */
+    if (handler) {
+        if (!(oact.sa_flags & SA_SIGINFO) || oact.sa_sigaction != handler)
+            goto done;
+    }
+    else {
+        if (oact.sa_handler != SIG_IGN)
+            goto done;
+    }
+
+    if (sigaction(sig, saved, NULL) != 0)
+        return -1;
+
+  done:
+    free(saved);
+    saved_actions[sig] = NULL;
+    return 0;
+}
+
+static void
+exit_handler(int sig, siginfo_t* info, void* ctx) {
+    struct refuse_obj_elem* elem;
+    struct sigaction* saved;
+#if defined(MULTITHREADED_REFUSE)
+    int rv;
+
+    /* pthread_mutex_lock(3) is NOT an async-signal-safe function. We
+     * assume it's okay, as the thread running this handler shouldn't
+     * be locking this mutex. */
+    rv = pthread_mutex_lock(&signal_mutex);
+    assert(rv == 0);
+#endif
+
+    for (elem = fuse_head; elem != NULL; elem = elem->next)
+        fuse_exit(elem->fuse);
+
+#if defined(MULTITHREADED_REFUSE)
+    rv = pthread_mutex_unlock(&signal_mutex);
+    assert(rv == 0);
+#endif
+
+    saved = saved_actions[sig];
+    assert(saved != NULL);
+
+    if (saved->sa_handler != SIG_DFL && saved->sa_handler != SIG_IGN) {
+        if (saved->sa_flags & SA_SIGINFO)
+            saved->sa_sigaction(sig, info, ctx);
+        else
+            saved->sa_handler(sig);
+    }
+}
+
+/* The original function appeared on FUSE 2.5 takes a pointer to
+ * "struct fuse_session" instead of "struct fuse". We have no such
+ * things as fuse sessions.
+ */
+int
+__fuse_set_signal_handlers(struct fuse* fuse) {
+    int ret = 0;
+    struct refuse_obj_elem* elem;
+#if defined(MULTITHREADED_REFUSE)
+    int rv;
+    sigset_t oset;
+
+    rv = block_signals(&oset);
+    assert(rv == 0);
+
+    rv = pthread_mutex_lock(&signal_mutex);
+    assert(rv == 0);
+#endif
+
+    /* Have we already installed our signal handlers? If the list is
+     * empty, it means we have not. */
+    if (fuse_head == NULL) {
+        if (set_signal_handler(SIGHUP, exit_handler) != 0 ||
+            set_signal_handler(SIGINT, exit_handler) != 0 ||
+            set_signal_handler(SIGTERM, exit_handler) != 0 ||
+            set_signal_handler(SIGPIPE, NULL) != 0) {
+
+            ret = -1;
+            goto done;
+        }
+    }
+
+    /* Add ourselves to the list of filesystems that want to be
+     * terminated upon receiving a signal. But only if we aren't
+     * already in the list. */
+    for (elem = fuse_head; elem != NULL; elem = elem->next) {
+        if (elem->fuse == fuse)
+            goto done;
+    }
+
+    elem = malloc(sizeof(*elem));
+    if (!elem) {
+        ret = -1;
+        goto done;
+    }
+    elem->fuse = fuse;
+    elem->next = fuse_head;
+    fuse_head  = elem;
+  done:
+
+#if defined(MULTITHREADED_REFUSE)
+    rv = pthread_mutex_unlock(&signal_mutex);
+    assert(rv == 0);
+
+    rv = unblock_signals(&oset);
+    assert(rv == 0);
+#endif
+    return ret;
+}
+
+int
+__fuse_remove_signal_handlers(struct fuse* fuse) {
+    int ret = 0;
+    struct refuse_obj_elem* prev;
+    struct refuse_obj_elem* elem;
+#if defined(MULTITHREADED_REFUSE)
+    int rv;
+    sigset_t oset;
+
+    rv = block_signals(&oset);
+    assert(rv == 0);
+
+    rv = pthread_mutex_lock(&signal_mutex);
+    assert(rv == 0);
+#endif
+
+    /* Remove ourselves from the list. */
+    for (prev = NULL, elem = fuse_head;
+         elem != NULL;
+         prev = elem, elem = elem->next) {
+
+        if (elem->fuse == fuse) {
+            if (prev)
+                prev->next = elem->next;
+            else
+                fuse_head = elem->next;
+            free(elem);
+        }
+    }
+
+    /* Restore handlers if we were the last one. */
+    if (fuse_head == NULL) {
+        if (restore_signal_handler(SIGHUP, exit_handler) == -1 ||
+            restore_signal_handler(SIGINT, exit_handler) == -1 ||
+            restore_signal_handler(SIGTERM, exit_handler) == -1 ||
+            restore_signal_handler(SIGPIPE, NULL) == -1) {
+
+            ret = -1;
+        }
+    }
+
+#if defined(MULTITHREADED_REFUSE)
+    rv = pthread_mutex_unlock(&signal_mutex);
+    assert(rv == 0);
+
+    rv = unblock_signals(&oset);
+    assert(rv == 0);
+#endif
+    return ret;
+}
-- 
2.34.0


From 6c4964593ffe0b2a6a0f1da6ffe053a86db73158 Mon Sep 17 00:00:00 2001
From: PHO <pho%cielonegro.org@localhost>
Date: Wed, 12 Jan 2022 17:07:41 +0900
Subject: [PATCH 04/17] Implement data buffer API appeared on FUSE 2.9

---
 distrib/sets/lists/comp/mi        |   1 +
 lib/librefuse/TODO                |   1 +
 lib/librefuse/fuse.h              |   1 +
 lib/librefuse/refuse/Makefile.inc |   2 +
 lib/librefuse/refuse/buf.c        | 320 ++++++++++++++++++++++++++++++
 lib/librefuse/refuse/buf.h        | 109 ++++++++++
 6 files changed, 434 insertions(+)
 create mode 100644 lib/librefuse/refuse/buf.c
 create mode 100644 lib/librefuse/refuse/buf.h

diff --git a/distrib/sets/lists/comp/mi b/distrib/sets/lists/comp/mi
index 046d005e3..9fa259602 100644
--- a/distrib/sets/lists/comp/mi
+++ b/distrib/sets/lists/comp/mi
@@ -3092,6 +3092,7 @@
 ./usr/include/readline.h			comp-obsolete		obsolete
 ./usr/include/readline/history.h		comp-c-include
 ./usr/include/readline/readline.h		comp-c-include
+./usr/include/refuse/buf.h			comp-refuse-include
 ./usr/include/refuse/session.h			comp-refuse-include
 ./usr/include/regex.h				comp-c-include
 ./usr/include/regexp.h				comp-c-include
diff --git a/lib/librefuse/TODO b/lib/librefuse/TODO
index 1bd1f5a2e..611b8b0d6 100644
--- a/lib/librefuse/TODO
+++ b/lib/librefuse/TODO
@@ -11,6 +11,7 @@ implement all sorts of compat tweaks to appease various file systems
 do proper implementations of dirfillers
 statfs - some fuse file systems want struct statfs and we only have
          statvfs available natively
+Support data buffers appeared on FUSE 2.9 (struct fuse_buf).
 
 Done
 ====
diff --git a/lib/librefuse/fuse.h b/lib/librefuse/fuse.h
index c52597c31..4d10f560a 100644
--- a/lib/librefuse/fuse.h
+++ b/lib/librefuse/fuse.h
@@ -30,6 +30,7 @@
 #ifndef FUSE_H_
 #define FUSE_H_	20211204
 
+#include <refuse/buf.h>
 #include <refuse/session.h>
 #include <sys/cdefs.h>
 #include <sys/stat.h>
diff --git a/lib/librefuse/refuse/Makefile.inc b/lib/librefuse/refuse/Makefile.inc
index a9c0d9e45..ad2dac4ec 100644
--- a/lib/librefuse/refuse/Makefile.inc
+++ b/lib/librefuse/refuse/Makefile.inc
@@ -2,6 +2,8 @@
 
 .PATH: ${.CURDIR}/refuse
 
+SRCS+=	buf.c
 SRCS+=	session.c
 
+INCS+=	refuse/buf.h
 INCS+=	refuse/session.h
diff --git a/lib/librefuse/refuse/buf.c b/lib/librefuse/refuse/buf.c
new file mode 100644
index 000000000..a254df934
--- /dev/null
+++ b/lib/librefuse/refuse/buf.c
@@ -0,0 +1,320 @@
+/* $NetBSD$ */
+
+/*
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior written
+ *    permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#if !defined(lint)
+__RCSID("$NetBSD$");
+#endif /* !lint */
+
+#include <assert.h>
+#include <errno.h>
+#include <fuse_internal.h>
+#include <machine/vmparam.h> /* for PAGE_SIZE */
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/param.h> /* for MIN(a, b) */
+#include <unistd.h>
+
+size_t
+fuse_buf_size(const struct fuse_bufvec *bufv) {
+    size_t i;
+    size_t total = 0;
+
+    for (i = 0; i < bufv->count; i++) {
+        total += bufv->buf[i].size;
+    }
+
+    return total;
+}
+
+/* Return the pointer to the current buffer in a buffer vector, or
+ * NULL if we have reached the end of the vector. */
+static const struct fuse_buf*
+fuse_buf_current(const struct fuse_bufvec *bufv) {
+    if (bufv->idx < bufv->count)
+        return &bufv->buf[bufv->idx];
+    else
+        return NULL;
+}
+
+/* Copy data from one fd to a memory buffer, and return the number of
+ * octets that have been copied, or -1 on failure. */
+static ssize_t
+fuse_buf_read_fd_to_mem(const struct fuse_buf *dst, size_t dst_off,
+                        const struct fuse_buf *src, size_t src_off,
+                        size_t len) {
+    ssize_t total = 0;
+
+    while (len > 0) {
+        ssize_t n_read;
+
+        if (src->flags & FUSE_BUF_FD_SEEK)
+            n_read = pread(src->fd, (uint8_t*)dst->mem + dst_off, len,
+                           src->pos + (off_t)src_off);
+        else
+            n_read = read(src->fd, (uint8_t*)dst->mem + dst_off, len);
+
+        if (n_read == -1) {
+            if (errno == EINTR)
+                continue;
+            else if (total == 0)
+                return -1;
+            else
+                /* The last pread(2) or read(2) failed but we have
+                 * already copied some data. */
+                break;
+        }
+        else if (n_read == 0) {
+            /* Reached EOF */
+            break;
+        }
+        else {
+            total   += n_read;
+            dst_off += (size_t)n_read;
+            src_off += (size_t)n_read;
+            len     -= (size_t)n_read;
+
+            if (src->flags & FUSE_BUF_FD_RETRY)
+                continue;
+        }
+    }
+
+    return total;
+}
+
+/* Copy data from one memory buffer to an fd, and return the number of
+ * octets that have been copied, or -1 on failure. */
+static ssize_t
+fuse_buf_write_mem_to_fd(const struct fuse_buf *dst, size_t dst_off,
+                         const struct fuse_buf *src, size_t src_off,
+                         size_t len) {
+    ssize_t total = 0;
+
+    while (len > 0) {
+        ssize_t n_wrote;
+
+        if (dst->flags & FUSE_BUF_FD_SEEK)
+            n_wrote = pwrite(dst->fd, (uint8_t*)src->mem + src_off, len,
+                             dst->pos + (off_t)dst_off);
+        else
+            n_wrote = write(dst->fd, (uint8_t*)src->mem + src_off, len);
+
+        if (n_wrote == -1) {
+            if (errno == EINTR)
+                continue;
+            else if (total == 0)
+                return -1;
+            else
+                /* The last pwrite(2) or write(2) failed but we have
+                 * already copied some data. */
+                break;
+        }
+        else if (n_wrote == 0) {
+            break;
+        }
+        else {
+            total   += n_wrote;
+            dst_off += (size_t)n_wrote;
+            src_off += (size_t)n_wrote;
+            len     -= (size_t)n_wrote;
+
+            if (dst->flags & FUSE_BUF_FD_RETRY)
+                continue;
+        }
+    }
+
+    return total;
+}
+
+/* Copy data from one fd to another, and return the number of octets
+ * that have been copied, or -1 on failure. */
+static ssize_t
+fuse_buf_copy_fd_to_fd(const struct fuse_buf *dst, size_t dst_off,
+                       const struct fuse_buf *src, size_t src_off,
+                       size_t len) {
+    ssize_t total = 0;
+    struct fuse_buf tmp;
+
+    tmp.size  = PAGE_SIZE;
+    tmp.flags = (enum fuse_buf_flags)0;
+    tmp.mem   = malloc(tmp.size);
+
+    if (tmp.mem == NULL) {
+        return -1;
+    }
+
+    while (len) {
+        size_t n_to_read = MIN(tmp.size, len);
+        ssize_t n_read;
+        ssize_t n_wrote;
+
+        n_read = fuse_buf_read_fd_to_mem(&tmp, 0, src, src_off, n_to_read);
+        if (n_read == -1) {
+            if (total == 0) {
+                free(tmp.mem);
+                return -1;
+            }
+            else {
+                /* We have already copied some data. */
+                break;
+            }
+        }
+        else if (n_read == 0) {
+            /* Reached EOF */
+            break;
+        }
+
+        n_wrote = fuse_buf_write_mem_to_fd(dst, dst_off, &tmp, 0, (size_t)n_read);
+        if (n_wrote == -1) {
+            if (total == 0) {
+                free(tmp.mem);
+                return -1;
+            }
+            else {
+                /* We have already copied some data. */
+                break;
+            }
+        }
+        else if (n_wrote == 0) {
+            break;
+        }
+
+        total   += n_wrote;
+        dst_off += (size_t)n_wrote;
+        src_off += (size_t)n_wrote;
+        len     -= (size_t)n_wrote;
+
+        if (n_wrote < n_read)
+            /* Now we have some data that were read but couldn't be
+             * written, and can't do anything about it. */
+            break;
+    }
+
+    free(tmp.mem);
+    return total;
+}
+
+/* Copy data from one buffer to another, and return the number of
+ * octets that have been copied. */
+static ssize_t
+fuse_buf_copy_one(const struct fuse_buf *dst, size_t dst_off,
+                  const struct fuse_buf *src, size_t src_off,
+                  size_t len,
+                  enum fuse_buf_copy_flags flags __attribute__((__unused__))) {
+
+    const bool dst_is_fd = !!(dst->flags & FUSE_BUF_IS_FD);
+    const bool src_is_fd = !!(src->flags & FUSE_BUF_IS_FD);
+
+    if (!dst_is_fd && !src_is_fd) {
+        void* dst_mem = (uint8_t*)dst->mem + dst_off;
+        void* src_mem = (uint8_t*)src->mem + src_off;
+
+        memmove(dst_mem, src_mem, len);
+
+        return (ssize_t)len;
+    }
+    else if (!dst_is_fd) {
+        return fuse_buf_read_fd_to_mem(dst, dst_off, src, src_off, len);
+    }
+    else if (!src_is_fd) {
+        return fuse_buf_write_mem_to_fd(dst, dst_off, src, src_off, len);
+    }
+    else {
+        return fuse_buf_copy_fd_to_fd(dst, dst_off, src, src_off, len);
+    }
+}
+
+/* Advance the buffer by a given number of octets. Return 0 on
+ * success, or -1 otherwise. Reaching the end of the buffer vector
+ * counts as a failure. */
+static int
+fuse_buf_advance(struct fuse_bufvec *bufv, size_t len) {
+    const struct fuse_buf *buf = fuse_buf_current(bufv);
+
+    assert(bufv->off + len <= buf->size);
+    bufv->off += len;
+    if (bufv->off == buf->size) {
+        /* Done with the current buffer. Advance to the next one. */
+        assert(bufv->idx < bufv->count);
+        bufv->idx++;
+        if (bufv->idx == bufv->count)
+            return -1; /* No more buffers in the vector. */
+        bufv->off = 0;
+    }
+    return 0;
+}
+
+ssize_t
+fuse_buf_copy(struct fuse_bufvec *dstv, struct fuse_bufvec *srcv,
+                      enum fuse_buf_copy_flags flags) {
+    ssize_t total = 0;
+
+    while (true) {
+        const struct fuse_buf* dst;
+        const struct fuse_buf* src;
+        size_t src_len;
+        size_t dst_len;
+        size_t len;
+        ssize_t n_copied;
+
+        dst = fuse_buf_current(dstv);
+        src = fuse_buf_current(srcv);
+        if (src == NULL || dst == NULL)
+            break;
+
+        src_len = src->size - srcv->off;
+        dst_len = dst->size - dstv->off;
+        len = MIN(src_len, dst_len);
+
+        n_copied = fuse_buf_copy_one(dst, dstv->off, src, srcv->off, len, flags);
+        if (n_copied == -1) {
+            if (total == 0)
+                return -1;
+            else
+                /* Failed to copy the current buffer but we have
+                 * already copied some part of the vector. It is
+                 * therefore inappropriate to return an error. */
+                break;
+        }
+        total += n_copied;
+
+        if (fuse_buf_advance(srcv, (size_t)n_copied) != 0 ||
+            fuse_buf_advance(dstv, (size_t)n_copied) != 0)
+            break;
+
+        if ((size_t)n_copied < len)
+            break;
+    }
+
+    return total;
+}
diff --git a/lib/librefuse/refuse/buf.h b/lib/librefuse/refuse/buf.h
new file mode 100644
index 000000000..b74b6fcc2
--- /dev/null
+++ b/lib/librefuse/refuse/buf.h
@@ -0,0 +1,109 @@
+/* $NetBSD$ */
+
+/*
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior written
+ *    permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#if !defined(_FUSE_BUF_H_)
+#define _FUSE_BUF_H_
+
+#include <sys/types.h>
+
+/* Data buffer API, appeared on FUSE 2.9. */
+
+#if !defined(FUSE_H_)
+#  error Do not include this header directly. Include <fuse.h> instead.
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum fuse_buf_flags {
+	/* The buffer is actually an fd: .mem is invalid but .fd and
+	 * .pos have valid values. */
+	FUSE_BUF_IS_FD		= (1 << 1),
+	/* The fd is seekable. */
+	FUSE_BUF_FD_SEEK	= (1 << 2),
+	/* Keep reading from/writing to the fd until reaching EOF. */
+	FUSE_BUF_FD_RETRY	= (1 << 3),
+};
+enum fuse_buf_copy_flags {
+	/* These flags are always ignored because splice(2) is a
+	 * Linux-specific syscall and there are no alternatives on
+	 * NetBSD atm. */
+	FUSE_BUF_NO_SPLICE		= (1 << 1),
+	FUSE_BUF_FORCE_SPLICE		= (1 << 2),
+	FUSE_BUF_SPLICE_MOVE		= (1 << 3),
+	FUSE_BUF_SPLICE_NONBLOCK	= (1 << 4),
+};
+struct fuse_buf {
+	size_t			size;
+	enum fuse_buf_flags	flags;
+	void			*mem;
+	int			fd;
+	off_t			pos;
+};
+struct fuse_bufvec {
+	/* The number of buffers in the vector. */
+	size_t		count;
+	/* The index of the current buffer in the vector. */
+	size_t		idx;
+	/* The current offset in the current buffer. */
+	size_t		off;
+	/* The vector of buffers. */
+	struct fuse_buf	buf[1];
+};
+#define FUSE_BUFVEC_INIT(size_)				\
+	((struct fuse_bufvec) {				\
+		/* .count = */ 1,			\
+		/* .idx = */ 0,				\
+		/* .off = */ 0,				\
+		/* .buf = */ {				\
+			/* [0] = */ {			\
+				/* .size = */ (size_)	\
+				/* .flags = */ (enum fuse_buf_flags)0,	\
+				/* .mem = */ NULL,	\
+				/* .fd = */ -1,		\
+				/* .pos = */ 0,		\
+			}				\
+		}					\
+	})
+
+/* Get the total size of data in a buffer vector. */
+size_t fuse_buf_size(const struct fuse_bufvec *bufv);
+
+/* Copy data from one buffer vector to another, and return the number
+ * of octets that have been copied. */
+ssize_t fuse_buf_copy(struct fuse_bufvec *dstv, struct fuse_bufvec *srcv,
+					  enum fuse_buf_copy_flags flags);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
-- 
2.34.0


From 634420b5eb6f6724ae9f79ddbe4c00dd5925e39b Mon Sep 17 00:00:00 2001
From: PHO <pho%cielonegro.org@localhost>
Date: Wed, 12 Jan 2022 17:11:50 +0900
Subject: [PATCH 05/17] Add stub functions for FUSE polling API

---
 distrib/sets/lists/comp/mi        |  1 +
 lib/librefuse/TODO                |  1 +
 lib/librefuse/fuse.h              |  1 +
 lib/librefuse/refuse/Makefile.inc |  2 ++
 lib/librefuse/refuse/poll.c       | 51 +++++++++++++++++++++++++++
 lib/librefuse/refuse/poll.h       | 57 +++++++++++++++++++++++++++++++
 6 files changed, 113 insertions(+)
 create mode 100644 lib/librefuse/refuse/poll.c
 create mode 100644 lib/librefuse/refuse/poll.h

diff --git a/distrib/sets/lists/comp/mi b/distrib/sets/lists/comp/mi
index 9fa259602..0190a429f 100644
--- a/distrib/sets/lists/comp/mi
+++ b/distrib/sets/lists/comp/mi
@@ -3093,6 +3093,7 @@
 ./usr/include/readline/history.h		comp-c-include
 ./usr/include/readline/readline.h		comp-c-include
 ./usr/include/refuse/buf.h			comp-refuse-include
+./usr/include/refuse/poll.h			comp-refuse-include
 ./usr/include/refuse/session.h			comp-refuse-include
 ./usr/include/regex.h				comp-c-include
 ./usr/include/regexp.h				comp-c-include
diff --git a/lib/librefuse/TODO b/lib/librefuse/TODO
index 611b8b0d6..ad259cfe9 100644
--- a/lib/librefuse/TODO
+++ b/lib/librefuse/TODO
@@ -11,6 +11,7 @@ implement all sorts of compat tweaks to appease various file systems
 do proper implementations of dirfillers
 statfs - some fuse file systems want struct statfs and we only have
          statvfs available natively
+Support polling appeared on FUSE 2.8 (struct fuse_pollhandle).
 Support data buffers appeared on FUSE 2.9 (struct fuse_buf).
 
 Done
diff --git a/lib/librefuse/fuse.h b/lib/librefuse/fuse.h
index 4d10f560a..a9e2c2a16 100644
--- a/lib/librefuse/fuse.h
+++ b/lib/librefuse/fuse.h
@@ -31,6 +31,7 @@
 #define FUSE_H_	20211204
 
 #include <refuse/buf.h>
+#include <refuse/poll.h>
 #include <refuse/session.h>
 #include <sys/cdefs.h>
 #include <sys/stat.h>
diff --git a/lib/librefuse/refuse/Makefile.inc b/lib/librefuse/refuse/Makefile.inc
index ad2dac4ec..04117b11e 100644
--- a/lib/librefuse/refuse/Makefile.inc
+++ b/lib/librefuse/refuse/Makefile.inc
@@ -3,7 +3,9 @@
 .PATH: ${.CURDIR}/refuse
 
 SRCS+=	buf.c
+SRCS+=	poll.c
 SRCS+=	session.c
 
 INCS+=	refuse/buf.h
+INCS+=	refuse/poll.h
 INCS+=	refuse/session.h
diff --git a/lib/librefuse/refuse/poll.c b/lib/librefuse/refuse/poll.c
new file mode 100644
index 000000000..b23834ca7
--- /dev/null
+++ b/lib/librefuse/refuse/poll.c
@@ -0,0 +1,51 @@
+/* $NetBSD$ */
+
+/*
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior written
+ *    permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#if !defined(lint)
+__RCSID("$NetBSD$");
+#endif /* !lint */
+
+#include <fuse_internal.h>
+
+/* XXX: ReFUSE doesn't support polling at the moment. It doesn't
+ * implement puffs_node_poll(), and will never invoke
+ * fuse_operations.poll(). So there is no valid way for a FUSE
+ * filesystem to actually call these functions. */
+
+int
+fuse_notify_poll(struct fuse_pollhandle *ph) {
+    return 0;
+}
+
+void
+fuse_pollhandle_destroy(struct fuse_pollhandle *ph) {
+}
diff --git a/lib/librefuse/refuse/poll.h b/lib/librefuse/refuse/poll.h
new file mode 100644
index 000000000..685de0c15
--- /dev/null
+++ b/lib/librefuse/refuse/poll.h
@@ -0,0 +1,57 @@
+/* $NetBSD$ */
+
+/*
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior written
+ *    permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#if !defined(_FUSE_POLL_H_)
+#define _FUSE_POLL_H_
+
+/* Polling API, appeared on FUSE 2.8 */
+
+#if !defined(FUSE_H_)
+#  error Do not include this header directly. Include <fuse.h> instead.
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* A private structure for polling, appeared on FUSE 2.8. */
+struct fuse_pollhandle;
+
+/* Notify the kernel of an IO readiness event. Appeared on FUSE 2.8. */
+int fuse_notify_poll(struct fuse_pollhandle *ph);
+
+/* Destroy a poll handle. Appeared on FUSE 2.8. */
+void fuse_pollhandle_destroy(struct fuse_pollhandle *ph);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
-- 
2.34.0


From 05bf6a1fff325508c2493accde72483981adfb0a Mon Sep 17 00:00:00 2001
From: PHO <pho%cielonegro.org@localhost>
Date: Wed, 12 Jan 2022 17:41:44 +0900
Subject: [PATCH 06/17] Add support for legacy types and functions

---
 distrib/sets/lists/comp/mi        |  1 +
 lib/librefuse/fuse.h              |  4 +-
 lib/librefuse/refuse.c            | 17 ++++++
 lib/librefuse/refuse/Makefile.inc |  2 +
 lib/librefuse/refuse/legacy.c     | 54 ++++++++++++++++++
 lib/librefuse/refuse/legacy.h     | 93 +++++++++++++++++++++++++++++++
 6 files changed, 170 insertions(+), 1 deletion(-)
 create mode 100644 lib/librefuse/refuse/legacy.c
 create mode 100644 lib/librefuse/refuse/legacy.h

diff --git a/distrib/sets/lists/comp/mi b/distrib/sets/lists/comp/mi
index 0190a429f..9e6895a9d 100644
--- a/distrib/sets/lists/comp/mi
+++ b/distrib/sets/lists/comp/mi
@@ -3093,6 +3093,7 @@
 ./usr/include/readline/history.h		comp-c-include
 ./usr/include/readline/readline.h		comp-c-include
 ./usr/include/refuse/buf.h			comp-refuse-include
+./usr/include/refuse/legacy.h			comp-refuse-include
 ./usr/include/refuse/poll.h			comp-refuse-include
 ./usr/include/refuse/session.h			comp-refuse-include
 ./usr/include/regex.h				comp-c-include
diff --git a/lib/librefuse/fuse.h b/lib/librefuse/fuse.h
index a9e2c2a16..2646bef03 100644
--- a/lib/librefuse/fuse.h
+++ b/lib/librefuse/fuse.h
@@ -31,6 +31,7 @@
 #define FUSE_H_	20211204
 
 #include <refuse/buf.h>
+#include <refuse/legacy.h>
 #include <refuse/poll.h>
 #include <refuse/session.h>
 #include <sys/cdefs.h>
@@ -110,7 +111,6 @@ struct fuse_args {
  */
 #define FUSE_ARGS_INIT(argc, argv) { argc, argv, 0 }
 
-typedef struct puffs_fuse_dirh *fuse_dirh_t;
 
 typedef int (*fuse_fill_dir_t)(void *, const char *, const struct stat *, off_t);
 typedef int (*fuse_dirfil_t)(fuse_dirh_t, const char *, int, ino_t);
@@ -166,6 +166,8 @@ struct fuse_operations {
 
 struct fuse *fuse_new(struct fuse_args *,
 	const struct fuse_operations *, size_t, void *);
+/* Invalidate cache for a given path. Appeared on FUSE 3.2. */
+int fuse_invalidate_path(struct fuse *fuse, const char *path);
 
 int fuse_mount(struct fuse *, const char *);
 void fuse_unmount(struct fuse *);
diff --git a/lib/librefuse/refuse.c b/lib/librefuse/refuse.c
index 87d1bc3b7..ba5fc39fd 100644
--- a/lib/librefuse/refuse.c
+++ b/lib/librefuse/refuse.c
@@ -1391,8 +1391,25 @@ fuse_unmount_compat22(const char *mp)
 	return;
 }
 
+int
+fuse_invalidate_path(struct fuse *fuse __attribute__((__unused__)),
+		     const char *path __attribute__((__unused__)))
+{
+    /* ReFUSE doesn't cache anything at the moment. No need to do
+     * anything. */
+    return -ENOENT;
+}
+
 int
 fuse_version(void)
 {
 	return FUSE_VERSION;
 }
+
+/* This is a legacy function that has been removed from the FUSE API,
+ * but is defined here because it needs to access refuse_opts. */
+int
+fuse_is_lib_option(const char *opt)
+{
+	return fuse_opt_match(refuse_opts, opt);
+}
diff --git a/lib/librefuse/refuse/Makefile.inc b/lib/librefuse/refuse/Makefile.inc
index 04117b11e..a2f1cc2bc 100644
--- a/lib/librefuse/refuse/Makefile.inc
+++ b/lib/librefuse/refuse/Makefile.inc
@@ -3,9 +3,11 @@
 .PATH: ${.CURDIR}/refuse
 
 SRCS+=	buf.c
+SRCS+=	legacy.c
 SRCS+=	poll.c
 SRCS+=	session.c
 
 INCS+=	refuse/buf.h
+INCS+=	refuse/legacy.h
 INCS+=	refuse/poll.h
 INCS+=	refuse/session.h
diff --git a/lib/librefuse/refuse/legacy.c b/lib/librefuse/refuse/legacy.c
new file mode 100644
index 000000000..9de4c2d90
--- /dev/null
+++ b/lib/librefuse/refuse/legacy.c
@@ -0,0 +1,54 @@
+/* $NetBSD$ */
+
+/*
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior written
+ *    permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#if !defined(lint)
+__RCSID("$NetBSD$");
+#endif /* !lint */
+
+#include <errno.h>
+#include <fuse_internal.h>
+
+int
+fuse_invalidate(struct fuse *fuse __attribute__((__unused__)),
+                const char *path __attribute__((__unused__)))
+{
+    int res = fuse_invalidate_path(fuse, path);
+
+    switch (res) {
+    case -ENOENT:
+        /* There was no entry to be invalidated. This isn't an
+         * error. */
+        return 0;
+    default:
+        return res;
+    }
+}
diff --git a/lib/librefuse/refuse/legacy.h b/lib/librefuse/refuse/legacy.h
new file mode 100644
index 000000000..94e51992b
--- /dev/null
+++ b/lib/librefuse/refuse/legacy.h
@@ -0,0 +1,93 @@
+/* $NetBSD$ */
+
+/*
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior written
+ *    permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#if !defined(_FUSE_LEGACY_H_)
+#define _FUSE_LEGACY_H_
+
+#include <sys/fstypes.h>
+
+/* Legacy data types and functions that had once existed but have been
+ * removed from the FUSE API. */
+
+#if !defined(FUSE_H_)
+#  error Do not include this header directly. Include <fuse.h> instead.
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* statfs structure used by FUSE < 1.9. On 2.1 it's been replaced with
+ * "struct statfs" and later replaced again with struct statvfs on
+ * 2.5. */
+struct fuse_statfs {
+	long block_size;
+	long blocks;
+	long blocks_free;
+	long files;
+	long files_free;
+	long namelen;
+};
+
+/* Linux-specific struct statfs; used by FUSE >= 2.1 && < 2.5. */
+struct statfs {
+	long		f_type;
+	long		f_bsize;
+	fsblkcnt_t	f_blocks;
+	fsblkcnt_t	f_bfree;
+	fsblkcnt_t	f_bavail;
+	fsfilcnt_t	f_files;
+	fsfilcnt_t	f_ffree;
+	fsid_t		f_fsid;
+	long		f_namelen;
+	long		f_frsize;
+	long		f_flags;
+};
+
+/* Handle for a getdir() operation. Removed as of FUSE 3.0. */
+typedef void *fuse_dirh_t;
+
+/* Enable debugging output. Removed on FUSE 3.0. */
+#define FUSE_DEBUG	(1 << 1)
+
+/* Invalidate cached data of a file. Added on FUSE 1.9 and removed on
+ * FUSE 3.0. Not to be confused with fuse_invalidate_path() appeared
+ * on FUSE 3.2. */
+int fuse_invalidate(struct fuse *f, const char *path);
+
+/* Check whether a mount option should be passed to the kernel or the
+ * library. Added on FUSE 1.9 and removed on FUSE 3.0. */
+int fuse_is_lib_option(const char *opt);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
-- 
2.34.0


From 8a188d121f1ad065065435988d43fc54e510b805 Mon Sep 17 00:00:00 2001
From: PHO <pho%cielonegro.org@localhost>
Date: Wed, 12 Jan 2022 17:53:46 +0900
Subject: [PATCH 07/17] Increase the warning level to spot more mistakes

---
 lib/librefuse/Makefile     |  2 +-
 lib/librefuse/TODO         |  2 +-
 lib/librefuse/refuse.c     | 20 ++++++++++----------
 lib/librefuse/refuse_opt.c | 22 +++++++++++-----------
 4 files changed, 23 insertions(+), 23 deletions(-)

diff --git a/lib/librefuse/Makefile b/lib/librefuse/Makefile
index e86f96368..62f0e6b08 100644
--- a/lib/librefuse/Makefile
+++ b/lib/librefuse/Makefile
@@ -15,7 +15,7 @@ CPPFLAGS+=	-I${.CURDIR}
 SRCS=		refuse.c refuse_log.c refuse_lowlevel.c
 SRCS+=		refuse_opt.c refuse_signals.c
 MAN=		refuse.3
-WARNS?=		5
+WARNS?=		6
 INCS=           fuse.h fuse_opt.h fuse_log.h fuse_lowlevel.h
 INCSDIR=        /usr/include
 
diff --git a/lib/librefuse/TODO b/lib/librefuse/TODO
index ad259cfe9..b797b701e 100644
--- a/lib/librefuse/TODO
+++ b/lib/librefuse/TODO
@@ -18,7 +18,7 @@ Done
 ====
 statvfs
 sync
-WARNS=4
+WARNS=6
 address lint
 special directory handling in open()
 Finish off manual page
diff --git a/lib/librefuse/refuse.c b/lib/librefuse/refuse.c
index ba5fc39fd..3d5fbfa99 100644
--- a/lib/librefuse/refuse.c
+++ b/lib/librefuse/refuse.c
@@ -301,7 +301,7 @@ puffs_fuse_fill_dir(void *buf, const char *name,
 		dtype = DT_UNKNOWN;
 		dino = fakeino++;
 	} else {
-		dtype = puffs_vtype2dt(puffs_mode2vt(stbuf->st_mode));
+		dtype = (uint8_t)puffs_vtype2dt(puffs_mode2vt(stbuf->st_mode));
 		dino = stbuf->st_ino;
 
 		/*
@@ -571,7 +571,7 @@ puffs_fuse_node_readlink(struct puffs_usermount *pu, void *opc,
 		if (!p)
 			return EINVAL;
 
-		*linklen = p - linkname;
+		*linklen = (size_t)(p - linkname);
 	}
 
 	return -ret;
@@ -948,9 +948,9 @@ puffs_fuse_node_read(struct puffs_usermount *pu, void *opc, uint8_t *buf,
 	set_fuse_context_uid_gid(pcr);
 
 	maxread = *resid;
-	if (maxread > pn->pn_va.va_size - offset) {
+	if (maxread > (size_t)((off_t)pn->pn_va.va_size - offset)) {
 		/*LINTED*/
-		maxread = pn->pn_va.va_size - offset;
+		maxread = (size_t)((off_t)pn->pn_va.va_size - offset);
 	}
 	if (maxread == 0)
 		return 0;
@@ -959,7 +959,7 @@ puffs_fuse_node_read(struct puffs_usermount *pu, void *opc, uint8_t *buf,
 	    &rn->file_info);
 
 	if (ret > 0) {
-		*resid -= ret;
+		*resid -= (size_t)ret;
 		ret = 0;
 	}
 
@@ -987,15 +987,15 @@ puffs_fuse_node_write(struct puffs_usermount *pu, void *opc, uint8_t *buf,
 	set_fuse_context_uid_gid(pcr);
 
 	if (ioflag & PUFFS_IO_APPEND)
-		offset = pn->pn_va.va_size;
+		offset = (off_t)pn->pn_va.va_size;
 
 	ret = (*fuse->op.write)(path, (char *)buf, *resid, offset,
 	    &rn->file_info);
 
 	if (ret >= 0) {
 		if ((uint64_t)(offset + ret) > pn->pn_va.va_size)
-			pn->pn_va.va_size = offset + ret;
-		*resid -= ret;
+			pn->pn_va.va_size = (u_quad_t)(offset + ret);
+		*resid -= (size_t)ret;
 		ret = (*resid == 0) ? 0 : ENOSPC;
 	} else {
 		ret = -ret;
@@ -1062,7 +1062,7 @@ puffs_fuse_node_readdir(struct puffs_usermount *pu, void *opc,
 			break;
 
 		memcpy(dent, fromdent, _DIRENT_SIZE(fromdent));
-		*readoff += _DIRENT_SIZE(fromdent);
+		*readoff += (off_t)_DIRENT_SIZE(fromdent);
 		*reslen -= _DIRENT_SIZE(fromdent);
 
 		dent = _DIRENT_NEXT(dent);
@@ -1255,7 +1255,7 @@ fuse_new(struct fuse_args *args,
 	struct fuse_context	*fusectx;
 	struct puffs_ops	*pops;
 	struct fuse		*fuse;
-	int			puffs_flags;
+	uint32_t		puffs_flags;
 
 	/* parse refuse options */
 	if (fuse_opt_parse(args, &config, refuse_opts, NULL) == -1)
diff --git a/lib/librefuse/refuse_opt.c b/lib/librefuse/refuse_opt.c
index 42a9b4c7d..e4ce2c694 100644
--- a/lib/librefuse/refuse_opt.c
+++ b/lib/librefuse/refuse_opt.c
@@ -60,7 +60,7 @@ fuse_opt_add_arg(struct fuse_args *args, const char *arg)
 	} else if (args->allocated == args->argc) {
 		int na = args->allocated + 10;
 
-		if (reallocarr(&args->argv, na, sizeof(*args->argv)) != 0)
+		if (reallocarr(&args->argv, (size_t)na, sizeof(*args->argv)) != 0)
 			return -1;
 
 		args->allocated = na;
@@ -125,7 +125,7 @@ fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg)
 	} else {
 		na = args->allocated + 10;
 	}
-	if (reallocarr(&args->argv, na, sizeof(*args->argv)) != 0) {
+	if (reallocarr(&args->argv, (size_t)na, sizeof(*args->argv)) != 0) {
 		warn("fuse_opt_insert_arg");
 		return -1;
 	}
@@ -180,17 +180,17 @@ int fuse_opt_add_opt_escaped(char **opts, const char *opt)
 	return add_opt(opts, opt, true);
 }
 
-static bool match_templ(const char *templ, const char *opt, int *sep_idx)
+static bool match_templ(const char *templ, const char *opt, ssize_t *sep_idx)
 {
 	const char *sep = strpbrk(templ, "= ");
 
 	if (sep != NULL && (sep[1] == '\0' || sep[1] == '%')) {
 		const size_t cmp_len =
-			sep[0] == '=' ? sep - templ + 1 : sep - templ;
+			(size_t)(sep[0] == '=' ? sep - templ + 1 : sep - templ);
 
 		if (strlen(opt) >= cmp_len && strncmp(templ, opt, cmp_len) == 0) {
 			if (sep_idx != NULL)
-				*sep_idx = sep - templ;
+				*sep_idx = (ssize_t)(sep - templ);
 			return true;
 		}
 		else {
@@ -210,7 +210,7 @@ static bool match_templ(const char *templ, const char *opt, int *sep_idx)
 }
 
 static const struct fuse_opt *
-find_opt(const struct fuse_opt *opts, const char *opt, int *sep_idx)
+find_opt(const struct fuse_opt *opts, const char *opt, ssize_t *sep_idx)
 {
 	for (; opts != NULL && opts->templ != NULL; opts++) {
 		if (match_templ(opts->templ, opt, sep_idx))
@@ -283,7 +283,7 @@ static int next_arg(const struct fuse_args *args, int *i)
 /* Parse a single argument with a matched template. */
 static int
 parse_matched_arg(const char* arg, struct fuse_args *outargs,
-		const struct fuse_opt* opt, int sep_idx, void* data,
+		const struct fuse_opt* opt, ssize_t sep_idx, void* data,
 		fuse_opt_proc_t proc, bool is_opt)
 {
 	if (opt->offset == -1) {
@@ -317,7 +317,7 @@ parse_matched_arg(const char* arg, struct fuse_args *outargs,
 #pragma GCC diagnostic pop
 					(void)fprintf(stderr, "fuse: '%s' is not a "
 								"valid parameter for option '%.*s'\n",
-								param, sep_idx, opt->templ);
+								param, (int)sep_idx, opt->templ);
 					return -1;
 				}
 			}
@@ -336,7 +336,7 @@ parse_arg(struct fuse_args* args, int *argi, const char* arg,
 		struct fuse_args *outargs, void *data,
 		const struct fuse_opt *opts, fuse_opt_proc_t proc, bool is_opt)
 {
-	int sep_idx;
+	ssize_t sep_idx;
 	const struct fuse_opt *opt = find_opt(opts, arg, &sep_idx);
 
 	if (opt) {
@@ -357,11 +357,11 @@ parse_arg(struct fuse_args* args, int *argi, const char* arg,
 
 				/* ...but processor callbacks expect a concatenated
 				 * argument "-xfoo". */
-				if ((new_arg = malloc(sep_idx +
+				if ((new_arg = malloc((size_t)sep_idx +
 									strlen(args->argv[*argi]) + 1)) == NULL)
 					return -1;
 
-				strncpy(new_arg, arg, sep_idx); /* -x */
+				strncpy(new_arg, arg, (size_t)sep_idx); /* -x */
 				strcpy(new_arg + sep_idx, args->argv[*argi]); /* foo */
 				rv = parse_matched_arg(new_arg, outargs, opt, sep_idx,
 									data, proc, is_opt);
-- 
2.34.0


From b1bd63775b305a9be62ed688c5b36efbf3587469 Mon Sep 17 00:00:00 2001
From: PHO <pho%cielonegro.org@localhost>
Date: Wed, 12 Jan 2022 18:17:46 +0900
Subject: [PATCH 08/17] Do not call fuse_operations.getattr() before
 initializing filesystem

---
 lib/librefuse/refuse.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/lib/librefuse/refuse.c b/lib/librefuse/refuse.c
index 3d5fbfa99..a8f13132e 100644
--- a/lib/librefuse/refuse.c
+++ b/lib/librefuse/refuse.c
@@ -1207,7 +1207,6 @@ int fuse_mount(struct fuse *fuse, const char *mountpoint)
 	struct puffs_pathobj	*po_root;
 	struct puffs_node	*pn_root;
 	struct refusenode	*rn_root;
-	struct stat		 st;
 	struct puffs_statvfs	 svfsb;
 
 	pn_root = newrn(fuse->pu);
@@ -1225,10 +1224,11 @@ int fuse_mount(struct fuse *fuse, const char *mountpoint)
 	puffs_vattr_null(&pn_root->pn_va);
 	pn_root->pn_va.va_type = VDIR;
 	pn_root->pn_va.va_mode = 0755;
-	if (fuse->op.getattr)
-		if (fuse->op.getattr(po_root->po_path, &st) == 0)
-			puffs_stat2vattr(&pn_root->pn_va, &st);
-	assert(pn_root->pn_va.va_type == VDIR);
+	/* It might be tempting to call op.getattr("/") here to
+	 * populate pn_root->pa_va, but that would mean invoking an
+	 * operation callback without initializing the filesystem. We
+	 * cannot call op.init() either, because that is supposed to
+	 * be called right before entering the main loop. */
 
 	puffs_set_prepost(fuse->pu, set_fuse_context_pid, NULL);
 
-- 
2.34.0


From 05353c01c3201818965845b83cd9cf6173b82615 Mon Sep 17 00:00:00 2001
From: PHO <pho%cielonegro.org@localhost>
Date: Wed, 12 Jan 2022 18:39:57 +0900
Subject: [PATCH 09/17] Implement a dummy pathconf() which always returns
 EINVAL

---
 lib/librefuse/refuse.c | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/lib/librefuse/refuse.c b/lib/librefuse/refuse.c
index a8f13132e..b6499d8c2 100644
--- a/lib/librefuse/refuse.c
+++ b/lib/librefuse/refuse.c
@@ -855,6 +855,19 @@ puffs_fuse_node_setattr(struct puffs_usermount *pu, void *opc,
 	return fuse_setattr(fuse, pn, path, va);
 }
 
+static int
+puffs_fuse_node_pathconf(struct puffs_usermount *pu, void *opc,
+	int name, __register_t *retval)
+{
+	/* Returning EINVAL for pathconf(2) means that this filesystem
+	 * does not support an association of the given name with the
+	 * file. This is necessary because the default error code
+	 * returned by the puffs kernel module (ENOTSUPP) is not
+	 * suitable for an errno from pathconf(2), and "ls -l"
+	 * complains about it. */
+	return EINVAL;
+}
+
 /* ARGSUSED2 */
 static int
 puffs_fuse_node_open(struct puffs_usermount *pu, void *opc, int mode,
@@ -1296,6 +1309,7 @@ fuse_new(struct fuse_args *args,
         PUFFSOP_SET(pops, puffs_fuse, node, lookup);
         PUFFSOP_SET(pops, puffs_fuse, node, getattr);
         PUFFSOP_SET(pops, puffs_fuse, node, setattr);
+	PUFFSOP_SET(pops, puffs_fuse, node, pathconf);
         PUFFSOP_SET(pops, puffs_fuse, node, readdir);
         PUFFSOP_SET(pops, puffs_fuse, node, readlink);
         PUFFSOP_SET(pops, puffs_fuse, node, mknod);
-- 
2.34.0


From 2d43e06d04fcb3fec1206c0b1eda4730de052cdc Mon Sep 17 00:00:00 2001
From: PHO <pho%cielonegro.org@localhost>
Date: Wed, 12 Jan 2022 18:52:07 +0900
Subject: [PATCH 10/17] Change the way how FUSE_*_VERSION are handled

* FUSE_MAKE_VERSION(maj, min) now generates a 3-digits number if the
  version is higher than 3.9. This is needed to support FUSE 3.10 API.

* FUSE_{MAJOR,MINOR}_VERSION no longer have a fixed value but are
  derived from FUSE_USE_VERSION specified by the user code. This is
  needed to support more FUSE filesystems in the wild.
---
 lib/librefuse/fuse.h          | 44 ++++++++++++++++++++++++++++-------
 lib/librefuse/fuse_internal.h |  4 ++--
 lib/librefuse/refuse.c        |  7 +-----
 3 files changed, 39 insertions(+), 16 deletions(-)

diff --git a/lib/librefuse/fuse.h b/lib/librefuse/fuse.h
index 2646bef03..36d5f9a6d 100644
--- a/lib/librefuse/fuse.h
+++ b/lib/librefuse/fuse.h
@@ -40,24 +40,52 @@
 #include <sys/types.h>
 #include <utime.h>
 
-/* The latest version of FUSE API currently provided by refuse. */
-#define FUSE_MAJOR_VERSION	2
-#define FUSE_MINOR_VERSION	6
+/* This used to be (maj) * 10 + (min) until FUSE 3.10, and then
+ * changed to (maj) * 100 + (min). We can't just use the "newer"
+ * definition because filesystems in the wild still use the older one
+ * in their FUSE_USE_VERSION request. */
+#define FUSE_MAKE_VERSION(maj, min)					\
+	(((maj) > 3 || ((maj) == 3 && (min) >= 10))			\
+	? (maj) * 100 + (min)						\
+	: (maj) *  10 + (min))
 
-#define FUSE_MAKE_VERSION(maj, min)	((maj) * 10 + (min))
-#define FUSE_VERSION	FUSE_MAKE_VERSION(FUSE_MAJOR_VERSION, FUSE_MINOR_VERSION)
+/* The latest version of FUSE API currently provided by ReFUSE. This
+ * is an implementation detail. User code should not rely on this
+ * constant. */
+#define _REFUSE_MAJOR_VERSION_	2
+#define _REFUSE_MINOR_VERSION_	6
+
+#define _REFUSE_VERSION_	FUSE_MAKE_VERSION(_REFUSE_MAJOR_VERSION_, _REFUSE_MINOR_VERSION_)
 
 /* FUSE_USE_VERSION is expected to be defined by user code to
  * determine the API to be used. Although defining this macro is
  * mandatory in the original FUSE implementation, refuse hasn't
  * required this so we only emit a warning if it's undefined. */
 #if defined(FUSE_USE_VERSION)
-#	if FUSE_USE_VERSION > FUSE_VERSION
+#	if FUSE_USE_VERSION > _REFUSE_VERSION_
 #		warning "The requested API version is higher than the latest one supported by refuse."
+#	elif FUSE_USE_VERSION < 11
+#		warning "The requested API version is lower than the oldest one supported by refuse."
 #	endif
 #else
-#	warning "User code including <fuse.h> should define FUSE_USE_VERSION before including this header. Defaulting to the latest version."
-#	define FUSE_USE_VERSION	FUSE_VERSION
+#	if !defined(_REFUSE_IMPLEMENTATION_)
+#		warning "User code including <fuse.h> should define FUSE_USE_VERSION before including this header. Defaulting to the latest version."
+#		define FUSE_USE_VERSION	_REFUSE_VERSION_
+#	endif
+#endif
+
+/* FUSE_VERSION is supposed to be the latest version of FUSE API
+ * supported by the library. However, due to the way how original FUSE
+ * is implemented, some filesystems set FUSE_USE_VERSION to some old
+ * one and then expect the actual API version exposed by the library
+ * to be something newer if FUSE_VERSION is higher than that. ReFUSE
+ * doesn't work that way, so this has to be always identical to
+ * FUSE_USE_VERSION.
+ */
+#if defined(FUSE_USE_VERSION)
+#	define FUSE_VERSION		FUSE_USE_VERSION
+#	define FUSE_MAJOR_VERSION	(FUSE_VERSION / 10)
+#	define FUSE_MINOR_VERSION	(FUSE_VERSION % 10)
 #endif
 
 #ifdef __cplusplus
diff --git a/lib/librefuse/fuse_internal.h b/lib/librefuse/fuse_internal.h
index 72eee00c9..36cc124aa 100644
--- a/lib/librefuse/fuse_internal.h
+++ b/lib/librefuse/fuse_internal.h
@@ -32,9 +32,9 @@
 #define FUSE_INTERNAL_H
 
 /* We emit a compiler warning for anyone including <fuse.h> without
- * defining FUSE_USE_VERSION. Define it here, or otherwise we'll be
+ * defining FUSE_USE_VERSION. Exempt ourselves here, or we'll be
  * warned too. */
-#define FUSE_USE_VERSION	FUSE_VERSION
+#define _REFUSE_IMPLEMENTATION_
 
 #include <fuse.h>
 #include <fuse_lowlevel.h>
diff --git a/lib/librefuse/refuse.c b/lib/librefuse/refuse.c
index b6499d8c2..6c6a7e7f1 100644
--- a/lib/librefuse/refuse.c
+++ b/lib/librefuse/refuse.c
@@ -34,11 +34,6 @@
 __RCSID("$NetBSD: refuse.c,v 1.103 2021/12/04 06:42:39 pho Exp $");
 #endif /* !lint */
 
-/* We emit a compiler warning for anyone including <fuse.h> without
- * defining FUSE_USE_VERSION. Define it here, or otherwise we'll be
- * warned too. */
-#define FUSE_USE_VERSION	FUSE_VERSION
-
 #include <sys/types.h>
 
 #include <assert.h>
@@ -1417,7 +1412,7 @@ fuse_invalidate_path(struct fuse *fuse __attribute__((__unused__)),
 int
 fuse_version(void)
 {
-	return FUSE_VERSION;
+	return _REFUSE_VERSION_;
 }
 
 /* This is a legacy function that has been removed from the FUSE API,
-- 
2.34.0


From db559c1fd454aec5a953ee9ce3f3b0f9d09343c2 Mon Sep 17 00:00:00 2001
From: PHO <pho%cielonegro.org@localhost>
Date: Wed, 12 Jan 2022 18:57:03 +0900
Subject: [PATCH 11/17] Cosmetic changes

---
 lib/librefuse/fuse.h     | 3 +--
 lib/librefuse/fuse_opt.h | 6 ++++--
 2 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/lib/librefuse/fuse.h b/lib/librefuse/fuse.h
index 36d5f9a6d..89215dd88 100644
--- a/lib/librefuse/fuse.h
+++ b/lib/librefuse/fuse.h
@@ -30,6 +30,7 @@
 #ifndef FUSE_H_
 #define FUSE_H_	20211204
 
+#include <fuse_opt.h>
 #include <refuse/buf.h>
 #include <refuse/legacy.h>
 #include <refuse/poll.h>
@@ -227,6 +228,4 @@ void fuse_unmount_compat22(const char *);
 }
 #endif
 
-#include <fuse_opt.h>
-
 #endif
diff --git a/lib/librefuse/fuse_opt.h b/lib/librefuse/fuse_opt.h
index c159ec3d6..167930029 100644
--- a/lib/librefuse/fuse_opt.h
+++ b/lib/librefuse/fuse_opt.h
@@ -35,7 +35,7 @@
 
 #ifdef __cplusplus
 extern "C" {
-#endif  
+#endif
 
 enum {
 	FUSE_OPT_KEY_OPT = -1,
@@ -44,6 +44,8 @@ enum {
 	FUSE_OPT_KEY_DISCARD = -4
 };
 
+struct fuse_args;
+
 struct fuse_opt {
 	const char	*templ;
 	int32_t		offset;
@@ -68,6 +70,6 @@ int fuse_opt_match(const struct fuse_opt *, const char *);
 
 #ifdef __cplusplus
 }
-#endif 
+#endif
 
 #endif /* _FUSE_OPT_H_ */
-- 
2.34.0


From c527ee2f6bd8ee0e400e1cf100e9afa863f0126b Mon Sep 17 00:00:00 2001
From: PHO <pho%cielonegro.org@localhost>
Date: Wed, 12 Jan 2022 20:13:19 +0900
Subject: [PATCH 12/17] Correct the wrong prototype of fuse_daemonize(3) while
 retaining ABI compatibility

---
 lib/librefuse/Makefile        |  2 +-
 lib/librefuse/fuse.h          | 11 +++++--
 lib/librefuse/refuse.c        | 18 +++++++++--
 lib/librefuse/refuse_compat.c | 60 +++++++++++++++++++++++++++++++++++
 4 files changed, 86 insertions(+), 5 deletions(-)
 create mode 100644 lib/librefuse/refuse_compat.c

diff --git a/lib/librefuse/Makefile b/lib/librefuse/Makefile
index 62f0e6b08..8b91c87d3 100644
--- a/lib/librefuse/Makefile
+++ b/lib/librefuse/Makefile
@@ -12,7 +12,7 @@ FUSE_OPT_DEBUG_FLAGS=	-g -DFUSE_OPT_DEBUG
 
 CFLAGS+=	${FUSE_OPT_DEBUG_FLAGS}
 CPPFLAGS+=	-I${.CURDIR}
-SRCS=		refuse.c refuse_log.c refuse_lowlevel.c
+SRCS=		refuse.c refuse_compat.c refuse_log.c refuse_lowlevel.c
 SRCS+=		refuse_opt.c refuse_signals.c
 MAN=		refuse.3
 WARNS?=		6
diff --git a/lib/librefuse/fuse.h b/lib/librefuse/fuse.h
index 89215dd88..782161295 100644
--- a/lib/librefuse/fuse.h
+++ b/lib/librefuse/fuse.h
@@ -201,13 +201,20 @@ int fuse_invalidate_path(struct fuse *fuse, const char *path);
 int fuse_mount(struct fuse *, const char *);
 void fuse_unmount(struct fuse *);
 
-int fuse_daemonize(struct fuse *);
-
 int fuse_main_real(int, char **, const struct fuse_operations *, size_t, void *);
+/* Functions that have existed since the beginning and have never
+ * changed between API versions. */
 int fuse_loop(struct fuse *);
 struct fuse_context *fuse_get_context(void);
 void fuse_exit(struct fuse *);
 void fuse_destroy(struct fuse *);
+
+/* Daemonize the calling process. Appeared on FUSE 2.6.
+ *
+ * NOTE: This function used to have a wrong prototype in librefuse at
+ * the time when FUSE_H_ < 20211204. */
+int fuse_daemonize(int foreground) __RENAME(fuse_daemonize_rev1);
+
 int fuse_version(void);
 
 #if FUSE_USE_VERSION == 22
diff --git a/lib/librefuse/refuse.c b/lib/librefuse/refuse.c
index 6c6a7e7f1..6bc873088 100644
--- a/lib/librefuse/refuse.c
+++ b/lib/librefuse/refuse.c
@@ -1248,9 +1248,23 @@ int fuse_mount(struct fuse *fuse, const char *mountpoint)
 	return 0;
 }
 
-int fuse_daemonize(struct fuse *fuse)
+int fuse_daemonize(int foreground)
 {
-	return puffs_daemon(fuse->pu, 0, 0);
+	/* There is an impedance mismatch here: FUSE wants to
+	 * daemonize the process without any contexts but puffs wants
+	 * one. */
+	struct fuse *fuse = fuse_get_context()->fuse;
+
+	if (!fuse)
+		/* FUSE would probably allow this, but we cannot. */
+		errx(EXIT_FAILURE,
+		     "%s: librefuse doesn't allow calling"
+		     " this function before fuse_new().", __func__);
+
+	if (!foreground)
+		return puffs_daemon(fuse->pu, 0, 0);
+
+	return 0;
 }
 
 /* ARGSUSED1 */
diff --git a/lib/librefuse/refuse_compat.c b/lib/librefuse/refuse_compat.c
new file mode 100644
index 000000000..35705fa1a
--- /dev/null
+++ b/lib/librefuse/refuse_compat.c
@@ -0,0 +1,60 @@
+/* $NetBSD$ */
+
+/*
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior written
+ *    permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#if !defined(lint)
+__RCSID("$NetBSD$");
+#endif /* !lint */
+
+#include <fuse_internal.h>
+
+/*
+ * Compatibility symbols that had existed in old versions of
+ * librefuse.
+ */
+
+int fuse_daemonize_rev0(struct fuse* fuse) __RENAME(fuse_daemonize);
+
+/* librefuse once had a function fuse_daemonize() with an incompatible
+ * prototype with that of FUSE. We keep ABI compatibility with
+ * executables compiled against old librefuse by having this shim
+ * function as a symbol "fuse_daemonize". However, we can NOT keep API
+ * compatibility with old code expecting the wrong prototype. The only
+ * way to work around the problem is to put "#if FUSE_H_ < 20211204"
+ * into old user code. pho@ really regrets this mistake... */
+__warn_references(
+    fuse_daemonize,
+    "warning: reference to compatibility fuse_daemonize();"
+    " include <fuse.h> for correct reference")
+int
+fuse_daemonize_rev0(struct fuse* fuse __attribute__((__unused__))) {
+    return fuse_daemonize(1);
+}
-- 
2.34.0


From b734d1c71936146a23d07d996fe28dbda1a62171 Mon Sep 17 00:00:00 2001
From: PHO <pho%cielonegro.org@localhost>
Date: Wed, 12 Jan 2022 20:37:59 +0900
Subject: [PATCH 13/17] Implement some missing functions that are part of the
 API

---
 lib/librefuse/fuse.h          | 33 ++++++++++++++++++++---
 lib/librefuse/fuse_lowlevel.h |  5 ++++
 lib/librefuse/refuse.c        | 49 +++++++++++++++++++++++++++++++++++
 3 files changed, 84 insertions(+), 3 deletions(-)

diff --git a/lib/librefuse/fuse.h b/lib/librefuse/fuse.h
index 782161295..f0979b237 100644
--- a/lib/librefuse/fuse.h
+++ b/lib/librefuse/fuse.h
@@ -195,8 +195,6 @@ struct fuse_operations {
 
 struct fuse *fuse_new(struct fuse_args *,
 	const struct fuse_operations *, size_t, void *);
-/* Invalidate cache for a given path. Appeared on FUSE 3.2. */
-int fuse_invalidate_path(struct fuse *fuse, const char *path);
 
 int fuse_mount(struct fuse *, const char *);
 void fuse_unmount(struct fuse *);
@@ -205,9 +203,12 @@ int fuse_main_real(int, char **, const struct fuse_operations *, size_t, void *)
 /* Functions that have existed since the beginning and have never
  * changed between API versions. */
 int fuse_loop(struct fuse *);
-struct fuse_context *fuse_get_context(void);
 void fuse_exit(struct fuse *);
 void fuse_destroy(struct fuse *);
+struct fuse_context *fuse_get_context(void);
+
+/* Print available library options. Appeared on FUSE 3.1. */
+void fuse_lib_help(struct fuse_args *args);
 
 /* Daemonize the calling process. Appeared on FUSE 2.6.
  *
@@ -215,6 +216,13 @@ void fuse_destroy(struct fuse *);
  * the time when FUSE_H_ < 20211204. */
 int fuse_daemonize(int foreground) __RENAME(fuse_daemonize_rev1);
 
+/* Check if a request has been interrupted. Appeared on FUSE 2.6. */
+int fuse_interrupted(void);
+
+/* Invalidate cache for a given path. Appeared on FUSE 3.2. */
+int fuse_invalidate_path(struct fuse *fuse, const char *path);
+
+/* Get the version number of the library. Appeared on FUSE 2.7. */
 int fuse_version(void);
 
 #if FUSE_USE_VERSION == 22
@@ -230,6 +238,25 @@ void fuse_unmount_compat22(const char *);
 #define fuse_main(argc, argv, op) \
             fuse_main_real(argc, argv, op, sizeof(*(op)), NULL)
 #endif
+/* Get the version string of the library. Appeared on FUSE 3.0. */
+const char *fuse_pkgversion(void);
+
+/* Get the current supplementary group IDs for the current request, or
+ * return -errno on failure. Appeared on FUSE 2.8. */
+int fuse_getgroups(int size, gid_t list[]);
+
+/* Start the cleanup thread when using option "-oremember". Appeared
+ * on FUSE 2.9. */
+int fuse_start_cleanup_thread(struct fuse *fuse);
+
+/* Stop the cleanup thread when using "-oremember". Appeared on FUSE
+ * 2.9. */
+void fuse_stop_cleanup_thread(struct fuse *fuse);
+
+/* Iterate over cache removing stale entries, used in conjunction with
+ * "-oremember". Return the number of seconds until the next
+ * cleanup. Appeared on FUSE 2.9. */
+int fuse_clean_cache(struct fuse *fuse);
 
 #ifdef __cplusplus
 }
diff --git a/lib/librefuse/fuse_lowlevel.h b/lib/librefuse/fuse_lowlevel.h
index e53c26768..693a06663 100644
--- a/lib/librefuse/fuse_lowlevel.h
+++ b/lib/librefuse/fuse_lowlevel.h
@@ -49,7 +49,12 @@ struct fuse_cmdline_opts {
 };
 
 int fuse_parse_cmdline(struct fuse_args *args, struct fuse_cmdline_opts *opts);
+/* Print low-level version information to stdout. Appeared on FUSE
+ * 3.0. */
 void fuse_lowlevel_version(void);
+
+/* Print available options for fuse_parse_cmdline(). Appeared on FUSE
+ * 3.0. */
 void fuse_cmdline_help(void);
 
 #ifdef __cplusplus
diff --git a/lib/librefuse/refuse.c b/lib/librefuse/refuse.c
index 6bc873088..0a002193b 100644
--- a/lib/librefuse/refuse.c
+++ b/lib/librefuse/refuse.c
@@ -1414,6 +1414,20 @@ fuse_unmount_compat22(const char *mp)
 	return;
 }
 
+void
+fuse_lib_help(struct fuse_args *args __attribute__((__unused__)))
+{
+	fuse_cmdline_help();
+}
+
+int
+fuse_interrupted(void)
+{
+	/* ReFUSE doesn't support request interruption at the
+	 * moment. */
+	return 0;
+}
+
 int
 fuse_invalidate_path(struct fuse *fuse __attribute__((__unused__)),
 		     const char *path __attribute__((__unused__)))
@@ -1429,6 +1443,41 @@ fuse_version(void)
 	return _REFUSE_VERSION_;
 }
 
+const char *
+fuse_pkgversion(void)
+{
+	return "ReFUSE " ___STRING(_REFUSE_MAJOR_VERSION_)
+		"." ___STRING(_REFUSE_MINOR_VERSION_);
+}
+
+int
+fuse_getgroups(int size, gid_t list[])
+{
+	/* XXX: In order to implement this, we need to save a pointer
+	 * to struct puffs_cred in struct fuse upon entering a puffs
+	 * callback, and set it back to NULL upon leaving it. Then we
+	 * can use puffs_cred_getgroups(3) here. */
+	return -ENOSYS;
+}
+
+int
+fuse_start_cleanup_thread(struct fuse *fuse)
+{
+	/* XXX: ReFUSE doesn't support -oremember at the moment. */
+	return 0;
+}
+
+void
+fuse_stop_cleanup_thread(struct fuse *fuse) {
+	/* XXX: ReFUSE doesn't support -oremember at the moment. */
+}
+
+int
+fuse_clean_cache(struct fuse *fuse) {
+	/* XXX: ReFUSE doesn't support -oremember at the moment. */
+	return 3600;
+}
+
 /* This is a legacy function that has been removed from the FUSE API,
  * but is defined here because it needs to access refuse_opts. */
 int
-- 
2.34.0


From 06db5fab770e422b314c4c0a9bb0778f62acd19c Mon Sep 17 00:00:00 2001
From: PHO <pho%cielonegro.org@localhost>
Date: Wed, 12 Jan 2022 20:50:11 +0900
Subject: [PATCH 14/17] Support the FUSE option -ho

It is supposed to print a help message without the usage
line. Although it is deprecated and has been removed as of FUSE 3.0,
filesystems in the wild still use it.
---
 lib/librefuse/fuse_internal.h   |  5 +++++
 lib/librefuse/refuse.c          | 16 +++++++++++-----
 lib/librefuse/refuse_lowlevel.c |  5 +++--
 3 files changed, 19 insertions(+), 7 deletions(-)

diff --git a/lib/librefuse/fuse_internal.h b/lib/librefuse/fuse_internal.h
index 36cc124aa..f658dccdf 100644
--- a/lib/librefuse/fuse_internal.h
+++ b/lib/librefuse/fuse_internal.h
@@ -44,6 +44,11 @@
 extern "C" {
 #endif
 
+enum refuse_show_help_variant {
+	REFUSE_SHOW_HELP_FULL		= 1,
+	REFUSE_SHOW_HELP_NO_HEADER	= 2,
+};
+
 /* Internal functions, hidden from users */
 __BEGIN_HIDDEN_DECLS
 int __fuse_set_signal_handlers(struct fuse* fuse);
diff --git a/lib/librefuse/refuse.c b/lib/librefuse/refuse.c
index 0a002193b..6d6b3efdf 100644
--- a/lib/librefuse/refuse.c
+++ b/lib/librefuse/refuse.c
@@ -1157,11 +1157,17 @@ fuse_main_real(int argc, char **argv, const struct fuse_operations *ops,
 	}
 
 	if (opts.show_help) {
-		if (args.argv[0] != NULL && args.argv[0][0] != '\0') {
-			/* argv[0] being empty means that the application doesn't
-			 * want us to print the usage string.
-			 */
-			printf("Usage: %s [options] mountpoint\n\n", args.argv[0]);
+		switch (opts.show_help) {
+		case REFUSE_SHOW_HELP_FULL:
+			if (args.argv[0] != NULL && args.argv[0][0] != '\0') {
+				/* argv[0] being empty means that the application doesn't
+				 * want us to print the usage string.
+				 */
+				printf("Usage: %s [options] mountpoint\n\n", args.argv[0]);
+			}
+			break;
+		case REFUSE_SHOW_HELP_NO_HEADER:
+			break;
 		}
 		fuse_cmdline_help();
 		rv = 0;
diff --git a/lib/librefuse/refuse_lowlevel.c b/lib/librefuse/refuse_lowlevel.c
index 6f6d1c717..05398f317 100644
--- a/lib/librefuse/refuse_lowlevel.c
+++ b/lib/librefuse/refuse_lowlevel.c
@@ -45,8 +45,9 @@ __RCSID("$NetBSD: refuse_lowlevel.c,v 1.2 2021/12/04 06:42:39 pho Exp $");
 	{ t, offsetof(struct fuse_cmdline_opts, p), v }
 
 static struct fuse_opt fuse_lowlevel_opts[] = {
-	REFUSE_LOWLEVEL_OPT("-h"       , show_help       , 1),
-	REFUSE_LOWLEVEL_OPT("--help"   , show_help       , 1),
+	REFUSE_LOWLEVEL_OPT("-h"       , show_help       , REFUSE_SHOW_HELP_FULL),
+	REFUSE_LOWLEVEL_OPT("--help"   , show_help       , REFUSE_SHOW_HELP_FULL),
+	REFUSE_LOWLEVEL_OPT("-ho"      , show_help       , REFUSE_SHOW_HELP_NO_HEADER),
 	REFUSE_LOWLEVEL_OPT("-V"       , show_version    , 1),
 	REFUSE_LOWLEVEL_OPT("--version", show_version    , 1),
 	REFUSE_LOWLEVEL_OPT("-d"       , debug           , 1),
-- 
2.34.0


From 2c5c16b61d6da966670e6ff665b81caf160bcabe Mon Sep 17 00:00:00 2001
From: PHO <pho%cielonegro.org@localhost>
Date: Wed, 12 Jan 2022 20:57:04 +0900
Subject: [PATCH 15/17] Add some missing struct fields, structs, and constants
 that are part of the API

---
 lib/librefuse/fuse.h | 104 ++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 93 insertions(+), 11 deletions(-)

diff --git a/lib/librefuse/fuse.h b/lib/librefuse/fuse.h
index f0979b237..c6b806f52 100644
--- a/lib/librefuse/fuse.h
+++ b/lib/librefuse/fuse.h
@@ -30,6 +30,7 @@
 #ifndef FUSE_H_
 #define FUSE_H_	20211204
 
+#include <fcntl.h>
 #include <fuse_opt.h>
 #include <refuse/buf.h>
 #include <refuse/legacy.h>
@@ -94,27 +95,36 @@ extern "C" {
 #endif
 
 struct fuse;
-struct fuse_args; /* XXXsupportme */
 
 struct fuse_file_info {
 	int32_t		flags;
-	uint32_t	fh_old;
-	int32_t		writepage;
+	uint32_t	fh_old;			/* Removed as of FUSE 3.0. */
+	int32_t		writepage:1;
 	uint32_t	direct_io:1;
 	uint32_t	keep_cache:1;
 	uint32_t	flush:1;
-	uint32_t	padding:29;
+	uint32_t	nonseekable:1;		/* Added on FUSE 2.8. */
+	uint32_t	flock_release:1;	/* Added on FUSE 2.9. */
+	uint32_t	cache_readdir:1;	/* Added on FUSE 3.5. */
+	uint32_t	padding:26;
 	uint64_t	fh;
-	uint64_t	lock_owner;
+	uint64_t	lock_owner;		/* Added on FUSE 2.6. */
+	uint32_t	poll_events;		/* Added on FUSE 3.0. */
 };
 
 struct fuse_conn_info {
-	uint32_t proto_major;
-	uint32_t proto_minor;
-	uint32_t async_read;
-	uint32_t max_write;
-	uint32_t max_readahead;
-	uint32_t reserved[27];
+	uint32_t	proto_major;
+	uint32_t	proto_minor;
+	uint32_t	async_read;		/* Removed as of FUSE 3.0. */
+	uint32_t	max_write;
+	uint32_t	max_read;		/* Added on FUSE 3.0. */
+	uint32_t	max_readahead;
+	uint32_t	capable;		/* Added on FUSE 2.8. */
+	uint32_t	want;			/* Added on FUSE 2.8. */
+	uint32_t	max_background;		/* Added on FUSE 3.0. */
+	uint32_t	congestion_threshold;	/* Added on FUSE 3.0. */
+	uint32_t	time_gran;		/* Added on FUSE 3.0. */
+	uint32_t	reserved[22];
 };
 
 /* equivalent'ish of puffs_cc */
@@ -124,6 +134,78 @@ struct fuse_context {
 	gid_t		gid;
 	pid_t		pid;
 	void		*private_data;
+	mode_t		umask;			/* Added on FUSE 2.8. */
+};
+
+/* Capability bits for fuse_conn_info.capable and
+ * fuse_conn_info.want */
+#define FUSE_CAP_ASYNC_READ		(1 << 0)
+#define FUSE_CAP_POSIX_LOCKS		(1 << 1)
+#define FUSE_CAP_ATOMIC_O_TRUNC		(1 << 3)
+#define FUSE_CAP_EXPORT_SUPPORT		(1 << 4)
+#define FUSE_CAP_BIG_WRITES		(1 << 5)	/* Removed as of FUSE 3.0. */
+#define FUSE_CAP_DONT_MASK		(1 << 6)
+#define FUSE_CAP_SPLICE_WRITE		(1 << 7)	/* Added on FUSE 3.0. */
+#define FUSE_CAP_SPLICE_MOVE		(1 << 8)	/* Added on FUSE 3.0. */
+#define FUSE_CAP_SPLICE_READ		(1 << 9)	/* Added on FUSE 3.0. */
+#define FUSE_CAP_FLOCK_LOCKS		(1 << 10)	/* Added on FUSE 3.0. */
+#define FUSE_CAP_IOCTL_DIR		(1 << 11)	/* Added on FUSE 3.0. */
+#define FUSE_CAP_AUTO_INVAL_DATA	(1 << 12)	/* Added on FUSE 3.0. */
+#define FUSE_CAP_READDIRPLUS		(1 << 13)	/* Added on FUSE 3.0. */
+#define FUSE_CAP_READDIRPLUS_AUTO	(1 << 14)	/* Added on FUSE 3.0. */
+#define FUSE_CAP_ASYNC_DIO		(1 << 15)	/* Added on FUSE 3.0. */
+#define FUSE_CAP_WRITEBACK_CACHE	(1 << 16)	/* Added on FUSE 3.0. */
+#define FUSE_CAP_NO_OPEN_SUPPORT	(1 << 17)	/* Added on FUSE 3.0. */
+#define FUSE_CAP_PARALLEL_DIROPS	(1 << 18)	/* Added on FUSE 3.0. */
+#define FUSE_CAP_POSIX_ACL		(1 << 19)	/* Added on FUSE 3.0. */
+#define FUSE_CAP_HANDLE_KILLPRIV	(1 << 20)	/* Added on FUSE 3.0. */
+#define FUSE_CAP_CACHE_SYMLINKS		(1 << 23)	/* Added on FUSE 3.10. */
+#define FUSE_CAP_NO_OPENDIR_SUPPORT	(1 << 24)	/* Added on FUSE 3.5. */
+
+/* ioctl flags */
+#define FUSE_IOCTL_COMPAT	(1 << 0)
+#define FUSE_IOCTL_UNRESTRICTED	(1 << 1)
+#define FUSE_IOCTL_RETRY	(1 << 2)
+#define FUSE_IOCTL_DIR		(1 << 4)	/* Added on FUSE 2.9. */
+#define FUSE_IOCTL_MAX_IOV	256
+
+/* readdir() flags, appeared on FUSE 3.0. */
+enum fuse_readdir_flags {
+	FUSE_READDIR_PLUS	= (1 << 0),
+};
+enum fuse_fill_dir_flags {
+	FUSE_FILL_DIR_PLUS	= (1 << 1),
+};
+
+/* Configuration of the high-level API, appeared on FUSE 3.0. */
+struct fuse_config {
+	int		set_gid;
+	unsigned int	gid;
+	int		set_uid;
+	unsigned int	uid;
+	int		set_mode;
+	unsigned int	umask;
+	double		entry_timeout;
+	double		negative_timeout;
+	double		attr_timeout;
+	int		intr;
+	int		intr_signal;
+	int		remember;
+	int		hard_remove;
+	int		use_ino;
+	int		readdir_ino;
+	int		direct_io;
+	int		kernel_cache;
+	int		auto_cache;
+	int		ac_attr_timeout_set;
+	double		ac_attr_timeout;
+	int		nullpath_ok;
+};
+
+/* Configuration of fuse_loop_mt(), appeared on FUSE 3.2. */
+struct fuse_loop_config {
+	int		clone_fd;
+	unsigned int	max_idle_threads;
 };
 
 /**
-- 
2.34.0


From 4964133b22750de0d8a7f281b03984716d2f34b2 Mon Sep 17 00:00:00 2001
From: PHO <pho%cielonegro.org@localhost>
Date: Wed, 12 Jan 2022 21:01:44 +0900
Subject: [PATCH 16/17] Zero-clear the fuse_context in fuse_destroy(3)

---
 lib/librefuse/refuse.c | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/lib/librefuse/refuse.c b/lib/librefuse/refuse.c
index 6d6b3efdf..4c85cc728 100644
--- a/lib/librefuse/refuse.c
+++ b/lib/librefuse/refuse.c
@@ -207,6 +207,19 @@ create_context_key(void)
 #endif
 }
 
+/* struct fuse_context is potentially reused among different
+ * invocations of fuse_new() / fuse_destroy() pair. Clear its content
+ * on fuse_destroy() so that no dangling pointers remain in the
+ * context. */
+static void
+clear_context(void)
+{
+	struct fuse_context	*ctx;
+
+	ctx = fuse_get_context();
+	memset(ctx, 0, sizeof(*ctx));
+}
+
 static void
 delete_context_key(void)
 {   
@@ -1384,6 +1397,7 @@ fuse_destroy(struct fuse *fuse)
 	 * threads exist
 	 */
 
+	clear_context();
 	delete_context_key();
 	/* XXXXXX: missing stuff */
 	free(fuse);
-- 
2.34.0


From d404e66de128120e34d8baa5834e562b40e56c46 Mon Sep 17 00:00:00 2001
From: PHO <pho%cielonegro.org@localhost>
Date: Wed, 12 Jan 2022 21:16:42 +0900
Subject: [PATCH 17/17] Implement all sorts of compat tweaks to appease various
 file systems

ReFUSE now supports all the FUSE API variants from FUSE 1.1 to FUSE
3.10. Sorry for the freaking giant patch. I could not break it down
any further.
---
 distrib/sets/lists/comp/mi                    |   15 +
 lib/librefuse/HACKING.md                      |   59 +
 lib/librefuse/TODO                            |   13 +-
 lib/librefuse/fuse.h                          |  558 ++++-
 lib/librefuse/fuse_internal.h                 |   23 +
 lib/librefuse/fuse_lowlevel.h                 |    9 +-
 lib/librefuse/refuse.c                        |  434 ++--
 lib/librefuse/refuse/Makefile.inc             |   24 +
 lib/librefuse/refuse/chan.c                   |  248 +++
 .../{fuse_internal.h => refuse/chan.h}        |   50 +-
 lib/librefuse/refuse/fs.c                     | 1802 +++++++++++++++++
 lib/librefuse/refuse/fs.h                     |  120 ++
 lib/librefuse/refuse/v11.c                    |  181 ++
 lib/librefuse/refuse/v11.h                    |   92 +
 lib/librefuse/refuse/v21.c                    |  108 +
 lib/librefuse/refuse/v21.h                    |   86 +
 .../{refuse_compat.c => refuse/v22.c}         |   45 +-
 lib/librefuse/refuse/v22.h                    |   89 +
 lib/librefuse/refuse/v23.h                    |   88 +
 lib/librefuse/refuse/v25.c                    |  115 ++
 lib/librefuse/refuse/v25.h                    |   99 +
 lib/librefuse/refuse/v26.c                    |  113 ++
 lib/librefuse/refuse/v26.h                    |  111 +
 lib/librefuse/refuse/v28.h                    |   95 +
 lib/librefuse/refuse/v29.h                    |  101 +
 .../{refuse_compat.c => refuse/v30.c}         |   48 +-
 lib/librefuse/refuse/v30.h                    |  129 ++
 .../{fuse_internal.h => refuse/v32.c}         |   36 +-
 .../{fuse_internal.h => refuse/v32.h}         |   30 +-
 lib/librefuse/refuse/v34.h                    |   95 +
 lib/librefuse/refuse/v35.h                    |   95 +
 lib/librefuse/refuse/v38.h                    |   96 +
 lib/librefuse/refuse_compat.c                 |  104 +
 lib/librefuse/refuse_lowlevel.c               |    3 +-
 34 files changed, 4860 insertions(+), 454 deletions(-)
 create mode 100644 lib/librefuse/HACKING.md
 create mode 100644 lib/librefuse/refuse/chan.c
 copy lib/librefuse/{fuse_internal.h => refuse/chan.h} (54%)
 create mode 100644 lib/librefuse/refuse/fs.c
 create mode 100644 lib/librefuse/refuse/fs.h
 create mode 100644 lib/librefuse/refuse/v11.c
 create mode 100644 lib/librefuse/refuse/v11.h
 create mode 100644 lib/librefuse/refuse/v21.c
 create mode 100644 lib/librefuse/refuse/v21.h
 copy lib/librefuse/{refuse_compat.c => refuse/v22.c} (60%)
 create mode 100644 lib/librefuse/refuse/v22.h
 create mode 100644 lib/librefuse/refuse/v23.h
 create mode 100644 lib/librefuse/refuse/v25.c
 create mode 100644 lib/librefuse/refuse/v25.h
 create mode 100644 lib/librefuse/refuse/v26.c
 create mode 100644 lib/librefuse/refuse/v26.h
 create mode 100644 lib/librefuse/refuse/v28.h
 create mode 100644 lib/librefuse/refuse/v29.h
 copy lib/librefuse/{refuse_compat.c => refuse/v30.c} (65%)
 create mode 100644 lib/librefuse/refuse/v30.h
 copy lib/librefuse/{fuse_internal.h => refuse/v32.c} (68%)
 copy lib/librefuse/{fuse_internal.h => refuse/v32.h} (70%)
 create mode 100644 lib/librefuse/refuse/v34.h
 create mode 100644 lib/librefuse/refuse/v35.h
 create mode 100644 lib/librefuse/refuse/v38.h

diff --git a/distrib/sets/lists/comp/mi b/distrib/sets/lists/comp/mi
index 9e6895a9d..5ae191db9 100644
--- a/distrib/sets/lists/comp/mi
+++ b/distrib/sets/lists/comp/mi
@@ -3093,9 +3093,24 @@
 ./usr/include/readline/history.h		comp-c-include
 ./usr/include/readline/readline.h		comp-c-include
 ./usr/include/refuse/buf.h			comp-refuse-include
+./usr/include/refuse/chan.h			comp-refuse-include
+./usr/include/refuse/fs.h			comp-refuse-include
 ./usr/include/refuse/legacy.h			comp-refuse-include
 ./usr/include/refuse/poll.h			comp-refuse-include
 ./usr/include/refuse/session.h			comp-refuse-include
+./usr/include/refuse/v11.h			comp-refuse-include
+./usr/include/refuse/v21.h			comp-refuse-include
+./usr/include/refuse/v22.h			comp-refuse-include
+./usr/include/refuse/v23.h			comp-refuse-include
+./usr/include/refuse/v25.h			comp-refuse-include
+./usr/include/refuse/v26.h			comp-refuse-include
+./usr/include/refuse/v28.h			comp-refuse-include
+./usr/include/refuse/v29.h			comp-refuse-include
+./usr/include/refuse/v30.h			comp-refuse-include
+./usr/include/refuse/v32.h			comp-refuse-include
+./usr/include/refuse/v34.h			comp-refuse-include
+./usr/include/refuse/v35.h			comp-refuse-include
+./usr/include/refuse/v38.h			comp-refuse-include
 ./usr/include/regex.h				comp-c-include
 ./usr/include/regexp.h				comp-c-include
 ./usr/include/res_update.h			comp-c-include
diff --git a/lib/librefuse/HACKING.md b/lib/librefuse/HACKING.md
new file mode 100644
index 000000000..e13b54011
--- /dev/null
+++ b/lib/librefuse/HACKING.md
@@ -0,0 +1,59 @@
+<!--
+    $NetBSD$
+-->
+
+# How to add support for a new API version
+
+1. Update `_REFUSE_MAJOR_VERSION_` and/or `_REFUSE_MINOR_VERSION_` in
+   `fuse.h`.
+2. If the new API introduces some new typedefs, enums, constants,
+   functions, or struct fields, define them *unconditionally*. Trying
+   to conditionalize them will only increase complexity of the
+   implementation without necessity.
+3. If the new API removes some existing declarations, move them to
+   `./refuse/legacy.[ch]`. There is no need to conditionally hide
+   them.
+4. If the new API doesn't change any of existing declarations, you are
+   lucky. You are **tremendously** lucky. But if it does, things get
+   interesting... (Spoiler: this happens all the time. All, the,
+   time. As if there still weren't enough API-breaking changes in the
+   history of FUSE API.)
+
+## If it breaks API compatibility by changing function prototypes or whatever
+
+1. Create a new header `./refuse/v${VERSION}.h`. Don't forget to add it
+   in `./refuse/Makefile.inc` and `../../distrib/sets/lists/comp/mi`.
+2. Include it from `./fuse.h` and add a new conditionalized section at
+   the bottom of it. The whole point of the section is to choose
+   correct symbols (or types, or whatever) and expose them without a
+   version postfix.
+
+### If it changes `struct fuse_operations`
+
+1. Add `#define _FUSE_OP_VERSION__` for the new API version in the
+   conditionalized section.
+2. Define `struct fuse_operations_v${VERSION}` in `v${VERSION}.h`.
+3. Update `./refuse/fs.c`. This is the abstraction layer which absorbs
+   all the subtle differences in `struct fuse_operations` between
+   versions. Every function has to be updated for the new version.
+
+### If it changes anything else that are already versioned
+
+1. Declare them in `./refuse/v${VERSION}.h` with a version postfix.
+2. Update the conditionalized section for the version in `./fuse.h` so
+   that it exposes the correct definition to user code.
+3. Create `./refuse/v${VERSION}.c` and implement version-specific
+   functions in it. Don't forget to add it to `./refuse/Makefile.inc`.
+
+### If it changes something that have never been changed before
+
+1. Move them from the unconditionalized part of the implementation to
+   `./refuse/v${VERSION}.[ch]`.
+2. Add a version postfix to them.
+3. Update every single conditionalized section in `./fuse.h` so that
+   they will be conditionally exposed without a version postfix
+   depending the value of `FUSE_USE_VERSION`. If you cannot just
+   `#define` them but need to introduce some functions, inline
+   functions are preferred over function macros because the latter
+   lack types and are therefore error-prone. Preprocessor conditionals
+   are already error-prone so don't make them worse.
diff --git a/lib/librefuse/TODO b/lib/librefuse/TODO
index b797b701e..b7fb2f7c0 100644
--- a/lib/librefuse/TODO
+++ b/lib/librefuse/TODO
@@ -7,15 +7,22 @@ implement proper lookup (pending some libpuffs stuff)
 support fuse_mt (i.e. worker threads, but that'll probably be smarter
 		 to do inside of libpuffs)
 support fuse_ll (i.e. "raw" vfs/vnode export)
-implement all sorts of compat tweaks to appease various file systems
 do proper implementations of dirfillers
-statfs - some fuse file systems want struct statfs and we only have
-         statvfs available natively
+Implement filesystem module API appeared on FUSE 2.7 (struct fuse_module).
+Support flags and options in struct fuse_file_info. They all are ignored atm.
+Support capabilities and other options in struct fuse_conn_info. They all are ignored atm.
 Support polling appeared on FUSE 2.8 (struct fuse_pollhandle).
 Support data buffers appeared on FUSE 2.9 (struct fuse_buf).
+Support fsync operation.
+Support access() operation.
+Support flock operation.
+Support fallocate operation.
+Support ioctl appeared on FUSE 2.8 (probably impossible due to incompatibilities with Linux).
 
 Done
 ====
+implement all sorts of compat tweaks to appease various file systems
+Linux-specific statfs
 statvfs
 sync
 WARNS=6
diff --git a/lib/librefuse/fuse.h b/lib/librefuse/fuse.h
index c6b806f52..528c50d2e 100644
--- a/lib/librefuse/fuse.h
+++ b/lib/librefuse/fuse.h
@@ -33,6 +33,7 @@
 #include <fcntl.h>
 #include <fuse_opt.h>
 #include <refuse/buf.h>
+#include <refuse/chan.h>
 #include <refuse/legacy.h>
 #include <refuse/poll.h>
 #include <refuse/session.h>
@@ -54,8 +55,8 @@
 /* The latest version of FUSE API currently provided by ReFUSE. This
  * is an implementation detail. User code should not rely on this
  * constant. */
-#define _REFUSE_MAJOR_VERSION_	2
-#define _REFUSE_MINOR_VERSION_	6
+#define _REFUSE_MAJOR_VERSION_	3
+#define _REFUSE_MINOR_VERSION_	10
 
 #define _REFUSE_VERSION_	FUSE_MAKE_VERSION(_REFUSE_MAJOR_VERSION_, _REFUSE_MINOR_VERSION_)
 
@@ -222,71 +223,10 @@ struct fuse_args {
  */
 #define FUSE_ARGS_INIT(argc, argv) { argc, argv, 0 }
 
-
-typedef int (*fuse_fill_dir_t)(void *, const char *, const struct stat *, off_t);
-typedef int (*fuse_dirfil_t)(fuse_dirh_t, const char *, int, ino_t);
-
-/*
- * These operations shadow those in puffs_usermount, and are used
- * as a table of callbacks to make when file system requests come
- * in.
- *
- * NOTE: keep same order as fuse
- */
-struct fuse_operations {
-	int	(*getattr)(const char *, struct stat *);
-	int	(*readlink)(const char *, char *, size_t);
-	int	(*getdir)(const char *, fuse_dirh_t, fuse_dirfil_t);
-	int	(*mknod)(const char *, mode_t, dev_t);
-	int	(*mkdir)(const char *, mode_t);
-	int	(*unlink)(const char *);
-	int	(*rmdir)(const char *);
-	int	(*symlink)(const char *, const char *);
-	int	(*rename)(const char *, const char *);
-	int	(*link)(const char *, const char *);
-	int	(*chmod)(const char *, mode_t);
-	int	(*chown)(const char *, uid_t, gid_t);
-	int	(*truncate)(const char *, off_t);
-	int	(*utime)(const char *, struct utimbuf *);
-	int	(*open)(const char *, struct fuse_file_info *);
-	int	(*read)(const char *, char *, size_t, off_t, struct fuse_file_info *);
-	int	(*write)(const char *, const char *, size_t, off_t, struct fuse_file_info *);
-	int	(*statfs)(const char *, struct statvfs *);
-	int	(*flush)(const char *, struct fuse_file_info *);
-	int	(*release)(const char *, struct fuse_file_info *);
-	int	(*fsync)(const char *, int, struct fuse_file_info *);
-	int	(*setxattr)(const char *, const char *, const char *, size_t, int);
-	int	(*getxattr)(const char *, const char *, char *, size_t);
-	int	(*listxattr)(const char *, char *, size_t);
-	int	(*removexattr)(const char *, const char *);
-	int	(*opendir)(const char *, struct fuse_file_info *);
-	int	(*readdir)(const char *, void *, fuse_fill_dir_t, off_t, struct fuse_file_info *);
-	int	(*releasedir)(const char *, struct fuse_file_info *);
-	int	(*fsyncdir)(const char *, int, struct fuse_file_info *);
-	void	*(*init)(struct fuse_conn_info *);
-	void	(*destroy)(void *);
-	int	(*access)(const char *, int);
-	int	(*create)(const char *, mode_t, struct fuse_file_info *);
-	int	(*ftruncate)(const char *, off_t, struct fuse_file_info *);
-	int	(*fgetattr)(const char *, struct stat *, struct fuse_file_info *);
-	int	(*lock)(const char *, struct fuse_file_info *, int, struct flock *);
-	int	(*utimens)(const char *, const struct timespec *);
-	int	(*bmap)(const char *, size_t , uint64_t *);
-};
-
-
-struct fuse *fuse_new(struct fuse_args *,
-	const struct fuse_operations *, size_t, void *);
-
-int fuse_mount(struct fuse *, const char *);
-void fuse_unmount(struct fuse *);
-
-int fuse_main_real(int, char **, const struct fuse_operations *, size_t, void *);
 /* Functions that have existed since the beginning and have never
  * changed between API versions. */
 int fuse_loop(struct fuse *);
 void fuse_exit(struct fuse *);
-void fuse_destroy(struct fuse *);
 struct fuse_context *fuse_get_context(void);
 
 /* Print available library options. Appeared on FUSE 3.1. */
@@ -307,19 +247,6 @@ int fuse_invalidate_path(struct fuse *fuse, const char *path);
 /* Get the version number of the library. Appeared on FUSE 2.7. */
 int fuse_version(void);
 
-#if FUSE_USE_VERSION == 22
-#define fuse_unmount fuse_unmount_compat22
-#endif
-
-void fuse_unmount_compat22(const char *);
-
-#if FUSE_USE_VERSION >= 26
-#define fuse_main(argc, argv, op, user_data) \
-            fuse_main_real(argc, argv, op, sizeof(*(op)), user_data)
-#else
-#define fuse_main(argc, argv, op) \
-            fuse_main_real(argc, argv, op, sizeof(*(op)), NULL)
-#endif
 /* Get the version string of the library. Appeared on FUSE 3.0. */
 const char *fuse_pkgversion(void);
 
@@ -340,6 +267,485 @@ void fuse_stop_cleanup_thread(struct fuse *fuse);
  * cleanup. Appeared on FUSE 2.9. */
 int fuse_clean_cache(struct fuse *fuse);
 
+/* Generic implementation of fuse_main(). The exact type of "op" is
+ * determined by op_version. This is only an implementation detail:
+ * user code should never call this directly. */
+int __fuse_main(int argc, char* argv[],
+		const void* op, int op_version, void* user_data);
+
+/* NOTE: Compatibility headers are included
+ * unconditionally. Declarations in these headers all have a version
+ * postfix, and need to be aliased depending on FUSE_USE_VERSION. */
+#include <refuse/v11.h>
+#include <refuse/v21.h>
+#include <refuse/v22.h>
+#include <refuse/v23.h>
+#include <refuse/v25.h>
+#include <refuse/v26.h>
+#include <refuse/v28.h>
+#include <refuse/v29.h>
+#include <refuse/v30.h>
+#include <refuse/v32.h>
+#include <refuse/v34.h>
+#include <refuse/v35.h>
+#include <refuse/v38.h>
+
+/* NOTE: refuse/fs.h relies on some typedef's in refuse/v*.h */
+#include <refuse/fs.h>
+
+#define _MK_FUSE_OPERATIONS_(VER)	__CONCAT(fuse_operations_v,VER)
+
+/* Version specific types and functions. */
+#if defined(FUSE_USE_VERSION)
+/* ===== FUSE 1.x ===== */
+#	if FUSE_USE_VERSION < 21
+		/* Types */
+#		define _FUSE_OP_VERSION__	11 /* Implementation detail */
+#		define fuse_dirfil_t		fuse_dirfil_t_v11
+#		define fuse_operations		_MK_FUSE_OPERATIONS_(_FUSE_OP_VERSION__)
+		/* Functions */
+static __inline int
+fuse_main(int argc, char *argv[], const struct fuse_operations *op) {
+    return __fuse_main(argc, argv, op, _FUSE_OP_VERSION__, NULL);
+}
+#		define fuse_mount		fuse_mount_v11
+#		define fuse_unmount		fuse_unmount_v11
+static __inline struct fuse *
+fuse_new(int fd, int flags, const struct fuse_operations *op) {
+    return fuse_new_v11(fd, flags, op, _FUSE_OP_VERSION__);
+}
+#		define fuse_destroy		fuse_destroy_v11
+#		define fuse_loop_mt		fuse_loop_mt_v11
+
+/* ===== FUSE 2.1 ===== */
+#	elif FUSE_USE_VERSION == 21
+		/* Types */
+#		define _FUSE_OP_VERSION__	21
+#		define fuse_dirfil_t		fuse_dirfil_t_v11
+#		define fuse_operations		_MK_FUSE_OPERATIONS_(_FUSE_OP_VERSION__)
+		/* Functions */
+static __inline int
+fuse_main(int argc, char *argv[], const struct fuse_operations *op) {
+    return __fuse_main(argc, argv, op, _FUSE_OP_VERSION__, NULL);
+}
+#		define fuse_mount		fuse_mount_v21
+#		define fuse_unmount		fuse_unmount_v11
+static __inline struct fuse *
+fuse_new(int fd, const char *opts, const struct fuse_operations *op) {
+    return fuse_new_v21(fd, opts, op, _FUSE_OP_VERSION__, NULL);
+}
+#		define fuse_destroy		fuse_destroy_v11
+#		define fuse_loop_mt		fuse_loop_mt_v11
+
+/* ===== FUSE 2.2 ===== */
+#	elif FUSE_USE_VERSION == 22
+		/* Types */
+#		define _FUSE_OP_VERSION__	22
+#		define fuse_dirfil_t		fuse_dirfil_t_v22
+#		define fuse_operations		_MK_FUSE_OPERATIONS_(_FUSE_OP_VERSION__)
+		/* Functions */
+static __inline int
+fuse_main(int argc, char *argv[], const struct fuse_operations *op) {
+    return __fuse_main(argc, argv, op, _FUSE_OP_VERSION__, NULL);
+}
+#		define fuse_mount		fuse_mount_v21
+#		define fuse_unmount		fuse_unmount_v11
+static __inline struct fuse *
+fuse_new(int fd, const char *opts, const struct fuse_operations *op) {
+    return fuse_new_v21(fd, opts, op, _FUSE_OP_VERSION__, NULL);
+}
+#		define fuse_destroy		fuse_destroy_v11
+#		define fuse_loop_mt		fuse_loop_mt_v11
+static __inline struct fuse *
+fuse_setup(int argc, char *argv[], const struct fuse_operations *op,
+	   size_t op_size __attribute__((__unused__)),
+	   char **mountpoint, int *multithreaded, int *fd) {
+    return fuse_setup_v22(argc, argv, op, _FUSE_OP_VERSION__,
+			  mountpoint, multithreaded, fd);
+}
+#		define fuse_teardown		fuse_teardown_v22
+
+/* ===== FUSE 2.3, 2.4 ===== */
+#	elif FUSE_USE_VERSION >= 23 && FUSE_USE_VERSION <= 24
+		/* Types */
+#		define _FUSE_OP_VERSION__	23
+#		define fuse_dirfil_t		fuse_dirfil_t_v22
+#		define fuse_fill_dir_t		fuse_fill_dir_t_v23
+#		define fuse_operations		_MK_FUSE_OPERATIONS_(_FUSE_OP_VERSION__)
+		/* Functions */
+static __inline int
+fuse_main(int argc, char *argv[], const struct fuse_operations *op) {
+    return __fuse_main(argc, argv, op, _FUSE_OP_VERSION__, NULL);
+}
+#		define fuse_mount		fuse_mount_v21
+#		define fuse_unmount		fuse_unmount_v11
+static __inline struct fuse *
+fuse_new(int fd, const char *opts, const struct fuse_operations *op,
+	 size_t op_size __attribute__((__unused__))) {
+    return fuse_new_v21(fd, opts, op, _FUSE_OP_VERSION__, NULL);
+}
+#		define fuse_destroy		fuse_destroy_v11
+#		define fuse_loop_mt		fuse_loop_mt_v11
+static __inline struct fuse *
+fuse_setup(int argc, char *argv[], const struct fuse_operations *op,
+	   size_t op_size __attribute__((__unused__)),
+	   char **mountpoint, int *multithreaded, int *fd) {
+    return fuse_setup_v22(argc, argv, op, _FUSE_OP_VERSION__,
+			  mountpoint, multithreaded, fd);
+}
+#		define fuse_teardown		fuse_teardown_v22
+
+/* ===== FUSE 2.5 ===== */
+#	elif FUSE_USE_VERSION == 25
+		/* Types */
+#		define _FUSE_OP_VERSION__	25
+#		define fuse_dirfil_t		fuse_dirfil_t_v22
+#		define fuse_fill_dir_t		fuse_fill_dir_t_v23
+#		define fuse_operations		_MK_FUSE_OPERATIONS_(_FUSE_OP_VERSION__)
+		/* Functions */
+static __inline int
+fuse_main(int argc, char *argv[], const struct fuse_operations *op) {
+    return __fuse_main(argc, argv, op, _FUSE_OP_VERSION__, NULL);
+}
+#		define fuse_mount		fuse_mount_v25
+#		define fuse_unmount		fuse_unmount_v11
+static __inline struct fuse *
+fuse_new(int fd, struct fuse_args *args, const struct fuse_operations *op,
+	 size_t op_size __attribute__((__unused__))) {
+    return fuse_new_v25(fd, args, op, _FUSE_OP_VERSION__, NULL);
+}
+#		define fuse_destroy		fuse_destroy_v11
+#		define fuse_loop_mt		fuse_loop_mt_v11
+static __inline struct fuse *
+fuse_setup(int argc, char *argv[], const struct fuse_operations *op,
+	   size_t op_size __attribute__((__unused__)),
+	   char **mountpoint, int *multithreaded, int *fd) {
+    return fuse_setup_v22(argc, argv, op, _FUSE_OP_VERSION__,
+			  mountpoint, multithreaded, fd);
+}
+#		define fuse_teardown		fuse_teardown_v22
+#		define fuse_parse_cmdline	fuse_parse_cmdline_v25
+
+/* ===== FUSE 2.6, 2.7 ===== */
+#	elif FUSE_USE_VERSION >= 26 && FUSE_USE_VERSION <= 27
+		/* Types */
+#		define _FUSE_OP_VERSION__	26
+#		define fuse_dirfil_t		fuse_dirfil_t_v22
+#		define fuse_fill_dir_t		fuse_fill_dir_t_v23
+#		define fuse_operations		_MK_FUSE_OPERATIONS_(_FUSE_OP_VERSION__)
+		/* Functions */
+static __inline struct fuse_fs *
+fuse_fs_new(const struct fuse_operations *op,
+	    size_t op_size __attribute__((__unused__)), void *user_data) {
+    return __fuse_fs_new(op, _FUSE_OP_VERSION__, user_data);
+}
+#		define fuse_fs_getattr		fuse_fs_getattr_v27
+#		define fuse_fs_rename		fuse_fs_rename_v27
+#		define fuse_fs_chmod		fuse_fs_chmod_v27
+#		define fuse_fs_chown		fuse_fs_chown_v27
+#		define fuse_fs_readdir		fuse_fs_readdir_v27
+#		define fuse_fs_truncate		fuse_fs_truncate_v27
+#		define fuse_fs_utimens		fuse_fs_utimens_v27
+#		define fuse_fs_init		fuse_fs_init_v27
+static __inline int
+fuse_main(int argc, char *argv[], const struct fuse_operations *op, void* user_data) {
+    return __fuse_main(argc, argv, op, _FUSE_OP_VERSION__, user_data);
+}
+#		define fuse_mount		fuse_mount_v26
+#		define fuse_unmount		fuse_unmount_v26
+static __inline struct fuse *
+fuse_new(struct fuse_chan *ch, struct fuse_args *args,
+	 const struct fuse_operations *op,
+	 size_t op_size __attribute__((__unused__)), void *user_data) {
+    return fuse_new_v26(ch, args, op, _FUSE_OP_VERSION__, user_data);
+}
+#		define fuse_destroy		fuse_destroy_v11
+#		define fuse_loop_mt		fuse_loop_mt_v11
+static __inline struct fuse *
+fuse_setup(int argc, char *argv[], const struct fuse_operations *op,
+	   size_t op_size __attribute__((__unused__)),
+	   char **mountpoint, int *multithreaded, void *user_data) {
+    return fuse_setup_v26(argc, argv, op, _FUSE_OP_VERSION__,
+			  mountpoint, multithreaded, user_data);
+}
+#		define fuse_teardown		fuse_teardown_v26
+#		define fuse_parse_cmdline	fuse_parse_cmdline_v25
+
+/* ===== FUSE 2.8 ===== */
+#	elif FUSE_USE_VERSION == 28
+		/* Types */
+#		define _FUSE_OP_VERSION__	28
+#		define fuse_dirfil_t		fuse_dirfil_t_v22
+#		define fuse_fill_dir_t		fuse_fill_dir_t_v23
+#		define fuse_operations		_MK_FUSE_OPERATIONS_(_FUSE_OP_VERSION__)
+		/* Functions */
+static __inline struct fuse_fs *
+fuse_fs_new(const struct fuse_operations *op,
+	    size_t op_size __attribute__((__unused__)), void *user_data) {
+    return __fuse_fs_new(op, _FUSE_OP_VERSION__, user_data);
+}
+#		define fuse_fs_getattr		fuse_fs_getattr_v27
+#		define fuse_fs_rename		fuse_fs_rename_v27
+#		define fuse_fs_chmod		fuse_fs_chmod_v27
+#		define fuse_fs_chown		fuse_fs_chown_v27
+#		define fuse_fs_readdir		fuse_fs_readdir_v27
+#		define fuse_fs_truncate		fuse_fs_truncate_v27
+#		define fuse_fs_utimens		fuse_fs_utimens_v27
+#		define fuse_fs_ioctl		fuse_fs_ioctl_v28
+#		define fuse_fs_init		fuse_fs_init_v27
+static __inline int
+fuse_main(int argc, char *argv[], const struct fuse_operations *op, void* user_data) {
+    return __fuse_main(argc, argv, op, _FUSE_OP_VERSION__, user_data);
+}
+#		define fuse_mount		fuse_mount_v26
+#		define fuse_unmount		fuse_unmount_v26
+static __inline struct fuse *
+fuse_new(struct fuse_chan *ch, struct fuse_args *args,
+	 const struct fuse_operations *op,
+	 size_t op_size __attribute__((__unused__)), void *user_data) {
+    return fuse_new_v26(ch, args, op, _FUSE_OP_VERSION__, user_data);
+}
+#		define fuse_destroy		fuse_destroy_v11
+#		define fuse_loop_mt		fuse_loop_mt_v11
+static __inline struct fuse *
+fuse_setup(int argc, char *argv[], const struct fuse_operations *op,
+	   size_t op_size __attribute__((__unused__)),
+	   char **mountpoint, int *multithreaded, void *user_data) {
+    return fuse_setup_v26(argc, argv, op, _FUSE_OP_VERSION__,
+			  mountpoint, multithreaded, user_data);
+}
+#		define fuse_teardown		fuse_teardown_v26
+#		define fuse_parse_cmdline	fuse_parse_cmdline_v25
+
+/* ===== FUSE 2.9 ===== */
+#	elif FUSE_USE_VERSION == 29
+		/* Types */
+#		define _FUSE_OP_VERSION__	29
+#		define fuse_dirfil_t		fuse_dirfil_t_v22
+#		define fuse_fill_dir_t		fuse_fill_dir_t_v23
+#		define fuse_operations		_MK_FUSE_OPERATIONS_(_FUSE_OP_VERSION__)
+		/* Functions */
+static __inline struct fuse_fs *
+fuse_fs_new(const struct fuse_operations *op,
+	    size_t op_size __attribute__((__unused__)), void *user_data) {
+    return __fuse_fs_new(op, _FUSE_OP_VERSION__, user_data);
+}
+#		define fuse_fs_getattr		fuse_fs_getattr_v27
+#		define fuse_fs_rename		fuse_fs_rename_v27
+#		define fuse_fs_chmod		fuse_fs_chmod_v27
+#		define fuse_fs_chown		fuse_fs_chown_v27
+#		define fuse_fs_readdir		fuse_fs_readdir_v27
+#		define fuse_fs_truncate		fuse_fs_truncate_v27
+#		define fuse_fs_utimens		fuse_fs_utimens_v27
+#		define fuse_fs_ioctl		fuse_fs_ioctl_v28
+#		define fuse_fs_init		fuse_fs_init_v27
+static __inline int
+fuse_main(int argc, char *argv[], const struct fuse_operations *op, void* user_data) {
+    return __fuse_main(argc, argv, op, _FUSE_OP_VERSION__, user_data);
+}
+#		define fuse_mount		fuse_mount_v26
+#		define fuse_unmount		fuse_unmount_v26
+static __inline struct fuse *
+fuse_new(struct fuse_chan *ch, struct fuse_args *args,
+	 const struct fuse_operations *op,
+	 size_t op_size __attribute__((__unused__)), void *user_data) {
+    return fuse_new_v26(ch, args, op, _FUSE_OP_VERSION__, user_data);
+}
+#		define fuse_destroy		fuse_destroy_v11
+#		define fuse_loop_mt		fuse_loop_mt_v11
+static __inline struct fuse *
+fuse_setup(int argc, char *argv[], const struct fuse_operations *op,
+	   size_t op_size __attribute__((__unused__)),
+	   char **mountpoint, int *multithreaded, void *user_data) {
+    return fuse_setup_v26(argc, argv, op, _FUSE_OP_VERSION__,
+			  mountpoint, multithreaded, user_data);
+}
+#		define fuse_teardown		fuse_teardown_v26
+#		define fuse_parse_cmdline	fuse_parse_cmdline_v25
+
+/* ===== FUSE 3.0, 3.1 ===== */
+#	elif FUSE_USE_VERSION >= 30 && FUSE_USE_VERSION <= 31
+		/* Types */
+#		define _FUSE_OP_VERSION__	30
+#		define fuse_fill_dir_t		fuse_fill_dir_t_v30
+#		define fuse_operations		_MK_FUSE_OPERATIONS_(_FUSE_OP_VERSION__)
+		/* Functions */
+static __inline struct fuse_fs *
+fuse_fs_new(const struct fuse_operations *op,
+	    size_t op_size __attribute__((__unused__)), void *user_data) {
+    return __fuse_fs_new(op, _FUSE_OP_VERSION__, user_data);
+}
+#		define fuse_fs_getattr		fuse_fs_getattr_v30
+#		define fuse_fs_rename		fuse_fs_rename_v30
+#		define fuse_fs_chmod		fuse_fs_chmod_v30
+#		define fuse_fs_chown		fuse_fs_chown_v30
+#		define fuse_fs_readdir		fuse_fs_readdir_v30
+#		define fuse_fs_truncate		fuse_fs_truncate_v30
+#		define fuse_fs_utimens		fuse_fs_utimens_v30
+#		define fuse_fs_ioctl		fuse_fs_ioctl_v28
+#		define fuse_fs_init		fuse_fs_init_v30
+static __inline int
+fuse_main(int argc, char *argv[], const struct fuse_operations *op, void* user_data) {
+    return __fuse_main(argc, argv, op, _FUSE_OP_VERSION__, user_data);
+}
+#		define fuse_mount		fuse_mount_v30
+#		define fuse_unmount		fuse_unmount_v30
+static __inline struct fuse *
+fuse_new(struct fuse_args *args, const struct fuse_operations *op,
+	 size_t op_size __attribute__((__unused__)), void *user_data) {
+    return fuse_new_v30(args, op, _FUSE_OP_VERSION__, user_data);
+}
+#		define fuse_destroy		fuse_destroy_v30
+#		define fuse_loop_mt		fuse_loop_mt_v30
+		/* fuse_setup(3) and fuse_teardown(3) have been removed as of FUSE 3.0. */
+#		define fuse_parse_cmdline	fuse_parse_cmdline_v30
+
+/* ===== FUSE 3.2, 3.3 ===== */
+#	elif FUSE_USE_VERSION >= 32 && FUSE_USE_VERSION <= 33
+		/* Types */
+#		define _FUSE_OP_VERSION__	30
+#		define fuse_fill_dir_t		fuse_fill_dir_t_v30
+#		define fuse_operations		_MK_FUSE_OPERATIONS_(_FUSE_OP_VERSION__)
+		/* Functions */
+static __inline struct fuse_fs *
+fuse_fs_new(const struct fuse_operations *op,
+	    size_t op_size __attribute__((__unused__)), void *user_data) {
+    return __fuse_fs_new(op, _FUSE_OP_VERSION__, user_data);
+}
+#		define fuse_fs_getattr		fuse_fs_getattr_v30
+#		define fuse_fs_rename		fuse_fs_rename_v30
+#		define fuse_fs_chmod		fuse_fs_chmod_v30
+#		define fuse_fs_chown		fuse_fs_chown_v30
+#		define fuse_fs_readdir		fuse_fs_readdir_v30
+#		define fuse_fs_truncate		fuse_fs_truncate_v30
+#		define fuse_fs_utimens		fuse_fs_utimens_v30
+#		define fuse_fs_ioctl		fuse_fs_ioctl_v28
+#		define fuse_fs_init		fuse_fs_init_v30
+static __inline int
+fuse_main(int argc, char *argv[], const struct fuse_operations *op, void* user_data) {
+    return __fuse_main(argc, argv, op, _FUSE_OP_VERSION__, user_data);
+}
+#		define fuse_mount		fuse_mount_v30
+#		define fuse_unmount		fuse_unmount_v30
+static __inline struct fuse *
+fuse_new(struct fuse_args *args, const struct fuse_operations *op,
+	 size_t op_size __attribute__((__unused__)), void *user_data) {
+    return fuse_new_v30(args, op, _FUSE_OP_VERSION__, user_data);
+}
+#		define fuse_destroy		fuse_destroy_v30
+#		define fuse_loop_mt		fuse_loop_mt_v32
+#		define fuse_parse_cmdline	fuse_parse_cmdline_v30
+
+/* ===== FUSE 3.4 ===== */
+#	elif FUSE_USE_VERSION >= 34
+		/* Types */
+#		define _FUSE_OP_VERSION__	34
+#		define fuse_fill_dir_t		fuse_fill_dir_t_v30
+		/* Functions */
+static __inline struct fuse_fs *
+fuse_fs_new(const struct fuse_operations *op,
+	    size_t op_size __attribute__((__unused__)), void *user_data) {
+    return __fuse_fs_new(op, _FUSE_OP_VERSION__, user_data);
+}
+#		define fuse_fs_getattr		fuse_fs_getattr_v30
+#		define fuse_fs_rename		fuse_fs_rename_v30
+#		define fuse_fs_chmod		fuse_fs_chmod_v30
+#		define fuse_fs_chown		fuse_fs_chown_v30
+#		define fuse_fs_readdir		fuse_fs_readdir_v30
+#		define fuse_fs_truncate		fuse_fs_truncate_v30
+#		define fuse_fs_utimens		fuse_fs_utimens_v30
+#		define fuse_fs_ioctl		fuse_fs_ioctl_v28
+#		define fuse_fs_init		fuse_fs_init_v30
+static __inline int
+fuse_main(int argc, char *argv[], const struct fuse_operations *op, void* user_data) {
+    return __fuse_main(argc, argv, op, _FUSE_OP_VERSION__, user_data);
+}
+#		define fuse_mount		fuse_mount_v30
+#		define fuse_unmount		fuse_unmount_v30
+static __inline struct fuse *
+fuse_new(struct fuse_args *args, const struct fuse_operations *op,
+	 size_t op_size __attribute__((__unused__)), void *user_data) {
+    return fuse_new_v30(args, op, _FUSE_OP_VERSION__, user_data);
+}
+#		define fuse_destroy		fuse_destroy_v30
+#		define fuse_loop_mt		fuse_loop_mt_v32
+#		define fuse_parse_cmdline	fuse_parse_cmdline_v30
+
+/* ===== FUSE 3.5, 3.6, 3.7 ===== */
+#	elif FUSE_USE_VERSION >= 35 && FUSE_USE_VERSION <= 37
+		/* Types */
+#		define _FUSE_OP_VERSION__	35
+#		define fuse_fill_dir_t		fuse_fill_dir_t_v30
+#		define fuse_operations		_MK_FUSE_OPERATIONS_(_FUSE_OP_VERSION__)
+		/* Functions */
+static __inline struct fuse_fs *
+fuse_fs_new(const struct fuse_operations *op,
+	    size_t op_size __attribute__((__unused__)), void *user_data) {
+    return __fuse_fs_new(op, _FUSE_OP_VERSION__, user_data);
+}
+#		define fuse_fs_getattr		fuse_fs_getattr_v30
+#		define fuse_fs_rename		fuse_fs_rename_v30
+#		define fuse_fs_chmod		fuse_fs_chmod_v30
+#		define fuse_fs_chown		fuse_fs_chown_v30
+#		define fuse_fs_readdir		fuse_fs_readdir_v30
+#		define fuse_fs_truncate		fuse_fs_truncate_v30
+#		define fuse_fs_utimens		fuse_fs_utimens_v30
+#		define fuse_fs_ioctl		fuse_fs_ioctl_v35
+#		define fuse_fs_init		fuse_fs_init_v30
+static __inline int
+fuse_main(int argc, char *argv[], const struct fuse_operations *op, void* user_data) {
+    return __fuse_main(argc, argv, op, _FUSE_OP_VERSION__, user_data);
+}
+#		define fuse_mount		fuse_mount_v30
+#		define fuse_unmount		fuse_unmount_v30
+static __inline struct fuse *
+fuse_new(struct fuse_args *args, const struct fuse_operations *op,
+	 size_t op_size __attribute__((__unused__)), void *user_data) {
+    return fuse_new_v30(args, op, _FUSE_OP_VERSION__, user_data);
+}
+#		define fuse_destroy		fuse_destroy_v30
+#		define fuse_loop_mt		fuse_loop_mt_v32
+#		define fuse_parse_cmdline	fuse_parse_cmdline_v30
+
+/* ===== FUSE 3.8, 3.9, 3.10 ===== */
+#	elif FUSE_USE_VERSION >= 38 && FUSE_USE_VERSION <= 310
+		/* Types */
+#		define _FUSE_OP_VERSION__	38
+#		define fuse_fill_dir_t		fuse_fill_dir_t_v30
+#		define fuse_operations		_MK_FUSE_OPERATIONS_(_FUSE_OP_VERSION__)
+		/* Functions */
+static __inline struct fuse_fs *
+fuse_fs_new(const struct fuse_operations *op,
+	    size_t op_size __attribute__((__unused__)), void *user_data) {
+    return __fuse_fs_new(op, _FUSE_OP_VERSION__, user_data);
+}
+#		define fuse_fs_getattr		fuse_fs_getattr_v30
+#		define fuse_fs_rename		fuse_fs_rename_v30
+#		define fuse_fs_chmod		fuse_fs_chmod_v30
+#		define fuse_fs_chown		fuse_fs_chown_v30
+#		define fuse_fs_readdir		fuse_fs_readdir_v30
+#		define fuse_fs_truncate		fuse_fs_truncate_v30
+#		define fuse_fs_utimens		fuse_fs_utimens_v30
+#		define fuse_fs_ioctl		fuse_fs_ioctl_v35
+#		define fuse_fs_init		fuse_fs_init_v30
+static __inline int
+fuse_main(int argc, char *argv[], const struct fuse_operations *op, void* user_data) {
+    return __fuse_main(argc, argv, op, _FUSE_OP_VERSION__, user_data);
+}
+#		define fuse_mount		fuse_mount_v30
+#		define fuse_unmount		fuse_unmount_v30
+static __inline struct fuse *
+fuse_new(struct fuse_args *args, const struct fuse_operations *op,
+	 size_t op_size __attribute__((__unused__)), void *user_data) {
+    return fuse_new_v30(args, op, _FUSE_OP_VERSION__, user_data);
+}
+#		define fuse_destroy		fuse_destroy_v30
+#		define fuse_loop_mt		fuse_loop_mt_v32
+#		define fuse_parse_cmdline	fuse_parse_cmdline_v30
+
+#	endif
+#endif /* defined(FUSE_USE_VERSION) */
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/lib/librefuse/fuse_internal.h b/lib/librefuse/fuse_internal.h
index f658dccdf..b5c65eb29 100644
--- a/lib/librefuse/fuse_internal.h
+++ b/lib/librefuse/fuse_internal.h
@@ -44,6 +44,14 @@
 extern "C" {
 #endif
 
+/* This is the private fuse structure. We can freely change its size
+ * and/or layout without worrying about ABI breakage. */
+struct fuse {
+	struct puffs_usermount	*pu;
+	int			dead;
+	struct fuse_fs		*fs; /* The base filesystem layer. */
+};
+
 enum refuse_show_help_variant {
 	REFUSE_SHOW_HELP_FULL		= 1,
 	REFUSE_SHOW_HELP_NO_HEADER	= 2,
@@ -53,6 +61,21 @@ enum refuse_show_help_variant {
 __BEGIN_HIDDEN_DECLS
 int __fuse_set_signal_handlers(struct fuse* fuse);
 int __fuse_remove_signal_handlers(struct fuse* fuse);
+struct fuse* __fuse_setup(int argc, char* argv[],
+                          const void* op, int op_version, void* user_data,
+                          struct fuse_cmdline_opts* opts);
+void __fuse_teardown(struct fuse* fuse);
+
+/* Generic implementation of fuse_new(). The exact type of "op" is
+ * determined by op_version. */
+struct fuse* __fuse_new(struct fuse_args *args, const void* op,
+                        int op_version, void* user_data);
+
+int __fuse_mount(struct fuse *fuse, const char *mountpoint);
+void __fuse_unmount(struct fuse *fuse);
+void __fuse_destroy(struct fuse *fuse);
+int __fuse_loop_mt(struct fuse *fuse, struct fuse_loop_config *config);
+int __fuse_parse_cmdline(struct fuse_args *args, struct fuse_cmdline_opts *opts);
 __END_HIDDEN_DECLS
 
 #ifdef __cplusplus
diff --git a/lib/librefuse/fuse_lowlevel.h b/lib/librefuse/fuse_lowlevel.h
index 693a06663..0319743a9 100644
--- a/lib/librefuse/fuse_lowlevel.h
+++ b/lib/librefuse/fuse_lowlevel.h
@@ -28,16 +28,19 @@
  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
-
 #ifndef _FUSE_LOWLEVEL_H_
 #define _FUSE_LOWLEVEL_H_
 
+/* <fuse_lowlevel.h> appeared on FUSE 2.4. */
+
 #include <fuse.h>
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
+/* NOTE: Changing the size and/or layout of this struct will break ABI
+ * compatibility. */
 struct fuse_cmdline_opts {
 	int singlethread;
 	int foreground;
@@ -46,9 +49,11 @@ struct fuse_cmdline_opts {
 	char *mountpoint;
 	int show_version;
 	int show_help;
+	int clone_fd;
+	unsigned int max_idle_threads;
+	int reserved[32];
 };
 
-int fuse_parse_cmdline(struct fuse_args *args, struct fuse_cmdline_opts *opts);
 /* Print low-level version information to stdout. Appeared on FUSE
  * 3.0. */
 void fuse_lowlevel_version(void);
diff --git a/lib/librefuse/refuse.c b/lib/librefuse/refuse.c
index 4c85cc728..1f2f21138 100644
--- a/lib/librefuse/refuse.c
+++ b/lib/librefuse/refuse.c
@@ -69,26 +69,6 @@ static struct fuse_opt refuse_opts[] = {
 	FUSE_OPT_END
 };
 
-/* this is the private fuse structure */
-struct fuse {
-	struct puffs_usermount	*pu;
-	int			dead;
-	struct fuse_operations	op;		/* switch table of operations */
-	int			compat;		/* compat level -
-						 * not used in puffs_fuse */
-	struct node		**name_table;
-	size_t			name_table_size;
-	struct node		**id_table;
-	size_t			id_table_size;
-	fuse_ino_t		ctr;
-	unsigned int		generation;
-	unsigned int		hidectr;
-	pthread_mutex_t		lock;
-	pthread_rwlock_t	tree_lock;
-	void			*user_data;
-	int			intr_installed;
-};
-
 struct puffs_fuse_dirh {
 	void *dbuf;
 	struct dirent *d;
@@ -299,7 +279,7 @@ fill_dirbuf(struct puffs_fuse_dirh *dh, const char *name, ino_t dino,
 /* XXX: I have no idea how "off" is supposed to be used */
 static int
 puffs_fuse_fill_dir(void *buf, const char *name,
-	const struct stat *stbuf, off_t off)
+	const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
 {
 	struct puffs_fuse_dirh *deh = buf;
 	ino_t dino;
@@ -325,39 +305,19 @@ puffs_fuse_fill_dir(void *buf, const char *name,
 	return fill_dirbuf(deh, name, dino, dtype);
 }
 
-static int
-puffs_fuse_dirfil(fuse_dirh_t h, const char *name, int type, ino_t ino)
-{
-	ino_t dino;
-	int dtype;
-
-	if ((dtype = type) == 0) {
-		dtype = DT_UNKNOWN;
-	}
-
-	dino = (ino) ? ino : fakeino++;
-
-	return fill_dirbuf(h, name, dino, dtype);
-}
-
-#define FUSE_ERR_UNLINK(fuse, file) if (fuse->op.unlink) fuse->op.unlink(file)
-#define FUSE_ERR_RMDIR(fuse, dir) if (fuse->op.rmdir) fuse->op.rmdir(dir)
-
 /* ARGSUSED1 */
 static int
 fuse_getattr(struct fuse *fuse, struct puffs_node *pn, const char *path,
 	struct vattr *va)
 {
+	struct refusenode	*rn = pn->pn_data;
+	struct fuse_file_info	*fi = rn->opencount > 0 ? &rn->file_info : NULL;
 	struct stat		 st;
 	int			ret;
 
-	if (fuse->op.getattr == NULL) {
-		return ENOSYS;
-	}
-
 	/* wrap up return code */
 	memset(&st, 0, sizeof(st));
-	ret = (*fuse->op.getattr)(path, &st);
+	ret = fuse_fs_getattr_v30(fuse->fs, path, &st, fi);
 
 	if (ret == 0) {
 		if (st.st_blksize == 0)
@@ -374,6 +334,7 @@ fuse_setattr(struct fuse *fuse, struct puffs_node *pn, const char *path,
 	const struct vattr *va)
 {
 	struct refusenode	*rn = pn->pn_data;
+	struct fuse_file_info	*fi = rn->opencount > 0 ? &rn->file_info : NULL;
 	mode_t			mode;
 	uid_t			uid;
 	gid_t			gid;
@@ -386,66 +347,31 @@ fuse_setattr(struct fuse *fuse, struct puffs_node *pn, const char *path,
 	gid = va->va_gid;
 
 	if (mode != (mode_t)PUFFS_VNOVAL) {
-		ret = 0;
-
-		if (fuse->op.chmod == NULL) {
-			error = -ENOSYS;
-		} else {
-			ret = fuse->op.chmod(path, mode);
-			if (ret)
-				error = ret;
-		}
+		ret = fuse_fs_chmod_v30(fuse->fs, path, mode, fi);
+		if (ret)
+			error = ret;
 	}
 	if (uid != (uid_t)PUFFS_VNOVAL || gid != (gid_t)PUFFS_VNOVAL) {
-		ret = 0;
-
-		if (fuse->op.chown == NULL) {
-			error = -ENOSYS;
-		} else {
-			ret = fuse->op.chown(path, uid, gid);
-			if (ret)
-				error = ret;
-		}
+		ret = fuse_fs_chown_v30(fuse->fs, path, uid, gid, fi);
+		if (ret)
+			error = ret;
 	}
 	if (va->va_atime.tv_sec != (time_t)PUFFS_VNOVAL
-	    || va->va_mtime.tv_sec != (long)PUFFS_VNOVAL) {
-		ret = 0;
+		|| va->va_mtime.tv_sec != (long)PUFFS_VNOVAL) {
 
-		if (fuse->op.utimens) {
-			struct timespec tv[2];
+		struct timespec	tv[2];
 
-			tv[0].tv_sec = va->va_atime.tv_sec;
-			tv[0].tv_nsec = va->va_atime.tv_nsec;
-			tv[1].tv_sec = va->va_mtime.tv_sec;
-			tv[1].tv_nsec = va->va_mtime.tv_nsec;
-
-			ret = fuse->op.utimens(path, tv);
-		} else if (fuse->op.utime) {
-			struct utimbuf timbuf;
-
-			timbuf.actime = va->va_atime.tv_sec;
-			timbuf.modtime = va->va_mtime.tv_sec;
-
-			ret = fuse->op.utime(path, &timbuf);
-		} else {
-			error = -ENOSYS;
-		}
+		tv[0].tv_sec	= va->va_atime.tv_sec;
+		tv[0].tv_nsec	= va->va_atime.tv_nsec;
+		tv[1].tv_sec	= va->va_mtime.tv_sec;
+		tv[1].tv_nsec	= va->va_mtime.tv_nsec;
 
+		ret = fuse_fs_utimens_v30(fuse->fs, path, tv, fi);
 		if (ret)
 			error = ret;
 	}
 	if (va->va_size != (u_quad_t)PUFFS_VNOVAL) {
-		ret = 0;
-
-		if (fuse->op.truncate) {
-			ret = fuse->op.truncate(path, (off_t)va->va_size);
-		} else if (fuse->op.ftruncate) {
-			ret = fuse->op.ftruncate(path, (off_t)va->va_size,
-			    &rn->file_info);
-		} else {
-			error = -ENOSYS;
-		}
-
+		ret = fuse_fs_truncate_v30(fuse->fs, path, (off_t)va->va_size, fi);
 		if (ret)
 			error = ret;
 	}
@@ -472,9 +398,9 @@ fuse_newnode(struct puffs_usermount *pu, const char *path,
 	pn = newrn(pu);
 	if (pn == NULL) {
 		if (va->va_type == VDIR) {
-			FUSE_ERR_RMDIR(fuse, path);
+			fuse_fs_rmdir(fuse->fs, path);
 		} else {
-			FUSE_ERR_UNLINK(fuse, path);
+			fuse_fs_unlink(fuse->fs, path);
 		}
 		return ENOMEM;
 	}
@@ -512,8 +438,7 @@ puffs_fuse_node_lookup(struct puffs_usermount *pu, void *opc,
 
 	set_fuse_context_uid_gid(pcn->pcn_cred);
 
-	ret = fuse->op.getattr(path, &st);
-
+	ret = fuse_fs_getattr_v30(fuse->fs, path, &st, NULL);
 	if (ret != 0) {
 		return -ret;
 	}
@@ -565,14 +490,11 @@ puffs_fuse_node_readlink(struct puffs_usermount *pu, void *opc,
 	int			ret;
 
 	fuse = puffs_getspecific(pu);
-	if (fuse->op.readlink == NULL) {
-		return ENOSYS;
-	}
 
 	set_fuse_context_uid_gid(cred);
 
 	/* wrap up return code */
-	ret = (*fuse->op.readlink)(path, linkname, *linklen);
+	ret = fuse_fs_readlink(fuse->fs, path, linkname, *linklen);
 
 	if (ret == 0) {
 		p = memchr(linkname, '\0', *linklen);
@@ -598,15 +520,12 @@ puffs_fuse_node_mknod(struct puffs_usermount *pu, void *opc,
 	int			ret;
 
 	fuse = puffs_getspecific(pu);
-	if (fuse->op.mknod == NULL) {
-		return ENOSYS;
-	}
 
 	set_fuse_context_uid_gid(pcn->pcn_cred);
 
 	/* wrap up return code */
 	mode = puffs_addvtype2mode(va->va_mode, va->va_type);
-	ret = (*fuse->op.mknod)(path, mode, va->va_rdev);
+	ret = fuse_fs_mknod(fuse->fs, path, mode, va->va_rdev);
 
 	if (ret == 0) {
 		ret = fuse_newnode(pu, path, va, NULL, pni, NULL);
@@ -631,12 +550,8 @@ puffs_fuse_node_mkdir(struct puffs_usermount *pu, void *opc,
 
 	set_fuse_context_uid_gid(pcn->pcn_cred);
 
-	if (fuse->op.mkdir == NULL) {
-		return ENOSYS;
-	}
-
 	/* wrap up return code */
-	ret = (*fuse->op.mkdir)(path, mode);
+	ret = fuse_fs_mkdir(fuse->fs, path, mode);
 
 	if (ret == 0) {
 		ret = fuse_newnode(pu, path, va, NULL, pni, NULL);
@@ -670,41 +585,37 @@ puffs_fuse_node_create(struct puffs_usermount *pu, void *opc,
 	set_fuse_context_uid_gid(pcn->pcn_cred);
 
 	memset(&fi, 0, sizeof(fi));
+	/* In puffs "create" and "open" are two separate operations
+	 * with atomicity achieved by locking the parent vnode. In
+	 * fuse, on the other hand, "create" is actually a
+	 * create-and-open-atomically and the open flags (O_RDWR,
+	 * O_APPEND, ...) are passed via fi.flags. So the only way to
+	 * emulate the fuse semantics is to open the file with dummy
+	 * flags and then immediately close it.
+	 *
+	 * You might think that we could simply use fuse->op.mknod all
+	 * the time but no, that's not possible because most file
+	 * systems nowadays expect op.mknod to be called only for
+	 * non-regular files and many don't even support it. */
 	created = 0;
-	if (fuse->op.create) {
-		/* In puffs "create" and "open" are two separate operations
-		 * with atomicity achieved by locking the parent vnode. In
-		 * fuse, on the other hand, "create" is actually a
-		 * create-and-open-atomically and the open flags (O_RDWR,
-		 * O_APPEND, ...) are passed via fi.flags. So the only way to
-		 * emulate the fuse semantics is to open the file with dummy
-		 * flags and then immediately close it.
-		 *
-		 * You might think that we could simply use fuse->op.mknod all
-		 * the time but no, that's not possible because most file
-		 * systems nowadays expect op.mknod to be called only for
-		 * non-regular files and many don't even support it. */
-		fi.flags = O_WRONLY | O_CREAT | O_EXCL;
-		ret = fuse->op.create(path, mode | S_IFREG, &fi);
-		if (ret == 0)
-			created = 1;
-
-	} else if (fuse->op.mknod) {
-		ret = fuse->op.mknod(path, mode | S_IFREG, 0);
-
-	} else {
-		ret = -ENOSYS;
+	fi.flags = O_WRONLY | O_CREAT | O_EXCL;
+	ret = fuse_fs_create(fuse->fs, path, mode | S_IFREG, &fi);
+	if (ret == 0) {
+		created = 1;
+	}
+	else if (ret == -ENOSYS) {
+		ret = fuse_fs_mknod(fuse->fs, path, mode | S_IFREG, 0);
 	}
 
 	if (ret == 0) {
 		ret = fuse_newnode(pu, path, va, &fi, pni, &pn);
 
 		/* sweet..  create also open the file */
-		if (created && fuse->op.release) {
+		if (created) {
 			struct refusenode *rn = pn->pn_data;
 			/* The return value of op.release is expected to be
 			 * discarded. */
-			(void)fuse->op.release(path, &rn->file_info);
+			(void)fuse_fs_release(fuse->fs, path, &rn->file_info);
 		}
 	}
 
@@ -726,12 +637,8 @@ puffs_fuse_node_remove(struct puffs_usermount *pu, void *opc, void *targ,
 
 	set_fuse_context_uid_gid(pcn->pcn_cred);
 
-	if (fuse->op.unlink == NULL) {
-		return ENOSYS;
-	}
-
 	/* wrap up return code */
-	ret = (*fuse->op.unlink)(path);
+	ret = fuse_fs_unlink(fuse->fs, path);
 
 	return -ret;
 }
@@ -751,12 +658,8 @@ puffs_fuse_node_rmdir(struct puffs_usermount *pu, void *opc, void *targ,
 
 	set_fuse_context_uid_gid(pcn->pcn_cred);
 
-	if (fuse->op.rmdir == NULL) {
-		return ENOSYS;
-	}
-
 	/* wrap up return code */
-	ret = (*fuse->op.rmdir)(path);
+	ret = fuse_fs_rmdir(fuse->fs, path);
 
 	return -ret;
 }
@@ -776,12 +679,8 @@ puffs_fuse_node_symlink(struct puffs_usermount *pu, void *opc,
 
 	set_fuse_context_uid_gid(pcn_src->pcn_cred);
 
-	if (fuse->op.symlink == NULL) {
-		return ENOSYS;
-	}
-
 	/* wrap up return code */
-	ret = fuse->op.symlink(link_target, path);
+	ret = fuse_fs_symlink(fuse->fs, link_target, path);
 
 	if (ret == 0) {
 		ret = fuse_newnode(pu, path, va, NULL, pni, NULL);
@@ -806,14 +705,7 @@ puffs_fuse_node_rename(struct puffs_usermount *pu, void *opc, void *src,
 
 	set_fuse_context_uid_gid(pcn_targ->pcn_cred);
 
-	if (fuse->op.rename == NULL) {
-		return ENOSYS;
-	}
-
-	ret = fuse->op.rename(path_src, path_dest);
-
-	if (ret == 0) {
-	}
+	ret = fuse_fs_rename_v30(fuse->fs, path_src, path_dest, 0);
 
 	return -ret;
 }
@@ -832,12 +724,8 @@ puffs_fuse_node_link(struct puffs_usermount *pu, void *opc, void *targ,
 
 	set_fuse_context_uid_gid(pcn->pcn_cred);
 
-	if (fuse->op.link == NULL) {
-		return ENOSYS;
-	}
-
 	/* wrap up return code */
-	ret = (*fuse->op.link)(PNPATH(pn), PCNPATH(pcn));
+	ret = fuse_fs_link(fuse->fs, PNPATH(pn), PCNPATH(pcn));
 
 	return -ret;
 }
@@ -886,6 +774,7 @@ puffs_fuse_node_open(struct puffs_usermount *pu, void *opc, int mode,
 	struct fuse_file_info	*fi = &rn->file_info;
 	struct fuse		*fuse;
 	const char		*path = PNPATH(pn);
+	int			ret;
 
 	fuse = puffs_getspecific(pu);
 
@@ -901,17 +790,17 @@ puffs_fuse_node_open(struct puffs_usermount *pu, void *opc, int mode,
 	fi->flags = (mode & ~(O_CREAT | O_EXCL | O_TRUNC)) - 1;
 
 	if (pn->pn_va.va_type == VDIR) {
-		if (fuse->op.opendir)
-			fuse->op.opendir(path, fi);
+		ret = fuse_fs_opendir(fuse->fs, path, fi);
 	} else {
-		if (fuse->op.open)
-			fuse->op.open(path, fi);
+		ret = fuse_fs_open(fuse->fs, path, fi);
 	}
 
-	rn->flags |= RN_OPEN;
-	rn->opencount++;
+	if (ret == 0) {
+		rn->flags |= RN_OPEN;
+		rn->opencount++;
+	}
 
-	return 0;
+	return -ret;
 }
 
 /* ARGSUSED2 */
@@ -934,11 +823,9 @@ puffs_fuse_node_close(struct puffs_usermount *pu, void *opc, int fflag,
 
 	if (rn->flags & RN_OPEN) {
 		if (pn->pn_va.va_type == VDIR) {
-			if (fuse->op.releasedir)
-				ret = fuse->op.releasedir(path, fi);
+			ret = fuse_fs_releasedir(fuse->fs, path, fi);
 		} else {
-			if (fuse->op.release)
-				ret = fuse->op.release(path, fi);
+			ret = fuse_fs_release(fuse->fs, path, fi);
 		}
 	}
 	rn->flags &= ~RN_OPEN;
@@ -962,9 +849,6 @@ puffs_fuse_node_read(struct puffs_usermount *pu, void *opc, uint8_t *buf,
 	int			ret;
 
 	fuse = puffs_getspecific(pu);
-	if (fuse->op.read == NULL) {
-		return ENOSYS;
-	}
 
 	set_fuse_context_uid_gid(pcr);
 
@@ -976,8 +860,8 @@ puffs_fuse_node_read(struct puffs_usermount *pu, void *opc, uint8_t *buf,
 	if (maxread == 0)
 		return 0;
 
-	ret = (*fuse->op.read)(path, (char *)buf, maxread, offset,
-	    &rn->file_info);
+	ret = fuse_fs_read(fuse->fs, path, (char *)buf, maxread, offset,
+			   &rn->file_info);
 
 	if (ret > 0) {
 		*resid -= (size_t)ret;
@@ -1001,17 +885,14 @@ puffs_fuse_node_write(struct puffs_usermount *pu, void *opc, uint8_t *buf,
 	int			ret;
 
 	fuse = puffs_getspecific(pu);
-	if (fuse->op.write == NULL) {
-		return ENOSYS;
-	}
 
 	set_fuse_context_uid_gid(pcr);
 
 	if (ioflag & PUFFS_IO_APPEND)
 		offset = (off_t)pn->pn_va.va_size;
 
-	ret = (*fuse->op.write)(path, (char *)buf, *resid, offset,
-	    &rn->file_info);
+	ret = fuse_fs_write(fuse->fs, path, (char *)buf, *resid, offset,
+			    &rn->file_info);
 
 	if (ret >= 0) {
 		if ((uint64_t)(offset + ret) > pn->pn_va.va_size)
@@ -1042,9 +923,6 @@ puffs_fuse_node_readdir(struct puffs_usermount *pu, void *opc,
 	int			ret;
 
 	fuse = puffs_getspecific(pu);
-	if (fuse->op.readdir == NULL && fuse->op.getdir == NULL) {
-		return ENOSYS;
-	}
 
 	set_fuse_context_uid_gid(pcr);
 
@@ -1062,11 +940,10 @@ puffs_fuse_node_readdir(struct puffs_usermount *pu, void *opc,
 		free(dirh->dbuf);
 		memset(dirh, 0, sizeof(struct puffs_fuse_dirh));
 
-		if (fuse->op.readdir)
-			ret = fuse->op.readdir(path, dirh, puffs_fuse_fill_dir,
-			    0, &rn->file_info);
-		else
-			ret = fuse->op.getdir(path, dirh, puffs_fuse_dirfil);
+		ret = fuse_fs_readdir_v30(
+			fuse->fs, path, dirh, puffs_fuse_fill_dir,
+			0, &rn->file_info, (enum fuse_readdir_flags)0);
+
 		if (ret)
 			return -ret;
 	}
@@ -1109,10 +986,7 @@ puffs_fuse_fs_unmount(struct puffs_usermount *pu, int flags)
 	struct fuse		*fuse;
 
 	fuse = puffs_getspecific(pu);
-	if (fuse->op.destroy == NULL) {
-		return 0;
-	}
-	(*fuse->op.destroy)(fuse);
+	fuse_fs_destroy(fuse->fs);
         return 0;
 }
 
@@ -1133,44 +1007,41 @@ puffs_fuse_fs_statvfs(struct puffs_usermount *pu, struct puffs_statvfs *svfsb)
 	int			ret;
 	struct statvfs		sb;
 
+	/* fuse_fs_statfs() is special: it returns 0 even if the
+	 * filesystem doesn't support statfs. So clear the struct
+	 * before calling it. */
+	memset(&sb, 0, sizeof(sb));
+
 	fuse = puffs_getspecific(pu);
-	if (fuse->op.statfs == NULL) {
-		if ((ret = statvfs(PNPATH(puffs_getroot(pu)), &sb)) == -1) {
-			return errno;
-		}
-	} else {
-		ret = fuse->op.statfs(PNPATH(puffs_getroot(pu)), &sb);
-	}
-	statvfs_to_puffs_statvfs(&sb, svfsb);
+	ret = fuse_fs_statfs(fuse->fs, PNPATH(puffs_getroot(pu)), &sb);
+
+	if (ret == 0)
+		statvfs_to_puffs_statvfs(&sb, svfsb);
 
         return -ret;
 }
 
-
 /* End of puffs_fuse operations */
-/* ARGSUSED3 */
-int
-fuse_main_real(int argc, char **argv, const struct fuse_operations *ops,
-	size_t size, void *user_data)
+
+struct fuse *
+__fuse_setup(int argc, char* argv[],
+	     const void* op, int op_version, void* user_data,
+	     struct fuse_cmdline_opts* opts)
 {
 	struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
-	struct fuse_cmdline_opts opts;
-	struct fuse *fuse;
-	int rv;
+	struct fuse *fuse = NULL;
 
 	/* parse low-level options */
-	if (fuse_parse_cmdline(&args, &opts) == -1) {
-		return 1;
-	}
+	if (fuse_parse_cmdline_v30(&args, opts) != 0)
+		return NULL;
 
-	if (opts.show_version) {
+	if (opts->show_version) {
 		fuse_lowlevel_version();
-		rv = 0;
 		goto free_args;
 	}
 
-	if (opts.show_help) {
-		switch (opts.show_help) {
+	if (opts->show_help) {
+		switch (opts->show_help) {
 		case REFUSE_SHOW_HELP_FULL:
 			if (args.argv[0] != NULL && args.argv[0][0] != '\0') {
 				/* argv[0] being empty means that the application doesn't
@@ -1183,53 +1054,77 @@ fuse_main_real(int argc, char **argv, const struct fuse_operations *ops,
 			break;
 		}
 		fuse_cmdline_help();
-		rv = 0;
 		goto free_args;
 	}
 
-	if (opts.mountpoint == NULL) {
+	if (opts->mountpoint == NULL) {
 		fprintf(stderr, "fuse: no mountpoint specified\n");
-		rv = 1;
 		goto free_args;
 	}
 
-	if (opts.debug) {
-		if (fuse_opt_add_arg(&args, "-odebug") == -1) {
-			rv = 1;
+	if (opts->debug) {
+		if (fuse_opt_add_arg(&args, "-odebug") != 0)
 			goto free_args;
-		}
 	}
 
-	fuse = fuse_new(&args, ops, size, user_data);
-	if (fuse == NULL) {
-		rv = 1;
+	fuse = __fuse_new(&args, op, op_version, user_data);
+	if (fuse == NULL)
 		goto free_args;
-	}
 
-	if (!opts.foreground) {
-		if (fuse_daemonize(fuse) == -1) {
-			rv = 1;
-			goto destroy;
-		}
-	}
+	if (fuse_daemonize(opts->foreground) != 0)
+		goto destroy;
+
+	if (fuse_mount_v30(fuse, opts->mountpoint) != 0)
+		goto destroy;
 
-	if (fuse_mount(fuse, opts.mountpoint) == -1) {
-		rv = 1;
+	if (__fuse_set_signal_handlers(fuse) != 0) {
+		warn("%s: Failed to set signal handlers", __func__);
 		goto destroy;
 	}
 
-	rv = fuse_loop(fuse);
+	goto done;
 
-	fuse_unmount(fuse);
 destroy:
-	fuse_destroy(fuse);
+	fuse_destroy_v30(fuse);
+	fuse = NULL;
 free_args:
-	free(opts.mountpoint);
+	free(opts->mountpoint);
+done:
 	fuse_opt_free_args(&args);
+	return fuse;
+}
+
+void
+__fuse_teardown(struct fuse* fuse)
+{
+	if (__fuse_remove_signal_handlers(fuse) != 0)
+		warn("%s: Failed to restore signal handlers", __func__);
+
+	fuse_unmount_v30(fuse);
+}
+
+/* ARGSUSED3 */
+int
+__fuse_main(int argc, char **argv, const void *op,
+	int op_version, void *user_data)
+{
+	struct fuse_cmdline_opts opts;
+	struct fuse *fuse;
+	int rv;
+
+	fuse = __fuse_setup(argc, argv, op, op_version, user_data, &opts);
+	if (fuse == NULL)
+		return -1;
+
+	rv = fuse_loop(fuse);
+
+	__fuse_teardown(fuse);
+
+	free(opts.mountpoint);
 	return rv;
 }
 
-int fuse_mount(struct fuse *fuse, const char *mountpoint)
+int __fuse_mount(struct fuse *fuse, const char *mountpoint)
 {
 	struct puffs_pathobj	*po_root;
 	struct puffs_node	*pn_root;
@@ -1286,10 +1181,8 @@ int fuse_daemonize(int foreground)
 	return 0;
 }
 
-/* ARGSUSED1 */
 struct fuse *
-fuse_new(struct fuse_args *args,
-	const struct fuse_operations *ops, size_t size, void *userdata)
+__fuse_new(struct fuse_args *args, const void *op, int op_version, void* user_data)
 {
 	struct refuse_config	config;
 	struct puffs_usermount	*pu;
@@ -1299,6 +1192,7 @@ fuse_new(struct fuse_args *args,
 	uint32_t		puffs_flags;
 
 	/* parse refuse options */
+	memset(&config, 0, sizeof(config));
 	if (fuse_opt_parse(args, &config, refuse_opts, NULL) == -1)
 		return NULL;
 
@@ -1306,9 +1200,6 @@ fuse_new(struct fuse_args *args,
 		err(EXIT_FAILURE, "fuse_new");
 	}
 
-	/* copy fuse ops to their own structure */
-	(void) memcpy(&fuse->op, ops, sizeof(fuse->op));
-
 	/* grab the pthread context key */
 	if (!create_context_key()) {
 		free(config.fsname);
@@ -1316,12 +1207,15 @@ fuse_new(struct fuse_args *args,
 		return NULL;
 	}
 
+	/* Create the base filesystem layer. */
+	fuse->fs = __fuse_fs_new(op, op_version, user_data);
+
 	fusectx = fuse_get_context();
 	fusectx->fuse = fuse;
 	fusectx->uid = 0;
 	fusectx->gid = 0;
 	fusectx->pid = 0;
-	fusectx->private_data = userdata;
+	fusectx->private_data = user_data;
 
 	/* initialise the puffs operations structure */
         PUFFSOP_INIT(pops);
@@ -1373,23 +1267,30 @@ fuse_new(struct fuse_args *args,
 int
 fuse_loop(struct fuse *fuse)
 {
-	if (fuse->op.init != NULL) {
-		struct fuse_context *fusectx = fuse_get_context();
+	struct fuse_conn_info conn;
+	struct fuse_config cfg;
 
-		/* XXX: prototype incompatible with FUSE: a secondary argument
-		 * of struct fuse_config* needs to be passed.
-		 *
-		 * XXX: Our struct fuse_conn_info is not fully compatible with
-		 * the FUSE one.
-		 */
-		fusectx->private_data = fuse->op.init(NULL);
-	}
+	/* struct fuse_conn_info is a part of the FUSE API so we must
+	 * expose it to users, but we currently don't use them at
+	 * all. The same goes for struct fuse_config. */
+	memset(&conn, 0, sizeof(conn));
+	memset(&cfg, 0, sizeof(cfg));
+
+	fuse_fs_init_v30(fuse->fs, &conn, &cfg);
 
 	return puffs_mainloop(fuse->pu);
 }
 
+int
+__fuse_loop_mt(struct fuse *fuse,
+	       struct fuse_loop_config *config __attribute__((__unused__)))
+{
+	/* TODO: Implement a proper multi-threaded loop. */
+	return fuse_loop(fuse);
+}
+
 void
-fuse_destroy(struct fuse *fuse)
+__fuse_destroy(struct fuse *fuse)
 {
 
 	/*
@@ -1416,9 +1317,8 @@ fuse_exit(struct fuse *fuse)
  * XXX: obviously not the most perfect of functions, but needs some
  * puffs tweaking for a better tomorrow
  */
-/*ARGSUSED*/
 void
-fuse_unmount(struct fuse* fuse)
+__fuse_unmount(struct fuse *fuse)
 {
 	/* XXX: puffs_exit() is WRONG */
 	if (fuse->dead == 0)
@@ -1426,14 +1326,6 @@ fuse_unmount(struct fuse* fuse)
 	fuse->dead = 1;
 }
 
-/*ARGSUSED*/
-void
-fuse_unmount_compat22(const char *mp)
-{
-
-	return;
-}
-
 void
 fuse_lib_help(struct fuse_args *args __attribute__((__unused__)))
 {
diff --git a/lib/librefuse/refuse/Makefile.inc b/lib/librefuse/refuse/Makefile.inc
index a2f1cc2bc..3dbf783fc 100644
--- a/lib/librefuse/refuse/Makefile.inc
+++ b/lib/librefuse/refuse/Makefile.inc
@@ -3,11 +3,35 @@
 .PATH: ${.CURDIR}/refuse
 
 SRCS+=	buf.c
+SRCS+=	chan.c
+SRCS+=	fs.c
 SRCS+=	legacy.c
 SRCS+=	poll.c
 SRCS+=	session.c
+SRCS+=	v11.c
+SRCS+=	v21.c
+SRCS+=	v22.c
+SRCS+=	v25.c
+SRCS+=	v26.c
+SRCS+=	v30.c
+SRCS+=	v32.c
 
 INCS+=	refuse/buf.h
+INCS+=	refuse/chan.h
+INCS+=	refuse/fs.h
 INCS+=	refuse/legacy.h
 INCS+=	refuse/poll.h
 INCS+=	refuse/session.h
+INCS+=	refuse/v11.h
+INCS+=	refuse/v21.h
+INCS+=	refuse/v22.h
+INCS+=	refuse/v23.h
+INCS+=	refuse/v25.h
+INCS+=	refuse/v26.h
+INCS+=	refuse/v28.h
+INCS+=	refuse/v29.h
+INCS+=	refuse/v30.h
+INCS+=	refuse/v32.h
+INCS+=	refuse/v34.h
+INCS+=	refuse/v35.h
+INCS+=	refuse/v38.h
diff --git a/lib/librefuse/refuse/chan.c b/lib/librefuse/refuse/chan.c
new file mode 100644
index 000000000..f6e0e78ea
--- /dev/null
+++ b/lib/librefuse/refuse/chan.c
@@ -0,0 +1,248 @@
+/* $NetBSD$ */
+
+/*
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior written
+ *    permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#if !defined(lint)
+__RCSID("$NetBSD$");
+#endif /* !lint */
+
+#include <assert.h>
+#include <err.h>
+#include <fuse_internal.h>
+#if defined(MULTITHREADED_REFUSE)
+#  include <pthread.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+
+/* The communication channel API is a part of the public interface of
+ * FUSE. However, it is actually an implementation detail and we can't
+ * really emulate its semantics. We only use it for implementing
+ * pre-3.0 fuse_mount(), i.e. the mount-before-new thingy.
+ */
+
+struct fuse_chan {
+    char* mountpoint;
+    struct fuse_args* args;
+    struct fuse* fuse;
+    bool is_to_be_destroyed;
+};
+
+struct refuse_chan_storage {
+    size_t n_alloc;
+    struct fuse_chan** vec;
+};
+
+#if defined(MULTITHREADED_REFUSE)
+static pthread_mutex_t storage_mutex = PTHREAD_MUTEX_INITIALIZER;
+#endif
+static struct refuse_chan_storage storage;
+
+
+struct fuse_chan* fuse_chan_new(const char* mountpoint, const struct fuse_args* args) {
+    struct fuse_chan* chan;
+
+    chan = calloc(1, sizeof(*chan));
+    if (!chan) {
+        warn("%s", __func__);
+        return NULL;
+    }
+
+    chan->mountpoint = strdup(mountpoint);
+    if (!chan->mountpoint) {
+        warn("%s", __func__);
+        free(chan);
+        return NULL;
+    }
+
+    chan->args = fuse_opt_deep_copy_args(args->argc, args->argv);
+    if (!chan->args) {
+        warn("%s", __func__);
+        free(chan->mountpoint);
+        free(chan);
+        return NULL;
+    }
+
+    return chan;
+}
+
+void
+fuse_chan_destroy(struct fuse_chan* chan) {
+    free(chan->mountpoint);
+    fuse_opt_free_args(chan->args);
+    free(chan->args);
+    free(chan);
+}
+
+int
+fuse_chan_stash(struct fuse_chan* chan) {
+    int idx;
+#if defined(MULTITHREADED_REFUSE)
+    int rv;
+
+    rv = pthread_mutex_lock(&storage_mutex);
+    assert(rv == 0);
+#endif
+
+    /* Find the first empty slot in the storage. */
+    for (idx = 0; idx < (int)storage.n_alloc; idx++) {
+        if (storage.vec[idx] == NULL) {
+            storage.vec[idx] = chan;
+            goto done;
+        }
+    }
+
+    /* Allocate more space */
+    storage.n_alloc = (storage.n_alloc + 8) * 2;
+    storage.vec     = realloc(storage.vec, sizeof(struct fuse_chan*) * storage.n_alloc);
+    if (!storage.vec) {
+        warn("%s", __func__);
+        idx = -1;
+        goto done;
+    }
+
+    storage.vec[idx] = chan;
+    memset(&storage.vec[idx+1], 0, sizeof(struct fuse_chan*) * (storage.n_alloc - (size_t)idx - 1));
+
+  done:
+#if defined(MULTITHREADED_REFUSE)
+    rv = pthread_mutex_unlock(&storage_mutex);
+    assert(rv == 0);
+#endif
+    return idx;
+}
+
+/* Acquire a pointer to a stashed channel with a given index. */
+struct fuse_chan* fuse_chan_peek(int idx) {
+    struct fuse_chan* chan = NULL;
+#if defined(MULTITHREADED_REFUSE)
+    int rv;
+
+    rv = pthread_mutex_lock(&storage_mutex);
+    assert(rv == 0);
+#endif
+
+    if (idx >= 0 && idx < (int)storage.n_alloc) {
+        chan = storage.vec[idx];
+    }
+
+#if defined(MULTITHREADED_REFUSE)
+    rv = pthread_mutex_unlock(&storage_mutex);
+    assert(rv == 0);
+#endif
+    return chan;
+}
+
+/* Like fuse_chan_peek() but also removes the channel from the
+ * storage. */
+struct fuse_chan* fuse_chan_take(int idx) {
+    struct fuse_chan* chan = NULL;
+#if defined(MULTITHREADED_REFUSE)
+    int rv;
+
+    rv = pthread_mutex_lock(&storage_mutex);
+    assert(rv == 0);
+#endif
+
+    if (idx >= 0 && idx < (int)storage.n_alloc) {
+        chan = storage.vec[idx];
+        storage.vec[idx] = NULL;
+    }
+
+#if defined(MULTITHREADED_REFUSE)
+    rv = pthread_mutex_unlock(&storage_mutex);
+    assert(rv == 0);
+#endif
+    return chan;
+}
+
+/* Find the first stashed channel satisfying a given predicate in the
+ * storage, or NULL if no channels satisfy it. */
+struct fuse_chan*
+fuse_chan_find(bool (*pred)(struct fuse_chan*, void*),
+               int* found_idx, void* priv) {
+    int idx;
+    struct fuse_chan* chan = NULL;
+#if defined(MULTITHREADED_REFUSE)
+    int rv;
+
+    rv = pthread_mutex_lock(&storage_mutex);
+    assert(rv == 0);
+#endif
+
+    for (idx = 0; idx < (int)storage.n_alloc; idx++) {
+        if (storage.vec[idx] != NULL) {
+            if (pred(storage.vec[idx], priv)) {
+                chan = storage.vec[idx];
+                if (found_idx)
+                    *found_idx = idx;
+                goto done;
+            }
+        }
+    }
+
+  done:
+#if defined(MULTITHREADED_REFUSE)
+    rv = pthread_mutex_unlock(&storage_mutex);
+    assert(rv == 0);
+#endif
+    return chan;
+}
+
+void
+fuse_chan_set_fuse(struct fuse_chan* chan, struct fuse* fuse) {
+    chan->fuse = fuse;
+}
+
+void
+fuse_chan_set_to_be_destroyed(struct fuse_chan* chan, bool is_to_be_destroyed) {
+    chan->is_to_be_destroyed = is_to_be_destroyed;
+}
+
+const char*
+fuse_chan_mountpoint(const struct fuse_chan* chan) {
+    return chan->mountpoint;
+}
+
+struct fuse_args*
+fuse_chan_args(struct fuse_chan* chan) {
+    return chan->args;
+}
+
+struct fuse*
+fuse_chan_fuse(struct fuse_chan* chan) {
+    return chan->fuse;
+}
+
+bool
+fuse_chan_is_to_be_destroyed(const struct fuse_chan* chan) {
+    return chan->is_to_be_destroyed;
+}
diff --git a/lib/librefuse/fuse_internal.h b/lib/librefuse/refuse/chan.h
similarity index 54%
copy from lib/librefuse/fuse_internal.h
copy to lib/librefuse/refuse/chan.h
index f658dccdf..f6eebb646 100644
--- a/lib/librefuse/fuse_internal.h
+++ b/lib/librefuse/refuse/chan.h
@@ -1,4 +1,4 @@
-/* $NetBSD: fuse_internal.h,v 1.1 2021/12/04 06:42:39 pho Exp $ */
+/* $NetBSD$ */
 
 /*
  * Copyright (c) 2021 The NetBSD Foundation, Inc.
@@ -28,31 +28,49 @@
  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
-#if !defined(FUSE_INTERNAL_H)
-#define FUSE_INTERNAL_H
+#if !defined(_FUSE_CHAN_H_)
+#define _FUSE_CHAN_H_
 
-/* We emit a compiler warning for anyone including <fuse.h> without
- * defining FUSE_USE_VERSION. Exempt ourselves here, or we'll be
- * warned too. */
-#define _REFUSE_IMPLEMENTATION_
+/*
+ * Fuse communication channel API, appeared on FUSE 2.4.
+ */
 
-#include <fuse.h>
-#include <fuse_lowlevel.h>
+#if !defined(FUSE_H_)
+#  error Do not include this header directly. Include <fuse.h> instead.
+#endif
+
+#include <stdbool.h>
+#include <stddef.h>
 #include <sys/cdefs.h>
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
-enum refuse_show_help_variant {
-	REFUSE_SHOW_HELP_FULL		= 1,
-	REFUSE_SHOW_HELP_NO_HEADER	= 2,
-};
+/* An opaque object representing a communication channel. */
+struct fuse_chan;
 
-/* Internal functions, hidden from users */
+/* Implementation details. User code should never call these functions
+ * directly. */
+struct fuse;
+struct fuse_args;
 __BEGIN_HIDDEN_DECLS
-int __fuse_set_signal_handlers(struct fuse* fuse);
-int __fuse_remove_signal_handlers(struct fuse* fuse);
+struct fuse_chan* fuse_chan_new(const char* mountpoint, const struct fuse_args* args);
+void              fuse_chan_destroy(struct fuse_chan* chan);
+
+int               fuse_chan_stash(struct fuse_chan* chan);
+struct fuse_chan* fuse_chan_peek(int idx);
+struct fuse_chan* fuse_chan_take(int idx);
+struct fuse_chan* fuse_chan_find(bool (*pred)(struct fuse_chan* chan, void* priv),
+                                 int* found_idx, void* priv);
+
+void              fuse_chan_set_fuse(struct fuse_chan* chan, struct fuse* fuse);
+void              fuse_chan_set_to_be_destroyed(struct fuse_chan* chan, bool is_to_be_destroyed);
+
+const char*       fuse_chan_mountpoint(const struct fuse_chan* chan);
+struct fuse_args* fuse_chan_args(struct fuse_chan* chan);
+struct fuse*      fuse_chan_fuse(struct fuse_chan* chan);
+bool              fuse_chan_is_to_be_destroyed(const struct fuse_chan* chan);
 __END_HIDDEN_DECLS
 
 #ifdef __cplusplus
diff --git a/lib/librefuse/refuse/fs.c b/lib/librefuse/refuse/fs.c
new file mode 100644
index 000000000..4a312392f
--- /dev/null
+++ b/lib/librefuse/refuse/fs.c
@@ -0,0 +1,1802 @@
+/* $NetBSD$ */
+
+/*
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior written
+ *    permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#if !defined(lint)
+__RCSID("$NetBSD$");
+#endif /* !lint */
+
+/*
+ * Filesystem Stacking API, appeared on FUSE 2.7.
+ *
+ * So many callback functions in struct fuse_operations have different
+ * prototypes between versions. We use the stacking API to abstract
+ * that away to implement puffs operations in a manageable way.
+ */
+
+#include <err.h>
+#include <fuse_internal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/dirent.h>
+#include <sys/errno.h>
+
+struct fuse_fs {
+    void* op;
+    int   op_version;
+    void* user_data;
+};
+
+#define UNKNOWN_VERSION(op_version)                                     \
+    errc(EXIT_FAILURE, ENOSYS, "%s: unknown fuse_operations version: %d", \
+         __func__, op_version)
+
+static void*
+clone_op(const void* op, int op_version) {
+    void* cloned;
+
+    switch (op_version) {
+#define CLONE_OP(VER)                                                   \
+    case VER:                                                           \
+        cloned = malloc(sizeof(struct __CONCAT(fuse_operations_v,VER)));      \
+        if (!cloned)                                                    \
+            return NULL;                                                \
+        memcpy(cloned, op, sizeof(struct __CONCAT(fuse_operations_v,VER)));   \
+        return cloned
+
+        CLONE_OP(11);
+        CLONE_OP(21);
+        CLONE_OP(22);
+        CLONE_OP(23);
+        CLONE_OP(25);
+        CLONE_OP(26);
+        CLONE_OP(28);
+        CLONE_OP(29);
+        CLONE_OP(30);
+        CLONE_OP(34);
+        CLONE_OP(35);
+        CLONE_OP(38);
+#undef CLONE_OP
+    default:
+        UNKNOWN_VERSION(op_version);
+    }
+}
+
+struct fuse_fs*
+__fuse_fs_new(const void* op, int op_version, void* user_data) {
+    struct fuse_fs* fs;
+
+    fs = malloc(sizeof(struct fuse_fs));
+    if (!fs)
+        err(EXIT_FAILURE, __func__);
+
+    /* Callers aren't obliged to keep "op" valid during the lifetime
+     * of struct fuse_fs*. We must clone it now, even though it's
+     * non-trivial. */
+    fs->op = clone_op(op, op_version);
+    if (!fs->op)
+        err(EXIT_FAILURE, __func__);
+
+    fs->op_version = op_version;
+    fs->user_data  = user_data;
+
+    return fs;
+}
+
+/* Clobber the context private_data with that of this filesystem
+ * layer. This function needs to be called before invoking any of
+ * operation callbacks. */
+static void
+clobber_context_user_data(struct fuse_fs* fs) {
+    fuse_get_context()->private_data = fs->user_data;
+}
+
+/* Ugly... These are like hand-written vtables... */
+int
+fuse_fs_getattr_v27(struct fuse_fs *fs, const char *path, struct stat *buf) {
+    return fuse_fs_getattr_v30(fs, path, buf, NULL);
+}
+
+int
+fuse_fs_getattr_v30(struct fuse_fs* fs, const char* path,
+                    struct stat* buf, struct fuse_file_info* fi) {
+    clobber_context_user_data(fs);
+    switch (fs->op_version) {
+#define CALL_OLD_GETATTR(VER)                                           \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->getattr) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->getattr(path, buf); \
+        else                                                            \
+            return -ENOSYS
+        CALL_OLD_GETATTR(11);
+        CALL_OLD_GETATTR(21);
+        CALL_OLD_GETATTR(22);
+        CALL_OLD_GETATTR(23);
+        CALL_OLD_GETATTR(25);
+        CALL_OLD_GETATTR(26);
+        CALL_OLD_GETATTR(28);
+        CALL_OLD_GETATTR(29);
+#undef CALL_OLD_GETATTR
+
+#define CALL_GETATTR(VER)                                               \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->getattr) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->getattr(path, buf, fi); \
+        else                                                            \
+            return -ENOSYS
+        CALL_GETATTR(30);
+        CALL_GETATTR(34);
+        CALL_GETATTR(38);
+#undef CALL_GETATTR
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+int
+fuse_fs_fgetattr(struct fuse_fs* fs, const char* path, struct stat* buf,
+                 struct fuse_file_info* fi) {
+    clobber_context_user_data(fs);
+    /* fgetattr() was introduced on FUSE 2.5 then disappeared on FUSE
+     * 3.0. Fall back to getattr() if it's missing. */
+    switch (fs->op_version) {
+    case 11:
+    case 21:
+    case 22:
+    case 23:
+        return fuse_fs_getattr_v30(fs, path, buf, fi);
+
+#define CALL_FGETATTR_OR_OLD_GETATTR(VER)       \
+    case VER:                                   \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->fgetattr) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->fgetattr(path, buf, fi); \
+        else if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->getattr) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->getattr(path, buf); \
+        else                                                            \
+            return -ENOSYS
+        CALL_FGETATTR_OR_OLD_GETATTR(25);
+        CALL_FGETATTR_OR_OLD_GETATTR(26);
+        CALL_FGETATTR_OR_OLD_GETATTR(28);
+        CALL_FGETATTR_OR_OLD_GETATTR(29);
+#undef CALL_FGETATTR_OR_OLD_GETATTR
+
+    case 30:
+    case 34:
+    case 38:
+        return fuse_fs_getattr_v30(fs, path, buf, fi);
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+int
+fuse_fs_rename_v27(struct fuse_fs* fs, const char* oldpath, const char* newpath) {
+    return fuse_fs_rename_v30(fs, oldpath, newpath, 0);
+}
+
+int
+fuse_fs_rename_v30(struct fuse_fs* fs, const char* oldpath,
+                   const char* newpath, unsigned int flags) {
+    clobber_context_user_data(fs);
+    switch (fs->op_version) {
+#define CALL_OLD_RENAME(VER)                                            \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->rename) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->rename(oldpath, newpath); \
+        else                                                            \
+            return -ENOSYS
+        CALL_OLD_RENAME(11);
+        CALL_OLD_RENAME(21);
+        CALL_OLD_RENAME(22);
+        CALL_OLD_RENAME(23);
+        CALL_OLD_RENAME(25);
+        CALL_OLD_RENAME(26);
+        CALL_OLD_RENAME(28);
+        CALL_OLD_RENAME(29);
+#undef CALL_OLD_RENAME
+
+#define CALL_RENAME(VER)                                                \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->rename) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->rename(oldpath, newpath, flags); \
+        else                                                            \
+            return -ENOSYS
+        CALL_RENAME(30);
+        CALL_RENAME(34);
+        CALL_RENAME(38);
+#undef CALL_RENAME
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+int
+fuse_fs_unlink(struct fuse_fs* fs, const char* path) {
+    clobber_context_user_data(fs);
+    switch (fs->op_version) {
+#define CALL_UNLINK(VER)                                                \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->unlink) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->unlink(path); \
+        else                                                            \
+            return -ENOSYS
+        CALL_UNLINK(11);
+        CALL_UNLINK(21);
+        CALL_UNLINK(22);
+        CALL_UNLINK(23);
+        CALL_UNLINK(25);
+        CALL_UNLINK(26);
+        CALL_UNLINK(28);
+        CALL_UNLINK(29);
+        CALL_UNLINK(30);
+        CALL_UNLINK(34);
+        CALL_UNLINK(38);
+#undef CALL_UNLINK
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+int
+fuse_fs_rmdir(struct fuse_fs* fs, const char* path) {
+    clobber_context_user_data(fs);
+    switch (fs->op_version) {
+#define CALL_RMDIR(VER)                                                 \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->rmdir) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->rmdir(path); \
+        else                                                            \
+            return -ENOSYS
+        CALL_RMDIR(11);
+        CALL_RMDIR(21);
+        CALL_RMDIR(22);
+        CALL_RMDIR(23);
+        CALL_RMDIR(25);
+        CALL_RMDIR(26);
+        CALL_RMDIR(28);
+        CALL_RMDIR(29);
+        CALL_RMDIR(30);
+        CALL_RMDIR(34);
+        CALL_RMDIR(38);
+#undef CALL_RMDIR
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+int
+fuse_fs_symlink(struct fuse_fs* fs, const char* linkname, const char* path) {
+    clobber_context_user_data(fs);
+    switch (fs->op_version) {
+#define CALL_SYMLINK(VER)                                               \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->symlink) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->symlink(linkname, path); \
+        else                                                            \
+            return -ENOSYS
+        CALL_SYMLINK(11);
+        CALL_SYMLINK(21);
+        CALL_SYMLINK(22);
+        CALL_SYMLINK(23);
+        CALL_SYMLINK(25);
+        CALL_SYMLINK(26);
+        CALL_SYMLINK(28);
+        CALL_SYMLINK(29);
+        CALL_SYMLINK(30);
+        CALL_SYMLINK(34);
+        CALL_SYMLINK(38);
+#undef CALL_SYMLINK
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+int
+fuse_fs_link(struct fuse_fs* fs, const char* oldpath, const char* newpath) {
+    clobber_context_user_data(fs);
+    switch (fs->op_version) {
+#define CALL_LINK(VER)                                               \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->link) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->link(oldpath, newpath); \
+        else                                                            \
+            return -ENOSYS
+        CALL_LINK(11);
+        CALL_LINK(21);
+        CALL_LINK(22);
+        CALL_LINK(23);
+        CALL_LINK(25);
+        CALL_LINK(26);
+        CALL_LINK(28);
+        CALL_LINK(29);
+        CALL_LINK(30);
+        CALL_LINK(34);
+        CALL_LINK(38);
+#undef CALL_LINK
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+int
+fuse_fs_release(struct fuse_fs* fs, const char* path, struct fuse_file_info* fi) {
+    clobber_context_user_data(fs);
+    switch (fs->op_version) {
+#define CALL_OLD_RELEASE(VER)                                           \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->release) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->release(path, fi->flags); \
+        else                                                            \
+            return 0 /* Special case */
+        CALL_OLD_RELEASE(11);
+        CALL_OLD_RELEASE(21);
+#undef CALL_OLD_RELEASE
+
+#define CALL_RELEASE(VER)                                               \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->release) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->release(path, fi); \
+        else                                                            \
+            return 0 /* Special case */
+        CALL_RELEASE(22);
+        CALL_RELEASE(23);
+        CALL_RELEASE(25);
+        CALL_RELEASE(26);
+        CALL_RELEASE(28);
+        CALL_RELEASE(29);
+        CALL_RELEASE(30);
+        CALL_RELEASE(34);
+        CALL_RELEASE(38);
+#undef CALL_RELEASE
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+int
+fuse_fs_open(struct fuse_fs* fs, const char* path, struct fuse_file_info* fi) {
+    clobber_context_user_data(fs);
+    switch (fs->op_version) {
+#define CALL_OLD_OPEN(VER)                                              \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->open) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->open(path, fi->flags); \
+        else                                                            \
+            return 0 /* Special case */
+        CALL_OLD_OPEN(11);
+        CALL_OLD_OPEN(21);
+#undef CALL_OLD_OPEN
+
+#define CALL_OPEN(VER)                                                  \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->open) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->open(path, fi); \
+        else                                                            \
+            return 0 /* Special case */
+        CALL_OPEN(22);
+        CALL_OPEN(23);
+        CALL_OPEN(25);
+        CALL_OPEN(26);
+        CALL_OPEN(28);
+        CALL_OPEN(29);
+        CALL_OPEN(30);
+        CALL_OPEN(34);
+        CALL_OPEN(38);
+#undef CALL_OPEN
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+int
+fuse_fs_read(struct fuse_fs* fs, const char* path, char* buf,
+             size_t size, off_t off, struct fuse_file_info* fi) {
+    clobber_context_user_data(fs);
+    switch (fs->op_version) {
+#define CALL_OLD_READ(VER)                                              \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->read) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->read(path, buf, size, off); \
+        else                                                            \
+            return -ENOSYS
+        CALL_OLD_READ(11);
+        CALL_OLD_READ(21);
+#undef CALL_OLD_READ
+
+#define CALL_READ(VER)                                                  \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->read) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->read(path, buf, size, off, fi); \
+        else                                                            \
+            return -ENOSYS
+        CALL_READ(22);
+        CALL_READ(23);
+        CALL_READ(25);
+        CALL_READ(26);
+        CALL_READ(28);
+        CALL_READ(29);
+        CALL_READ(30);
+        CALL_READ(34);
+        CALL_READ(38);
+#undef CALL_READ
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+int
+fuse_fs_read_buf(struct fuse_fs* fs, const char* path,
+                 struct fuse_bufvec** bufp, size_t size, off_t off,
+                 struct fuse_file_info* fi) {
+    clobber_context_user_data(fs);
+    switch (fs->op_version) {
+        /* FUSE < 2.9 didn't have read_buf(). */
+    case 11:
+    case 21:
+    case 22:
+    case 23:
+    case 25:
+    case 26:
+    case 28:
+        return -ENOSYS;
+#define CALL_READ_BUF(VER)                                              \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->read_buf) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->read_buf(path, bufp, size, off, fi); \
+        else                                                            \
+            return -ENOSYS
+        CALL_READ_BUF(29);
+        CALL_READ_BUF(30);
+        CALL_READ_BUF(34);
+        CALL_READ_BUF(38);
+#undef CALL_READ_BUF
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+int
+fuse_fs_write(struct fuse_fs* fs, const char* path, const char* buf,
+              size_t size, off_t off, struct fuse_file_info* fi) {
+    clobber_context_user_data(fs);
+    switch (fs->op_version) {
+#define CALL_OLD_WRITE(VER)                                             \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->write) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->write(path, buf, size, off); \
+        else                                                            \
+            return -ENOSYS
+        CALL_OLD_WRITE(11);
+        CALL_OLD_WRITE(21);
+#undef CALL_OLD_WRITE
+
+#define CALL_WRITE(VER)                                                 \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->write) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->write(path, buf, size, off, fi); \
+        else                                                            \
+            return -ENOSYS
+        CALL_WRITE(22);
+        CALL_WRITE(23);
+        CALL_WRITE(25);
+        CALL_WRITE(26);
+        CALL_WRITE(28);
+        CALL_WRITE(29);
+        CALL_WRITE(30);
+        CALL_WRITE(34);
+        CALL_WRITE(38);
+#undef CALL_WRITE
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+int
+fuse_fs_write_buf(struct fuse_fs* fs, const char* path,
+                  struct fuse_bufvec* bufp, off_t off,
+                  struct fuse_file_info* fi) {
+    clobber_context_user_data(fs);
+    switch (fs->op_version) {
+        /* FUSE < 2.9 didn't have write_buf(). */
+    case 11:
+    case 21:
+    case 22:
+    case 23:
+    case 25:
+    case 26:
+    case 28:
+        return -ENOSYS;
+#define CALL_WRITE_BUF(VER)                                             \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->write_buf) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->write_buf(path, bufp, off, fi); \
+        else                                                            \
+            return -ENOSYS
+        CALL_WRITE_BUF(29);
+        CALL_WRITE_BUF(30);
+        CALL_WRITE_BUF(34);
+        CALL_WRITE_BUF(38);
+#undef CALL_WRITE_BUF
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+int
+fuse_fs_fsync(struct fuse_fs* fs, const char* path, int datasync, struct fuse_file_info* fi) {
+    clobber_context_user_data(fs);
+    switch (fs->op_version) {
+#define CALL_OLD_FSYNC(VER)                                             \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->fsync) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->fsync(path, datasync); \
+        else                                                            \
+            return -ENOSYS
+        CALL_OLD_FSYNC(11);
+        CALL_OLD_FSYNC(21);
+#undef CALL_OLD_FSYNC
+
+#define CALL_FSYNC(VER)                                                 \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->fsync) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->fsync(path, datasync, fi); \
+        else                                                            \
+            return -ENOSYS
+        CALL_FSYNC(22);
+        CALL_FSYNC(23);
+        CALL_FSYNC(25);
+        CALL_FSYNC(26);
+        CALL_FSYNC(28);
+        CALL_FSYNC(29);
+        CALL_FSYNC(30);
+        CALL_FSYNC(34);
+        CALL_FSYNC(38);
+#undef CALL_FSYNC
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+int
+fuse_fs_flush(struct fuse_fs* fs, const char* path, struct fuse_file_info* fi) {
+    clobber_context_user_data(fs);
+    /* flush() appeared on FUSE 2.1 and its prototype was changed on
+     * 2.2. */
+    switch (fs->op_version) {
+    case 11:
+        return -ENOSYS;
+    case 21:
+        if (((const struct fuse_operations_v21 *)fs->op)->flush)
+            return ((const struct fuse_operations_v21 *)fs->op)->flush(path);
+        else
+            return -ENOSYS;
+
+#define CALL_FLUSH(VER)                                                 \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->flush) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->flush(path, fi); \
+        else                                                            \
+            return -ENOSYS
+        CALL_FLUSH(22);
+        CALL_FLUSH(23);
+        CALL_FLUSH(25);
+        CALL_FLUSH(26);
+        CALL_FLUSH(28);
+        CALL_FLUSH(29);
+        CALL_FLUSH(30);
+        CALL_FLUSH(34);
+        CALL_FLUSH(38);
+#undef CALL_FLUSH
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+static void
+zero_statvfs(struct statvfs* dst) {
+    dst->f_bsize   = 0;
+    dst->f_frsize  = 0;
+    dst->f_blocks  = 0;
+    dst->f_bfree   = 0;
+    dst->f_bavail  = 0;
+    dst->f_files   = 0;
+    dst->f_ffree   = 0;
+    dst->f_fresvd  = 0;
+}
+static void
+fuse_statfs_to_statvfs(struct statvfs* dst, const struct fuse_statfs* src) {
+    dst->f_bsize   = (unsigned long)src->block_size;
+    dst->f_frsize  = (unsigned long)src->block_size; /* Dunno if this is correct. */
+    dst->f_blocks  = (fsblkcnt_t)src->blocks;
+    dst->f_bfree   = (fsblkcnt_t)src->blocks_free;
+    dst->f_bavail  = (fsblkcnt_t)src->blocks_free;
+    dst->f_files   = (fsfilcnt_t)src->files;
+    dst->f_ffree   = (fsfilcnt_t)src->files_free;
+}
+static void
+linux_statfs_to_statvfs(struct statvfs* dst, const struct statfs* src) {
+    dst->f_bsize   = (unsigned long)src->f_bsize;
+    dst->f_frsize  = (unsigned long)src->f_bsize; /* Dunno if this is correct. */
+    dst->f_blocks  = src->f_blocks;
+    dst->f_bfree   = src->f_bfree;
+    dst->f_bavail  = src->f_bavail;
+    dst->f_files   = src->f_files;
+    dst->f_ffree   = src->f_ffree;
+}
+int
+fuse_fs_statfs(struct fuse_fs* fs, const char* path, struct statvfs* buf) {
+    clobber_context_user_data(fs);
+
+    zero_statvfs(buf);
+
+    switch (fs->op_version) {
+        /* FUSE < 2.1 used "struct fuse_statfs". */
+    case 11:
+        if (((const struct fuse_operations_v11*)fs->op)->statfs) {
+            struct fuse_statfs statfs_v11;
+            int ret;
+
+            ret = ((const struct fuse_operations_v11*)fs->op)->statfs(path, &statfs_v11);
+            if (ret == 0)
+                fuse_statfs_to_statvfs(buf, &statfs_v11);
+
+            return ret;
+        }
+        else
+            return 0; /* Special case */
+
+        /* FUSE >= 2.2 && < 2.5 used Linux-specific "struct
+         * statfs". */
+#define CALL_LINUX_STATFS(VER)                                          \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->statfs) { \
+            struct statfs statfs_v22;                                   \
+            int ret;                                                    \
+                                                                        \
+            ret = ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->statfs(path, &statfs_v22); \
+            if (ret == 0)                                               \
+                linux_statfs_to_statvfs(buf, &statfs_v22);              \
+                                                                        \
+            return ret;                                                 \
+        }                                                               \
+        else                                                            \
+            return 0; /* Special case */
+        CALL_LINUX_STATFS(22);
+        CALL_LINUX_STATFS(23);
+#undef CALL_STATFS
+
+        /* FUSE >= 2.5 use struct statvfs. */
+#define CALL_STATFS(VER)                                                \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->statfs) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->statfs(path, buf); \
+        else                                                            \
+            return 0; /* Special case */
+        CALL_STATFS(25);
+        CALL_STATFS(26);
+        CALL_STATFS(28);
+        CALL_STATFS(29);
+        CALL_STATFS(30);
+        CALL_STATFS(34);
+        CALL_STATFS(38);
+#undef CALL_STATFS
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+int
+fuse_fs_opendir(struct fuse_fs* fs, const char* path, struct fuse_file_info* fi) {
+    clobber_context_user_data(fs);
+    switch (fs->op_version) {
+        /* FUSE < 2.3 didn't have opendir() and used to read
+         * directories without opening them. */
+    case 11:
+    case 21:
+    case 22:
+        return 0; /* Special case */
+
+#define CALL_OPENDIR(VER)                                               \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->opendir) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->opendir(path, fi); \
+        else                                                            \
+            return 0 /* Special case */
+        CALL_OPENDIR(23);
+        CALL_OPENDIR(25);
+        CALL_OPENDIR(26);
+        CALL_OPENDIR(28);
+        CALL_OPENDIR(29);
+        CALL_OPENDIR(30);
+        CALL_OPENDIR(34);
+        CALL_OPENDIR(38);
+#undef CALL_OPENDIR
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+/* ===================================
+ *     -=- The readdir Madness -=-
+ *     Juggling with Nested Shims
+ * =================================== */
+
+struct fuse_fill_dir_v23_shim {
+    void*               dirh;
+    fuse_fill_dir_t_v23 fill_dir_v23;
+};
+
+/* Translate dirent DT_* to mode_t. Needed by shim functions. */
+static mode_t
+dt_to_mode(int dt) {
+    switch (dt) {
+    case DT_UNKNOWN: return 0;
+    case DT_FIFO:    return S_IFIFO;
+    case DT_CHR:     return S_IFCHR;
+    case DT_DIR:     return S_IFCHR;
+    case DT_BLK:     return S_IFBLK;
+    case DT_REG:     return S_IFREG;
+    case DT_LNK:     return S_IFLNK;
+    case DT_SOCK:    return S_IFSOCK;
+    case DT_WHT:     return S_IFWHT;
+    default:
+        errx(EXIT_FAILURE, "%s: unknown dirent type: %d",
+             __func__, dt);
+    }
+}
+
+/* This is a shim function that satisfies the type of
+ * fuse_dirfil_t_v11 but calls fuse_fill_dir_v23. */
+static int
+fuse_dirfil_v11_to_fill_dir_v23(fuse_dirh_t handle, const char* name, int type) {
+    struct fuse_fill_dir_v23_shim* shim = handle;
+    struct stat stbuf;
+    int res; /* 1 or 0 */
+
+    memset(&stbuf, 0, sizeof(stbuf));
+    stbuf.st_mode = dt_to_mode(type);
+
+    res = shim->fill_dir_v23(shim->dirh, name, &stbuf, 0);
+    return res ? -ENOMEM : 0;
+}
+
+/* This is a shim function that satisfies the type of
+ * fuse_dirfil_t_v22 but calls fuse_fill_dir_v23. */
+static int
+fuse_dirfil_v22_to_fill_dir_v23(fuse_dirh_t handle, const char* name, int type, ino_t ino) {
+    struct fuse_fill_dir_v23_shim* shim = handle;
+    struct stat stbuf;
+    int res; /* 1 or 0 */
+
+    memset(&stbuf, 0, sizeof(stbuf));
+    stbuf.st_mode = dt_to_mode(type);
+    stbuf.st_ino  = ino;
+
+    res = shim->fill_dir_v23(shim->dirh, name, &stbuf, 0);
+    return res ? -ENOMEM : 0;
+}
+
+struct fuse_fill_dir_v30_shim {
+    void*               dirh;
+    fuse_fill_dir_t_v30 fill_dir_v30;
+};
+
+/* This is a shim function that satisfies the type of
+ * fuse_fill_dir_v23 but calls fuse_fill_dir_v30. */
+static int
+fuse_fill_dir_v23_to_v30(void* buf, const char* name,
+                         const struct stat* stat, off_t off) {
+
+    struct fuse_fill_dir_v30_shim* shim = buf;
+
+    return shim->fill_dir_v30(shim->dirh, name, stat, off, (enum fuse_fill_dir_flags)0);
+}
+
+int
+fuse_fs_readdir_v27(struct fuse_fs* fs, const char* path, void* buf,
+                    fuse_fill_dir_t_v23 filler, off_t off,
+                    struct fuse_file_info* fi) {
+
+    struct fuse_fill_dir_v23_shim v23_shim;
+
+    v23_shim.dirh         = buf;
+    v23_shim.fill_dir_v23 = filler;
+
+    clobber_context_user_data(fs);
+
+    switch (fs->op_version) {
+        /* FUSE < 2.2 had getdir() that used fuse_dirfil_t_v11. */
+#define CALL_GETDIR_V11(VER)                                        \
+    case VER:                                                       \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->getdir) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->getdir(path, &v23_shim, fuse_dirfil_v11_to_fill_dir_v23); \
+        else                                                            \
+            return -ENOSYS
+        CALL_GETDIR_V11(11);
+        CALL_GETDIR_V11(21);
+#undef CALL_GETDIR_V11
+
+        /* FUSE 2.2 had getdir() that used fuse_dirfil_t_v22 but
+         * didn't have readdir(). */
+    case 22:
+        if (((const struct fuse_operations_v22*)fs->op)->getdir)
+            return ((const struct fuse_operations_v22*)fs->op)->getdir(path, &v23_shim, fuse_dirfil_v22_to_fill_dir_v23);
+        else
+            return -ENOSYS;
+
+        /* FUSE 2.3 introduced readdir() but still had getdir() as
+         * deprecated operation. It had been this way until FUSE 3.0
+         * finally removed getdir() and also changed the prototype of
+         * readdir(). */
+#define CALL_READDIR_OR_GETDIR(VER)                                     \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->readdir) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->readdir(path, buf, filler, off, fi); \
+        else if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->getdir) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->getdir(path, &v23_shim, fuse_dirfil_v22_to_fill_dir_v23); \
+        else                                                            \
+            return -ENOSYS
+        CALL_READDIR_OR_GETDIR(23);
+        CALL_READDIR_OR_GETDIR(25);
+        CALL_READDIR_OR_GETDIR(26);
+        CALL_READDIR_OR_GETDIR(28);
+        CALL_READDIR_OR_GETDIR(29);
+#undef CALL_READDIR_OR_GETDIR
+
+    default:
+        /* FUSE >= 3.0 filesystems will never call this function. We
+         * can safely ignore them here. */
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+int
+fuse_fs_readdir_v30(struct fuse_fs* fs, const char* path, void* buf,
+                    fuse_fill_dir_t_v30 filler, off_t off,
+                    struct fuse_file_info* fi, enum fuse_readdir_flags flags) {
+    clobber_context_user_data(fs);
+
+    if (fs->op_version < 30) {
+        struct fuse_fill_dir_v30_shim v30_shim;
+
+        v30_shim.dirh         = buf;
+        v30_shim.fill_dir_v30 = filler;
+
+        return fuse_fs_readdir_v27(fs, path, &v30_shim, fuse_fill_dir_v23_to_v30, off, fi);
+    }
+    else {
+        switch (fs->op_version) {
+#define CALL_READDIR(VER)                                               \
+            case VER:                                                   \
+                if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->readdir) \
+                    return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->readdir(path, buf, filler, off, fi, flags); \
+                else                                                    \
+                    return -ENOSYS
+            CALL_READDIR(30);
+            CALL_READDIR(34);
+            CALL_READDIR(38);
+#undef CALL_READDIR
+        default:
+            UNKNOWN_VERSION(fs->op_version);
+        }
+    }
+}
+
+/* ==============================
+ *   The End of readdir Madness
+ * ============================== */
+
+int
+fuse_fs_fsyncdir(struct fuse_fs* fs, const char* path, int datasync, struct fuse_file_info* fi) {
+    clobber_context_user_data(fs);
+    /* fsyncdir() appeared on FUSE 2.3. */
+    switch (fs->op_version) {
+    case 11:
+    case 21:
+    case 22:
+        return -ENOSYS;
+
+#define CALL_FSYNCDIR(VER)                                              \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->fsyncdir) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->fsyncdir(path, datasync, fi); \
+        else                                                            \
+            return -ENOSYS
+        CALL_FSYNCDIR(23);
+        CALL_FSYNCDIR(25);
+        CALL_FSYNCDIR(26);
+        CALL_FSYNCDIR(28);
+        CALL_FSYNCDIR(29);
+        CALL_FSYNCDIR(30);
+        CALL_FSYNCDIR(34);
+        CALL_FSYNCDIR(38);
+#undef CALL_FSYNCDIR
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+int
+fuse_fs_releasedir(struct fuse_fs* fs, const char* path, struct fuse_file_info* fi) {
+    clobber_context_user_data(fs);
+    switch (fs->op_version) {
+        /* FUSE < 2.3 didn't have releasedir() and was reading
+         * directories without opening them. */
+    case 11:
+    case 21:
+    case 22:
+        return 0; /* Special case */
+
+#define CALL_RELEASEDIR(VER)                                            \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->releasedir) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->releasedir(path, fi); \
+        else                                                            \
+            return 0 /* Special case */
+        CALL_RELEASEDIR(23);
+        CALL_RELEASEDIR(25);
+        CALL_RELEASEDIR(26);
+        CALL_RELEASEDIR(28);
+        CALL_RELEASEDIR(29);
+        CALL_RELEASEDIR(30);
+        CALL_RELEASEDIR(34);
+        CALL_RELEASEDIR(38);
+#undef CALL_RELEASEDIR
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+int
+fuse_fs_create(struct fuse_fs* fs, const char* path, mode_t mode, struct fuse_file_info* fi) {
+    clobber_context_user_data(fs);
+    switch (fs->op_version) {
+        /* FUSE < 2.5 didn't have create(). */
+    case 11:
+    case 21:
+    case 22:
+    case 23:
+        return -ENOSYS;
+
+#define CALL_CREATE(VER)                                                \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->create) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->create(path, mode, fi); \
+        else                                                            \
+            return -ENOSYS
+        CALL_CREATE(25);
+        CALL_CREATE(26);
+        CALL_CREATE(28);
+        CALL_CREATE(29);
+        CALL_CREATE(30);
+        CALL_CREATE(34);
+        CALL_CREATE(38);
+#undef CALL_CREATE
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+int
+fuse_fs_lock(struct fuse_fs* fs, const char* path, struct fuse_file_info* fi,
+             int cmd, struct flock* lock) {
+    clobber_context_user_data(fs);
+    /* locK() appeared on FUSE 2.6. */
+    switch (fs->op_version) {
+    case 11:
+    case 21:
+    case 22:
+    case 23:
+    case 25:
+        return -ENOSYS;
+
+#define CALL_LOCK(VER)                                                  \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->lock) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->lock(path, fi, cmd, lock); \
+        else                                                            \
+            return -ENOSYS
+        CALL_LOCK(26);
+        CALL_LOCK(28);
+        CALL_LOCK(29);
+        CALL_LOCK(30);
+        CALL_LOCK(34);
+        CALL_LOCK(38);
+#undef CALL_LOCK
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+int
+fuse_fs_flock(struct fuse_fs* fs, const char* path, struct fuse_file_info* fi, int op) {
+    clobber_context_user_data(fs);
+    /* flocK() appeared on FUSE 2.9. */
+    switch (fs->op_version) {
+    case 11:
+    case 21:
+    case 22:
+    case 23:
+    case 25:
+    case 26:
+    case 28:
+        return -ENOSYS;
+
+#define CALL_FLOCK(VER)                                                 \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->flock) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->flock(path, fi, op); \
+        else                                                            \
+            return -ENOSYS
+        CALL_FLOCK(29);
+        CALL_FLOCK(30);
+        CALL_FLOCK(34);
+        CALL_FLOCK(38);
+#undef CALL_FLOCK
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+int
+fuse_fs_chmod_v27(struct fuse_fs *fs, const char *path, mode_t mode) {
+    return fuse_fs_chmod_v30(fs, path, mode, NULL);
+}
+
+int
+fuse_fs_chmod_v30(struct fuse_fs* fs, const char* path,
+                  mode_t mode, struct fuse_file_info* fi) {
+    clobber_context_user_data(fs);
+    switch (fs->op_version) {
+#define CALL_OLD_CHMOD(VER)                                             \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->chmod) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->chmod(path, mode); \
+        else                                                            \
+            return -ENOSYS
+        CALL_OLD_CHMOD(11);
+        CALL_OLD_CHMOD(21);
+        CALL_OLD_CHMOD(22);
+        CALL_OLD_CHMOD(23);
+        CALL_OLD_CHMOD(25);
+        CALL_OLD_CHMOD(26);
+        CALL_OLD_CHMOD(28);
+        CALL_OLD_CHMOD(29);
+#undef CALL_OLD_CHMOD
+
+#define CALL_CHMOD(VER)                                                 \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->chmod) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->chmod(path, mode, fi); \
+        else                                                            \
+            return -ENOSYS
+        CALL_CHMOD(30);
+        CALL_CHMOD(34);
+        CALL_CHMOD(38);
+#undef CALL_CHMOD
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+int fuse_fs_chown_v27(struct fuse_fs *fs, const char *path, uid_t uid, gid_t gid) {
+    return fuse_fs_chown_v30(fs, path, uid, gid, NULL);
+}
+
+int
+fuse_fs_chown_v30(struct fuse_fs* fs, const char* path,
+                  uid_t uid, gid_t gid, struct fuse_file_info* fi) {
+    clobber_context_user_data(fs);
+    switch (fs->op_version) {
+#define CALL_OLD_CHOWN(VER)                                             \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->chown) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->chown(path, uid, gid); \
+        else                                                            \
+            return -ENOSYS
+        CALL_OLD_CHOWN(11);
+        CALL_OLD_CHOWN(21);
+        CALL_OLD_CHOWN(22);
+        CALL_OLD_CHOWN(23);
+        CALL_OLD_CHOWN(25);
+        CALL_OLD_CHOWN(26);
+        CALL_OLD_CHOWN(28);
+        CALL_OLD_CHOWN(29);
+#undef CALL_OLD_CHOWN
+
+#define CALL_CHOWN(VER)                         \
+    case VER:                                   \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->chown) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->chown(path, uid, gid, fi); \
+        else                                                            \
+            return -ENOSYS
+        CALL_CHOWN(30);
+        CALL_CHOWN(34);
+        CALL_CHOWN(38);
+#undef CALL_CHOWN
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+int fuse_fs_truncate_v27(struct fuse_fs *fs, const char *path, off_t size) {
+    return fuse_fs_truncate_v30(fs, path, size, NULL);
+}
+
+int
+fuse_fs_truncate_v30(struct fuse_fs* fs, const char* path, off_t size, struct fuse_file_info* fi) {
+    clobber_context_user_data(fs);
+    switch (fs->op_version) {
+#define CALL_OLD_TRUNCATE(VER)                                          \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->truncate) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->truncate(path, size); \
+        else                                                            \
+            return -ENOSYS
+        CALL_OLD_TRUNCATE(11);
+        CALL_OLD_TRUNCATE(21);
+        CALL_OLD_TRUNCATE(22);
+        CALL_OLD_TRUNCATE(23);
+        CALL_OLD_TRUNCATE(25);
+        CALL_OLD_TRUNCATE(26);
+        CALL_OLD_TRUNCATE(28);
+        CALL_OLD_TRUNCATE(29);
+#undef CALL_OLD_TRUNCATE
+
+#define CALL_TRUNCATE(VER)                      \
+    case VER:                                   \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->truncate) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->truncate(path, size, fi); \
+        else                                                            \
+            return -ENOSYS
+        CALL_TRUNCATE(30);
+        CALL_TRUNCATE(34);
+        CALL_TRUNCATE(38);
+#undef CALL_TRUNCATE
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+int
+fuse_fs_ftruncate(struct fuse_fs* fs, const char* path, off_t size, struct fuse_file_info* fi) {
+    clobber_context_user_data(fs);
+    switch (fs->op_version) {
+        /* FUSE < 2.5 didn't have ftruncate(). Always fall back to
+         * truncate(). */
+#define CALL_OLD_TRUNCATE(VER)                                          \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->truncate) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->truncate(path, size); \
+        else                                                            \
+            return -ENOSYS
+        CALL_OLD_TRUNCATE(11);
+        CALL_OLD_TRUNCATE(21);
+        CALL_OLD_TRUNCATE(22);
+        CALL_OLD_TRUNCATE(23);
+#undef CALL_OLD_TRUNCATE
+
+        /* ftruncate() appeared on FUSE 2.5 and then disappeared on
+         * FUSE 3.0. Call it if it exists, or fall back to truncate()
+         * otherwise. */
+#define CALL_FTRUNCATE_OR_TRUNCATE(VER)                                 \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->ftruncate) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->ftruncate(path, size, fi); \
+        else if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->truncate) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->truncate(path, size); \
+        else                                                            \
+            return -ENOSYS
+        CALL_FTRUNCATE_OR_TRUNCATE(25);
+        CALL_FTRUNCATE_OR_TRUNCATE(26);
+        CALL_FTRUNCATE_OR_TRUNCATE(28);
+        CALL_FTRUNCATE_OR_TRUNCATE(29);
+#undef CALL_FTRUNCATE_OR_TRUNCATE
+
+        /* FUSE >= 3.0 have truncate() but with a different function
+         * type. */
+#define CALL_TRUNCATE(VER)                      \
+    case VER:                                   \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->truncate) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->truncate(path, size, fi); \
+        else                                                            \
+            return -ENOSYS
+        CALL_TRUNCATE(30);
+        CALL_TRUNCATE(34);
+        CALL_TRUNCATE(38);
+#undef CALL_TRUNCATE
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+int
+fuse_fs_utimens_v27(struct fuse_fs *fs, const char *path, const struct timespec tv[2]) {
+    return fuse_fs_utimens_v30(fs, path, tv, NULL);
+}
+
+int
+fuse_fs_utimens_v30(struct fuse_fs* fs, const char* path,
+                    const struct timespec tv[2], struct fuse_file_info* fi) {
+    struct utimbuf timbuf;
+
+    timbuf.actime  = tv[0].tv_sec;
+    timbuf.modtime = tv[1].tv_sec;
+
+    clobber_context_user_data(fs);
+
+    switch (fs->op_version) {
+        /* FUSE < 2.6 didn't have utimens() but had utime()
+         * instead. */
+#define CALL_UTIME(VER)                                                 \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->utime) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->utime(path, &timbuf); \
+        else                                                            \
+            return -ENOSYS
+        CALL_UTIME(11);
+        CALL_UTIME(21);
+        CALL_UTIME(22);
+        CALL_UTIME(23);
+        CALL_UTIME(25);
+#undef CALL_UTIME
+
+        /* utimens() appeared on FUSE 2.6. Call it if it exists, or fall back to
+         * utime() otherwise. */
+#define CALL_UTIMENS_OR_UTIME(VER)                                      \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->utimens) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->utimens(path, tv); \
+        else if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->utime) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->utime(path, &timbuf); \
+        else                                                            \
+            return -ENOSYS
+        CALL_UTIMENS_OR_UTIME(26);
+        CALL_UTIMENS_OR_UTIME(28);
+        CALL_UTIMENS_OR_UTIME(29);
+#undef CALL_UTIMENS_OR_UTIME
+
+        /* utime() disappeared on FUSE 3.0. */
+#define CALL_UTIMENS(VER)                       \
+    case VER:                                   \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->utimens) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->utimens(path, tv, fi); \
+        else                                                            \
+            return -ENOSYS
+        CALL_UTIMENS(30);
+        CALL_UTIMENS(34);
+        CALL_UTIMENS(38);
+#undef CALL_UTIMENS
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+int
+fuse_fs_access(struct fuse_fs* fs, const char* path, int mask) {
+    clobber_context_user_data(fs);
+    /* access() appeared on FUSE 2.5. */
+    switch (fs->op_version) {
+    case 11:
+    case 21:
+    case 22:
+    case 23:
+        return -ENOSYS;
+#define CALL_ACCESS(VER)                                                \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->access) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->access(path, mask); \
+        else                                                            \
+            return -ENOSYS
+        CALL_ACCESS(25);
+        CALL_ACCESS(26);
+        CALL_ACCESS(28);
+        CALL_ACCESS(29);
+        CALL_ACCESS(30);
+        CALL_ACCESS(34);
+        CALL_ACCESS(38);
+#undef CALL_ACCESS
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+int
+fuse_fs_readlink(struct fuse_fs* fs, const char* path, char* buf, size_t len) {
+    clobber_context_user_data(fs);
+    switch (fs->op_version) {
+#define CALL_READLINK(VER)                                              \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->readlink) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->readlink(path, buf, len); \
+        else                                                            \
+            return -ENOSYS
+        CALL_READLINK(11);
+        CALL_READLINK(21);
+        CALL_READLINK(22);
+        CALL_READLINK(23);
+        CALL_READLINK(25);
+        CALL_READLINK(26);
+        CALL_READLINK(28);
+        CALL_READLINK(29);
+        CALL_READLINK(30);
+        CALL_READLINK(34);
+        CALL_READLINK(38);
+#undef CALL_READLINK
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+int
+fuse_fs_mknod(struct fuse_fs* fs, const char* path, mode_t mode, dev_t rdev) {
+    clobber_context_user_data(fs);
+    switch (fs->op_version) {
+#define CALL_MKNOD(VER)                                                 \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->mknod) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->mknod(path, mode, rdev); \
+        else                                                            \
+            return -ENOSYS
+        CALL_MKNOD(11);
+        CALL_MKNOD(21);
+        CALL_MKNOD(22);
+        CALL_MKNOD(23);
+        CALL_MKNOD(25);
+        CALL_MKNOD(26);
+        CALL_MKNOD(28);
+        CALL_MKNOD(29);
+        CALL_MKNOD(30);
+        CALL_MKNOD(34);
+        CALL_MKNOD(38);
+#undef CALL_MKNOD
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+int
+fuse_fs_mkdir(struct fuse_fs* fs, const char* path, mode_t mode) {
+    clobber_context_user_data(fs);
+    switch (fs->op_version) {
+#define CALL_MKDIR(VER)                                                 \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->mkdir) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->mkdir(path, mode); \
+        else                                                            \
+            return -ENOSYS
+        CALL_MKDIR(11);
+        CALL_MKDIR(21);
+        CALL_MKDIR(22);
+        CALL_MKDIR(23);
+        CALL_MKDIR(25);
+        CALL_MKDIR(26);
+        CALL_MKDIR(28);
+        CALL_MKDIR(29);
+        CALL_MKDIR(30);
+        CALL_MKDIR(34);
+        CALL_MKDIR(38);
+#undef CALL_MKDIR
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+int fuse_fs_setxattr(struct fuse_fs* fs, const char* path, const char* name,
+                     const char* value, size_t size, int flags) {
+    clobber_context_user_data(fs);
+    /* setxattr() appeared on FUSE 2.1. */
+    switch (fs->op_version) {
+    case 11:
+        return -ENOSYS;
+#define CALL_SETXATTR(VER)                                              \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->setxattr) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->setxattr(path, name, value, size, flags); \
+        else                                                            \
+            return -ENOSYS
+        CALL_SETXATTR(21);
+        CALL_SETXATTR(22);
+        CALL_SETXATTR(23);
+        CALL_SETXATTR(25);
+        CALL_SETXATTR(26);
+        CALL_SETXATTR(28);
+        CALL_SETXATTR(29);
+        CALL_SETXATTR(30);
+        CALL_SETXATTR(34);
+        CALL_SETXATTR(38);
+#undef CALL_SETXATTR
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+int
+fuse_fs_getxattr(struct fuse_fs* fs, const char* path, const char* name,
+                 char* value, size_t size) {
+    clobber_context_user_data(fs);
+    /* getxattr() appeared on FUSE 2.1. */
+    switch (fs->op_version) {
+    case 11:
+        return -ENOSYS;
+#define CALL_GETXATTR(VER)                                              \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->getxattr) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->getxattr(path, name, value, size); \
+        else                                                            \
+            return -ENOSYS
+        CALL_GETXATTR(21);
+        CALL_GETXATTR(22);
+        CALL_GETXATTR(23);
+        CALL_GETXATTR(25);
+        CALL_GETXATTR(26);
+        CALL_GETXATTR(28);
+        CALL_GETXATTR(29);
+        CALL_GETXATTR(30);
+        CALL_GETXATTR(34);
+        CALL_GETXATTR(38);
+#undef CALL_GETXATTR
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+int fuse_fs_listxattr(struct fuse_fs* fs, const char* path, char* list, size_t size) {
+    clobber_context_user_data(fs);
+    /* listxattr() appeared on FUSE 2.1. */
+    switch (fs->op_version) {
+    case 11:
+        return -ENOSYS;
+#define CALL_LISTXATTR(VER)                                             \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->listxattr) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->listxattr(path, list, size); \
+        else                                                            \
+            return -ENOSYS
+        CALL_LISTXATTR(21);
+        CALL_LISTXATTR(22);
+        CALL_LISTXATTR(23);
+        CALL_LISTXATTR(25);
+        CALL_LISTXATTR(26);
+        CALL_LISTXATTR(28);
+        CALL_LISTXATTR(29);
+        CALL_LISTXATTR(30);
+        CALL_LISTXATTR(34);
+        CALL_LISTXATTR(38);
+#undef CALL_LISTXATTR
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+int
+fuse_fs_removexattr(struct fuse_fs* fs, const char* path, const char* name) {
+    clobber_context_user_data(fs);
+    /* removexattr() appeared on FUSE 2.1. */
+    switch (fs->op_version) {
+    case 11:
+        return -ENOSYS;
+#define CALL_REMOVEXATTR(VER)                                           \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->removexattr) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->removexattr(path, name); \
+        else                                                            \
+            return -ENOSYS
+        CALL_REMOVEXATTR(21);
+        CALL_REMOVEXATTR(22);
+        CALL_REMOVEXATTR(23);
+        CALL_REMOVEXATTR(25);
+        CALL_REMOVEXATTR(26);
+        CALL_REMOVEXATTR(28);
+        CALL_REMOVEXATTR(29);
+        CALL_REMOVEXATTR(30);
+        CALL_REMOVEXATTR(34);
+        CALL_REMOVEXATTR(38);
+#undef CALL_REMOVEXATTR
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+int
+fuse_fs_bmap(struct fuse_fs* fs, const char* path, size_t blocksize, uint64_t *idx) {
+    clobber_context_user_data(fs);
+    /* bmap() appeared on FUSE 2.6. */
+    switch (fs->op_version) {
+    case 11:
+    case 22:
+    case 23:
+    case 25:
+        return -ENOSYS;
+#define CALL_BMAP(VER)                                                  \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->bmap) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->bmap(path, blocksize, idx); \
+        else                                                            \
+            return -ENOSYS
+        CALL_BMAP(26);
+        CALL_BMAP(28);
+        CALL_BMAP(29);
+        CALL_BMAP(30);
+        CALL_BMAP(34);
+        CALL_BMAP(38);
+#undef CALL_BMAP
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+int fuse_fs_ioctl_v28(struct fuse_fs* fs, const char* path, int cmd, void* arg,
+                      struct fuse_file_info* fi, unsigned int flags, void* data) {
+    return fuse_fs_ioctl_v35(fs, path, (unsigned int)cmd, arg, fi, flags, data);
+}
+
+int fuse_fs_ioctl_v35(struct fuse_fs* fs, const char* path, unsigned int cmd, void* arg,
+                      struct fuse_file_info* fi, unsigned int flags, void* data) {
+    clobber_context_user_data(fs);
+    switch (fs->op_version) {
+        /* ioctl() appeared on FUSE 2.8 but with (int)cmd. */
+    case 11:
+    case 22:
+    case 23:
+    case 25:
+    case 26:
+        return -ENOSYS;
+#define CALL_OLD_IOCTL(VER)                                                 \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->ioctl) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->ioctl(path, (int)cmd, arg, fi, flags, data); \
+        else                                                            \
+            return -ENOSYS
+        CALL_OLD_IOCTL(28);
+        CALL_OLD_IOCTL(29);
+        CALL_OLD_IOCTL(30);
+        CALL_OLD_IOCTL(34);
+#undef CALL_OLD_IOCTL
+
+        /* It was then changed to (unsigned int)cmd on FUSE 3.5. */
+#define CALL_IOCTL(VER)                                                 \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->ioctl) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->ioctl(path, cmd, arg, fi, flags, data); \
+        else                                                            \
+            return -ENOSYS
+        CALL_IOCTL(35);
+        CALL_IOCTL(38);
+#undef CALL_IOCTL
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+int
+fuse_fs_poll(struct fuse_fs* fs, const char* path, struct fuse_file_info* fi,
+             struct fuse_pollhandle* ph, unsigned* reventsp) {
+    clobber_context_user_data(fs);
+    /* poll() appeared on FUSE 2.8. */
+    switch (fs->op_version) {
+    case 11:
+    case 22:
+    case 23:
+    case 25:
+    case 26:
+        return -ENOSYS;
+#define CALL_POLL(VER)                                                  \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->poll) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->poll(path, fi, ph, reventsp); \
+        else                                                            \
+            return -ENOSYS
+        CALL_POLL(28);
+        CALL_POLL(29);
+        CALL_POLL(30);
+        CALL_POLL(34);
+        CALL_POLL(38);
+#undef CALL_POLL
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+int
+fuse_fs_fallocate(struct fuse_fs* fs, const char* path, int mode, off_t offset,
+                  off_t length, struct fuse_file_info* fi) {
+    clobber_context_user_data(fs);
+    /* fallocate() appeared on FUSE 2.9. */
+    switch (fs->op_version) {
+    case 11:
+    case 22:
+    case 23:
+    case 25:
+    case 26:
+    case 28:
+        return -ENOSYS;
+#define CALL_FALLOCATE(VER)                                             \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->fallocate) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->fallocate(path, mode, offset, length, fi); \
+        else                                                            \
+            return -ENOSYS
+        CALL_FALLOCATE(29);
+        CALL_FALLOCATE(30);
+        CALL_FALLOCATE(34);
+        CALL_FALLOCATE(38);
+#undef CALL_FALLOCATE
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+ssize_t
+fuse_fs_copy_file_range(struct fuse_fs *fs,
+                        const char *path_in, struct fuse_file_info *fi_in, off_t off_in,
+                        const char *path_out, struct fuse_file_info *fi_out, off_t off_out,
+                        size_t len, int flags) {
+    clobber_context_user_data(fs);
+    /* copy_file_range() appeared on FUSE 3.4. */
+    switch (fs->op_version) {
+    case 11:
+    case 22:
+    case 23:
+    case 25:
+    case 26:
+    case 28:
+    case 29:
+    case 30:
+        return -ENOSYS;
+#define CALL_COPY_FILE_RANGE(VER)                                       \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->copy_file_range) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->copy_file_range(path_in, fi_in, off_in, path_out, fi_out, off_out, len, flags); \
+        else                                                            \
+            return -ENOSYS
+        CALL_COPY_FILE_RANGE(34);
+        CALL_COPY_FILE_RANGE(38);
+#undef CALL_COPY_FILE_RANGE
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+off_t
+fuse_fs_lseek(struct fuse_fs* fs, const char* path, off_t off, int whence,
+              struct fuse_file_info* fi) {
+    clobber_context_user_data(fs);
+    /* lseek() appeared on FUSE 3.8. */
+    switch (fs->op_version) {
+    case 11:
+    case 22:
+    case 23:
+    case 25:
+    case 26:
+    case 28:
+    case 29:
+    case 30:
+    case 34:
+        return -ENOSYS;
+#define CALL_LSEEK(VER)                         \
+    case VER:                                   \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->lseek) \
+            return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->lseek(path, off, whence, fi); \
+        else                                                            \
+            return -ENOSYS
+        CALL_LSEEK(38);
+#undef CALL_LSEEK
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+void
+fuse_fs_init_v27(struct fuse_fs *fs, struct fuse_conn_info *conn) {
+    fuse_fs_init_v30(fs, conn, NULL);
+}
+
+void
+fuse_fs_init_v30(struct fuse_fs* fs, struct fuse_conn_info* conn,
+                 struct fuse_config* cfg) {
+    clobber_context_user_data(fs);
+    switch (fs->op_version) {
+    case 11:
+    case 21:
+    case 22:
+        break;
+
+        /* init() appeared on FUSE 2.3 as init(void). */
+#define CALL_NULLARY_INIT(VER)                                          \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->init) \
+            fs->user_data = ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->init(); \
+        break
+        CALL_NULLARY_INIT(23);
+        CALL_NULLARY_INIT(25);
+#undef CALL_NULLARY_INIT
+
+        /* It was changed to init(struct fuse_conn_info*) on FUSE
+         * 2.6. */
+#define CALL_UNARY_INIT(VER)                                            \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->init) \
+            fs->user_data = ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->init(conn); \
+        break
+        CALL_UNARY_INIT(26);
+        CALL_UNARY_INIT(28);
+        CALL_UNARY_INIT(29);
+#undef CALL_INIT
+
+        /* It was again changed to init(struct fuse_conn_info*, struct
+         * fuse_config*) on FUSE 3.0. */
+#define CALL_BINARY_INIT(VER)                   \
+    case VER:                                   \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->init) \
+            fs->user_data = ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->init(conn, cfg); \
+        break
+        CALL_BINARY_INIT(30);
+        CALL_BINARY_INIT(34);
+        CALL_BINARY_INIT(38);
+#undef CALL_BINARY_INIT
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+}
+
+void
+fuse_fs_destroy(struct fuse_fs *fs) {
+    clobber_context_user_data(fs);
+    switch (fs->op_version) {
+        /* destroy() appeared on FUSE 2.3. */
+    case 11:
+    case 21:
+    case 22:
+        break;
+
+#define CALL_DESTROY(VER)                                               \
+    case VER:                                                           \
+        if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->destroy) \
+            ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->destroy(fs->user_data); \
+        break
+        CALL_DESTROY(23);
+        CALL_DESTROY(25);
+        CALL_DESTROY(26);
+        CALL_DESTROY(28);
+        CALL_DESTROY(29);
+        CALL_DESTROY(30);
+        CALL_DESTROY(34);
+        CALL_DESTROY(38);
+#undef CALL_DESTROY
+    default:
+        UNKNOWN_VERSION(fs->op_version);
+    }
+
+    /* fuse_fs_destroy(3) also deallocates struct fuse_fs itself. */
+    free(fs->op);
+    free(fs);
+}
diff --git a/lib/librefuse/refuse/fs.h b/lib/librefuse/refuse/fs.h
new file mode 100644
index 000000000..a37d5e20c
--- /dev/null
+++ b/lib/librefuse/refuse/fs.h
@@ -0,0 +1,120 @@
+/* $NetBSD$ */
+
+/*
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior written
+ *    permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#if !defined(_FUSE_FS_H_)
+#define _FUSE_FS_H_
+
+/*
+ * Filesystem Stacking API, appeared on FUSE 2.7
+ */
+
+#if !defined(FUSE_H_)
+#  error Do not include this header directly. Include <fuse.h> instead.
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* An opaque object representing a filesystem layer. */
+struct fuse_fs;
+
+/* Create a filesystem layer. api_version has to be the version of
+ * struct fuse_operations which is not necessarily the same as API
+ * version. Note that user code should always use fuse_fs_new() and
+ * should never call this internal function directly. */
+struct fuse_fs *__fuse_fs_new(const void* op, int op_version, void* user_data);
+
+/* These functions call the relevant filesystem operation, and return
+ * the result. If the operation is not defined, they return -ENOSYS,
+ * with the exception of fuse_fs_open, fuse_fs_release,
+ * fuse_fs_opendir, fuse_fs_releasedir, and fuse_fs_statfs, which return 0. */
+int fuse_fs_getattr_v27(struct fuse_fs *fs, const char *path, struct stat *buf);
+int fuse_fs_getattr_v30(struct fuse_fs* fs, const char* path, struct stat* buf, struct fuse_file_info* fi);
+int fuse_fs_fgetattr(struct fuse_fs* fs, const char* path, struct stat* buf, struct fuse_file_info* fi);
+int fuse_fs_rename_v27(struct fuse_fs* fs, const char* oldpath, const char* newpath);
+int fuse_fs_rename_v30(struct fuse_fs* fs, const char* oldpath, const char* newpath, unsigned int flags);
+int fuse_fs_unlink(struct fuse_fs* fs, const char* path);
+int fuse_fs_rmdir(struct fuse_fs* fs, const char* path);
+int fuse_fs_symlink(struct fuse_fs* fs, const char* linkname, const char* path);
+int fuse_fs_link(struct fuse_fs* fs, const char* oldpath, const char* newpath);
+int fuse_fs_release(struct fuse_fs* fs, const char* path, struct fuse_file_info* fi);
+int fuse_fs_open(struct fuse_fs* fs, const char* path, struct fuse_file_info* fi);
+int fuse_fs_read(struct fuse_fs* fs, const char* path, char* buf, size_t size, off_t off, struct fuse_file_info* fi);
+int fuse_fs_read_buf(struct fuse_fs* fs, const char* path, struct fuse_bufvec** bufp, size_t size, off_t off, struct fuse_file_info* fi);
+int fuse_fs_write(struct fuse_fs* fs, const char* path, const char* buf, size_t size, off_t off, struct fuse_file_info* fi);
+int fuse_fs_write_buf(struct fuse_fs* fs, const char* path, struct fuse_bufvec *buf, off_t off, struct fuse_file_info* fi);
+int fuse_fs_fsync(struct fuse_fs* fs, const char* path, int datasync, struct fuse_file_info* fi);
+int fuse_fs_flush(struct fuse_fs* fs, const char* path, struct fuse_file_info* fi);
+int fuse_fs_statfs(struct fuse_fs* fs, const char* path, struct statvfs* buf);
+int fuse_fs_opendir(struct fuse_fs* fs, const char* path, struct fuse_file_info* fi);
+int fuse_fs_readdir_v27(struct fuse_fs* fs, const char* path, void* buf, fuse_fill_dir_t_v23 filler, off_t off, struct fuse_file_info* fi);
+int fuse_fs_readdir_v30(struct fuse_fs* fs, const char* path, void* buf, fuse_fill_dir_t_v30 filler, off_t off, struct fuse_file_info* fi, enum fuse_readdir_flags flags);
+int fuse_fs_fsyncdir(struct fuse_fs* fs, const char* path, int datasync, struct fuse_file_info* fi);
+int fuse_fs_releasedir(struct fuse_fs* fs, const char* path, struct fuse_file_info* fi);
+int fuse_fs_create(struct fuse_fs* fs, const char* path, mode_t mode, struct fuse_file_info* fi);
+int fuse_fs_lock(struct fuse_fs* fs, const char* path, struct fuse_file_info* fi, int cmd, struct flock* lock);
+int fuse_fs_flock(struct fuse_fs* fs, const char* path, struct fuse_file_info* fi, int op);
+int fuse_fs_chmod_v27(struct fuse_fs *fs, const char *path, mode_t mode);
+int fuse_fs_chmod_v30(struct fuse_fs* fs, const char* path, mode_t mode, struct fuse_file_info* fi);
+int fuse_fs_chown_v27(struct fuse_fs *fs, const char *path, uid_t uid, gid_t gid);
+int fuse_fs_chown_v30(struct fuse_fs* fs, const char* path, uid_t uid, gid_t gid, struct fuse_file_info* fi);
+int fuse_fs_truncate_v27(struct fuse_fs *fs, const char *path, off_t size);
+int fuse_fs_truncate_v30(struct fuse_fs* fs, const char* path, off_t size, struct fuse_file_info* fi);
+int fuse_fs_ftruncate(struct fuse_fs* fs, const char* path, off_t size, struct fuse_file_info* fi);
+int fuse_fs_utimens_v27(struct fuse_fs *fs, const char *path, const struct timespec tv[2]);
+int fuse_fs_utimens_v30(struct fuse_fs* fs, const char* path, const struct timespec tv[2], struct fuse_file_info* fi);
+int fuse_fs_access(struct fuse_fs* fs, const char* path, int mask);
+int fuse_fs_readlink(struct fuse_fs* fs, const char* path, char* buf, size_t len);
+int fuse_fs_mknod(struct fuse_fs* fs, const char* path, mode_t mode, dev_t rdev);
+int fuse_fs_mkdir(struct fuse_fs* fs, const char* path, mode_t mode);
+int fuse_fs_setxattr(struct fuse_fs* fs, const char* path, const char* name, const char* value, size_t size, int flags);
+int fuse_fs_getxattr(struct fuse_fs* fs, const char* path, const char* name, char* value, size_t size);
+int fuse_fs_listxattr(struct fuse_fs* fs, const char* path, char* list, size_t size);
+int fuse_fs_removexattr(struct fuse_fs* fs, const char* path, const char* name);
+int fuse_fs_bmap(struct fuse_fs* fs, const char* path, size_t blocksize, uint64_t *idx);
+int fuse_fs_ioctl_v28(struct fuse_fs* fs, const char* path, int cmd, void* arg, struct fuse_file_info* fi, unsigned int flags, void* data);
+int fuse_fs_ioctl_v35(struct fuse_fs* fs, const char* path, unsigned int cmd, void* arg, struct fuse_file_info* fi, unsigned int flags, void* data);
+int fuse_fs_poll(struct fuse_fs* fs, const char* path, struct fuse_file_info* fi, struct fuse_pollhandle* ph, unsigned* reventsp);
+int fuse_fs_fallocate(struct fuse_fs* fs, const char* path, int mode, off_t offset, off_t length, struct fuse_file_info* fi);
+ssize_t fuse_fs_copy_file_range(struct fuse_fs* fs,
+                                const char* path_in, struct fuse_file_info* fi_in, off_t off_in,
+                                const char* path_out, struct fuse_file_info* fi_out, off_t off_out,
+                                size_t len, int flags);
+off_t fuse_fs_lseek(struct fuse_fs* fs, const char* path, off_t off, int whence, struct fuse_file_info* fi);
+void fuse_fs_init_v27(struct fuse_fs* fs, struct fuse_conn_info* conn);
+void fuse_fs_init_v30(struct fuse_fs* fs, struct fuse_conn_info* conn, struct fuse_config* cfg);
+void fuse_fs_destroy(struct fuse_fs* fs);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/librefuse/refuse/v11.c b/lib/librefuse/refuse/v11.c
new file mode 100644
index 000000000..99e32b780
--- /dev/null
+++ b/lib/librefuse/refuse/v11.c
@@ -0,0 +1,181 @@
+/* $NetBSD$ */
+
+/*
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior written
+ *    permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#if !defined(lint)
+__RCSID("$NetBSD$");
+#endif /* !lint */
+
+#include <err.h>
+#include <fuse_internal.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* FUSE < 3.0 had a very strange interface. Filesystems were supposed
+ * to be mounted first, before creating an instance of struct
+ * fuse. They revised the interface SO MANY TIMES but the fundamental
+ * weirdness stayed the same. */
+int
+fuse_mount_v11(const char *mountpoint, const char *argv[]) {
+    struct fuse_args args = FUSE_ARGS_INIT(0, NULL);
+    int nominal_fd = -1;
+
+    /* The argv is supposed to be a NULL-terminated array of
+     * additional arguments to fusermount(8), and should not have a
+     * program name at argv[0]. Our __fuse_new() expects one. So
+     * prepend a dummy name. */
+    if (fuse_opt_add_arg(&args, "dummy") != 0)
+        goto free_args;
+
+    if (argv) {
+        for (size_t i = 0; argv[i] != NULL; i++) {
+            if (fuse_opt_add_arg(&args, argv[i]) != 0)
+                goto free_args;
+        }
+    }
+
+    nominal_fd = fuse_mount_v25(mountpoint, &args);
+
+free_args:
+    fuse_opt_free_args(&args);
+    return nominal_fd;
+}
+
+static bool
+is_same_mountpoint(struct fuse_chan* chan, void* priv) {
+    const char* mountpoint = priv;
+
+    return strcmp(fuse_chan_mountpoint(chan), mountpoint) == 0;
+}
+
+static bool
+is_same_fuse(struct fuse_chan* chan, void* priv) {
+    struct fuse* fuse = priv;
+
+    return fuse_chan_fuse(chan) == fuse;
+}
+
+/* FUSE < 3.0 didn't require filesystems to call fuse_unmount()
+ * before fuse_destroy(). That is, it was completely legal to call
+ * fuse_unmount() *after* fuse_destroy(), and it was even legal to
+ * call fuse_mount() and then fuse_unmount() without calling
+ * fuse_new() in the first place. On the other hand, our libpuffs
+ * (like FUSE 3.0) wants a context in order to unmount a
+ * filesystem. So, we have to do a workaround as follows:
+ *
+ * 1. fuse_mount() creates a struct fuse_chan and stashes it in a
+ *    global channel list, but without actually mounting a filesystem.
+ *
+ * 2. fuse_new() fetches the stashed fuse_chan and creates a fuse
+ *    object out of it, then mounts a filesystem. The fuse object is
+ *    also stored in fuse_chan.
+ *
+ * 3. When fuse_destroy() is called without first unmounting the
+ *    filesystem, it doesn't actually destroy the fuse object but it
+ *    merely schedules it for destruction.
+ *
+ * 4. fuse_unmount() searches for the corresponding fuse_chan in the
+ *    global list. If it's scheduled for destruction, destroy the fuse
+ *    object after unmounting the filesystem. It then removes and
+ *    deallocates the fuse_chan from the list.
+ *
+ * Note that there will be a space leak if a user calls fuse_destroy()
+ * but never calls fuse_unmount(). The fuse_chan will forever be in
+ * the global list in this case. There's nothing we can do about it,
+ * and users aren't supposed to do it after all.
+ */
+void
+fuse_unmount_v11(const char *mountpoint) {
+    int idx;
+    struct fuse_chan* chan;
+    struct fuse* fuse;
+
+    /* Search for the fuse_chan having the given mountpoint. It must
+     * be in the global list. */
+    chan = fuse_chan_find(is_same_mountpoint, &idx, __UNCONST(mountpoint));
+    if (!chan)
+        errx(EXIT_FAILURE, "%s: cannot find a channel for the mountpoint: %s",
+             __func__, mountpoint);
+
+    fuse = fuse_chan_fuse(chan);
+    if (fuse) {
+        /* The user did call fuse_new() after fuse_mount(). */
+        fuse_unmount_v30(fuse);
+    }
+
+    if (fuse_chan_is_to_be_destroyed(chan)) {
+        /* The user called fuse_destroy() before
+         * fuse_unmount(). Destroy it now. */
+        fuse_destroy_v30(fuse);
+    }
+
+    /* Remove the channel from the global list so that fuse_destroy(),
+     * if it's called after this, can know that it's already been
+     * unmounted. */
+    fuse_chan_take(idx);
+    fuse_chan_destroy(chan);
+}
+
+struct fuse *
+fuse_new_v11(int fd, int flags, const void *op, int op_version) {
+    const char *opts = NULL;
+
+    /* FUSE_DEBUG was the only option allowed in this era. */
+    if (flags & FUSE_DEBUG)
+        opts = "debug";
+
+    return fuse_new_v21(fd, opts, op, op_version, NULL);
+}
+
+void
+fuse_destroy_v11(struct fuse *fuse) {
+    struct fuse_chan* chan;
+
+    /* Search for the fuse_chan that was used while creating this
+     * struct fuse*. If it's not there it means the filesystem was
+     * first unmounted before destruction. */
+    chan = fuse_chan_find(is_same_fuse, NULL, fuse);
+    if (chan) {
+        /* The filesystem is still mounted and the user may later call
+         * fuse_unmount() on it. Can't destroy the fuse object atm. */
+        fuse_chan_set_to_be_destroyed(chan, true);
+    }
+    else {
+        /* It's already been unmounted. Safe to destroy the fuse
+         * object right now. */
+        fuse_destroy_v30(fuse);
+    }
+}
+
+int
+fuse_loop_mt_v11(struct fuse *fuse) {
+    return __fuse_loop_mt(fuse, 0);
+}
diff --git a/lib/librefuse/refuse/v11.h b/lib/librefuse/refuse/v11.h
new file mode 100644
index 000000000..12e2183c3
--- /dev/null
+++ b/lib/librefuse/refuse/v11.h
@@ -0,0 +1,92 @@
+/* $NetBSD$ */
+
+/*
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior written
+ *    permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#if !defined(_FUSE_V11_H_)
+#define _FUSE_V11_H_
+
+/* Compatibility header with FUSE 1.1 API. Only declarations that have
+ * differences between versions should be listed here. */
+
+#if !defined(FUSE_H_)
+#  error Do not include this header directly. Include <fuse.h> instead.
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Function to add an entry in a getdir() operation in FUSE < 2.2. It
+ * didn't take ino_t at that time. */
+typedef int (*fuse_dirfil_t_v11)(fuse_dirh_t handle, const char *name, int type);
+
+/* The table of file system operations in FUSE < 2.1. */
+struct fuse_operations_v11 {
+	int	(*getattr)	(const char *, struct stat *);
+	int	(*readlink)	(const char *, char *, size_t);
+	int	(*getdir)	(const char *, fuse_dirh_t, fuse_dirfil_t_v11);
+	int	(*mknod)	(const char *, mode_t, dev_t);
+	int	(*mkdir)	(const char *, mode_t);
+	int	(*unlink)	(const char *);
+	int	(*rmdir)	(const char *);
+	int	(*symlink)	(const char *, const char *);
+	int	(*rename)	(const char *, const char *);
+	int	(*link)		(const char *, const char *);
+	int	(*chmod)	(const char *, mode_t);
+	int	(*chown)	(const char *, uid_t, gid_t);
+	int	(*truncate)	(const char *, off_t);
+	int	(*utime)	(const char *, struct utimbuf *);
+	int	(*open)		(const char *, int);
+	int	(*read)		(const char *, char *, size_t, off_t);
+	int	(*write)	(const char *, const char *, size_t, off_t);
+	int	(*statfs)	(const char *, struct fuse_statfs *);
+	int	(*release)	(const char *, int);
+	int	(*fsync)	(const char *, int);
+};
+
+/* Mount a FUSE < 2.1 filesystem. */
+int fuse_mount_v11(const char *mountpoint, const char *argv[]);
+
+/* Unmount a FUSE < 2.6 filesystem. */
+void fuse_unmount_v11(const char *mountpoint);
+
+/* Create a FUSE < 2.1 filesystem. */
+struct fuse *fuse_new_v11(int fd, int flags, const void *op, int op_version);
+
+/* Destroy a FUSE < 3.0 filesystem. */
+void fuse_destroy_v11(struct fuse *);
+
+/* Multithreaded event loop for FUSE < 3.0 */
+int fuse_loop_mt_v11(struct fuse *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/librefuse/refuse/v21.c b/lib/librefuse/refuse/v21.c
new file mode 100644
index 000000000..9a861762f
--- /dev/null
+++ b/lib/librefuse/refuse/v21.c
@@ -0,0 +1,108 @@
+/* $NetBSD$ */
+
+/*
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior written
+ *    permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#if !defined(lint)
+__RCSID("$NetBSD$");
+#endif /* !lint */
+
+#include <err.h>
+#include <fuse_internal.h>
+
+/* Like FUSE 1.x, fuse_mount() is supposed to be called before
+ * fuse_new(). The argument "opts" is a comma-separated string of
+ * mount options. */
+int
+fuse_mount_v21(const char *mountpoint, const char *opts) {
+    struct fuse_args args = FUSE_ARGS_INIT(0, NULL);
+    int nominal_fd = -1;
+
+    if (opts) {
+        if (fuse_opt_add_arg(&args, "-o") != 0)
+            goto free_args;
+
+        if (fuse_opt_add_arg(&args, opts) != 0)
+            goto free_args;
+    }
+
+    nominal_fd = fuse_mount_v25(mountpoint, &args);
+
+  free_args:
+    fuse_opt_free_args(&args);
+    return nominal_fd;
+}
+
+struct fuse *
+fuse_new_v21(int fd, const char *opts, const void *op,
+             int op_version, void *user_data) {
+    struct fuse_chan* chan;
+    struct fuse* fuse;
+    int rv;
+
+    chan = fuse_chan_peek(fd);
+    if (!chan) {
+        warnx("%s: invalid channel: %d", __func__, fd);
+        return NULL;
+    }
+
+    /* "opts" is another set of options to be interpreted by the FUSE
+     * library, as opposed to the ones passed to fuse_mount() which is
+     * handled by Linux kernel. We don't have such a distinction, so
+     * insert them at argv[1] (not at the end of argv, because there
+     * may be non-options there). */
+    if (opts) {
+        if (fuse_opt_insert_arg(fuse_chan_args(chan), 1, "-o") != 0) {
+            fuse_chan_destroy(chan);
+            return NULL;
+        }
+        if (fuse_opt_insert_arg(fuse_chan_args(chan), 2, opts) != 0) {
+            fuse_chan_destroy(chan);
+            return NULL;
+        }
+    }
+
+    fuse = __fuse_new(fuse_chan_args(chan), op, op_version, user_data);
+    if (!fuse) {
+        fuse_chan_destroy(chan);
+        return NULL;
+    }
+    fuse_chan_set_fuse(chan, fuse);
+
+    /* Now we can finally mount it. Hope it's not too late. */
+    rv = fuse_mount_v30(fuse, fuse_chan_mountpoint(chan));
+    if (rv != 0) {
+        fuse_destroy_v30(fuse);
+        fuse_chan_destroy(chan);
+        return NULL;
+    }
+
+    return fuse;
+}
diff --git a/lib/librefuse/refuse/v21.h b/lib/librefuse/refuse/v21.h
new file mode 100644
index 000000000..e2f8e8228
--- /dev/null
+++ b/lib/librefuse/refuse/v21.h
@@ -0,0 +1,86 @@
+/* $NetBSD$ */
+
+/*
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior written
+ *    permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#if !defined(_FUSE_V21_H_)
+#define _FUSE_V21_H_
+
+/* Compatibility header with FUSE 2.1 API. Only declarations that have
+ * differences between versions should be listed here. */
+
+#if !defined(FUSE_H_)
+#  error Do not include this header directly. Include <fuse.h> instead.
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* The table of file system operations in FUSE 2.1. */
+struct fuse_operations_v21 {
+	int	(*getattr)		(const char *, struct stat *);
+	int	(*readlink)		(const char *, char *, size_t);
+	int	(*getdir)		(const char *, fuse_dirh_t, fuse_dirfil_t_v11);
+	int	(*mknod)		(const char *, mode_t, dev_t);
+	int	(*mkdir)		(const char *, mode_t);
+	int	(*unlink)		(const char *);
+	int	(*rmdir)		(const char *);
+	int	(*symlink)		(const char *, const char *);
+	int	(*rename)		(const char *, const char *);
+	int	(*link)			(const char *, const char *);
+	int	(*chmod)		(const char *, mode_t);
+	int	(*chown)		(const char *, uid_t, gid_t);
+	int	(*truncate)		(const char *, off_t);
+	int	(*utime)		(const char *, struct utimbuf *);
+	int	(*open)			(const char *, int);
+	int	(*read)			(const char *, char *, size_t, off_t);
+	int	(*write)		(const char *, const char *, size_t, off_t);
+	int	(*statfs)		(const char *, struct statfs *);
+	int	(*flush)		(const char *);
+	int	(*release)		(const char *, int);
+	int	(*fsync)		(const char *, int);
+	int	(*setxattr)		(const char *, const char *, const char *, size_t, int);
+	int	(*getxattr)		(const char *, const char *, char *, size_t);
+	int	(*listxattr)	(const char *, char *, size_t);
+	int	(*removexattr)	(const char *, const char *);
+};
+
+/* Mount a FUSE >= 2.1 && < 2.5 filesystem. */
+int fuse_mount_v21(const char *mountpoint, const char *opts);
+
+/* Create a FUSE 2.1 filesystem. The prototype of fuse_new() has
+ * changed over and over. */
+struct fuse *fuse_new_v21(int fd, const char *opts, const void *op,
+			  int op_version, void *user_data);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/librefuse/refuse_compat.c b/lib/librefuse/refuse/v22.c
similarity index 60%
copy from lib/librefuse/refuse_compat.c
copy to lib/librefuse/refuse/v22.c
index 35705fa1a..36d5db320 100644
--- a/lib/librefuse/refuse_compat.c
+++ b/lib/librefuse/refuse/v22.c
@@ -36,25 +36,32 @@ __RCSID("$NetBSD$");
 
 #include <fuse_internal.h>
 
-/*
- * Compatibility symbols that had existed in old versions of
- * librefuse.
- */
+struct fuse *
+fuse_setup_v22(int argc, char *argv[], const void *op, int op_version,
+               char **mountpoint, int *multithreaded, int *fd) {
+    /*
+     * This is conceptually the part of fuse_main() before the event
+     * loop. However, FUSE 2.2 fuse_setup() takes a pointer to store a
+     * channel fd, which is supposed to be obtained by calling FUSE
+     * 2.1 fuse_mount(). The problem is that we don't really have such
+     * a thing as channel fd. Luckily for us, the only valid use of
+     * the channel fd is to pass to fuse_new(), which is a part of
+     * fuse_setup() itself. So it should be okay to just put a dummy
+     * value there.
+     */
+    struct fuse* fuse;
 
-int fuse_daemonize_rev0(struct fuse* fuse) __RENAME(fuse_daemonize);
+    fuse = fuse_setup_v26(argc, argv, op, op_version, mountpoint, multithreaded, NULL);
+    if (fuse == NULL)
+        return NULL;
 
-/* librefuse once had a function fuse_daemonize() with an incompatible
- * prototype with that of FUSE. We keep ABI compatibility with
- * executables compiled against old librefuse by having this shim
- * function as a symbol "fuse_daemonize". However, we can NOT keep API
- * compatibility with old code expecting the wrong prototype. The only
- * way to work around the problem is to put "#if FUSE_H_ < 20211204"
- * into old user code. pho@ really regrets this mistake... */
-__warn_references(
-    fuse_daemonize,
-    "warning: reference to compatibility fuse_daemonize();"
-    " include <fuse.h> for correct reference")
-int
-fuse_daemonize_rev0(struct fuse* fuse __attribute__((__unused__))) {
-    return fuse_daemonize(1);
+    *fd = -1;
+    return fuse;
+}
+
+void
+fuse_teardown_v22(struct fuse *fuse,
+                  int fd __attribute__((__unused__)),
+                  char *mountpoint __attribute__((__unused__))) {
+    __fuse_teardown(fuse);
 }
diff --git a/lib/librefuse/refuse/v22.h b/lib/librefuse/refuse/v22.h
new file mode 100644
index 000000000..92c8f6c81
--- /dev/null
+++ b/lib/librefuse/refuse/v22.h
@@ -0,0 +1,89 @@
+/* $NetBSD$ */
+
+/*
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior written
+ *    permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#if !defined(_FUSE_V22_H_)
+#define _FUSE_V22_H_
+
+/* Compatibility header with FUSE 2.2 API. Only declarations that have
+ * differences between versions should be listed here. */
+
+#if !defined(FUSE_H_)
+#  error Do not include this header directly. Include <fuse.h> instead.
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Function to add an entry in a getdir() operation in FUSE >= 2.2 <
+ * 3.0. It was then removed on FUSE 3.0. */
+typedef int (*fuse_dirfil_t_v22)(fuse_dirh_t handle, const char *name, int type, ino_t ino);
+
+/* The table of file system operations in FUSE 2.2. */
+struct fuse_operations_v22 {
+	int	(*getattr)		(const char *, struct stat *);
+	int	(*readlink)		(const char *, char *, size_t);
+	int	(*getdir)		(const char *, fuse_dirh_t, fuse_dirfil_t_v22);
+	int	(*mknod)		(const char *, mode_t, dev_t);
+	int	(*mkdir)		(const char *, mode_t);
+	int	(*unlink)		(const char *);
+	int	(*rmdir)		(const char *);
+	int	(*symlink)		(const char *, const char *);
+	int	(*rename)		(const char *, const char *);
+	int	(*link)			(const char *, const char *);
+	int	(*chmod)		(const char *, mode_t);
+	int	(*chown)		(const char *, uid_t, gid_t);
+	int	(*truncate)		(const char *, off_t);
+	int	(*utime)		(const char *, struct utimbuf *);
+	int	(*open)			(const char *, struct fuse_file_info *);
+	int	(*read)			(const char *, char *, size_t, off_t, struct fuse_file_info *);
+	int	(*write)		(const char *, const char *, size_t, off_t, struct fuse_file_info *);
+	int	(*statfs)		(const char *, struct statfs *);
+	int	(*flush)		(const char *, struct fuse_file_info *);
+	int	(*release)		(const char *, struct fuse_file_info *);
+	int	(*fsync)		(const char *, int, struct fuse_file_info *);
+	int	(*setxattr)		(const char *, const char *, const char *, size_t, int);
+	int	(*getxattr)		(const char *, const char *, char *, size_t);
+	int	(*listxattr)	(const char *, char *, size_t);
+	int	(*removexattr)	(const char *, const char *);
+};
+
+/* The part of fuse_main() before the event loop. */
+struct fuse *fuse_setup_v22(int argc, char *argv[], const void *op, int op_version,
+							char **mountpoint, int *multithreaded, int *fd);
+
+/* The part of fuse_main() after the event loop. */
+void fuse_teardown_v22(struct fuse *fuse, int fd, char *mountpoint);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/librefuse/refuse/v23.h b/lib/librefuse/refuse/v23.h
new file mode 100644
index 000000000..7b47a213f
--- /dev/null
+++ b/lib/librefuse/refuse/v23.h
@@ -0,0 +1,88 @@
+/* $NetBSD$ */
+
+/*
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior written
+ *    permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#if !defined(_FUSE_V23_H_)
+#define _FUSE_V23_H_
+
+/* Compatibility header with FUSE 2.3 API. Only declarations that have
+ * differences between versions should be listed here. */
+
+#if !defined(FUSE_H_)
+#  error Do not include this header directly. Include <fuse.h> instead.
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Function to add an entry in a readdir() operation. Introduced on
+ * FUSE 2.3 and changed on FUSE 3.0. */
+typedef int (*fuse_fill_dir_t_v23)(void *, const char *, const struct stat *, off_t);
+
+/* The table of file system operations in FUSE 2.3. */
+struct fuse_operations_v23 {
+	int	(*getattr)		(const char *, struct stat *);
+	int	(*readlink)		(const char *, char *, size_t);
+	int	(*getdir)		(const char *, fuse_dirh_t, fuse_dirfil_t_v22);
+	int	(*mknod)		(const char *, mode_t, dev_t);
+	int	(*mkdir)		(const char *, mode_t);
+	int	(*unlink)		(const char *);
+	int	(*rmdir)		(const char *);
+	int	(*symlink)		(const char *, const char *);
+	int	(*rename)		(const char *, const char *);
+	int	(*link)			(const char *, const char *);
+	int	(*chmod)		(const char *, mode_t);
+	int	(*chown)		(const char *, uid_t, gid_t);
+	int	(*truncate)		(const char *, off_t);
+	int	(*utime)		(const char *, struct utimbuf *);
+	int	(*open)			(const char *, struct fuse_file_info *);
+	int	(*read)			(const char *, char *, size_t, off_t, struct fuse_file_info *);
+	int	(*write)		(const char *, const char *, size_t, off_t, struct fuse_file_info *);
+	int	(*statfs)		(const char *, struct statfs *);
+	int	(*flush)		(const char *, struct fuse_file_info *);
+	int	(*release)		(const char *, struct fuse_file_info *);
+	int	(*fsync)		(const char *, int, struct fuse_file_info *);
+	int	(*setxattr)		(const char *, const char *, const char *, size_t, int);
+	int	(*getxattr)		(const char *, const char *, char *, size_t);
+	int	(*listxattr)	(const char *, char *, size_t);
+	int	(*removexattr)	(const char *, const char *);
+	int	(*opendir)		(const char *, struct fuse_file_info *);
+	int	(*readdir)		(const char *, void *, fuse_fill_dir_t_v23, off_t, struct fuse_file_info *);
+	int	(*releasedir)	(const char *, struct fuse_file_info *);
+	int	(*fsyncdir)		(const char *, int, struct fuse_file_info *);
+	void	*(*init)	(void);
+	void	(*destroy)	(void *);
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/librefuse/refuse/v25.c b/lib/librefuse/refuse/v25.c
new file mode 100644
index 000000000..d03231762
--- /dev/null
+++ b/lib/librefuse/refuse/v25.c
@@ -0,0 +1,115 @@
+/* $NetBSD$ */
+
+/*
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior written
+ *    permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#if !defined(lint)
+__RCSID("$NetBSD$");
+#endif /* !lint */
+
+#include <err.h>
+#include <fuse_internal.h>
+#include <stdbool.h>
+#include <string.h>
+
+int
+fuse_mount_v25(const char *mountpoint, struct fuse_args *args) {
+    struct fuse_chan* chan;
+
+    /* Of course we cannot accurately emulate the semantics, so we
+     * save arguments and use them later in fuse_new(). */
+    chan = fuse_chan_new(mountpoint, args);
+    if (!chan)
+        return -1;
+
+    /* But, the return type of this function is just an int which is
+     * supposed to be a file descriptor. It's too narrow to store a
+     * pointer ofc, so... */
+    return fuse_chan_stash(chan);
+}
+
+int
+fuse_parse_cmdline_v25(struct fuse_args *args, char **mountpoint,
+                       int *multithreaded, int *foreground) {
+    struct fuse_cmdline_opts opts;
+
+    if (fuse_parse_cmdline_v30(args, &opts) != 0)
+        return -1;
+
+    *mountpoint    = opts.mountpoint; /* Transfer the ownership of the string. */
+    *multithreaded = !opts.singlethread;
+    *foreground    = opts.foreground;
+    return 0;
+}
+
+/* The interface of fuse_mount() and fuse_new() became even stranger
+ * in FUSE 2.5. Now they both take "struct fuse_args" which are
+ * expected to be identical between those two calls (which isn't
+ * explained clearly in the documentation). Our implementation just
+ * assume they are identical, because we can't recover from situations
+ * where they differ, as there is no obvious way to merge them
+ * together. */
+struct fuse *
+fuse_new_v25(int fd, struct fuse_args *args, const void *op,
+             int op_version, void *user_data) {
+    struct fuse_chan* chan;
+    struct fuse_args* mount_args;
+    bool args_differ = false;
+
+    /* But at least we can emit a warning when they differ... */
+    chan = fuse_chan_peek(fd);
+    if (!chan) {
+        warnx("%s: invalid channel: %d", __func__, fd);
+        return NULL;
+    }
+
+    mount_args = fuse_chan_args(chan);
+
+    if (mount_args->argc != args->argc) {
+        args_differ = true;
+    }
+    else {
+        int i;
+
+        for (i = 0; i < args->argc; i++) {
+            if (strcmp(mount_args->argv[i], args->argv[i]) != 0) {
+                args_differ = true;
+                break;
+            }
+        }
+    }
+
+    if (args_differ) {
+        warnx("%s: the argument vector differs from "
+              "that were passed to fuse_mount()", __func__);
+    }
+
+    return fuse_new_v21(fd, NULL, op, op_version, user_data);
+}
diff --git a/lib/librefuse/refuse/v25.h b/lib/librefuse/refuse/v25.h
new file mode 100644
index 000000000..f8d05ed1b
--- /dev/null
+++ b/lib/librefuse/refuse/v25.h
@@ -0,0 +1,99 @@
+/* $NetBSD$ */
+
+/*
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior written
+ *    permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#if !defined(_FUSE_V25_H_)
+#define _FUSE_V25_H_
+
+/* Compatibility header with FUSE 2.5 API. Only declarations that have
+ * differences between versions should be listed here. */
+
+#if !defined(FUSE_H_)
+#  error Do not include this header directly. Include <fuse.h> instead.
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* The table of file system operations in FUSE 2.5. */
+struct fuse_operations_v25 {
+	int	(*getattr)		(const char *, struct stat *);
+	int	(*readlink)		(const char *, char *, size_t);
+	int	(*getdir)		(const char *, fuse_dirh_t, fuse_dirfil_t_v22);
+	int	(*mknod)		(const char *, mode_t, dev_t);
+	int	(*mkdir)		(const char *, mode_t);
+	int	(*unlink)		(const char *);
+	int	(*rmdir)		(const char *);
+	int	(*symlink)		(const char *, const char *);
+	int	(*rename)		(const char *, const char *);
+	int	(*link)			(const char *, const char *);
+	int	(*chmod)		(const char *, mode_t);
+	int	(*chown)		(const char *, uid_t, gid_t);
+	int	(*truncate)		(const char *, off_t);
+	int	(*utime)		(const char *, struct utimbuf *);
+	int	(*open)			(const char *, struct fuse_file_info *);
+	int	(*read)			(const char *, char *, size_t, off_t, struct fuse_file_info *);
+	int	(*write)		(const char *, const char *, size_t, off_t, struct fuse_file_info *);
+	int	(*statfs)		(const char *, struct statvfs *);
+	int	(*flush)		(const char *, struct fuse_file_info *);
+	int	(*release)		(const char *, struct fuse_file_info *);
+	int	(*fsync)		(const char *, int, struct fuse_file_info *);
+	int	(*setxattr)		(const char *, const char *, const char *, size_t, int);
+	int	(*getxattr)		(const char *, const char *, char *, size_t);
+	int	(*listxattr)	(const char *, char *, size_t);
+	int	(*removexattr)	(const char *, const char *);
+	int	(*opendir)		(const char *, struct fuse_file_info *);
+	int	(*readdir)		(const char *, void *, fuse_fill_dir_t_v23, off_t, struct fuse_file_info *);
+	int	(*releasedir)	(const char *, struct fuse_file_info *);
+	int	(*fsyncdir)		(const char *, int, struct fuse_file_info *);
+	void	*(*init)	(void);
+	void	(*destroy)	(void *);
+	int	(*access)		(const char *, int);
+	int	(*create)		(const char *, mode_t, struct fuse_file_info *);
+	int	(*ftruncate)	(const char *, off_t, struct fuse_file_info *);
+	int	(*fgetattr)		(const char *, struct stat *, struct fuse_file_info *);
+};
+
+/* Mount a FUSE 2.5 filesystem. */
+int fuse_mount_v25(const char *mountpoint, struct fuse_args *args);
+
+/* Parse common options for FUSE >= 2.5 && < 3.0 */
+int fuse_parse_cmdline_v25(struct fuse_args *args, char **mountpoint,
+						   int *multithreaded, int *foreground);
+
+/* Create a FUSE 2.5 filesystem. */
+struct fuse *fuse_new_v25(int fd, struct fuse_args *args, const void *op,
+			  int op_version, void *user_data);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/librefuse/refuse/v26.c b/lib/librefuse/refuse/v26.c
new file mode 100644
index 000000000..e05161387
--- /dev/null
+++ b/lib/librefuse/refuse/v26.c
@@ -0,0 +1,113 @@
+/* $NetBSD$ */
+
+/*
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior written
+ *    permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#if !defined(lint)
+__RCSID("$NetBSD$");
+#endif /* !lint */
+
+#include <err.h>
+#include <fuse_internal.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct fuse_chan *
+fuse_mount_v26(const char *mountpoint, struct fuse_args *args) {
+    int nominal_fd;
+
+    /* fuse_mount() in FUSE 2.6 returns a fuse_chan instead of fd. We
+     * still need to store the channel in the global list, because
+     * users may call fuse_destroy() before fuse_unmount().
+     */
+    nominal_fd = fuse_mount_v25(mountpoint, args);
+    if (nominal_fd == -1)
+        return NULL;
+
+    return fuse_chan_peek(nominal_fd);
+}
+
+static bool
+is_same_channel(struct fuse_chan* chan, void* priv) {
+    return chan == (struct fuse_chan*)priv;
+}
+
+void
+fuse_unmount_v26(const char *mountpoint, struct fuse_chan *ch) {
+    /* Although the API documentation doesn't say so, fuse_unmount()
+     * from FUSE >= 2.6 < 3.0 in fact allows "ch" to be NULL. */
+    if (ch)
+        if (strcmp(mountpoint, fuse_chan_mountpoint(ch)) != 0)
+            warnx("%s: mountpoint `%s' differs from that was passed to fuse_mount(): %s",
+                  __func__, mountpoint, fuse_chan_mountpoint(ch));
+
+    /* Ask fuse_unmount_v11() to find the channel object that is
+     * already in our hand. We are going to need to know its index in
+     * the global list anyway. */
+    fuse_unmount_v11(mountpoint);
+}
+
+struct fuse *
+fuse_new_v26(struct fuse_chan *ch, struct fuse_args *args,
+             const void *op, int op_version,
+             void *user_data) {
+    int idx;
+
+    /* Although the fuse_chan object is already in our hand, we need
+     * to know its index in the global list because that's what
+     * fuse_new_v25() wants. */
+    if (fuse_chan_find(is_same_channel, &idx, ch) == NULL)
+        errx(EXIT_FAILURE, "%s: cannot find the channel index", __func__);
+
+    return fuse_new_v25(idx, args, op, op_version, user_data);
+}
+
+struct fuse *
+fuse_setup_v26(int argc, char *argv[],
+               const void *op, int op_version,
+               char **mountpoint, int *multithreaded,
+               void *user_data) {
+    struct fuse* fuse;
+    struct fuse_cmdline_opts opts;
+
+    fuse = __fuse_setup(argc, argv, op, op_version, user_data, &opts);
+    if (fuse == NULL)
+        return NULL;
+
+    *mountpoint = opts.mountpoint; /* Transfer the ownership of the string. */
+    *multithreaded = !opts.singlethread;
+    return fuse;
+}
+
+void
+fuse_teardown_v26(struct fuse *fuse,
+                  char *mountpoint __attribute__((__unused__))) {
+    __fuse_teardown(fuse);
+}
diff --git a/lib/librefuse/refuse/v26.h b/lib/librefuse/refuse/v26.h
new file mode 100644
index 000000000..035838f4c
--- /dev/null
+++ b/lib/librefuse/refuse/v26.h
@@ -0,0 +1,111 @@
+/* $NetBSD$ */
+
+/*
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior written
+ *    permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#if !defined(_FUSE_V26_H_)
+#define _FUSE_V26_H_
+
+/* Compatibility header with FUSE 2.6 API. Only declarations that have
+ * differences between versions should be listed here. */
+
+#if !defined(FUSE_H_)
+#  error Do not include this header directly. Include <fuse.h> instead.
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* The table of file system operations in FUSE >= 2.6 && < 2.8. */
+struct fuse_operations_v26 {
+	int	(*getattr)		(const char *, struct stat *);
+	int	(*readlink)		(const char *, char *, size_t);
+	int	(*getdir)		(const char *, fuse_dirh_t, fuse_dirfil_t_v22);
+	int	(*mknod)		(const char *, mode_t, dev_t);
+	int	(*mkdir)		(const char *, mode_t);
+	int	(*unlink)		(const char *);
+	int	(*rmdir)		(const char *);
+	int	(*symlink)		(const char *, const char *);
+	int	(*rename)		(const char *, const char *);
+	int	(*link)			(const char *, const char *);
+	int	(*chmod)		(const char *, mode_t);
+	int	(*chown)		(const char *, uid_t, gid_t);
+	int	(*truncate)		(const char *, off_t);
+	int	(*utime)		(const char *, struct utimbuf *);
+	int	(*open)			(const char *, struct fuse_file_info *);
+	int	(*read)			(const char *, char *, size_t, off_t, struct fuse_file_info *);
+	int	(*write)		(const char *, const char *, size_t, off_t, struct fuse_file_info *);
+	int	(*statfs)		(const char *, struct statvfs *);
+	int	(*flush)		(const char *, struct fuse_file_info *);
+	int	(*release)		(const char *, struct fuse_file_info *);
+	int	(*fsync)		(const char *, int, struct fuse_file_info *);
+	int	(*setxattr)		(const char *, const char *, const char *, size_t, int);
+	int	(*getxattr)		(const char *, const char *, char *, size_t);
+	int	(*listxattr)	(const char *, char *, size_t);
+	int	(*removexattr)	(const char *, const char *);
+	int	(*opendir)		(const char *, struct fuse_file_info *);
+	int	(*readdir)		(const char *, void *, fuse_fill_dir_t_v23, off_t, struct fuse_file_info *);
+	int	(*releasedir)	(const char *, struct fuse_file_info *);
+	int	(*fsyncdir)		(const char *, int, struct fuse_file_info *);
+	void	*(*init)	(struct fuse_conn_info *);
+	void	(*destroy)	(void *);
+	int	(*access)		(const char *, int);
+	int	(*create)		(const char *, mode_t, struct fuse_file_info *);
+	int	(*ftruncate)	(const char *, off_t, struct fuse_file_info *);
+	int	(*fgetattr)		(const char *, struct stat *, struct fuse_file_info *);
+	int	(*lock)			(const char *, struct fuse_file_info *, int, struct flock *);
+	int	(*utimens)		(const char *, const struct timespec[2]);
+	int	(*bmap)			(const char *, size_t , uint64_t *);
+};
+
+/* Mount a FUSE >= 2.6 && < 3.0 filesystem. */
+struct fuse_chan *fuse_mount_v26(const char *mountpoint, struct fuse_args *args);
+
+/* Unmount a FUSE >= 2.6 && < 3.0 filesystem. */
+void fuse_unmount_v26(const char *mountpoint, struct fuse_chan *ch);
+
+/* Create a FUSE >= 2.6 && < 3.0 filesystem. */
+struct fuse *fuse_new_v26(struct fuse_chan *ch, struct fuse_args *args,
+						  const void *op, int op_version,
+						  void *user_data);
+
+/* The part of fuse_main() before the event loop. */
+struct fuse *fuse_setup_v26(int argc, char *argv[],
+							const void *op, int op_version,
+							char **mountpoint, int *multithreaded,
+							void *user_data);
+
+/* The part of fuse_main() after the event loop. */
+void fuse_teardown_v26(struct fuse *fuse, char *mountpoint);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/librefuse/refuse/v28.h b/lib/librefuse/refuse/v28.h
new file mode 100644
index 000000000..7479d307e
--- /dev/null
+++ b/lib/librefuse/refuse/v28.h
@@ -0,0 +1,95 @@
+/* $NetBSD$ */
+
+/*
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior written
+ *    permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#if !defined(_FUSE_V28_H_)
+#define _FUSE_V28_H_
+
+/* Compatibility header with FUSE 2.8 API. Only declarations that have
+ * differences between versions should be listed here. */
+
+#if !defined(FUSE_H_)
+#  error Do not include this header directly. Include <fuse.h> instead.
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* The table of file system operations in FUSE 2.8. */
+struct fuse_operations_v28 {
+	int	(*getattr)		(const char *, struct stat *);
+	int	(*readlink)		(const char *, char *, size_t);
+	int	(*getdir)		(const char *, fuse_dirh_t, fuse_dirfil_t_v22);
+	int	(*mknod)		(const char *, mode_t, dev_t);
+	int	(*mkdir)		(const char *, mode_t);
+	int	(*unlink)		(const char *);
+	int	(*rmdir)		(const char *);
+	int	(*symlink)		(const char *, const char *);
+	int	(*rename)		(const char *, const char *);
+	int	(*link)			(const char *, const char *);
+	int	(*chmod)		(const char *, mode_t);
+	int	(*chown)		(const char *, uid_t, gid_t);
+	int	(*truncate)		(const char *, off_t);
+	int	(*utime)		(const char *, struct utimbuf *);
+	int	(*open)			(const char *, struct fuse_file_info *);
+	int	(*read)			(const char *, char *, size_t, off_t, struct fuse_file_info *);
+	int	(*write)		(const char *, const char *, size_t, off_t, struct fuse_file_info *);
+	int	(*statfs)		(const char *, struct statvfs *);
+	int	(*flush)		(const char *, struct fuse_file_info *);
+	int	(*release)		(const char *, struct fuse_file_info *);
+	int	(*fsync)		(const char *, int, struct fuse_file_info *);
+	int	(*setxattr)		(const char *, const char *, const char *, size_t, int);
+	int	(*getxattr)		(const char *, const char *, char *, size_t);
+	int	(*listxattr)	(const char *, char *, size_t);
+	int	(*removexattr)	(const char *, const char *);
+	int	(*opendir)		(const char *, struct fuse_file_info *);
+	int	(*readdir)		(const char *, void *, fuse_fill_dir_t_v23, off_t, struct fuse_file_info *);
+	int	(*releasedir)	(const char *, struct fuse_file_info *);
+	int	(*fsyncdir)		(const char *, int, struct fuse_file_info *);
+	void	*(*init)	(struct fuse_conn_info *);
+	void	(*destroy)	(void *);
+	int	(*access)		(const char *, int);
+	int	(*create)		(const char *, mode_t, struct fuse_file_info *);
+	int	(*ftruncate)	(const char *, off_t, struct fuse_file_info *);
+	int	(*fgetattr)		(const char *, struct stat *, struct fuse_file_info *);
+	int	(*lock)			(const char *, struct fuse_file_info *, int, struct flock *);
+	int	(*utimens)		(const char *, const struct timespec[2]);
+	int	(*bmap)			(const char *, size_t , uint64_t *);
+	unsigned int flag_nullpath_ok:1;
+	unsigned int flag_reserved:31;
+	int	(*ioctl)		(const char *, int, void *, struct fuse_file_info *, unsigned int, void *);
+	int	(*poll)			(const char *, struct fuse_file_info *, struct fuse_pollhandle *, unsigned *);
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/librefuse/refuse/v29.h b/lib/librefuse/refuse/v29.h
new file mode 100644
index 000000000..afc0a9bea
--- /dev/null
+++ b/lib/librefuse/refuse/v29.h
@@ -0,0 +1,101 @@
+/* $NetBSD$ */
+
+/*
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior written
+ *    permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#if !defined(_FUSE_V29_H_)
+#define _FUSE_V29_H_
+
+/* Compatibility header with FUSE 2.9 API. Only declarations that have
+ * differences between versions should be listed here. */
+
+#if !defined(FUSE_H_)
+#  error Do not include this header directly. Include <fuse.h> instead.
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* The table of file system operations in FUSE 2.9. */
+struct fuse_operations_v29 {
+	int	(*getattr)		(const char *, struct stat *);
+	int	(*readlink)		(const char *, char *, size_t);
+	int	(*getdir)		(const char *, fuse_dirh_t, fuse_dirfil_t_v22);
+	int	(*mknod)		(const char *, mode_t, dev_t);
+	int	(*mkdir)		(const char *, mode_t);
+	int	(*unlink)		(const char *);
+	int	(*rmdir)		(const char *);
+	int	(*symlink)		(const char *, const char *);
+	int	(*rename)		(const char *, const char *);
+	int	(*link)			(const char *, const char *);
+	int	(*chmod)		(const char *, mode_t);
+	int	(*chown)		(const char *, uid_t, gid_t);
+	int	(*truncate)		(const char *, off_t);
+	int	(*utime)		(const char *, struct utimbuf *);
+	int	(*open)			(const char *, struct fuse_file_info *);
+	int	(*read)			(const char *, char *, size_t, off_t, struct fuse_file_info *);
+	int	(*write)		(const char *, const char *, size_t, off_t, struct fuse_file_info *);
+	int	(*statfs)		(const char *, struct statvfs *);
+	int	(*flush)		(const char *, struct fuse_file_info *);
+	int	(*release)		(const char *, struct fuse_file_info *);
+	int	(*fsync)		(const char *, int, struct fuse_file_info *);
+	int	(*setxattr)		(const char *, const char *, const char *, size_t, int);
+	int	(*getxattr)		(const char *, const char *, char *, size_t);
+	int	(*listxattr)	(const char *, char *, size_t);
+	int	(*removexattr)	(const char *, const char *);
+	int	(*opendir)		(const char *, struct fuse_file_info *);
+	int	(*readdir)		(const char *, void *, fuse_fill_dir_t_v23, off_t, struct fuse_file_info *);
+	int	(*releasedir)	(const char *, struct fuse_file_info *);
+	int	(*fsyncdir)		(const char *, int, struct fuse_file_info *);
+	void	*(*init)	(struct fuse_conn_info *);
+	void	(*destroy)	(void *);
+	int	(*access)		(const char *, int);
+	int	(*create)		(const char *, mode_t, struct fuse_file_info *);
+	int	(*ftruncate)	(const char *, off_t, struct fuse_file_info *);
+	int	(*fgetattr)		(const char *, struct stat *, struct fuse_file_info *);
+	int	(*lock)			(const char *, struct fuse_file_info *, int, struct flock *);
+	int	(*utimens)		(const char *, const struct timespec[2]);
+	int	(*bmap)			(const char *, size_t , uint64_t *);
+	unsigned int flag_nullpath_ok:1;
+	unsigned int flag_nopath:1;
+	unsigned int flag_utime_omit_ok:1;
+	unsigned int flag_reserved:29;
+	int	(*ioctl)		(const char *, int, void *, struct fuse_file_info *, unsigned int, void *);
+	int	(*poll)			(const char *, struct fuse_file_info *, struct fuse_pollhandle *, unsigned *);
+	int	(*write_buf)	(const char *, struct fuse_bufvec *, off_t, struct fuse_file_info *);
+	int	(*read_buf)		(const char *, struct fuse_bufvec **, size_t, off_t, struct fuse_file_info *);
+	int	(*flock)		(const char *, struct fuse_file_info *, int);
+	int	(*fallocate)	(const char *, int, off_t, off_t, struct fuse_file_info *);
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/librefuse/refuse_compat.c b/lib/librefuse/refuse/v30.c
similarity index 65%
copy from lib/librefuse/refuse_compat.c
copy to lib/librefuse/refuse/v30.c
index 35705fa1a..2f2889edd 100644
--- a/lib/librefuse/refuse_compat.c
+++ b/lib/librefuse/refuse/v30.c
@@ -36,25 +36,37 @@ __RCSID("$NetBSD$");
 
 #include <fuse_internal.h>
 
-/*
- * Compatibility symbols that had existed in old versions of
- * librefuse.
- */
+int
+fuse_mount_v30(struct fuse *fuse, const char *mountpoint) {
+    return __fuse_mount(fuse, mountpoint);
+}
+
+void
+fuse_unmount_v30(struct fuse *fuse) {
+    __fuse_unmount(fuse);
+}
+
+struct fuse *
+fuse_new_v30(struct fuse_args *args,
+             const void *op, int op_version, void *user_data) {
+    return __fuse_new(args, op, op_version, user_data);
+}
+
+void
+fuse_destroy_v30(struct fuse *fuse) {
+    return __fuse_destroy(fuse);
+}
 
-int fuse_daemonize_rev0(struct fuse* fuse) __RENAME(fuse_daemonize);
+int
+fuse_loop_mt_v30(struct fuse *fuse, int clone_fd) {
+    struct fuse_loop_config config = {
+        .clone_fd         = clone_fd,
+        .max_idle_threads = 10 /* The default value when "config" is NULL. */
+    };
+    return __fuse_loop_mt(fuse, &config);
+}
 
-/* librefuse once had a function fuse_daemonize() with an incompatible
- * prototype with that of FUSE. We keep ABI compatibility with
- * executables compiled against old librefuse by having this shim
- * function as a symbol "fuse_daemonize". However, we can NOT keep API
- * compatibility with old code expecting the wrong prototype. The only
- * way to work around the problem is to put "#if FUSE_H_ < 20211204"
- * into old user code. pho@ really regrets this mistake... */
-__warn_references(
-    fuse_daemonize,
-    "warning: reference to compatibility fuse_daemonize();"
-    " include <fuse.h> for correct reference")
 int
-fuse_daemonize_rev0(struct fuse* fuse __attribute__((__unused__))) {
-    return fuse_daemonize(1);
+fuse_parse_cmdline_v30(struct fuse_args *args, struct fuse_cmdline_opts *opts) {
+    return __fuse_parse_cmdline(args, opts);
 }
diff --git a/lib/librefuse/refuse/v30.h b/lib/librefuse/refuse/v30.h
new file mode 100644
index 000000000..ff5398b35
--- /dev/null
+++ b/lib/librefuse/refuse/v30.h
@@ -0,0 +1,129 @@
+/* $NetBSD$ */
+
+/*
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior written
+ *    permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#if !defined(_FUSE_V30_H_)
+#define _FUSE_V30_H_
+
+/* Compatibility header with FUSE 3.0 API. Only declarations that have
+ * differences between versions should be listed here. */
+
+#if !defined(FUSE_H_)
+#  error Do not include this header directly. Include <fuse.h> instead.
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Defined in <fuse_lowlevel.h> */
+struct fuse_cmdline_opts;
+
+/* Function to add an entry in a readdir() operation. Introduced on
+ * FUSE 2.3 and changed on FUSE 3.0. */
+typedef int (*fuse_fill_dir_t_v30)(
+	void *, const char *, const struct stat *, off_t, enum fuse_fill_dir_flags flags);
+
+/* The table of file system operations in FUSE 3.0. */
+struct fuse_operations_v30 {
+	int	(*getattr)		(const char *, struct stat *, struct fuse_file_info *fi);
+	int	(*readlink)		(const char *, char *, size_t);
+	/* getdir has been removed as of FUSE 3.0. */
+	int	(*mknod)		(const char *, mode_t, dev_t);
+	int	(*mkdir)		(const char *, mode_t);
+	int	(*unlink)		(const char *);
+	int	(*rmdir)		(const char *);
+	int	(*symlink)		(const char *, const char *);
+	int	(*rename)		(const char *, const char *, unsigned int);
+	int	(*link)			(const char *, const char *);
+	int	(*chmod)		(const char *, mode_t, struct fuse_file_info *fi);
+	int	(*chown)		(const char *, uid_t, gid_t, struct fuse_file_info *fi);
+	int	(*truncate)		(const char *, off_t, struct fuse_file_info *fi);
+	/* utime has been removed as of FUSE 3.0. */
+	int	(*open)			(const char *, struct fuse_file_info *);
+	int	(*read)			(const char *, char *, size_t, off_t, struct fuse_file_info *);
+	int	(*write)		(const char *, const char *, size_t, off_t, struct fuse_file_info *);
+	int	(*statfs)		(const char *, struct statvfs *);
+	int	(*flush)		(const char *, struct fuse_file_info *);
+	int	(*release)		(const char *, struct fuse_file_info *);
+	int	(*fsync)		(const char *, int, struct fuse_file_info *);
+	int	(*setxattr)		(const char *, const char *, const char *, size_t, int);
+	int	(*getxattr)		(const char *, const char *, char *, size_t);
+	int	(*listxattr)	(const char *, char *, size_t);
+	int	(*removexattr)	(const char *, const char *);
+	int	(*opendir)		(const char *, struct fuse_file_info *);
+	int	(*readdir)		(const char *, void *, fuse_fill_dir_t_v30, off_t, struct fuse_file_info *, enum fuse_readdir_flags);
+	int	(*releasedir)	(const char *, struct fuse_file_info *);
+	int	(*fsyncdir)		(const char *, int, struct fuse_file_info *);
+	void	*(*init)	(struct fuse_conn_info *, struct fuse_config *);
+	void	(*destroy)	(void *);
+	int	(*access)		(const char *, int);
+	int	(*create)		(const char *, mode_t, struct fuse_file_info *);
+	/* ftruncate has been removed as of FUSE 3.0. */
+	/* fgetattr has been removed as of FUSE 3.0. */
+	int	(*lock)			(const char *, struct fuse_file_info *, int, struct flock *);
+	int	(*utimens)		(const char *, const struct timespec[2], struct fuse_file_info *);
+	int	(*bmap)			(const char *, size_t , uint64_t *);
+	/* flag_* have been removed as of FUSE 3.0. */
+	int	(*ioctl)		(const char *, int, void *, struct fuse_file_info *, unsigned int, void *);
+	int	(*poll)			(const char *, struct fuse_file_info *, struct fuse_pollhandle *, unsigned *);
+	int	(*write_buf)	(const char *, struct fuse_bufvec *, off_t, struct fuse_file_info *);
+	int	(*read_buf)		(const char *, struct fuse_bufvec **, size_t, off_t, struct fuse_file_info *);
+	int	(*flock)		(const char *, struct fuse_file_info *, int);
+	int	(*fallocate)	(const char *, int, off_t, off_t, struct fuse_file_info *);
+};
+
+/* Mount a FUSE 3.0 filesystem. */
+int fuse_mount_v30(struct fuse *fuse, const char *mountpoint);
+
+/* Unmount a FUSE 3.0 filesystem. */
+void fuse_unmount_v30(struct fuse *);
+
+/* Create a FUSE 3.0 filesystem. */
+struct fuse *fuse_new_v30(struct fuse_args *args,
+						  const void *op, int op_version, void *user_data);
+
+/* Destroy a FUSE 3.0 filesystem. */
+void fuse_destroy_v30(struct fuse *fuse);
+
+/* Multithreaded event loop for FUSE >= 3.0 < 3.2. Of course, every
+ * time they change a function prototype to take a single extra
+ * option, they change it again so it takes a struct instead. Every
+ * single time. */
+int fuse_loop_mt_v30(struct fuse *fuse, int clone_fd);
+
+/* Parse common options for FUSE 3.0 */
+int fuse_parse_cmdline_v30(struct fuse_args *args,
+						   struct fuse_cmdline_opts *opts);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/librefuse/fuse_internal.h b/lib/librefuse/refuse/v32.c
similarity index 68%
copy from lib/librefuse/fuse_internal.h
copy to lib/librefuse/refuse/v32.c
index f658dccdf..c789f3298 100644
--- a/lib/librefuse/fuse_internal.h
+++ b/lib/librefuse/refuse/v32.c
@@ -1,4 +1,4 @@
-/* $NetBSD: fuse_internal.h,v 1.1 2021/12/04 06:42:39 pho Exp $ */
+/* $NetBSD$ */
 
 /*
  * Copyright (c) 2021 The NetBSD Foundation, Inc.
@@ -28,35 +28,15 @@
  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
-#if !defined(FUSE_INTERNAL_H)
-#define FUSE_INTERNAL_H
 
-/* We emit a compiler warning for anyone including <fuse.h> without
- * defining FUSE_USE_VERSION. Exempt ourselves here, or we'll be
- * warned too. */
-#define _REFUSE_IMPLEMENTATION_
-
-#include <fuse.h>
-#include <fuse_lowlevel.h>
 #include <sys/cdefs.h>
+#if !defined(lint)
+__RCSID("$NetBSD$");
+#endif /* !lint */
 
-#ifdef __cplusplus
-extern "C" {
-#endif
+#include <fuse_internal.h>
 
-enum refuse_show_help_variant {
-	REFUSE_SHOW_HELP_FULL		= 1,
-	REFUSE_SHOW_HELP_NO_HEADER	= 2,
-};
-
-/* Internal functions, hidden from users */
-__BEGIN_HIDDEN_DECLS
-int __fuse_set_signal_handlers(struct fuse* fuse);
-int __fuse_remove_signal_handlers(struct fuse* fuse);
-__END_HIDDEN_DECLS
-
-#ifdef __cplusplus
+int
+fuse_loop_mt_v32(struct fuse *fuse, struct fuse_loop_config *config) {
+    return __fuse_loop_mt(fuse, config);
 }
-#endif
-
-#endif
diff --git a/lib/librefuse/fuse_internal.h b/lib/librefuse/refuse/v32.h
similarity index 70%
copy from lib/librefuse/fuse_internal.h
copy to lib/librefuse/refuse/v32.h
index f658dccdf..f01d4aa7b 100644
--- a/lib/librefuse/fuse_internal.h
+++ b/lib/librefuse/refuse/v32.h
@@ -1,4 +1,4 @@
-/* $NetBSD: fuse_internal.h,v 1.1 2021/12/04 06:42:39 pho Exp $ */
+/* $NetBSD$ */
 
 /*
  * Copyright (c) 2021 The NetBSD Foundation, Inc.
@@ -28,32 +28,22 @@
  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
-#if !defined(FUSE_INTERNAL_H)
-#define FUSE_INTERNAL_H
+#if !defined(_FUSE_V32_H_)
+#define _FUSE_V32_H_
 
-/* We emit a compiler warning for anyone including <fuse.h> without
- * defining FUSE_USE_VERSION. Exempt ourselves here, or we'll be
- * warned too. */
-#define _REFUSE_IMPLEMENTATION_
+/* Compatibility header with FUSE 3.2 API. Only declarations that have
+ * differences between versions should be listed here. */
 
-#include <fuse.h>
-#include <fuse_lowlevel.h>
-#include <sys/cdefs.h>
+#if !defined(FUSE_H_)
+#  error Do not include this header directly. Include <fuse.h> instead.
+#endif
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
-enum refuse_show_help_variant {
-	REFUSE_SHOW_HELP_FULL		= 1,
-	REFUSE_SHOW_HELP_NO_HEADER	= 2,
-};
-
-/* Internal functions, hidden from users */
-__BEGIN_HIDDEN_DECLS
-int __fuse_set_signal_handlers(struct fuse* fuse);
-int __fuse_remove_signal_handlers(struct fuse* fuse);
-__END_HIDDEN_DECLS
+/* Multithreaded event loop for FUSE 3.2 */
+int fuse_loop_mt_v32(struct fuse *fuse, struct fuse_loop_config *config);
 
 #ifdef __cplusplus
 }
diff --git a/lib/librefuse/refuse/v34.h b/lib/librefuse/refuse/v34.h
new file mode 100644
index 000000000..b3dd72214
--- /dev/null
+++ b/lib/librefuse/refuse/v34.h
@@ -0,0 +1,95 @@
+/* $NetBSD$ */
+
+/*
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior written
+ *    permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#if !defined(_FUSE_V34_H_)
+#define _FUSE_V34_H_
+
+/* Compatibility header with FUSE 3.4 API. Only declarations that have
+ * differences between versions should be listed here. */
+
+#if !defined(FUSE_H_)
+#  error Do not include this header directly. Include <fuse.h> instead.
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* The table of file system operations in FUSE 3.4. */
+struct fuse_operations_v34 {
+	int	(*getattr)		(const char *, struct stat *, struct fuse_file_info *fi);
+	int	(*readlink)		(const char *, char *, size_t);
+	int	(*mknod)		(const char *, mode_t, dev_t);
+	int	(*mkdir)		(const char *, mode_t);
+	int	(*unlink)		(const char *);
+	int	(*rmdir)		(const char *);
+	int	(*symlink)		(const char *, const char *);
+	int	(*rename)		(const char *, const char *, unsigned int);
+	int	(*link)			(const char *, const char *);
+	int	(*chmod)		(const char *, mode_t, struct fuse_file_info *fi);
+	int	(*chown)		(const char *, uid_t, gid_t, struct fuse_file_info *fi);
+	int	(*truncate)		(const char *, off_t, struct fuse_file_info *fi);
+	int	(*open)			(const char *, struct fuse_file_info *);
+	int	(*read)			(const char *, char *, size_t, off_t, struct fuse_file_info *);
+	int	(*write)		(const char *, const char *, size_t, off_t, struct fuse_file_info *);
+	int	(*statfs)		(const char *, struct statvfs *);
+	int	(*flush)		(const char *, struct fuse_file_info *);
+	int	(*release)		(const char *, struct fuse_file_info *);
+	int	(*fsync)		(const char *, int, struct fuse_file_info *);
+	int	(*setxattr)		(const char *, const char *, const char *, size_t, int);
+	int	(*getxattr)		(const char *, const char *, char *, size_t);
+	int	(*listxattr)	(const char *, char *, size_t);
+	int	(*removexattr)	(const char *, const char *);
+	int	(*opendir)		(const char *, struct fuse_file_info *);
+	int	(*readdir)		(const char *, void *, fuse_fill_dir_t_v30, off_t, struct fuse_file_info *, enum fuse_readdir_flags);
+	int	(*releasedir)	(const char *, struct fuse_file_info *);
+	int	(*fsyncdir)		(const char *, int, struct fuse_file_info *);
+	void	*(*init)	(struct fuse_conn_info *, struct fuse_config *);
+	void	(*destroy)	(void *);
+	int	(*access)		(const char *, int);
+	int	(*create)		(const char *, mode_t, struct fuse_file_info *);
+	int	(*lock)			(const char *, struct fuse_file_info *, int, struct flock *);
+	int	(*utimens)		(const char *, const struct timespec[2], struct fuse_file_info *);
+	int	(*bmap)			(const char *, size_t , uint64_t *);
+	int	(*ioctl)		(const char *, int, void *, struct fuse_file_info *, unsigned int, void *);
+	int	(*poll)			(const char *, struct fuse_file_info *, struct fuse_pollhandle *, unsigned *);
+	int	(*write_buf)	(const char *, struct fuse_bufvec *, off_t, struct fuse_file_info *);
+	int	(*read_buf)		(const char *, struct fuse_bufvec **, size_t, off_t, struct fuse_file_info *);
+	int	(*flock)		(const char *, struct fuse_file_info *, int);
+	int	(*fallocate)	(const char *, int, off_t, off_t, struct fuse_file_info *);
+	/* copy_file_range appeared on FUSE 3.4. */
+	ssize_t	(*copy_file_range)	(const char *, struct fuse_file_info *, off_t, const char *, struct fuse_file_info *, off_t, size_t, int);
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/librefuse/refuse/v35.h b/lib/librefuse/refuse/v35.h
new file mode 100644
index 000000000..7c53af42f
--- /dev/null
+++ b/lib/librefuse/refuse/v35.h
@@ -0,0 +1,95 @@
+/* $NetBSD$ */
+
+/*
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior written
+ *    permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#if !defined(_FUSE_V35_H_)
+#define _FUSE_V35_H_
+
+/* Compatibility header with FUSE 3.5 API. Only declarations that have
+ * differences between versions should be listed here. */
+
+#if !defined(FUSE_H_)
+#  error Do not include this header directly. Include <fuse.h> instead.
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* The table of file system operations in FUSE 3.5. */
+struct fuse_operations_v35 {
+	int	(*getattr)		(const char *, struct stat *, struct fuse_file_info *fi);
+	int	(*readlink)		(const char *, char *, size_t);
+	int	(*mknod)		(const char *, mode_t, dev_t);
+	int	(*mkdir)		(const char *, mode_t);
+	int	(*unlink)		(const char *);
+	int	(*rmdir)		(const char *);
+	int	(*symlink)		(const char *, const char *);
+	int	(*rename)		(const char *, const char *, unsigned int);
+	int	(*link)			(const char *, const char *);
+	int	(*chmod)		(const char *, mode_t, struct fuse_file_info *fi);
+	int	(*chown)		(const char *, uid_t, gid_t, struct fuse_file_info *fi);
+	int	(*truncate)		(const char *, off_t, struct fuse_file_info *fi);
+	int	(*open)			(const char *, struct fuse_file_info *);
+	int	(*read)			(const char *, char *, size_t, off_t, struct fuse_file_info *);
+	int	(*write)		(const char *, const char *, size_t, off_t, struct fuse_file_info *);
+	int	(*statfs)		(const char *, struct statvfs *);
+	int	(*flush)		(const char *, struct fuse_file_info *);
+	int	(*release)		(const char *, struct fuse_file_info *);
+	int	(*fsync)		(const char *, int, struct fuse_file_info *);
+	int	(*setxattr)		(const char *, const char *, const char *, size_t, int);
+	int	(*getxattr)		(const char *, const char *, char *, size_t);
+	int	(*listxattr)	(const char *, char *, size_t);
+	int	(*removexattr)	(const char *, const char *);
+	int	(*opendir)		(const char *, struct fuse_file_info *);
+	int	(*readdir)		(const char *, void *, fuse_fill_dir_t_v30, off_t, struct fuse_file_info *, enum fuse_readdir_flags);
+	int	(*releasedir)	(const char *, struct fuse_file_info *);
+	int	(*fsyncdir)		(const char *, int, struct fuse_file_info *);
+	void	*(*init)	(struct fuse_conn_info *, struct fuse_config *);
+	void	(*destroy)	(void *);
+	int	(*access)		(const char *, int);
+	int	(*create)		(const char *, mode_t, struct fuse_file_info *);
+	int	(*lock)			(const char *, struct fuse_file_info *, int, struct flock *);
+	int	(*utimens)		(const char *, const struct timespec[2], struct fuse_file_info *);
+	int	(*bmap)			(const char *, size_t , uint64_t *);
+	/* ioctl() changed on FUSE 3.5. */
+	int	(*ioctl)		(const char *, unsigned int, void *, struct fuse_file_info *, unsigned int, void *);
+	int	(*poll)			(const char *, struct fuse_file_info *, struct fuse_pollhandle *, unsigned *);
+	int	(*write_buf)	(const char *, struct fuse_bufvec *, off_t, struct fuse_file_info *);
+	int	(*read_buf)		(const char *, struct fuse_bufvec **, size_t, off_t, struct fuse_file_info *);
+	int	(*flock)		(const char *, struct fuse_file_info *, int);
+	int	(*fallocate)	(const char *, int, off_t, off_t, struct fuse_file_info *);
+	ssize_t	(*copy_file_range)	(const char *, struct fuse_file_info *, off_t, const char *, struct fuse_file_info *, off_t, size_t, int);
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/librefuse/refuse/v38.h b/lib/librefuse/refuse/v38.h
new file mode 100644
index 000000000..d87360063
--- /dev/null
+++ b/lib/librefuse/refuse/v38.h
@@ -0,0 +1,96 @@
+/* $NetBSD$ */
+
+/*
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior written
+ *    permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#if !defined(_FUSE_V38_H_)
+#define _FUSE_V38_H_
+
+/* Compatibility header with FUSE 3.8 API. Only declarations that have
+ * differences between versions should be listed here. */
+
+#if !defined(FUSE_H_)
+#  error Do not include this header directly. Include <fuse.h> instead.
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* The table of file system operations in FUSE 3.8. */
+struct fuse_operations_v38 {
+	int	(*getattr)		(const char *, struct stat *, struct fuse_file_info *fi);
+	int	(*readlink)		(const char *, char *, size_t);
+	int	(*mknod)		(const char *, mode_t, dev_t);
+	int	(*mkdir)		(const char *, mode_t);
+	int	(*unlink)		(const char *);
+	int	(*rmdir)		(const char *);
+	int	(*symlink)		(const char *, const char *);
+	int	(*rename)		(const char *, const char *, unsigned int);
+	int	(*link)			(const char *, const char *);
+	int	(*chmod)		(const char *, mode_t, struct fuse_file_info *fi);
+	int	(*chown)		(const char *, uid_t, gid_t, struct fuse_file_info *fi);
+	int	(*truncate)		(const char *, off_t, struct fuse_file_info *fi);
+	int	(*open)			(const char *, struct fuse_file_info *);
+	int	(*read)			(const char *, char *, size_t, off_t, struct fuse_file_info *);
+	int	(*write)		(const char *, const char *, size_t, off_t, struct fuse_file_info *);
+	int	(*statfs)		(const char *, struct statvfs *);
+	int	(*flush)		(const char *, struct fuse_file_info *);
+	int	(*release)		(const char *, struct fuse_file_info *);
+	int	(*fsync)		(const char *, int, struct fuse_file_info *);
+	int	(*setxattr)		(const char *, const char *, const char *, size_t, int);
+	int	(*getxattr)		(const char *, const char *, char *, size_t);
+	int	(*listxattr)	(const char *, char *, size_t);
+	int	(*removexattr)	(const char *, const char *);
+	int	(*opendir)		(const char *, struct fuse_file_info *);
+	int	(*readdir)		(const char *, void *, fuse_fill_dir_t_v30, off_t, struct fuse_file_info *, enum fuse_readdir_flags);
+	int	(*releasedir)	(const char *, struct fuse_file_info *);
+	int	(*fsyncdir)		(const char *, int, struct fuse_file_info *);
+	void	*(*init)	(struct fuse_conn_info *, struct fuse_config *);
+	void	(*destroy)	(void *);
+	int	(*access)		(const char *, int);
+	int	(*create)		(const char *, mode_t, struct fuse_file_info *);
+	int	(*lock)			(const char *, struct fuse_file_info *, int, struct flock *);
+	int	(*utimens)		(const char *, const struct timespec[2], struct fuse_file_info *);
+	int	(*bmap)			(const char *, size_t , uint64_t *);
+	int	(*ioctl)		(const char *, unsigned int, void *, struct fuse_file_info *, unsigned int, void *);
+	int	(*poll)			(const char *, struct fuse_file_info *, struct fuse_pollhandle *, unsigned *);
+	int	(*write_buf)	(const char *, struct fuse_bufvec *, off_t, struct fuse_file_info *);
+	int	(*read_buf)		(const char *, struct fuse_bufvec **, size_t, off_t, struct fuse_file_info *);
+	int	(*flock)		(const char *, struct fuse_file_info *, int);
+	int	(*fallocate)	(const char *, int, off_t, off_t, struct fuse_file_info *);
+	ssize_t	(*copy_file_range)	(const char *, struct fuse_file_info *, off_t, const char *, struct fuse_file_info *, off_t, size_t, int);
+	/* lseek() appeared on FUSE 3.8. */
+	off_t	(*lseek)	(const char *, off_t, int, struct fuse_file_info *);
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/librefuse/refuse_compat.c b/lib/librefuse/refuse_compat.c
index 35705fa1a..7d100189c 100644
--- a/lib/librefuse/refuse_compat.c
+++ b/lib/librefuse/refuse_compat.c
@@ -35,13 +35,33 @@ __RCSID("$NetBSD$");
 #endif /* !lint */
 
 #include <fuse_internal.h>
+#include <string.h>
 
 /*
  * Compatibility symbols that had existed in old versions of
  * librefuse.
  */
 
+struct fuse_cmdline_opts_rev0 {
+	int singlethread;
+	int foreground;
+	int debug;
+	int nodefault_fsname;
+	char *mountpoint;
+	int show_version;
+	int show_help;
+};
+
 int fuse_daemonize_rev0(struct fuse* fuse) __RENAME(fuse_daemonize);
+int fuse_mount(struct fuse *fuse, const char *mountpoint);
+int fuse_main_real(int argc, char **argv, const struct fuse_operations_v26 *op,
+                   size_t size, void *user_data);
+struct fuse *fuse_new(struct fuse_args *args, const void *op, size_t op_size,
+                      void *user_data);
+void fuse_destroy(struct fuse* fuse);
+int fuse_parse_cmdline(struct fuse_args *args, struct fuse_cmdline_opts_rev0 *opts);
+void fuse_unmount(struct fuse* fuse);
+void fuse_unmount_compat22(const char *mountpoint);
 
 /* librefuse once had a function fuse_daemonize() with an incompatible
  * prototype with that of FUSE. We keep ABI compatibility with
@@ -58,3 +78,87 @@ int
 fuse_daemonize_rev0(struct fuse* fuse __attribute__((__unused__))) {
     return fuse_daemonize(1);
 }
+
+/* librefuse once had a function fuse_main_real() which was specific
+ * to FUSE 2.6 API. */
+__warn_references(
+    fuse_main_real,
+    "warning: reference to compatibility fuse_main_real();"
+    " include <fuse.h> for correct reference")
+int
+fuse_main_real(int argc, char **argv, const struct fuse_operations_v26 *op,
+               size_t size __attribute__((__unused__)), void *user_data) {
+    return __fuse_main(argc, argv, op, 26, user_data);
+}
+
+/* librefuse once had a function fuse_mount() for FUSE 3.0 but without
+ * a version postfix. */
+__warn_references(
+    fuse_mount,
+    "warning: reference to compatibility fuse_mount();"
+    " include <fuse.h> for correct reference")
+int
+fuse_mount(struct fuse *fuse, const char *mountpoint) {
+    return fuse_mount_v30(fuse, mountpoint);
+}
+
+/* librefuse once had a function fuse_new() for FUSE 3.0 but without a
+ * version postfix. */
+__warn_references(
+    fuse_new,
+    "warning: reference to compatibility fuse_new();"
+    " include <fuse.h> for correct reference")
+struct fuse *
+fuse_new(struct fuse_args *args, const void *op,
+         size_t op_size __attribute__((__unused__)),
+         void *user_data) {
+    return fuse_new_v30(args, op, 30, user_data);
+}
+
+/* librefuse once had a function fuse_destroy() for FUSE 3.0 but
+ * without a version postfix. */
+__warn_references(
+    fuse_new,
+    "warning: reference to compatibility fuse_destroy();"
+    " include <fuse.h> for correct reference")
+void
+fuse_destroy(struct fuse *fuse) {
+    fuse_destroy_v30(fuse);
+}
+
+/* librefuse once had a function fuse_parse_cmdline() for FUSE 3.0 but
+ * without a version postfix. It expected an old definition of struct
+ * fuse_cmdline_opts too. */
+__warn_references(
+    fuse_parse_cmdline,
+    "warning: reference to compatibility fuse_parse_cmdline();"
+    " include <fuse.h> for correct reference")
+int
+fuse_parse_cmdline(struct fuse_args *args, struct fuse_cmdline_opts_rev0 *opts) {
+    struct fuse_cmdline_opts tmp;
+
+    if (fuse_parse_cmdline_v30(args, &tmp) != 0)
+        return -1;
+
+    memcpy(opts, &tmp, sizeof(*opts));
+    return 0;
+}
+
+/* librefuse once had a function fuse_unmount() for FUSE 3.0 but
+ * without a version postfix. */
+__warn_references(
+    fuse_unmount,
+    "warning: reference to compatibility fuse_unmount();"
+    " include <fuse.h> for correct reference")
+void
+fuse_unmount(struct fuse* fuse) {
+    return fuse_unmount_v30(fuse);
+}
+
+/* librefuse once had a function fuse_unmount_compat22() which was an
+ * implementation of fuse_unmount() to be used when FUSE_USE_VERSION
+ * was set to 22 or below. The function was actually a no-op. */
+void
+fuse_unmount_compat22(const char *mountpoint) {
+    fuse_unmount_v11(mountpoint);
+}
diff --git a/lib/librefuse/refuse_lowlevel.c b/lib/librefuse/refuse_lowlevel.c
index 05398f317..c2c77a459 100644
--- a/lib/librefuse/refuse_lowlevel.c
+++ b/lib/librefuse/refuse_lowlevel.c
@@ -121,7 +121,8 @@ static int add_default_fsname(struct fuse_args *args)
 	}
 }
 
-int fuse_parse_cmdline(struct fuse_args *args, struct fuse_cmdline_opts *opts)
+int
+__fuse_parse_cmdline(struct fuse_args *args, struct fuse_cmdline_opts *opts)
 {
 	memset(opts, 0, sizeof(*opts));
 
-- 
2.34.0




Home | Main Index | Thread Index | Old Index