diff -pruN 2.3.5-1/debian/changelog 2.3.5-1ubuntu1/debian/changelog
--- 2.3.5-1/debian/changelog	2025-11-19 06:43:19.000000000 +0000
+++ 2.3.5-1ubuntu1/debian/changelog	2025-11-19 20:41:31.000000000 +0000
@@ -1,3 +1,45 @@
+zfs-linux (2.3.5-1ubuntu1) resolute; urgency=medium
+
+  * Merge from Debian (LP: #2131959)
+    - debian/control:
+      - drop dependencies on "zfs-modules | zfs-dkms" such that all
+        packages can be installed in containers, on hosts that have zfs module
+        loaded
+    - debian/patches:
+      - adding
+        - ubuntu/0001-Revert-Revert-systemd-Use-non-absolute-paths-in-Exec.patch
+        - ubuntu/0002-Revert-etc-systemd-zfs-mount-generator-rewrite-in-C.patch
+        - ubuntu/0003-enable-linux-experimental.patch
+        - ubuntu//0004-Linux-6.18-replace-nth_page.patch
+        - ubuntu/0005-Linux-6.18-convert-ida_simple_-calls.patch
+        - ubuntu/0006-linux-atomic-fill-out-API-for-atomic-pointer-ops.patch
+        - ubuntu/0007-Linux-6.18-block_device_operations-getgeo-takes-stru.patch
+        - ubuntu/0008-Linux-6.18-replace-write_cache_pages.patch
+        - ubuntu/0009-Linux-6.18-namespace-type-moved-to-ns_common.patch
+        - ubuntu/0010-sha256_generic-make-internal-functions-a-little-more.patc
+        - ubuntu/0011-Linux-6.18-generic_drop_inode-and-generic_delete_ino.patch
+        - ubuntu/4000-zsys-support.patch
+        - ubuntu/4001-dracut-Open-and-mount-luks-keystore.patch
+        - ubuntu/4510-silently-ignore-modprobe-failure.patch
+        - ubuntu/fixup-abi.patch
+      - removing
+        - ubuntu/0001-Revert-etc-systemd-zfs-mount-generator-serialise-han.patch
+        - ubuntu/0002-Revert-etc-systemd-zfs-mount-generator-output-tweaks.patch
+        - ubuntu/0003-Revert-etc-systemd-zfs-mount-generator-rewrite-in-C.patch
+    - debian/rules:
+      - enforce abi check on Ubuntu amd64
+      - allow kernel version not matching META
+    - debian/tests/:
+      - reduce testing to smoketest only
+      - Ubuntu Kernel regression testing covers zfs testsuite
+      - Drop depends on linux-headers-*.
+      - draid tests fail upstream, so marking failures as expected
+        to fail
+    - debian/libzpool6linux.symbols
+      - adding spa_get_min_alloc_range to symbols
+
+ -- John Cabaj <john.cabaj@canonical.com>  Wed, 19 Nov 2025 14:41:31 -0600
+
 zfs-linux (2.3.5-1) unstable; urgency=medium
 
   * New upstream version 2.3.5 (Closes: #1119915)
diff -pruN 2.3.5-1/debian/control 2.3.5-1ubuntu1/debian/control
--- 2.3.5-1/debian/control	2025-03-21 05:09:46.000000000 +0000
+++ 2.3.5-1ubuntu1/debian/control	2025-11-19 20:41:31.000000000 +0000
@@ -1,7 +1,8 @@
 Source: zfs-linux
 Section: contrib/kernel
 Priority: optional
-Maintainer: Debian ZFS on Linux maintainers <pkg-zfsonlinux-devel@alioth-lists.debian.net>
+Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
+XSBC-Original-Maintainer: Debian ZFS on Linux maintainers <pkg-zfsonlinux-devel@alioth-lists.debian.net
 Uploaders: Aron Xu <aron@debian.org>,
            Carlos Alberto Lopez Perez <clopez@igalia.com>,
            Mo Zhou <lumin@debian.org>,
@@ -216,7 +217,6 @@ Architecture: all
 Multi-Arch: foreign
 Depends: busybox-initramfs | busybox-static | busybox,
          initramfs-tools,
-         zfs-modules | zfs-dkms,
          zfsutils-linux (>= ${source:Version}),
          ${misc:Depends}
 Breaks: zfsutils-linux (<= 0.7.11-2)
@@ -233,7 +233,6 @@ Package: zfs-dracut
 Architecture: all
 Multi-Arch: foreign
 Depends: dracut,
-         zfs-modules | zfs-dkms,
          zfsutils-linux (>= ${source:Version}),
          ${misc:Depends}
 Description: OpenZFS root filesystem capabilities for Linux - dracut
@@ -255,7 +254,7 @@ Depends: libnvpair3linux (= ${binary:Ver
          python3,
          ${misc:Depends},
          ${shlibs:Depends}
-Recommends: zfs-modules | zfs-dkms, zfs-zed
+Recommends: zfs-zed
 Breaks: spl (<< 0.7.9-2),
         spl-dkms (<< 0.8.0~rc1),
         zfs-dkms (<< ${source:Version}),
@@ -278,8 +277,7 @@ Package: zfs-zed
 Section: contrib/admin
 Architecture: linux-any
 Pre-Depends: ${misc:Pre-Depends}
-Depends: zfs-modules | zfs-dkms,
-         zfsutils-linux (>= ${binary:Version}),
+Depends: zfsutils-linux (>= ${binary:Version}),
          ${misc:Depends},
          ${shlibs:Depends}
 Description: OpenZFS Event Daemon
@@ -309,7 +307,6 @@ Depends: acl,
          python3-pyzfs,
          sudo,
          sysstat,
-         zfs-modules | zfs-dkms,
          zfsutils-linux (>=${binary:Version}),
          ${misc:Depends},
          ${shlibs:Depends}
diff -pruN 2.3.5-1/debian/libzpool6linux.symbols 2.3.5-1ubuntu1/debian/libzpool6linux.symbols
--- 2.3.5-1/debian/libzpool6linux.symbols	2025-11-17 08:28:58.000000000 +0000
+++ 2.3.5-1ubuntu1/debian/libzpool6linux.symbols	2025-11-19 20:41:31.000000000 +0000
@@ -1920,6 +1920,7 @@ libzpool.so.6 libzpool6linux #MINVER#
  spa_get_last_removal_txg@Base 0.8.2
  spa_get_last_scrubbed_txg@Base 2.3
  spa_get_log_state@Base 0.8.2
+ spa_get_min_alloc_range@Base 2.3.5
  spa_get_rootblkptr@Base 0.8.2
  spa_get_slop_space@Base 0.8.2
  spa_get_stats@Base 0.8.2
diff -pruN 2.3.5-1/debian/patches/series 2.3.5-1ubuntu1/debian/patches/series
--- 2.3.5-1/debian/patches/series	2025-11-19 06:41:16.000000000 +0000
+++ 2.3.5-1ubuntu1/debian/patches/series	2025-11-19 20:41:31.000000000 +0000
@@ -10,13 +10,23 @@ force-verbose-rules.patch
 move-arcstat-1-to-8.patch
 skip-on-PREEMPT_RT.patch
 cross-compile.patch
-#ubuntu/0001-Revert-etc-systemd-zfs-mount-generator-serialise-han.patch
-#ubuntu/0002-Revert-etc-systemd-zfs-mount-generator-output-tweaks.patch
-#ubuntu/0003-Revert-etc-systemd-zfs-mount-generator-rewrite-in-C.patch
-#ubuntu/4000-zsys-support.patch
-#ubuntu/4100-disable-bpool-upgrade.patch
+ubuntu/0001-Revert-Revert-systemd-Use-non-absolute-paths-in-Exec.patch
+ubuntu/0002-Revert-etc-systemd-zfs-mount-generator-rewrite-in-C.patch
+ubuntu/4000-zsys-support.patch
+ubuntu/4001-dracut-Open-and-mount-luks-keystore.patch
+ubuntu/4100-disable-bpool-upgrade.patch
 ubuntu/zfs-mount-container-start.patch
-#ubuntu/4510-silently-ignore-modprobe-failure.patch
-#ubuntu/4751-suppress-types.patch
+ubuntu/4510-silently-ignore-modprobe-failure.patch
+ubuntu/4751-suppress-types.patch
+ubuntu/fixup-abi.patch
 fix-pyzfs-version.patch
 BUILD_EXCLUSIVE_KERNEL_MIN.patch
+ubuntu/0003-enable-linux-experimental.patch
+ubuntu/0004-Linux-6.18-replace-nth_page.patch
+ubuntu/0005-Linux-6.18-convert-ida_simple_-calls.patch
+ubuntu/0006-linux-atomic-fill-out-API-for-atomic-pointer-ops.patch
+ubuntu/0007-Linux-6.18-block_device_operations-getgeo-takes-stru.patch
+ubuntu/0008-Linux-6.18-replace-write_cache_pages.patch
+ubuntu/0009-Linux-6.18-namespace-type-moved-to-ns_common.patch
+ubuntu/0010-sha256_generic-make-internal-functions-a-little-more.patch
+ubuntu/0011-Linux-6.18-generic_drop_inode-and-generic_delete_ino.patch
diff -pruN 2.3.5-1/debian/patches/ubuntu/0001-Revert-Revert-systemd-Use-non-absolute-paths-in-Exec.patch 2.3.5-1ubuntu1/debian/patches/ubuntu/0001-Revert-Revert-systemd-Use-non-absolute-paths-in-Exec.patch
--- 2.3.5-1/debian/patches/ubuntu/0001-Revert-Revert-systemd-Use-non-absolute-paths-in-Exec.patch	1970-01-01 00:00:00.000000000 +0000
+++ 2.3.5-1ubuntu1/debian/patches/ubuntu/0001-Revert-Revert-systemd-Use-non-absolute-paths-in-Exec.patch	2025-11-19 20:31:55.000000000 +0000
@@ -0,0 +1,105 @@
+From bf69bed17f2b711a658c58e70c6c135a9fac4dea Mon Sep 17 00:00:00 2001
+From: Dimitri John Ledkov <dimitri.ledkov@canonical.com>
+Date: Thu, 6 Jul 2023 18:08:38 +0100
+Subject: [PATCH] Revert "Revert "systemd: Use non-absolute paths in Exec*
+ lines""
+
+This reverts commit 6c962690245a6a2a4dfc2350c71a249641139c26.
+---
+ etc/systemd/system/zfs-import-cache.service.in |  2 +-
+ etc/systemd/system/zfs-import-scan.service.in  |  2 +-
+ etc/systemd/system/zfs-mount.service.in        |  2 +-
+ etc/systemd/system/zfs-scrub@.service.in       | 10 +++++-----
+ etc/systemd/system/zfs-share.service.in        |  2 +-
+ etc/systemd/system/zfs-trim@.service.in        | 10 +++++-----
+ etc/systemd/system/zfs-volume-wait.service.in  |  2 +-
+ etc/systemd/system/zfs-zed.service.in          |  2 +-
+ 8 files changed, 16 insertions(+), 16 deletions(-)
+
+--- a/etc/systemd/system/zfs-import-cache.service.in
++++ b/etc/systemd/system/zfs-import-cache.service.in
+@@ -17,7 +17,7 @@
+ Type=oneshot
+ RemainAfterExit=yes
+ EnvironmentFile=-@initconfdir@/zfs
+-ExecStart=@sbindir@/zpool import -c @sysconfdir@/zfs/zpool.cache -aN $ZPOOL_IMPORT_OPTS
++ExecStart=zpool import -c @sysconfdir@/zfs/zpool.cache -aN $ZPOOL_IMPORT_OPTS
+ 
+ [Install]
+ WantedBy=zfs-import.target
+--- a/etc/systemd/system/zfs-import-scan.service.in
++++ b/etc/systemd/system/zfs-import-scan.service.in
+@@ -16,7 +16,7 @@
+ Type=oneshot
+ RemainAfterExit=yes
+ EnvironmentFile=-@initconfdir@/zfs
+-ExecStart=@sbindir@/zpool import -aN -o cachefile=none $ZPOOL_IMPORT_OPTS
++ExecStart=zpool import -aN -o cachefile=none $ZPOOL_IMPORT_OPTS
+ 
+ [Install]
+ WantedBy=zfs-import.target
+--- a/etc/systemd/system/zfs-mount.service.in
++++ b/etc/systemd/system/zfs-mount.service.in
+@@ -19,7 +19,7 @@
+ Type=oneshot
+ RemainAfterExit=yes
+ EnvironmentFile=-@initconfdir@/zfs
+-ExecStart=@sbindir@/zfs mount -a
++ExecStart=zfs mount -a
+ 
+ [Install]
+ WantedBy=zfs.target
+--- a/etc/systemd/system/zfs-scrub@.service.in
++++ b/etc/systemd/system/zfs-scrub@.service.in
+@@ -8,8 +8,8 @@
+ 
+ [Service]
+ EnvironmentFile=-@initconfdir@/zfs
+-ExecStart=/bin/sh -c '\
+-if @sbindir@/zpool status %i | grep -q "scrub in progress"; then\
+-exec @sbindir@/zpool wait -t scrub %i;\
+-else exec @sbindir@/zpool scrub -w %i; fi'
+-ExecStop=-/bin/sh -c '@sbindir@/zpool scrub -p %i 2>/dev/null || true'
++ExecStart=sh -c '\
++if zpool status %i | grep -q "scrub in progress"; then\
++exec zpool wait -t scrub %i;\
++else exec zpool scrub -w %i; fi'
++ExecStop=-sh -c 'zpool scrub -p %i 2>/dev/null || true'
+--- a/etc/systemd/system/zfs-share.service.in
++++ b/etc/systemd/system/zfs-share.service.in
+@@ -14,7 +14,7 @@
+ Type=oneshot
+ RemainAfterExit=yes
+ EnvironmentFile=-@initconfdir@/zfs
+-ExecStart=@sbindir@/zfs share -a
++ExecStart=zfs share -a
+ 
+ [Install]
+ WantedBy=zfs.target
+--- a/etc/systemd/system/zfs-trim@.service.in
++++ b/etc/systemd/system/zfs-trim@.service.in
+@@ -8,8 +8,8 @@
+ 
+ [Service]
+ EnvironmentFile=-@initconfdir@/zfs
+-ExecStart=/bin/sh -c '\
+-if @sbindir@/zpool status %i | grep -q "(trimming)"; then\
+-exec @sbindir@/zpool wait -t trim %i;\
+-else exec @sbindir@/zpool trim -w %i; fi'
+-ExecStop=-/bin/sh -c '@sbindir@/zpool trim -s %i 2>/dev/null || true'
++ExecStart=sh -c '\
++if zpool status %i | grep -q "(trimming)"; then\
++exec zpool wait -t trim %i;\
++else exec zpool trim -w %i; fi'
++ExecStop=-sh -c 'zpool trim -s %i 2>/dev/null || true'
+--- a/etc/systemd/system/zfs-volume-wait.service.in
++++ b/etc/systemd/system/zfs-volume-wait.service.in
+@@ -9,7 +9,7 @@
+ Type=oneshot
+ RemainAfterExit=yes
+ EnvironmentFile=-@initconfdir@/zfs
+-ExecStart=@bindir@/zvol_wait
++ExecStart=zvol_wait
+ 
+ [Install]
+ WantedBy=zfs-volumes.target
diff -pruN 2.3.5-1/debian/patches/ubuntu/0001-Revert-etc-systemd-zfs-mount-generator-output-tweaks.patch 2.3.5-1ubuntu1/debian/patches/ubuntu/0001-Revert-etc-systemd-zfs-mount-generator-output-tweaks.patch
--- 2.3.5-1/debian/patches/ubuntu/0001-Revert-etc-systemd-zfs-mount-generator-output-tweaks.patch	2025-03-03 06:31:51.000000000 +0000
+++ 2.3.5-1ubuntu1/debian/patches/ubuntu/0001-Revert-etc-systemd-zfs-mount-generator-output-tweaks.patch	1970-01-01 00:00:00.000000000 +0000
@@ -1,73 +0,0 @@
-From b2fc7357863095f306f3a03eef460158158f7875 Mon Sep 17 00:00:00 2001
-From: Dimitri John Ledkov <dimitri.ledkov@canonical.com>
-Date: Mon, 17 Jan 2022 13:57:28 +0000
-Subject: [PATCH 1/2] Revert "etc/systemd/zfs-mount-generator: output tweaks"
-
-This reverts commit ec3b25825e64f37f74605c451cfc026c28920715.
-
-This is to continue support for ubuntu specific zsys patch that
-currently relies on the shell based implementation of the
-generator. See https://bugs.launchpad.net/bugs/1958142
-
-Signed-off-by: Dimitri John Ledkov <dimitri.ledkov@canonical.com>
----
- .../system-generators/zfs-mount-generator.c   | 22 ++++++++++++-------
- 1 file changed, 14 insertions(+), 8 deletions(-)
-
-diff --git a/etc/systemd/system-generators/zfs-mount-generator.c b/etc/systemd/system-generators/zfs-mount-generator.c
-index b806339deb..8deeed9df0 100644
---- a/etc/systemd/system-generators/zfs-mount-generator.c
-+++ b/etc/systemd/system-generators/zfs-mount-generator.c
-@@ -281,7 +281,7 @@ line_worker(char *line, const char *cachefile)
- 
- 	if (strcmp(p_encroot, "-") != 0) {
- 		char *keyloadunit =
--		    systemd_escape(p_encroot, "zfs-load-key@", ".service");
-+		    systemd_escape(p_encroot, "zfs-load-key-", ".service");
- 
- 		if (strcmp(dataset, p_encroot) == 0) {
- 			const char *keymountdep = NULL;
-@@ -360,27 +360,33 @@ line_worker(char *line, const char *cachefile)
- 			    "# dataset is a parent of the root filesystem.\n"
- 			    "StandardOutput=null\n"
- 			    "StandardError=null\n"
--			    "ExecStart=/bin/sh -euc '"
--			        "[ \"$$(" ZFS " get -H -o value keystatus \"%s\")\" = \"unavailable\" ] || exit 0;",
-+			    "ExecStart=/bin/sh -c '"
-+			        "set -eu;"
-+			        "keystatus=\"$$(" ZFS " get -H -o value keystatus \"%s\")\";"
-+			        "[ \"$$keystatus\" = \"unavailable\" ] || exit 0;",
- 			    dataset);
- 			if (is_prompt)
- 				fprintf(keyloadunit_f,
--				    "for i in 1 2 3; do "
-+				    "count=0;"
-+				    "while [ $$count -lt 3 ]; do "
- 				        "systemd-ask-password --id=\"zfs:%s\" \"Enter passphrase for %s:\" |"
- 				        "" ZFS " load-key \"%s\" && exit 0;"
-+				        "count=$$((count + 1));"
- 				    "done;"
- 				    "exit 1",
- 				    dataset, dataset, dataset);
- 			else
- 				fprintf(keyloadunit_f,
--				    "exec " ZFS " load-key \"%s\"",
-+				    "" ZFS " load-key \"%s\"",
- 				    dataset);
- 
- 			fprintf(keyloadunit_f,
- 				"'\n"
--				"ExecStop=/bin/sh -euc '"
--				    "[ \"$$(" ZFS " get -H -o value keystatus \"%s\")\" = \"available\" ] || exit 0;"
--				    "exec " ZFS " unload-key \"%s\""
-+				"ExecStop=/bin/sh -c '"
-+				    "set -eu;"
-+				    "keystatus=\"$$(" ZFS " get -H -o value keystatus \"%s\")\";"
-+				    "[ \"$$keystatus\" = \"available\" ] || exit 0;"
-+				    "" ZFS " unload-key \"%s\""
- 				"'\n",
- 				dataset, dataset);
- 			/* END CSTYLED */
--- 
-2.32.0
-
diff -pruN 2.3.5-1/debian/patches/ubuntu/0001-Revert-etc-systemd-zfs-mount-generator-serialise-han.patch 2.3.5-1ubuntu1/debian/patches/ubuntu/0001-Revert-etc-systemd-zfs-mount-generator-serialise-han.patch
--- 2.3.5-1/debian/patches/ubuntu/0001-Revert-etc-systemd-zfs-mount-generator-serialise-han.patch	2025-03-03 06:31:51.000000000 +0000
+++ 2.3.5-1ubuntu1/debian/patches/ubuntu/0001-Revert-etc-systemd-zfs-mount-generator-serialise-han.patch	1970-01-01 00:00:00.000000000 +0000
@@ -1,780 +0,0 @@
-From 6e60b28bc979477ba8d48347dd556acc5296bb25 Mon Sep 17 00:00:00 2001
-From: Dimitri John Ledkov <dimitri.ledkov@canonical.com>
-Date: Tue, 19 Apr 2022 15:58:46 +0100
-Subject: [PATCH 1/3] Revert "etc/systemd/zfs-mount-generator: serialise,
- handle keylocation=http[s]://"
-
-This reverts commit fe6f2651f55de3bf68ac4729386b5e85aa23a447.
-
-This is to continue support for ubuntu specific zsys patch that
-currently relies on the shell based implementation of the
-generator. See https://bugs.launchpad.net/bugs/1958142
-
-Signed-off-by: Dimitri John Ledkov <dimitri.ledkov@canonical.com>
----
- .../system-generators/zfs-mount-generator.c   | 437 +++++++++++-------
- man/man8/zfs-mount-generator.8.in             |  19 +-
- 2 files changed, 275 insertions(+), 181 deletions(-)
-
-diff --git a/etc/systemd/system-generators/zfs-mount-generator.c b/etc/systemd/system-generators/zfs-mount-generator.c
-index f4c6c26a0b..b806339deb 100644
---- a/etc/systemd/system-generators/zfs-mount-generator.c
-+++ b/etc/systemd/system-generators/zfs-mount-generator.c
-@@ -27,6 +27,9 @@
- #include <sys/types.h>
- #include <sys/time.h>
- #include <sys/stat.h>
-+#include <sys/wait.h>
-+#include <sys/mman.h>
-+#include <semaphore.h>
- #include <stdbool.h>
- #include <unistd.h>
- #include <fcntl.h>
-@@ -41,16 +44,25 @@
- #include <errno.h>
- #include <libzfs.h>
- 
--/*
-- * For debugging only.
-- *
-- * Free statics with trivial life-times,
-- * but saved line filenames are replaced with a static string.
-- */
--#define	FREE_STATICS false
--
--#define	nitems(arr) (sizeof (arr) / sizeof (*arr))
- #define	STRCMP ((int(*)(const void *, const void *))&strcmp)
-+#define	PID_T_CMP ((int(*)(const void *, const void *))&pid_t_cmp)
-+
-+static int
-+pid_t_cmp(const pid_t *lhs, const pid_t *rhs)
-+{
-+	/*
-+	 * This is always valid, quoth sys_types.h(7posix):
-+	 * > blksize_t, pid_t, and ssize_t shall be signed integer types.
-+	 */
-+	return (*lhs - *rhs);
-+}
-+
-+#define	EXIT_ENOMEM() \
-+	do { \
-+		fprintf(stderr, PROGNAME "[%d]: " \
-+		    "not enough memory (L%d)!\n", getpid(), __LINE__); \
-+		_exit(1); \
-+	} while (0)
- 
- 
- #define	PROGNAME "zfs-mount-generator"
-@@ -68,11 +80,20 @@
- #define	URI_REGEX_S "^\\([A-Za-z][A-Za-z0-9+.\\-]*\\):\\/\\/\\(.*\\)$"
- static regex_t uri_regex;
- 
-+static char *argv0;
-+
- static const char *destdir = "/tmp";
- static int destdir_fd = -1;
- 
- static void *known_pools = NULL; /* tsearch() of C strings */
--static void *noauto_files = NULL; /* tsearch() of C strings */
-+static struct {
-+	sem_t noauto_not_on_sem;
-+
-+	sem_t noauto_names_sem;
-+	size_t noauto_names_len;
-+	size_t noauto_names_max;
-+	char noauto_names[][NAME_MAX];
-+} *noauto_files;
- 
- 
- static char *
-@@ -82,12 +103,8 @@ systemd_escape(const char *input, const char *prepend, const char *append)
- 	size_t applen = strlen(append);
- 	size_t prelen = strlen(prepend);
- 	char *ret = malloc(4 * len + prelen + applen + 1);
--	if (!ret) {
--		fprintf(stderr, PROGNAME "[%d]: "
--		    "out of memory to escape \"%s%s%s\"!\n",
--		    getpid(), prepend, input, append);
--		return (NULL);
--	}
-+	if (!ret)
-+		EXIT_ENOMEM();
- 
- 	memcpy(ret, prepend, prelen);
- 	char *out = ret + prelen;
-@@ -149,12 +166,8 @@ systemd_escape_path(char *input, const char *prepend, const char *append)
- {
- 	if (strcmp(input, "/") == 0) {
- 		char *ret;
--		if (asprintf(&ret, "%s-%s", prepend, append) == -1) {
--			fprintf(stderr, PROGNAME "[%d]: "
--			    "out of memory to escape \"%s%s%s\"!\n",
--			    getpid(), prepend, input, append);
--			ret = NULL;
--		}
-+		if (asprintf(&ret, "%s-%s", prepend, append) == -1)
-+			EXIT_ENOMEM();
- 		return (ret);
- 	} else {
- 		/*
-@@ -196,10 +209,6 @@ fopenat(int dirfd, const char *pathname, int flags,
- static int
- line_worker(char *line, const char *cachefile)
- {
--	int ret = 0;
--	void *tofree_all[8];
--	void **tofree = tofree_all;
--
- 	char *toktmp;
- 	/* BEGIN CSTYLED */
- 	const char *dataset                     = strtok_r(line, "\t", &toktmp);
-@@ -231,9 +240,11 @@ line_worker(char *line, const char *cachefile)
- 	if (p_nbmand == NULL) {
- 		fprintf(stderr, PROGNAME "[%d]: %s: not enough tokens!\n",
- 		    getpid(), dataset);
--		goto err;
-+		return (1);
- 	}
- 
-+	strncpy(argv0, dataset, strlen(argv0));
-+
- 	/* Minimal pre-requisites to mount a ZFS dataset */
- 	const char *after = "zfs-import.target";
- 	const char *wants = "zfs-import.target";
-@@ -269,31 +280,28 @@ line_worker(char *line, const char *cachefile)
- 
- 
- 	if (strcmp(p_encroot, "-") != 0) {
--		char *keyloadunit = *(tofree++) =
-+		char *keyloadunit =
- 		    systemd_escape(p_encroot, "zfs-load-key@", ".service");
--		if (keyloadunit == NULL)
--			goto err;
- 
- 		if (strcmp(dataset, p_encroot) == 0) {
- 			const char *keymountdep = NULL;
- 			bool is_prompt = false;
--			bool need_network = false;
- 
- 			regmatch_t uri_matches[3];
- 			if (regexec(&uri_regex, p_keyloc,
--			    nitems(uri_matches), uri_matches, 0) == 0) {
--				p_keyloc[uri_matches[1].rm_eo] = '\0';
-+			    sizeof (uri_matches) / sizeof (*uri_matches),
-+			    uri_matches, 0) == 0) {
- 				p_keyloc[uri_matches[2].rm_eo] = '\0';
--				const char *scheme =
--				    &p_keyloc[uri_matches[1].rm_so];
- 				const char *path =
- 				    &p_keyloc[uri_matches[2].rm_so];
- 
--				if (strcmp(scheme, "https") == 0 ||
--				    strcmp(scheme, "http") == 0)
--					need_network = true;
--				else
--					keymountdep = path;
-+				/*
-+				 * Assumes all URI keylocations need
-+				 * the mount for their path;
-+				 * http://, for example, wouldn't
-+				 * (but it'd need network-online.target et al.)
-+				 */
-+				keymountdep = path;
- 			} else {
- 				if (strcmp(p_keyloc, "prompt") != 0)
- 					fprintf(stderr, PROGNAME "[%d]: %s: "
-@@ -313,7 +321,7 @@ line_worker(char *line, const char *cachefile)
- 				    "couldn't open %s under %s: %s\n",
- 				    getpid(), dataset, keyloadunit, destdir,
- 				    strerror(errno));
--				goto err;
-+				return (1);
- 			}
- 
- 			fprintf(keyloadunit_f,
-@@ -327,22 +335,20 @@ line_worker(char *line, const char *cachefile)
- 			    "After=%s\n",
- 			    dataset, cachefile, wants, after);
- 
--			if (need_network)
--				fprintf(keyloadunit_f,
--				    "Wants=network-online.target\n"
--				    "After=network-online.target\n");
--
- 			if (p_systemd_requires)
- 				fprintf(keyloadunit_f,
- 				    "Requires=%s\n", p_systemd_requires);
- 
--			if (p_systemd_requiresmountsfor)
--				fprintf(keyloadunit_f,
--				    "RequiresMountsFor=%s\n",
--				    p_systemd_requiresmountsfor);
--			if (keymountdep)
--				fprintf(keyloadunit_f,
--				    "RequiresMountsFor='%s'\n", keymountdep);
-+			if (p_systemd_requiresmountsfor || keymountdep) {
-+				fprintf(keyloadunit_f, "RequiresMountsFor=");
-+				if (p_systemd_requiresmountsfor)
-+					fprintf(keyloadunit_f,
-+					    "%s ", p_systemd_requiresmountsfor);
-+				if (keymountdep)
-+					fprintf(keyloadunit_f,
-+					    "'%s'", keymountdep);
-+				fprintf(keyloadunit_f, "\n");
-+			}
- 
- 			/* BEGIN CSTYLED */
- 			fprintf(keyloadunit_f,
-@@ -387,13 +393,9 @@ line_worker(char *line, const char *cachefile)
- 		if (after[0] == '\0')
- 			after = keyloadunit;
- 		else if (asprintf(&toktmp, "%s %s", after, keyloadunit) != -1)
--			after = *(tofree++) = toktmp;
--		else {
--			fprintf(stderr, PROGNAME "[%d]: %s: "
--			    "out of memory to generate after=\"%s %s\"!\n",
--			    getpid(), dataset, after, keyloadunit);
--			goto err;
--		}
-+			after = toktmp;
-+		else
-+			EXIT_ENOMEM();
- 	}
- 
- 
-@@ -402,12 +404,12 @@ line_worker(char *line, const char *cachefile)
- 	    strcmp(p_systemd_ignore, "off") == 0) {
- 		/* ok */
- 	} else if (strcmp(p_systemd_ignore, "on") == 0)
--		goto end;
-+		return (0);
- 	else {
- 		fprintf(stderr, PROGNAME "[%d]: %s: "
- 		    "invalid org.openzfs.systemd:ignore=%s\n",
- 		    getpid(), dataset, p_systemd_ignore);
--		goto err;
-+		return (1);
- 	}
- 
- 	/* Check for canmount */
-@@ -416,21 +418,21 @@ line_worker(char *line, const char *cachefile)
- 	} else if (strcmp(p_canmount, "noauto") == 0)
- 		noauto = true;
- 	else if (strcmp(p_canmount, "off") == 0)
--		goto end;
-+		return (0);
- 	else {
- 		fprintf(stderr, PROGNAME "[%d]: %s: invalid canmount=%s\n",
- 		    getpid(), dataset, p_canmount);
--		goto err;
-+		return (1);
- 	}
- 
- 	/* Check for legacy and blank mountpoints */
- 	if (strcmp(p_mountpoint, "legacy") == 0 ||
- 	    strcmp(p_mountpoint, "none") == 0)
--		goto end;
-+		return (0);
- 	else if (p_mountpoint[0] != '/') {
- 		fprintf(stderr, PROGNAME "[%d]: %s: invalid mountpoint=%s\n",
- 		    getpid(), dataset, p_mountpoint);
--		goto err;
-+		return (1);
- 	}
- 
- 	/* Escape the mountpoint per systemd policy */
-@@ -440,7 +442,7 @@ line_worker(char *line, const char *cachefile)
- 		fprintf(stderr,
- 		    PROGNAME "[%d]: %s: abnormal simplified mountpoint: %s\n",
- 		    getpid(), dataset, p_mountpoint);
--		goto err;
-+		return (1);
- 	}
- 
- 
-@@ -550,7 +552,8 @@ line_worker(char *line, const char *cachefile)
- 	 * 	files if we're sure they were created by us. (see 5.)
- 	 * 2.	We handle files differently based on canmount.
- 	 * 	Units with canmount=on always have precedence over noauto.
--	 * 	This is enforced by processing these units before all others.
-+	 * 	This is enforced by the noauto_not_on_sem semaphore,
-+	 * 	which is only unlocked when the last canmount=on process exits.
- 	 * 	It is important to use p_canmount and not noauto here,
- 	 * 	since we categorise by canmount while other properties,
- 	 * 	e.g. org.openzfs.systemd:wanted-by, also modify noauto.
-@@ -558,7 +561,7 @@ line_worker(char *line, const char *cachefile)
- 	 * 	Additionally, we use noauto_files to track the unit file names
- 	 * 	(which are the systemd-escaped mountpoints) of all (exclusively)
- 	 * 	noauto datasets that had a file created.
--	 * 4.	If the file to be created is found in the tracking tree,
-+	 * 4.	If the file to be created is found in the tracking array,
- 	 * 	we do NOT create it.
- 	 * 5.	If a file exists for a noauto dataset,
- 	 * 	we check whether the file name is in the array.
-@@ -568,14 +571,29 @@ line_worker(char *line, const char *cachefile)
- 	 * 	further noauto datasets creating a file for this path again.
- 	 */
- 
-+	{
-+		sem_t *our_sem = (strcmp(p_canmount, "on") == 0) ?
-+		    &noauto_files->noauto_names_sem :
-+		    &noauto_files->noauto_not_on_sem;
-+		while (sem_wait(our_sem) == -1 && errno == EINTR)
-+			;
-+	}
-+
- 	struct stat stbuf;
- 	bool already_exists = fstatat(destdir_fd, mountfile, &stbuf, 0) == 0;
--	bool is_known = tfind(mountfile, &noauto_files, STRCMP) != NULL;
- 
--	*(tofree++) = (void *)mountfile;
-+	bool is_known = false;
-+	for (size_t i = 0; i < noauto_files->noauto_names_len; ++i) {
-+		if (strncmp(
-+		    noauto_files->noauto_names[i], mountfile, NAME_MAX) == 0) {
-+			is_known = true;
-+			break;
-+		}
-+	}
-+
- 	if (already_exists) {
- 		if (is_known) {
--			/* If it's in noauto_files, we must be noauto too */
-+			/* If it's in $noauto_files, we must be noauto too */
- 
- 			/* See 5 */
- 			errno = 0;
-@@ -596,31 +614,43 @@ line_worker(char *line, const char *cachefile)
- 		}
- 
- 		/* File exists: skip current dataset */
--		goto end;
-+		if (strcmp(p_canmount, "on") == 0)
-+			sem_post(&noauto_files->noauto_names_sem);
-+		return (0);
- 	} else {
- 		if (is_known) {
- 			/* See 4 */
--			goto end;
-+			if (strcmp(p_canmount, "on") == 0)
-+				sem_post(&noauto_files->noauto_names_sem);
-+			return (0);
- 		} else if (strcmp(p_canmount, "noauto") == 0) {
--			if (tsearch(mountfile, &noauto_files, STRCMP) == NULL)
-+			if (noauto_files->noauto_names_len ==
-+			    noauto_files->noauto_names_max)
- 				fprintf(stderr, PROGNAME "[%d]: %s: "
--				    "out of memory for noauto datasets! "
--				    "Not tracking %s.\n",
--				    getpid(), dataset, mountfile);
--			else
--				/* mountfile escaped to noauto_files */
--				*(--tofree) = NULL;
-+				    "noauto dataset limit (%zu) reached! "
-+				    "Not tracking %s. Please report this to "
-+				    "https://github.com/openzfs/zfs\n",
-+				    getpid(), dataset,
-+				    noauto_files->noauto_names_max, mountfile);
-+			else {
-+				strncpy(noauto_files->noauto_names[
-+				    noauto_files->noauto_names_len],
-+				    mountfile, NAME_MAX);
-+				++noauto_files->noauto_names_len;
-+			}
- 		}
- 	}
- 
- 
- 	FILE *mountfile_f = fopenat(destdir_fd, mountfile,
- 	    O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, "w", 0644);
-+	if (strcmp(p_canmount, "on") == 0)
-+		sem_post(&noauto_files->noauto_names_sem);
- 	if (!mountfile_f) {
- 		fprintf(stderr,
- 		    PROGNAME "[%d]: %s: couldn't open %s under %s: %s\n",
- 		    getpid(), dataset, mountfile, destdir, strerror(errno));
--		goto err;
-+		return (1);
- 	}
- 
- 	fprintf(mountfile_f,
-@@ -669,17 +699,12 @@ line_worker(char *line, const char *cachefile)
- 	(void) fclose(mountfile_f);
- 
- 	if (!requiredby && !wantedby)
--		goto end;
-+		return (0);
- 
- 	/* Finally, create the appropriate dependencies */
- 	char *linktgt;
--	if (asprintf(&linktgt, "../%s", mountfile) == -1) {
--		fprintf(stderr, PROGNAME "[%d]: %s: "
--		    "out of memory for dependents of %s!\n",
--		    getpid(), dataset, mountfile);
--		goto err;
--	}
--	*(tofree++) = linktgt;
-+	if (asprintf(&linktgt, "../%s", mountfile) == -1)
-+		EXIT_ENOMEM();
- 
- 	char *dependencies[][2] = {
- 		{"wants", wantedby},
-@@ -694,14 +719,8 @@ line_worker(char *line, const char *cachefile)
- 		    reqby;
- 		    reqby = strtok_r(NULL, " ", &toktmp)) {
- 			char *depdir;
--			if (asprintf(
--			    &depdir, "%s.%s", reqby, (*dep)[0]) == -1) {
--				fprintf(stderr, PROGNAME "[%d]: %s: "
--				    "out of memory for dependent dir name "
--				    "\"%s.%s\"!\n",
--				    getpid(), dataset, reqby, (*dep)[0]);
--				continue;
--			}
-+			if (asprintf(&depdir, "%s.%s", reqby, (*dep)[0]) == -1)
-+				EXIT_ENOMEM();
- 
- 			(void) mkdirat(destdir_fd, depdir, 0755);
- 			int depdir_fd = openat(destdir_fd, depdir,
-@@ -727,24 +746,7 @@ line_worker(char *line, const char *cachefile)
- 		}
- 	}
- 
--end:
--	if (tofree >= tofree_all + nitems(tofree_all)) {
--		/*
--		 * This won't happen as-is:
--		 * we've got 8 slots and allocate 4 things at most.
--		 */
--		fprintf(stderr,
--		    PROGNAME "[%d]: %s: need to free %zu > %zu!\n",
--		    getpid(), dataset, tofree - tofree_all, nitems(tofree_all));
--		ret = tofree - tofree_all;
--	}
--
--	while (tofree-- != tofree_all)
--		free(*tofree);
--	return (ret);
--err:
--	ret = 1;
--	goto end;
-+	return (0);
- }
- 
- 
-@@ -778,11 +780,12 @@ main(int argc, char **argv)
- 		if (kmfd >= 0) {
- 			(void) dup2(kmfd, STDERR_FILENO);
- 			(void) close(kmfd);
--
--			setlinebuf(stderr);
- 		}
- 	}
- 
-+	uint8_t debug = 0;
-+
-+	argv0 = argv[0];
- 	switch (argc) {
- 	case 1:
- 		/* Use default */
-@@ -841,9 +844,33 @@ main(int argc, char **argv)
- 		}
- 	}
- 
--	bool debug = false;
-+	{
-+		/*
-+		 * We could just get a gigabyte here and Not Care,
-+		 * but if vm.overcommit_memory=2, then MAP_NORESERVE is ignored
-+		 * and we'd try (and likely fail) to rip it out of swap
-+		 */
-+		noauto_files = mmap(NULL, 4 * 1024 * 1024,
-+		    PROT_READ | PROT_WRITE,
-+		    MAP_SHARED | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);
-+		if (noauto_files == MAP_FAILED) {
-+			fprintf(stderr,
-+			    PROGNAME "[%d]: couldn't allocate IPC region: %s\n",
-+			    getpid(), strerror(errno));
-+			_exit(1);
-+		}
-+
-+		sem_init(&noauto_files->noauto_not_on_sem, true, 0);
-+		sem_init(&noauto_files->noauto_names_sem, true, 1);
-+		noauto_files->noauto_names_len = 0;
-+		/* Works out to 16447ish, *well* enough */
-+		noauto_files->noauto_names_max =
-+		    (4 * 1024 * 1024 - sizeof (*noauto_files)) / NAME_MAX;
-+	}
-+
- 	char *line = NULL;
- 	size_t linelen = 0;
-+	struct timespec time_start = {};
- 	{
- 		const char *dbgenv = getenv("ZFS_DEBUG");
- 		if (dbgenv)
-@@ -852,7 +879,7 @@ main(int argc, char **argv)
- 			FILE *cmdline = fopen("/proc/cmdline", "re");
- 			if (cmdline != NULL) {
- 				if (getline(&line, &linelen, cmdline) >= 0)
--					debug = strstr(line, "debug");
-+					debug = strstr(line, "debug") ? 2 : 0;
- 				(void) fclose(cmdline);
- 			}
- 		}
-@@ -861,17 +888,19 @@ main(int argc, char **argv)
- 			dup2(STDERR_FILENO, STDOUT_FILENO);
- 	}
- 
--	struct timespec time_start = {};
-+	size_t forked_canmount_on = 0;
-+	size_t forked_canmount_not_on = 0;
-+	size_t canmount_on_pids_len = 128;
-+	pid_t *canmount_on_pids =
-+	    malloc(canmount_on_pids_len * sizeof (*canmount_on_pids));
-+	if (canmount_on_pids == NULL)
-+		canmount_on_pids_len = 0;
-+
- 	if (debug)
- 		clock_gettime(CLOCK_MONOTONIC_RAW, &time_start);
- 
--	struct line {
--		char *line;
--		const char *fname;
--		struct line *next;
--	} *lines_canmount_not_on = NULL;
--
--	int ret = 0;
-+	ssize_t read;
-+	pid_t pid;
- 	struct dirent *cachent;
- 	while ((cachent = readdir(fslist_dir)) != NULL) {
- 		if (strcmp(cachent->d_name, ".") == 0 ||
-@@ -887,67 +916,129 @@ main(int argc, char **argv)
- 			continue;
- 		}
- 
--		const char *filename = FREE_STATICS ? "(elided)" : NULL;
--
--		ssize_t read;
- 		while ((read = getline(&line, &linelen, cachefile)) >= 0) {
- 			line[read - 1] = '\0'; /* newline */
- 
--			char *canmount = line;
--			canmount += strcspn(canmount, "\t");
--			canmount += strspn(canmount, "\t");
--			canmount += strcspn(canmount, "\t");
--			canmount += strspn(canmount, "\t");
--			bool canmount_on = strncmp(canmount, "on", 2) == 0;
--
--			if (canmount_on)
--				ret |= line_worker(line, cachent->d_name);
--			else {
--				if (filename == NULL)
--					filename =
--					    strdup(cachent->d_name) ?: "(?)";
--
--				struct line *l = calloc(1, sizeof (*l));
--				char *nl = strdup(line);
--				if (l == NULL || nl == NULL) {
--					fprintf(stderr, PROGNAME "[%d]: "
--					    "out of memory for \"%s\" in %s\n",
--					    getpid(), line, cachent->d_name);
--					free(l);
--					free(nl);
--					continue;
-+			switch (pid = fork()) {
-+			case -1:
-+				fprintf(stderr,
-+				    PROGNAME "[%d]: couldn't fork for %s: %s\n",
-+				    getpid(), line, strerror(errno));
-+				break;
-+			case 0: /* child */
-+				_exit(line_worker(line, cachent->d_name));
-+			default: { /* parent */
-+				char *tmp;
-+				char *dset = strtok_r(line, "\t", &tmp);
-+				strtok_r(NULL, "\t", &tmp);
-+				char *canmount = strtok_r(NULL, "\t", &tmp);
-+				bool canmount_on =
-+				    canmount && strncmp(canmount, "on", 2) == 0;
-+
-+				if (debug >= 2)
-+					printf(PROGNAME ": forked %d, "
-+					    "canmount_on=%d, dataset=%s\n",
-+					    (int)pid, canmount_on, dset);
-+
-+				if (canmount_on &&
-+				    forked_canmount_on ==
-+				    canmount_on_pids_len) {
-+					size_t new_len =
-+					    (canmount_on_pids_len ?: 16) * 2;
-+					void *new_pidlist =
-+					    realloc(canmount_on_pids,
-+					    new_len *
-+					    sizeof (*canmount_on_pids));
-+					if (!new_pidlist) {
-+						fprintf(stderr,
-+						    PROGNAME "[%d]: "
-+						    "out of memory! "
-+						    "Mount ordering may be "
-+						    "affected.\n", getpid());
-+						continue;
-+					}
-+
-+					canmount_on_pids = new_pidlist;
-+					canmount_on_pids_len = new_len;
- 				}
--				l->line = nl;
--				l->fname = filename;
--				l->next = lines_canmount_not_on;
--				lines_canmount_not_on = l;
-+
-+				if (canmount_on) {
-+					canmount_on_pids[forked_canmount_on] =
-+					    pid;
-+					++forked_canmount_on;
-+				} else
-+					++forked_canmount_not_on;
-+				break;
-+			}
- 			}
- 		}
- 
--		fclose(cachefile);
-+		(void) fclose(cachefile);
- 	}
- 	free(line);
- 
--	while (lines_canmount_not_on) {
--		struct line *l = lines_canmount_not_on;
--		lines_canmount_not_on = l->next;
-+	if (forked_canmount_on == 0) {
-+		/* No canmount=on processes to finish, so don't deadlock here */
-+		for (size_t i = 0; i < forked_canmount_not_on; ++i)
-+			sem_post(&noauto_files->noauto_not_on_sem);
-+	} else {
-+		/* Likely a no-op, since we got these from a narrow fork loop */
-+		qsort(canmount_on_pids, forked_canmount_on,
-+		    sizeof (*canmount_on_pids), PID_T_CMP);
-+	}
- 
--		ret |= line_worker(l->line, l->fname);
--		if (FREE_STATICS) {
--			free(l->line);
--			free(l);
-+	int status, ret = 0;
-+	struct rusage usage;
-+	size_t forked_canmount_on_max = forked_canmount_on;
-+	while ((pid = wait4(-1, &status, 0, &usage)) != -1) {
-+		ret |= WEXITSTATUS(status) | WTERMSIG(status);
-+
-+		if (forked_canmount_on != 0) {
-+			if (bsearch(&pid, canmount_on_pids,
-+			    forked_canmount_on_max, sizeof (*canmount_on_pids),
-+			    PID_T_CMP))
-+				--forked_canmount_on;
-+
-+			if (forked_canmount_on == 0) {
-+				/*
-+				 * All canmount=on processes have finished,
-+				 * let all the lower-priority ones finish now
-+				 */
-+				for (size_t i = 0;
-+				    i < forked_canmount_not_on; ++i)
-+					sem_post(
-+					    &noauto_files->noauto_not_on_sem);
-+			}
- 		}
-+
-+		if (debug >= 2)
-+			printf(PROGNAME ": %d done, user=%llu.%06us, "
-+			    "system=%llu.%06us, maxrss=%ldB, ex=0x%x\n",
-+			    (int)pid,
-+			    (unsigned long long) usage.ru_utime.tv_sec,
-+			    (unsigned int) usage.ru_utime.tv_usec,
-+			    (unsigned long long) usage.ru_stime.tv_sec,
-+			    (unsigned int) usage.ru_stime.tv_usec,
-+			    usage.ru_maxrss * 1024, status);
- 	}
- 
- 	if (debug) {
- 		struct timespec time_end = {};
- 		clock_gettime(CLOCK_MONOTONIC_RAW, &time_end);
- 
--		struct rusage usage;
- 		getrusage(RUSAGE_SELF, &usage);
- 		printf(
- 		    "\n"
--		    PROGNAME ": "
-+		    PROGNAME ": self    : "
-+		    "user=%llu.%06us, system=%llu.%06us, maxrss=%ldB\n",
-+		    (unsigned long long) usage.ru_utime.tv_sec,
-+		    (unsigned int) usage.ru_utime.tv_usec,
-+		    (unsigned long long) usage.ru_stime.tv_sec,
-+		    (unsigned int) usage.ru_stime.tv_usec,
-+		    usage.ru_maxrss * 1024);
-+
-+		getrusage(RUSAGE_CHILDREN, &usage);
-+		printf(PROGNAME ": children: "
- 		    "user=%llu.%06us, system=%llu.%06us, maxrss=%ldB\n",
- 		    (unsigned long long) usage.ru_utime.tv_sec,
- 		    (unsigned int) usage.ru_utime.tv_usec,
-@@ -977,7 +1068,7 @@ main(int argc, char **argv)
- 		    time_init.tv_nsec / 1000000000;
- 		time_init.tv_nsec %= 1000000000;
- 
--		printf(PROGNAME ": "
-+		printf(PROGNAME ": wall    : "
- 		    "total=%llu.%09llus = "
- 		    "init=%llu.%09llus + real=%llu.%09llus\n",
- 		    (unsigned long long) time_init.tv_sec,
-@@ -986,15 +1077,7 @@ main(int argc, char **argv)
- 		    (unsigned long long) time_start.tv_nsec,
- 		    (unsigned long long) time_end.tv_sec,
- 		    (unsigned long long) time_end.tv_nsec);
--
--		fflush(stdout);
- 	}
- 
--	if (FREE_STATICS) {
--		closedir(fslist_dir);
--		tdestroy(noauto_files, free);
--		tdestroy(known_pools, free);
--		regfree(&uri_regex);
--	}
- 	_exit(ret);
- }
-diff --git a/man/man8/zfs-mount-generator.8.in b/man/man8/zfs-mount-generator.8.in
-index ae8937038e..7aa332ba81 100644
---- a/man/man8/zfs-mount-generator.8.in
-+++ b/man/man8/zfs-mount-generator.8.in
-@@ -142,11 +142,22 @@ ZEDLET, if enabled
- .Pq see Xr zed 8 .
- .
- .Sh ENVIRONMENT
--If the
-+The
- .Sy ZFS_DEBUG
--environment variable is nonzero
--.Pq or unset and Pa /proc/cmdline No contains Qq Sy debug ,
--print summary accounting information at the end.
-+environment variable can either be
-+.Sy 0
-+(default),
-+.Sy 1
-+(print summary accounting information at the end), or at least
-+.Sy 2
-+(print accounting information for each subprocess as it finishes).
-+.
-+If not present,
-+.Pa /proc/cmdline
-+is additionally checked for
-+.Qq debug ,
-+in which case the debug level is set to
-+.Sy 2 .
- .
- .Sh EXAMPLES
- To begin, enable tracking for the pool:
--- 
-2.32.0
-
diff -pruN 2.3.5-1/debian/patches/ubuntu/0002-Revert-etc-systemd-zfs-mount-generator-output-tweaks.patch 2.3.5-1ubuntu1/debian/patches/ubuntu/0002-Revert-etc-systemd-zfs-mount-generator-output-tweaks.patch
--- 2.3.5-1/debian/patches/ubuntu/0002-Revert-etc-systemd-zfs-mount-generator-output-tweaks.patch	2025-03-03 06:31:51.000000000 +0000
+++ 2.3.5-1ubuntu1/debian/patches/ubuntu/0002-Revert-etc-systemd-zfs-mount-generator-output-tweaks.patch	1970-01-01 00:00:00.000000000 +0000
@@ -1,73 +0,0 @@
-From 83fbd45b461f5ddec2eedfe96ea4375452637406 Mon Sep 17 00:00:00 2001
-From: Dimitri John Ledkov <dimitri.ledkov@canonical.com>
-Date: Mon, 17 Jan 2022 13:57:28 +0000
-Subject: [PATCH 2/3] Revert "etc/systemd/zfs-mount-generator: output tweaks"
-
-This reverts commit ec3b25825e64f37f74605c451cfc026c28920715.
-
-This is to continue support for ubuntu specific zsys patch that
-currently relies on the shell based implementation of the
-generator. See https://bugs.launchpad.net/bugs/1958142
-
-Signed-off-by: Dimitri John Ledkov <dimitri.ledkov@canonical.com>
----
- .../system-generators/zfs-mount-generator.c   | 22 ++++++++++++-------
- 1 file changed, 14 insertions(+), 8 deletions(-)
-
-diff --git a/etc/systemd/system-generators/zfs-mount-generator.c b/etc/systemd/system-generators/zfs-mount-generator.c
-index b806339deb..8deeed9df0 100644
---- a/etc/systemd/system-generators/zfs-mount-generator.c
-+++ b/etc/systemd/system-generators/zfs-mount-generator.c
-@@ -281,7 +281,7 @@ line_worker(char *line, const char *cachefile)
- 
- 	if (strcmp(p_encroot, "-") != 0) {
- 		char *keyloadunit =
--		    systemd_escape(p_encroot, "zfs-load-key@", ".service");
-+		    systemd_escape(p_encroot, "zfs-load-key-", ".service");
- 
- 		if (strcmp(dataset, p_encroot) == 0) {
- 			const char *keymountdep = NULL;
-@@ -360,27 +360,33 @@ line_worker(char *line, const char *cachefile)
- 			    "# dataset is a parent of the root filesystem.\n"
- 			    "StandardOutput=null\n"
- 			    "StandardError=null\n"
--			    "ExecStart=/bin/sh -euc '"
--			        "[ \"$$(" ZFS " get -H -o value keystatus \"%s\")\" = \"unavailable\" ] || exit 0;",
-+			    "ExecStart=/bin/sh -c '"
-+			        "set -eu;"
-+			        "keystatus=\"$$(" ZFS " get -H -o value keystatus \"%s\")\";"
-+			        "[ \"$$keystatus\" = \"unavailable\" ] || exit 0;",
- 			    dataset);
- 			if (is_prompt)
- 				fprintf(keyloadunit_f,
--				    "for i in 1 2 3; do "
-+				    "count=0;"
-+				    "while [ $$count -lt 3 ]; do "
- 				        "systemd-ask-password --id=\"zfs:%s\" \"Enter passphrase for %s:\" |"
- 				        "" ZFS " load-key \"%s\" && exit 0;"
-+				        "count=$$((count + 1));"
- 				    "done;"
- 				    "exit 1",
- 				    dataset, dataset, dataset);
- 			else
- 				fprintf(keyloadunit_f,
--				    "exec " ZFS " load-key \"%s\"",
-+				    "" ZFS " load-key \"%s\"",
- 				    dataset);
- 
- 			fprintf(keyloadunit_f,
- 				"'\n"
--				"ExecStop=/bin/sh -euc '"
--				    "[ \"$$(" ZFS " get -H -o value keystatus \"%s\")\" = \"available\" ] || exit 0;"
--				    "exec " ZFS " unload-key \"%s\""
-+				"ExecStop=/bin/sh -c '"
-+				    "set -eu;"
-+				    "keystatus=\"$$(" ZFS " get -H -o value keystatus \"%s\")\";"
-+				    "[ \"$$keystatus\" = \"available\" ] || exit 0;"
-+				    "" ZFS " unload-key \"%s\""
- 				"'\n",
- 				dataset, dataset);
- 			/* END CSTYLED */
--- 
-2.32.0
-
diff -pruN 2.3.5-1/debian/patches/ubuntu/0002-Revert-etc-systemd-zfs-mount-generator-rewrite-in-C.patch 2.3.5-1ubuntu1/debian/patches/ubuntu/0002-Revert-etc-systemd-zfs-mount-generator-rewrite-in-C.patch
--- 2.3.5-1/debian/patches/ubuntu/0002-Revert-etc-systemd-zfs-mount-generator-rewrite-in-C.patch	2025-03-03 06:31:51.000000000 +0000
+++ 2.3.5-1ubuntu1/debian/patches/ubuntu/0002-Revert-etc-systemd-zfs-mount-generator-rewrite-in-C.patch	2025-11-19 20:31:55.000000000 +0000
@@ -1,7 +1,7 @@
-From 474ddc9fa5440312e5224f394052264ef7644b00 Mon Sep 17 00:00:00 2001
-From: Dimitri John Ledkov <dimitri.ledkov@canonical.com>
-Date: Mon, 17 Jan 2022 14:00:42 +0000
-Subject: [PATCH 2/2] Revert "etc/systemd/zfs-mount-generator: rewrite in C"
+From 96e20d3f61225165d806cdd3f65c802e00a3be8f Mon Sep 17 00:00:00 2001
+From: Heitor Alves de Siqueira <halves@canonical.com>
+Date: Mon, 20 Jan 2025 11:37:00 -0300
+Subject: [PATCH] Revert "etc/systemd/zfs-mount-generator: rewrite in C"
 
 This reverts commit 0382362ce06a5514a97bbbf11dfe55e7e408898a.
 
@@ -9,1151 +9,31 @@ This is to continue support for ubuntu s
 currently relies on the shell based implementation of the
 generator. See https://bugs.launchpad.net/bugs/1958142
 
-Signed-off-by: Dimitri John Ledkov <dimitri.ledkov@canonical.com>
+Last-Update: 2025-01-20
+Signed-off-by: Heitor Alves de Siqueira <halves@canonical.com>
 ---
- Makefile.am                                   |    2 +-
- etc/systemd/system-generators/Makefile.am     |   14 +-
- .../system-generators/zfs-mount-generator.c   | 1089 -----------------
- .../system-generators/zfs-mount-generator.in  |  474 +++++++
- 4 files changed, 478 insertions(+), 1101 deletions(-)
- delete mode 100644 etc/systemd/system-generators/zfs-mount-generator.c
+ etc/systemd/system-generators/Makefile.am     |   6 +
+ .../system-generators/zfs-mount-generator.in  | 474 ++++++++++++++++++
+ man/man8/zfs-mount-generator.8.in             | 331 +++++++-----
+ 3 files changed, 673 insertions(+), 138 deletions(-)
+ create mode 100644 etc/systemd/system-generators/Makefile.am
  create mode 100755 etc/systemd/system-generators/zfs-mount-generator.in
 
-diff --git a/Makefile.am b/Makefile.am
-index 34fe16ce41..d4e69a749c 100644
---- a/Makefile.am
-+++ b/Makefile.am
-@@ -8,7 +8,7 @@ SUBDIRS += rpm
- endif
- 
- if CONFIG_USER
--SUBDIRS += man scripts lib tests cmd etc contrib
-+SUBDIRS += etc man scripts lib tests cmd contrib
- if BUILD_LINUX
- SUBDIRS += udev
- endif
-diff --git a/etc/systemd/system-generators/Makefile.am b/etc/systemd/system-generators/Makefile.am
-index e5920bf392..fee88dad8c 100644
---- a/etc/systemd/system-generators/Makefile.am
-+++ b/etc/systemd/system-generators/Makefile.am
-@@ -1,14 +1,6 @@
--include $(top_srcdir)/config/Rules.am
+Index: zfs-linux/etc/systemd/system-generators/Makefile.am
+===================================================================
+--- /dev/null
++++ zfs-linux/etc/systemd/system-generators/Makefile.am
+@@ -0,0 +1,6 @@
 +include $(top_srcdir)/config/Substfiles.am
- 
--systemdgenerator_PROGRAMS = \
++
 +systemdgenerator_SCRIPTS = \
- 	zfs-mount-generator
- 
--zfs_mount_generator_SOURCES = \
--	zfs-mount-generator.c
--
--zfs_mount_generator_LDADD = \
--	$(abs_top_builddir)/lib/libzfs/libzfs.la
--
--zfs_mount_generator_LDFLAGS = -pthread
--
--include $(top_srcdir)/config/CppCheck.am
++	zfs-mount-generator
++
 +SUBSTFILES += $(systemdgenerator_SCRIPTS)
-diff --git a/etc/systemd/system-generators/zfs-mount-generator.c b/etc/systemd/system-generators/zfs-mount-generator.c
-deleted file mode 100644
-index 8deeed9df0..0000000000
---- a/etc/systemd/system-generators/zfs-mount-generator.c
-+++ /dev/null
-@@ -1,1089 +0,0 @@
--/*
-- * Copyright (c) 2017 Antonio Russo <antonio.e.russo@gmail.com>
-- * Copyright (c) 2020 InsanePrawn <insane.prawny@gmail.com>
-- *
-- * Permission is hereby granted, free of charge, to any person obtaining
-- * a copy of this software and associated documentation files (the
-- * "Software"), to deal in the Software without restriction, including
-- * without limitation the rights to use, copy, modify, merge, publish,
-- * distribute, sublicense, and/or sell copies of the Software, and to
-- * permit persons to whom the Software is furnished to do so, subject to
-- * the following conditions:
-- *
-- * The above copyright notice and this permission notice shall be
-- * included in all copies or substantial portions of the Software.
-- *
-- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-- * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-- * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-- * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-- */
--
--
--#include <sys/resource.h>
--#include <sys/types.h>
--#include <sys/time.h>
--#include <sys/stat.h>
--#include <sys/wait.h>
--#include <sys/mman.h>
--#include <semaphore.h>
--#include <stdbool.h>
--#include <unistd.h>
--#include <fcntl.h>
--#include <stdio.h>
--#include <time.h>
--#include <regex.h>
--#include <search.h>
--#include <dirent.h>
--#include <string.h>
--#include <stdlib.h>
--#include <limits.h>
--#include <errno.h>
--#include <libzfs.h>
--
--#define	STRCMP ((int(*)(const void *, const void *))&strcmp)
--#define	PID_T_CMP ((int(*)(const void *, const void *))&pid_t_cmp)
--
--static int
--pid_t_cmp(const pid_t *lhs, const pid_t *rhs)
--{
--	/*
--	 * This is always valid, quoth sys_types.h(7posix):
--	 * > blksize_t, pid_t, and ssize_t shall be signed integer types.
--	 */
--	return (*lhs - *rhs);
--}
--
--#define	EXIT_ENOMEM() \
--	do { \
--		fprintf(stderr, PROGNAME "[%d]: " \
--		    "not enough memory (L%d)!\n", getpid(), __LINE__); \
--		_exit(1); \
--	} while (0)
--
--
--#define	PROGNAME "zfs-mount-generator"
--#define	FSLIST SYSCONFDIR "/zfs/zfs-list.cache"
--#define	ZFS SBINDIR "/zfs"
--
--#define	OUTPUT_HEADER \
--	"# Automatically generated by " PROGNAME "\n" \
--	"\n"
--
--/*
-- * Starts like the one in libzfs_util.c but also matches "//"
-- * and captures until the end, since we actually use it for path extraxion
-- */
--#define	URI_REGEX_S "^\\([A-Za-z][A-Za-z0-9+.\\-]*\\):\\/\\/\\(.*\\)$"
--static regex_t uri_regex;
--
--static char *argv0;
--
--static const char *destdir = "/tmp";
--static int destdir_fd = -1;
--
--static void *known_pools = NULL; /* tsearch() of C strings */
--static struct {
--	sem_t noauto_not_on_sem;
--
--	sem_t noauto_names_sem;
--	size_t noauto_names_len;
--	size_t noauto_names_max;
--	char noauto_names[][NAME_MAX];
--} *noauto_files;
--
--
--static char *
--systemd_escape(const char *input, const char *prepend, const char *append)
--{
--	size_t len = strlen(input);
--	size_t applen = strlen(append);
--	size_t prelen = strlen(prepend);
--	char *ret = malloc(4 * len + prelen + applen + 1);
--	if (!ret)
--		EXIT_ENOMEM();
--
--	memcpy(ret, prepend, prelen);
--	char *out = ret + prelen;
--
--	const char *cur = input;
--	if (*cur == '.') {
--		memcpy(out, "\\x2e", 4);
--		out += 4;
--		++cur;
--	}
--	for (; *cur; ++cur) {
--		if (*cur == '/')
--			*(out++) = '-';
--		else if (strchr(
--		    "0123456789"
--		    "abcdefghijklmnopqrstuvwxyz"
--		    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
--		    ":_.", *cur))
--			*(out++) = *cur;
--		else {
--			sprintf(out, "\\x%02x", (int)*cur);
--			out += 4;
--		}
--	}
--
--	memcpy(out, append, applen + 1);
--	return (ret);
--}
--
--static void
--simplify_path(char *path)
--{
--	char *out = path;
--	for (char *cur = path; *cur; ++cur) {
--		if (*cur == '/') {
--			while (*(cur + 1) == '/')
--				++cur;
--			*(out++) = '/';
--		} else
--			*(out++) = *cur;
--	}
--
--	*(out++) = '\0';
--}
--
--static bool
--strendswith(const char *what, const char *suff)
--{
--	size_t what_l = strlen(what);
--	size_t suff_l = strlen(suff);
--
--	return ((what_l >= suff_l) &&
--	    (strcmp(what + what_l - suff_l, suff) == 0));
--}
--
--/* Assumes already-simplified path, doesn't modify input */
--static char *
--systemd_escape_path(char *input, const char *prepend, const char *append)
--{
--	if (strcmp(input, "/") == 0) {
--		char *ret;
--		if (asprintf(&ret, "%s-%s", prepend, append) == -1)
--			EXIT_ENOMEM();
--		return (ret);
--	} else {
--		/*
--		 * path_is_normalized() (flattened for absolute paths here),
--		 * required for proper escaping
--		 */
--		if (strstr(input, "/./") || strstr(input, "/../") ||
--		    strendswith(input, "/.") || strendswith(input, "/.."))
--			return (NULL);
--
--
--		if (input[0] == '/')
--			++input;
--
--		char *back = &input[strlen(input) - 1];
--		bool deslash = *back == '/';
--		if (deslash)
--			*back = '\0';
--
--		char *ret = systemd_escape(input, prepend, append);
--
--		if (deslash)
--			*back = '/';
--		return (ret);
--	}
--}
--
--static FILE *
--fopenat(int dirfd, const char *pathname, int flags,
--    const char *stream_mode, mode_t mode)
--{
--	int fd = openat(dirfd, pathname, flags, mode);
--	if (fd < 0)
--		return (NULL);
--
--	return (fdopen(fd, stream_mode));
--}
--
--static int
--line_worker(char *line, const char *cachefile)
--{
--	char *toktmp;
--	/* BEGIN CSTYLED */
--	const char *dataset                     = strtok_r(line, "\t", &toktmp);
--	      char *p_mountpoint                = strtok_r(NULL, "\t", &toktmp);
--	const char *p_canmount                  = strtok_r(NULL, "\t", &toktmp);
--	const char *p_atime                     = strtok_r(NULL, "\t", &toktmp);
--	const char *p_relatime                  = strtok_r(NULL, "\t", &toktmp);
--	const char *p_devices                   = strtok_r(NULL, "\t", &toktmp);
--	const char *p_exec                      = strtok_r(NULL, "\t", &toktmp);
--	const char *p_readonly                  = strtok_r(NULL, "\t", &toktmp);
--	const char *p_setuid                    = strtok_r(NULL, "\t", &toktmp);
--	const char *p_nbmand                    = strtok_r(NULL, "\t", &toktmp);
--	const char *p_encroot                   = strtok_r(NULL, "\t", &toktmp) ?: "-";
--	      char *p_keyloc                    = strtok_r(NULL, "\t", &toktmp) ?: strdupa("none");
--	const char *p_systemd_requires          = strtok_r(NULL, "\t", &toktmp) ?: "-";
--	const char *p_systemd_requiresmountsfor = strtok_r(NULL, "\t", &toktmp) ?: "-";
--	const char *p_systemd_before            = strtok_r(NULL, "\t", &toktmp) ?: "-";
--	const char *p_systemd_after             = strtok_r(NULL, "\t", &toktmp) ?: "-";
--	      char *p_systemd_wantedby          = strtok_r(NULL, "\t", &toktmp) ?: strdupa("-");
--	      char *p_systemd_requiredby        = strtok_r(NULL, "\t", &toktmp) ?: strdupa("-");
--	const char *p_systemd_nofail            = strtok_r(NULL, "\t", &toktmp) ?: "-";
--	const char *p_systemd_ignore            = strtok_r(NULL, "\t", &toktmp) ?: "-";
--	/* END CSTYLED */
--
--	const char *pool = dataset;
--	if ((toktmp = strchr(pool, '/')) != NULL)
--		pool = strndupa(pool, toktmp - pool);
--
--	if (p_nbmand == NULL) {
--		fprintf(stderr, PROGNAME "[%d]: %s: not enough tokens!\n",
--		    getpid(), dataset);
--		return (1);
--	}
--
--	strncpy(argv0, dataset, strlen(argv0));
--
--	/* Minimal pre-requisites to mount a ZFS dataset */
--	const char *after = "zfs-import.target";
--	const char *wants = "zfs-import.target";
--	const char *bindsto = NULL;
--	char *wantedby = NULL;
--	char *requiredby = NULL;
--	bool noauto = false;
--	bool wantedby_append = true;
--
--	/*
--	 * zfs-import.target is not needed if the pool is already imported.
--	 * This avoids a dependency loop on root-on-ZFS systems:
--	 *   systemd-random-seed.service After (via RequiresMountsFor)
--	 *   var-lib.mount After
--	 *   zfs-import.target After
--	 *   zfs-import-{cache,scan}.service After
--	 *   cryptsetup.service After
--	 *   systemd-random-seed.service
--	 */
--	if (tfind(pool, &known_pools, STRCMP)) {
--		after = "";
--		wants = "";
--	}
--
--	if (strcmp(p_systemd_after, "-") == 0)
--		p_systemd_after = NULL;
--	if (strcmp(p_systemd_before, "-") == 0)
--		p_systemd_before = NULL;
--	if (strcmp(p_systemd_requires, "-") == 0)
--		p_systemd_requires = NULL;
--	if (strcmp(p_systemd_requiresmountsfor, "-") == 0)
--		p_systemd_requiresmountsfor = NULL;
--
--
--	if (strcmp(p_encroot, "-") != 0) {
--		char *keyloadunit =
--		    systemd_escape(p_encroot, "zfs-load-key-", ".service");
--
--		if (strcmp(dataset, p_encroot) == 0) {
--			const char *keymountdep = NULL;
--			bool is_prompt = false;
--
--			regmatch_t uri_matches[3];
--			if (regexec(&uri_regex, p_keyloc,
--			    sizeof (uri_matches) / sizeof (*uri_matches),
--			    uri_matches, 0) == 0) {
--				p_keyloc[uri_matches[2].rm_eo] = '\0';
--				const char *path =
--				    &p_keyloc[uri_matches[2].rm_so];
--
--				/*
--				 * Assumes all URI keylocations need
--				 * the mount for their path;
--				 * http://, for example, wouldn't
--				 * (but it'd need network-online.target et al.)
--				 */
--				keymountdep = path;
--			} else {
--				if (strcmp(p_keyloc, "prompt") != 0)
--					fprintf(stderr, PROGNAME "[%d]: %s: "
--					    "unknown non-URI keylocation=%s\n",
--					    getpid(), dataset, p_keyloc);
--
--				is_prompt = true;
--			}
--
--
--			/* Generate the key-load .service unit */
--			FILE *keyloadunit_f = fopenat(destdir_fd, keyloadunit,
--			    O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, "w",
--			    0644);
--			if (!keyloadunit_f) {
--				fprintf(stderr, PROGNAME "[%d]: %s: "
--				    "couldn't open %s under %s: %s\n",
--				    getpid(), dataset, keyloadunit, destdir,
--				    strerror(errno));
--				return (1);
--			}
--
--			fprintf(keyloadunit_f,
--			    OUTPUT_HEADER
--			    "[Unit]\n"
--			    "Description=Load ZFS key for %s\n"
--			    "SourcePath=" FSLIST "/%s\n"
--			    "Documentation=man:zfs-mount-generator(8)\n"
--			    "DefaultDependencies=no\n"
--			    "Wants=%s\n"
--			    "After=%s\n",
--			    dataset, cachefile, wants, after);
--
--			if (p_systemd_requires)
--				fprintf(keyloadunit_f,
--				    "Requires=%s\n", p_systemd_requires);
--
--			if (p_systemd_requiresmountsfor || keymountdep) {
--				fprintf(keyloadunit_f, "RequiresMountsFor=");
--				if (p_systemd_requiresmountsfor)
--					fprintf(keyloadunit_f,
--					    "%s ", p_systemd_requiresmountsfor);
--				if (keymountdep)
--					fprintf(keyloadunit_f,
--					    "'%s'", keymountdep);
--				fprintf(keyloadunit_f, "\n");
--			}
--
--			/* BEGIN CSTYLED */
--			fprintf(keyloadunit_f,
--			    "\n"
--			    "[Service]\n"
--			    "Type=oneshot\n"
--			    "RemainAfterExit=yes\n"
--			    "# This avoids a dependency loop involving systemd-journald.socket if this\n"
--			    "# dataset is a parent of the root filesystem.\n"
--			    "StandardOutput=null\n"
--			    "StandardError=null\n"
--			    "ExecStart=/bin/sh -c '"
--			        "set -eu;"
--			        "keystatus=\"$$(" ZFS " get -H -o value keystatus \"%s\")\";"
--			        "[ \"$$keystatus\" = \"unavailable\" ] || exit 0;",
--			    dataset);
--			if (is_prompt)
--				fprintf(keyloadunit_f,
--				    "count=0;"
--				    "while [ $$count -lt 3 ]; do "
--				        "systemd-ask-password --id=\"zfs:%s\" \"Enter passphrase for %s:\" |"
--				        "" ZFS " load-key \"%s\" && exit 0;"
--				        "count=$$((count + 1));"
--				    "done;"
--				    "exit 1",
--				    dataset, dataset, dataset);
--			else
--				fprintf(keyloadunit_f,
--				    "" ZFS " load-key \"%s\"",
--				    dataset);
--
--			fprintf(keyloadunit_f,
--				"'\n"
--				"ExecStop=/bin/sh -c '"
--				    "set -eu;"
--				    "keystatus=\"$$(" ZFS " get -H -o value keystatus \"%s\")\";"
--				    "[ \"$$keystatus\" = \"available\" ] || exit 0;"
--				    "" ZFS " unload-key \"%s\""
--				"'\n",
--				dataset, dataset);
--			/* END CSTYLED */
--
--			(void) fclose(keyloadunit_f);
--		}
--
--		/* Update dependencies for the mount file to want this */
--		bindsto = keyloadunit;
--		if (after[0] == '\0')
--			after = keyloadunit;
--		else if (asprintf(&toktmp, "%s %s", after, keyloadunit) != -1)
--			after = toktmp;
--		else
--			EXIT_ENOMEM();
--	}
--
--
--	/* Skip generation of the mount unit if org.openzfs.systemd:ignore=on */
--	if (strcmp(p_systemd_ignore, "-") == 0 ||
--	    strcmp(p_systemd_ignore, "off") == 0) {
--		/* ok */
--	} else if (strcmp(p_systemd_ignore, "on") == 0)
--		return (0);
--	else {
--		fprintf(stderr, PROGNAME "[%d]: %s: "
--		    "invalid org.openzfs.systemd:ignore=%s\n",
--		    getpid(), dataset, p_systemd_ignore);
--		return (1);
--	}
--
--	/* Check for canmount */
--	if (strcmp(p_canmount, "on") == 0) {
--		/* ok */
--	} else if (strcmp(p_canmount, "noauto") == 0)
--		noauto = true;
--	else if (strcmp(p_canmount, "off") == 0)
--		return (0);
--	else {
--		fprintf(stderr, PROGNAME "[%d]: %s: invalid canmount=%s\n",
--		    getpid(), dataset, p_canmount);
--		return (1);
--	}
--
--	/* Check for legacy and blank mountpoints */
--	if (strcmp(p_mountpoint, "legacy") == 0 ||
--	    strcmp(p_mountpoint, "none") == 0)
--		return (0);
--	else if (p_mountpoint[0] != '/') {
--		fprintf(stderr, PROGNAME "[%d]: %s: invalid mountpoint=%s\n",
--		    getpid(), dataset, p_mountpoint);
--		return (1);
--	}
--
--	/* Escape the mountpoint per systemd policy */
--	simplify_path(p_mountpoint);
--	const char *mountfile = systemd_escape_path(p_mountpoint, "", ".mount");
--	if (mountfile == NULL) {
--		fprintf(stderr,
--		    PROGNAME "[%d]: %s: abnormal simplified mountpoint: %s\n",
--		    getpid(), dataset, p_mountpoint);
--		return (1);
--	}
--
--
--	/*
--	 * Parse options, cf. lib/libzfs/libzfs_mount.c:zfs_add_options
--	 *
--	 * The longest string achievable here is
--	 * ",atime,strictatime,nodev,noexec,rw,nosuid,nomand".
--	 */
--	char opts[64] = "";
--
--	/* atime */
--	if (strcmp(p_atime, "on") == 0) {
--		/* relatime */
--		if (strcmp(p_relatime, "on") == 0)
--			strcat(opts, ",atime,relatime");
--		else if (strcmp(p_relatime, "off") == 0)
--			strcat(opts, ",atime,strictatime");
--		else
--			fprintf(stderr,
--			    PROGNAME "[%d]: %s: invalid relatime=%s\n",
--			    getpid(), dataset, p_relatime);
--	} else if (strcmp(p_atime, "off") == 0) {
--		strcat(opts, ",noatime");
--	} else
--		fprintf(stderr, PROGNAME "[%d]: %s: invalid atime=%s\n",
--		    getpid(), dataset, p_atime);
--
--	/* devices */
--	if (strcmp(p_devices, "on") == 0)
--		strcat(opts, ",dev");
--	else if (strcmp(p_devices, "off") == 0)
--		strcat(opts, ",nodev");
--	else
--		fprintf(stderr, PROGNAME "[%d]: %s: invalid devices=%s\n",
--		    getpid(), dataset, p_devices);
--
--	/* exec */
--	if (strcmp(p_exec, "on") == 0)
--		strcat(opts, ",exec");
--	else if (strcmp(p_exec, "off") == 0)
--		strcat(opts, ",noexec");
--	else
--		fprintf(stderr, PROGNAME "[%d]: %s: invalid exec=%s\n",
--		    getpid(), dataset, p_exec);
--
--	/* readonly */
--	if (strcmp(p_readonly, "on") == 0)
--		strcat(opts, ",ro");
--	else if (strcmp(p_readonly, "off") == 0)
--		strcat(opts, ",rw");
--	else
--		fprintf(stderr, PROGNAME "[%d]: %s: invalid readonly=%s\n",
--		    getpid(), dataset, p_readonly);
--
--	/* setuid */
--	if (strcmp(p_setuid, "on") == 0)
--		strcat(opts, ",suid");
--	else if (strcmp(p_setuid, "off") == 0)
--		strcat(opts, ",nosuid");
--	else
--		fprintf(stderr, PROGNAME "[%d]: %s: invalid setuid=%s\n",
--		    getpid(), dataset, p_setuid);
--
--	/* nbmand */
--	if (strcmp(p_nbmand, "on") == 0)
--		strcat(opts, ",mand");
--	else if (strcmp(p_nbmand, "off") == 0)
--		strcat(opts, ",nomand");
--	else
--		fprintf(stderr, PROGNAME "[%d]: %s: invalid nbmand=%s\n",
--		    getpid(), dataset, p_setuid);
--
--	if (strcmp(p_systemd_wantedby, "-") != 0) {
--		noauto = true;
--
--		if (strcmp(p_systemd_wantedby, "none") != 0)
--			wantedby = p_systemd_wantedby;
--	}
--
--	if (strcmp(p_systemd_requiredby, "-") != 0) {
--		noauto = true;
--
--		if (strcmp(p_systemd_requiredby, "none") != 0)
--			requiredby = p_systemd_requiredby;
--	}
--
--	/*
--	 * For datasets with canmount=on, a dependency is created for
--	 * local-fs.target by default. To avoid regressions, this dependency
--	 * is reduced to "wants" rather than "requires" when nofail!=off.
--	 * **THIS MAY CHANGE**
--	 * noauto=on disables this behavior completely.
--	 */
--	if (!noauto) {
--		if (strcmp(p_systemd_nofail, "off") == 0)
--			requiredby = strdupa("local-fs.target");
--		else {
--			wantedby = strdupa("local-fs.target");
--			wantedby_append = strcmp(p_systemd_nofail, "on") != 0;
--		}
--	}
--
--	/*
--	 * Handle existing files:
--	 * 1.	We never overwrite existing files, although we may delete
--	 * 	files if we're sure they were created by us. (see 5.)
--	 * 2.	We handle files differently based on canmount.
--	 * 	Units with canmount=on always have precedence over noauto.
--	 * 	This is enforced by the noauto_not_on_sem semaphore,
--	 * 	which is only unlocked when the last canmount=on process exits.
--	 * 	It is important to use p_canmount and not noauto here,
--	 * 	since we categorise by canmount while other properties,
--	 * 	e.g. org.openzfs.systemd:wanted-by, also modify noauto.
--	 * 3.	If no unit file exists for a noauto dataset, we create one.
--	 * 	Additionally, we use noauto_files to track the unit file names
--	 * 	(which are the systemd-escaped mountpoints) of all (exclusively)
--	 * 	noauto datasets that had a file created.
--	 * 4.	If the file to be created is found in the tracking array,
--	 * 	we do NOT create it.
--	 * 5.	If a file exists for a noauto dataset,
--	 * 	we check whether the file name is in the array.
--	 * 	If it is, we have multiple noauto datasets for the same
--	 * 	mountpoint. In such cases, we remove the file for safety.
--	 * 	We leave the file name in the tracking array to avoid
--	 * 	further noauto datasets creating a file for this path again.
--	 */
--
--	{
--		sem_t *our_sem = (strcmp(p_canmount, "on") == 0) ?
--		    &noauto_files->noauto_names_sem :
--		    &noauto_files->noauto_not_on_sem;
--		while (sem_wait(our_sem) == -1 && errno == EINTR)
--			;
--	}
--
--	struct stat stbuf;
--	bool already_exists = fstatat(destdir_fd, mountfile, &stbuf, 0) == 0;
--
--	bool is_known = false;
--	for (size_t i = 0; i < noauto_files->noauto_names_len; ++i) {
--		if (strncmp(
--		    noauto_files->noauto_names[i], mountfile, NAME_MAX) == 0) {
--			is_known = true;
--			break;
--		}
--	}
--
--	if (already_exists) {
--		if (is_known) {
--			/* If it's in $noauto_files, we must be noauto too */
--
--			/* See 5 */
--			errno = 0;
--			(void) unlinkat(destdir_fd, mountfile, 0);
--
--			/* See 2 */
--			fprintf(stderr, PROGNAME "[%d]: %s: "
--			    "removing duplicate noauto unit %s%s%s\n",
--			    getpid(), dataset, mountfile,
--			    errno ? "" : " failed: ",
--			    errno ? "" : strerror(errno));
--		} else {
--			/* Don't log for canmount=noauto */
--			if (strcmp(p_canmount, "on") == 0)
--				fprintf(stderr, PROGNAME "[%d]: %s: "
--				    "%s already exists. Skipping.\n",
--				    getpid(), dataset, mountfile);
--		}
--
--		/* File exists: skip current dataset */
--		if (strcmp(p_canmount, "on") == 0)
--			sem_post(&noauto_files->noauto_names_sem);
--		return (0);
--	} else {
--		if (is_known) {
--			/* See 4 */
--			if (strcmp(p_canmount, "on") == 0)
--				sem_post(&noauto_files->noauto_names_sem);
--			return (0);
--		} else if (strcmp(p_canmount, "noauto") == 0) {
--			if (noauto_files->noauto_names_len ==
--			    noauto_files->noauto_names_max)
--				fprintf(stderr, PROGNAME "[%d]: %s: "
--				    "noauto dataset limit (%zu) reached! "
--				    "Not tracking %s. Please report this to "
--				    "https://github.com/openzfs/zfs\n",
--				    getpid(), dataset,
--				    noauto_files->noauto_names_max, mountfile);
--			else {
--				strncpy(noauto_files->noauto_names[
--				    noauto_files->noauto_names_len],
--				    mountfile, NAME_MAX);
--				++noauto_files->noauto_names_len;
--			}
--		}
--	}
--
--
--	FILE *mountfile_f = fopenat(destdir_fd, mountfile,
--	    O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, "w", 0644);
--	if (strcmp(p_canmount, "on") == 0)
--		sem_post(&noauto_files->noauto_names_sem);
--	if (!mountfile_f) {
--		fprintf(stderr,
--		    PROGNAME "[%d]: %s: couldn't open %s under %s: %s\n",
--		    getpid(), dataset, mountfile, destdir, strerror(errno));
--		return (1);
--	}
--
--	fprintf(mountfile_f,
--	    OUTPUT_HEADER
--	    "[Unit]\n"
--	    "SourcePath=" FSLIST "/%s\n"
--	    "Documentation=man:zfs-mount-generator(8)\n"
--	    "\n"
--	    "Before=",
--	    cachefile);
--
--	if (p_systemd_before)
--		fprintf(mountfile_f, "%s ", p_systemd_before);
--	fprintf(mountfile_f, "zfs-mount.service"); /* Ensures we don't race */
--	if (requiredby)
--		fprintf(mountfile_f, " %s", requiredby);
--	if (wantedby && wantedby_append)
--		fprintf(mountfile_f, " %s", wantedby);
--
--	fprintf(mountfile_f,
--	    "\n"
--	    "After=");
--	if (p_systemd_after)
--		fprintf(mountfile_f, "%s ", p_systemd_after);
--	fprintf(mountfile_f, "%s\n", after);
--
--	fprintf(mountfile_f, "Wants=%s\n", wants);
--
--	if (bindsto)
--		fprintf(mountfile_f, "BindsTo=%s\n", bindsto);
--	if (p_systemd_requires)
--		fprintf(mountfile_f, "Requires=%s\n", p_systemd_requires);
--	if (p_systemd_requiresmountsfor)
--		fprintf(mountfile_f,
--		    "RequiresMountsFor=%s\n", p_systemd_requiresmountsfor);
--
--	fprintf(mountfile_f,
--	    "\n"
--	    "[Mount]\n"
--	    "Where=%s\n"
--	    "What=%s\n"
--	    "Type=zfs\n"
--	    "Options=defaults%s,zfsutil\n",
--	    p_mountpoint, dataset, opts);
--
--	(void) fclose(mountfile_f);
--
--	if (!requiredby && !wantedby)
--		return (0);
--
--	/* Finally, create the appropriate dependencies */
--	char *linktgt;
--	if (asprintf(&linktgt, "../%s", mountfile) == -1)
--		EXIT_ENOMEM();
--
--	char *dependencies[][2] = {
--		{"wants", wantedby},
--		{"requires", requiredby},
--		{}
--	};
--	for (__typeof__(&*dependencies) dep = &*dependencies; **dep; ++dep) {
--		if (!(*dep)[1])
--			continue;
--
--		for (char *reqby = strtok_r((*dep)[1], " ", &toktmp);
--		    reqby;
--		    reqby = strtok_r(NULL, " ", &toktmp)) {
--			char *depdir;
--			if (asprintf(&depdir, "%s.%s", reqby, (*dep)[0]) == -1)
--				EXIT_ENOMEM();
--
--			(void) mkdirat(destdir_fd, depdir, 0755);
--			int depdir_fd = openat(destdir_fd, depdir,
--			    O_PATH | O_DIRECTORY | O_CLOEXEC);
--			if (depdir_fd < 0) {
--				fprintf(stderr, PROGNAME "[%d]: %s: "
--				    "couldn't open %s under %s: %s\n",
--				    getpid(), dataset, depdir, destdir,
--				    strerror(errno));
--				free(depdir);
--				continue;
--			}
--
--			if (symlinkat(linktgt, depdir_fd, mountfile) == -1)
--				fprintf(stderr, PROGNAME "[%d]: %s: "
--				    "couldn't symlink at "
--				    "%s under %s under %s: %s\n",
--				    getpid(), dataset, mountfile,
--				    depdir, destdir, strerror(errno));
--
--			(void) close(depdir_fd);
--			free(depdir);
--		}
--	}
--
--	return (0);
--}
--
--
--static int
--pool_enumerator(zpool_handle_t *pool, void *data __attribute__((unused)))
--{
--	int ret = 0;
--
--	/*
--	 * Pools are guaranteed-unique by the kernel,
--	 * no risk of leaking dupes here
--	 */
--	char *name = strdup(zpool_get_name(pool));
--	if (!name || !tsearch(name, &known_pools, STRCMP)) {
--		free(name);
--		ret = ENOMEM;
--	}
--
--	zpool_close(pool);
--	return (ret);
--}
--
--int
--main(int argc, char **argv)
--{
--	struct timespec time_init = {};
--	clock_gettime(CLOCK_MONOTONIC_RAW, &time_init);
--
--	{
--		int kmfd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC);
--		if (kmfd >= 0) {
--			(void) dup2(kmfd, STDERR_FILENO);
--			(void) close(kmfd);
--		}
--	}
--
--	uint8_t debug = 0;
--
--	argv0 = argv[0];
--	switch (argc) {
--	case 1:
--		/* Use default */
--		break;
--	case 2:
--	case 4:
--		destdir = argv[1];
--		break;
--	default:
--		fprintf(stderr,
--		    PROGNAME "[%d]: wrong argument count: %d\n",
--		    getpid(), argc - 1);
--		_exit(1);
--	}
--
--	{
--		destdir_fd = open(destdir, O_PATH | O_DIRECTORY | O_CLOEXEC);
--		if (destdir_fd < 0) {
--			fprintf(stderr, PROGNAME "[%d]: "
--			    "can't open destination directory %s: %s\n",
--			    getpid(), destdir, strerror(errno));
--			_exit(1);
--		}
--	}
--
--	DIR *fslist_dir = opendir(FSLIST);
--	if (!fslist_dir) {
--		if (errno != ENOENT)
--			fprintf(stderr,
--			    PROGNAME "[%d]: couldn't open " FSLIST ": %s\n",
--			    getpid(), strerror(errno));
--		_exit(0);
--	}
--
--	{
--		libzfs_handle_t *libzfs = libzfs_init();
--		if (libzfs) {
--			if (zpool_iter(libzfs, pool_enumerator, NULL) != 0)
--				fprintf(stderr, PROGNAME "[%d]: "
--				    "error listing pools, ignoring\n",
--				    getpid());
--			libzfs_fini(libzfs);
--		} else
--			fprintf(stderr, PROGNAME "[%d]: "
--			    "couldn't start libzfs, ignoring\n",
--			    getpid());
--	}
--
--	{
--		int regerr = regcomp(&uri_regex, URI_REGEX_S, 0);
--		if (regerr != 0) {
--			fprintf(stderr,
--			    PROGNAME "[%d]: invalid regex: %d\n",
--			    getpid(), regerr);
--			_exit(1);
--		}
--	}
--
--	{
--		/*
--		 * We could just get a gigabyte here and Not Care,
--		 * but if vm.overcommit_memory=2, then MAP_NORESERVE is ignored
--		 * and we'd try (and likely fail) to rip it out of swap
--		 */
--		noauto_files = mmap(NULL, 4 * 1024 * 1024,
--		    PROT_READ | PROT_WRITE,
--		    MAP_SHARED | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);
--		if (noauto_files == MAP_FAILED) {
--			fprintf(stderr,
--			    PROGNAME "[%d]: couldn't allocate IPC region: %s\n",
--			    getpid(), strerror(errno));
--			_exit(1);
--		}
--
--		sem_init(&noauto_files->noauto_not_on_sem, true, 0);
--		sem_init(&noauto_files->noauto_names_sem, true, 1);
--		noauto_files->noauto_names_len = 0;
--		/* Works out to 16447ish, *well* enough */
--		noauto_files->noauto_names_max =
--		    (4 * 1024 * 1024 - sizeof (*noauto_files)) / NAME_MAX;
--	}
--
--	char *line = NULL;
--	size_t linelen = 0;
--	struct timespec time_start = {};
--	{
--		const char *dbgenv = getenv("ZFS_DEBUG");
--		if (dbgenv)
--			debug = atoi(dbgenv);
--		else {
--			FILE *cmdline = fopen("/proc/cmdline", "re");
--			if (cmdline != NULL) {
--				if (getline(&line, &linelen, cmdline) >= 0)
--					debug = strstr(line, "debug") ? 2 : 0;
--				(void) fclose(cmdline);
--			}
--		}
--
--		if (debug && !isatty(STDOUT_FILENO))
--			dup2(STDERR_FILENO, STDOUT_FILENO);
--	}
--
--	size_t forked_canmount_on = 0;
--	size_t forked_canmount_not_on = 0;
--	size_t canmount_on_pids_len = 128;
--	pid_t *canmount_on_pids =
--	    malloc(canmount_on_pids_len * sizeof (*canmount_on_pids));
--	if (canmount_on_pids == NULL)
--		canmount_on_pids_len = 0;
--
--	if (debug)
--		clock_gettime(CLOCK_MONOTONIC_RAW, &time_start);
--
--	ssize_t read;
--	pid_t pid;
--	struct dirent *cachent;
--	while ((cachent = readdir(fslist_dir)) != NULL) {
--		if (strcmp(cachent->d_name, ".") == 0 ||
--		    strcmp(cachent->d_name, "..") == 0)
--			continue;
--
--		FILE *cachefile = fopenat(dirfd(fslist_dir), cachent->d_name,
--		    O_RDONLY | O_CLOEXEC, "r", 0);
--		if (!cachefile) {
--			fprintf(stderr, PROGNAME "[%d]: "
--			    "couldn't open %s under " FSLIST ": %s\n",
--			    getpid(), cachent->d_name, strerror(errno));
--			continue;
--		}
--
--		while ((read = getline(&line, &linelen, cachefile)) >= 0) {
--			line[read - 1] = '\0'; /* newline */
--
--			switch (pid = fork()) {
--			case -1:
--				fprintf(stderr,
--				    PROGNAME "[%d]: couldn't fork for %s: %s\n",
--				    getpid(), line, strerror(errno));
--				break;
--			case 0: /* child */
--				_exit(line_worker(line, cachent->d_name));
--			default: { /* parent */
--				char *tmp;
--				char *dset = strtok_r(line, "\t", &tmp);
--				strtok_r(NULL, "\t", &tmp);
--				char *canmount = strtok_r(NULL, "\t", &tmp);
--				bool canmount_on =
--				    canmount && strncmp(canmount, "on", 2) == 0;
--
--				if (debug >= 2)
--					printf(PROGNAME ": forked %d, "
--					    "canmount_on=%d, dataset=%s\n",
--					    (int)pid, canmount_on, dset);
--
--				if (canmount_on &&
--				    forked_canmount_on ==
--				    canmount_on_pids_len) {
--					size_t new_len =
--					    (canmount_on_pids_len ?: 16) * 2;
--					void *new_pidlist =
--					    realloc(canmount_on_pids,
--					    new_len *
--					    sizeof (*canmount_on_pids));
--					if (!new_pidlist) {
--						fprintf(stderr,
--						    PROGNAME "[%d]: "
--						    "out of memory! "
--						    "Mount ordering may be "
--						    "affected.\n", getpid());
--						continue;
--					}
--
--					canmount_on_pids = new_pidlist;
--					canmount_on_pids_len = new_len;
--				}
--
--				if (canmount_on) {
--					canmount_on_pids[forked_canmount_on] =
--					    pid;
--					++forked_canmount_on;
--				} else
--					++forked_canmount_not_on;
--				break;
--			}
--			}
--		}
--
--		(void) fclose(cachefile);
--	}
--	free(line);
--
--	if (forked_canmount_on == 0) {
--		/* No canmount=on processes to finish, so don't deadlock here */
--		for (size_t i = 0; i < forked_canmount_not_on; ++i)
--			sem_post(&noauto_files->noauto_not_on_sem);
--	} else {
--		/* Likely a no-op, since we got these from a narrow fork loop */
--		qsort(canmount_on_pids, forked_canmount_on,
--		    sizeof (*canmount_on_pids), PID_T_CMP);
--	}
--
--	int status, ret = 0;
--	struct rusage usage;
--	size_t forked_canmount_on_max = forked_canmount_on;
--	while ((pid = wait4(-1, &status, 0, &usage)) != -1) {
--		ret |= WEXITSTATUS(status) | WTERMSIG(status);
--
--		if (forked_canmount_on != 0) {
--			if (bsearch(&pid, canmount_on_pids,
--			    forked_canmount_on_max, sizeof (*canmount_on_pids),
--			    PID_T_CMP))
--				--forked_canmount_on;
--
--			if (forked_canmount_on == 0) {
--				/*
--				 * All canmount=on processes have finished,
--				 * let all the lower-priority ones finish now
--				 */
--				for (size_t i = 0;
--				    i < forked_canmount_not_on; ++i)
--					sem_post(
--					    &noauto_files->noauto_not_on_sem);
--			}
--		}
--
--		if (debug >= 2)
--			printf(PROGNAME ": %d done, user=%llu.%06us, "
--			    "system=%llu.%06us, maxrss=%ldB, ex=0x%x\n",
--			    (int)pid,
--			    (unsigned long long) usage.ru_utime.tv_sec,
--			    (unsigned int) usage.ru_utime.tv_usec,
--			    (unsigned long long) usage.ru_stime.tv_sec,
--			    (unsigned int) usage.ru_stime.tv_usec,
--			    usage.ru_maxrss * 1024, status);
--	}
--
--	if (debug) {
--		struct timespec time_end = {};
--		clock_gettime(CLOCK_MONOTONIC_RAW, &time_end);
--
--		getrusage(RUSAGE_SELF, &usage);
--		printf(
--		    "\n"
--		    PROGNAME ": self    : "
--		    "user=%llu.%06us, system=%llu.%06us, maxrss=%ldB\n",
--		    (unsigned long long) usage.ru_utime.tv_sec,
--		    (unsigned int) usage.ru_utime.tv_usec,
--		    (unsigned long long) usage.ru_stime.tv_sec,
--		    (unsigned int) usage.ru_stime.tv_usec,
--		    usage.ru_maxrss * 1024);
--
--		getrusage(RUSAGE_CHILDREN, &usage);
--		printf(PROGNAME ": children: "
--		    "user=%llu.%06us, system=%llu.%06us, maxrss=%ldB\n",
--		    (unsigned long long) usage.ru_utime.tv_sec,
--		    (unsigned int) usage.ru_utime.tv_usec,
--		    (unsigned long long) usage.ru_stime.tv_sec,
--		    (unsigned int) usage.ru_stime.tv_usec,
--		    usage.ru_maxrss * 1024);
--
--		if (time_start.tv_nsec > time_end.tv_nsec) {
--			time_end.tv_nsec =
--			    1000000000 + time_end.tv_nsec - time_start.tv_nsec;
--			time_end.tv_sec -= 1;
--		} else
--			time_end.tv_nsec -= time_start.tv_nsec;
--		time_end.tv_sec -= time_start.tv_sec;
--
--		if (time_init.tv_nsec > time_start.tv_nsec) {
--			time_start.tv_nsec =
--			    1000000000 + time_start.tv_nsec - time_init.tv_nsec;
--			time_start.tv_sec -= 1;
--		} else
--			time_start.tv_nsec -= time_init.tv_nsec;
--		time_start.tv_sec -= time_init.tv_sec;
--
--		time_init.tv_nsec = time_start.tv_nsec + time_end.tv_nsec;
--		time_init.tv_sec =
--		    time_start.tv_sec + time_end.tv_sec +
--		    time_init.tv_nsec / 1000000000;
--		time_init.tv_nsec %= 1000000000;
--
--		printf(PROGNAME ": wall    : "
--		    "total=%llu.%09llus = "
--		    "init=%llu.%09llus + real=%llu.%09llus\n",
--		    (unsigned long long) time_init.tv_sec,
--		    (unsigned long long) time_init.tv_nsec,
--		    (unsigned long long) time_start.tv_sec,
--		    (unsigned long long) time_start.tv_nsec,
--		    (unsigned long long) time_end.tv_sec,
--		    (unsigned long long) time_end.tv_nsec);
--	}
--
--	_exit(ret);
--}
-diff --git a/etc/systemd/system-generators/zfs-mount-generator.in b/etc/systemd/system-generators/zfs-mount-generator.in
-new file mode 100755
-index 0000000000..c276fbbce5
+Index: zfs-linux/etc/systemd/system-generators/zfs-mount-generator.in
+===================================================================
 --- /dev/null
-+++ b/etc/systemd/system-generators/zfs-mount-generator.in
++++ zfs-linux/etc/systemd/system-generators/zfs-mount-generator.in
 @@ -0,0 +1,474 @@
 +#!/bin/sh
 +
@@ -1629,6 +509,352 @@ index 0000000000..c276fbbce5
 +    done
 +  )
 +done
--- 
-2.32.0
-
+Index: zfs-linux/man/man8/zfs-mount-generator.8.in
+===================================================================
+--- zfs-linux.orig/man/man8/zfs-mount-generator.8.in
++++ zfs-linux/man/man8/zfs-mount-generator.8.in
+@@ -21,98 +21,31 @@
+ .\" LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ .\" OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ .\" WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+-.\"
+-.Dd May 31, 2021
+-.Dt ZFS-MOUNT-GENERATOR 8
+-.Os
+-.
+-.Sh NAME
+-.Nm zfs-mount-generator
+-.Nd generate systemd mount units for ZFS filesystems
+-.Sh SYNOPSIS
+-.Pa @systemdgeneratordir@/zfs-mount-generator
+-.
+-.Sh DESCRIPTION
+-.Nm
+-is a
+-.Xr systemd.generator 7
+-that generates native
+-.Xr systemd.mount 5
+-units for configured ZFS datasets.
+-.
+-.Ss Properties
+-.Bl -tag -compact -width "org.openzfs.systemd:required-by=unit[ unit]…"
+-.It Sy mountpoint Ns =
+-.No Skipped if Sy legacy No or Sy none .
+-.
+-.It Sy canmount Ns =
+-.No Skipped if Sy off .
+-.No Skipped if only Sy noauto
+-datasets exist for a given mountpoint and there's more than one.
+-.No Datasets with Sy yes No take precedence over ones with Sy noauto No for the same mountpoint .
+-.No Sets logical Em noauto No flag if Sy noauto .
+-Encryption roots always generate
+-.Sy zfs-load-key@ Ns Ar root Ns Sy .service ,
+-even if
+-.Sy off .
+-.
+-.It Sy atime Ns = , Sy relatime Ns = , Sy devices Ns = , Sy exec Ns = , Sy readonly Ns = , Sy setuid Ns = , Sy nbmand Ns =
+-Used to generate mount options equivalent to
+-.Nm zfs Cm mount .
+-.
+-.It Sy encroot Ns = , Sy keylocation Ns =
+-If the dataset is an encryption root, its mount unit will bind to
+-.Sy zfs-load-key@ Ns Ar root Ns Sy .service ,
+-with additional dependencies as follows:
+-.Bl -tag -compact -offset Ds -width "keylocation=https://URL (et al.)"
+-.It Sy keylocation Ns = Ns Sy prompt
+-None, uses
+-.Xr systemd-ask-password 1
+-.It Sy keylocation Ns = Ns Sy https:// Ns Ar URL Pq et al.\&
+-.Sy Wants Ns = , Sy After Ns = : Pa network-online.target
+-.It Sy keylocation Ns = Ns Sy file:// Ns < Ns Ar path Ns >
+-.Sy RequiresMountsFor Ns = Ns Ar path
+-.El
+-.
+-The service also uses the same
+-.Sy Wants Ns = ,
+-.Sy After Ns = ,
+-.Sy Requires Ns = , No and
+-.Sy RequiresMountsFor Ns = ,
+-as the mount unit.
+-.
+-.It Sy org.openzfs.systemd:requires Ns = Ns Pa path Ns Oo " " Ns Pa path Oc Ns …
+-.No Sets Sy Requires Ns = for the mount- and key-loading unit.
+-.
+-.It Sy org.openzfs.systemd:requires-mounts-for Ns = Ns Pa path Ns Oo " " Ns Pa path Oc Ns …
+-.No Sets Sy RequiresMountsFor Ns = for the mount- and key-loading unit.
+-.
+-.It Sy org.openzfs.systemd:before Ns = Ns Pa unit Ns Oo " " Ns Pa unit Oc Ns …
+-.No Sets Sy Before Ns = for the mount unit.
+-.
+-.It Sy org.openzfs.systemd:after Ns = Ns Pa unit Ns Oo " " Ns Pa unit Oc Ns …
+-.No Sets Sy After Ns = for the mount unit.
+-.
+-.It Sy org.openzfs.systemd:wanted-by Ns = Ns Pa unit Ns Oo " " Ns Pa unit Oc Ns …
+-.No Sets logical Em noauto No flag (see below) .
+-.No If not Sy none , No sets Sy WantedBy Ns = for the mount unit.
+-.It Sy org.openzfs.systemd:required-by Ns = Ns Pa unit Ns Oo " " Ns Pa unit Oc Ns …
+-.No Sets logical Em noauto No flag (see below) .
+-.No If not Sy none , No sets Sy RequiredBy Ns = for the mount unit.
+-.
+-.It Sy org.openzfs.systemd:nofail Ns = Ns (unset) Ns | Ns Sy on Ns | Ns Sy off
+-Waxes or wanes strength of default reverse dependencies of the mount unit, see
+-below.
+-.
+-.It Sy org.openzfs.systemd:ignore Ns = Ns Sy on Ns | Ns Sy off
+-.No Skip if Sy on .
+-.No Defaults to Sy off .
+-.El
+-.
+-.Ss Unit Ordering And Dependencies
+-Additionally, unless the pool the dataset resides on
+-is imported at generation time, both units gain
+-.Sy Wants Ns = Ns Pa zfs-import.target
++
++.TH ZFS-MOUNT-GENERATOR 8 "Aug 24, 2020" OpenZFS
++
++.SH "NAME"
++zfs\-mount\-generator \- generates systemd mount units for ZFS
++.SH SYNOPSIS
++.B @systemdgeneratordir@/zfs\-mount\-generator
++.sp
++.SH DESCRIPTION
++zfs\-mount\-generator implements the \fBGenerators Specification\fP
++of
++.BR systemd (1),
++and is called during early boot to generate
++.BR systemd.mount (5)
++units for automatically mounted datasets. Mount ordering and dependencies
++are created for all tracked pools (see below).
++
++.SS ENCRYPTION KEYS
++If the dataset is an encryption root, a service that loads the associated key (either from file or through a
++.BR systemd\-ask\-password (1)
++prompt) will be created. This service
++. BR RequiresMountsFor
++the path of the key (if file-based) and also copies the mount unit's
++.BR After ,
++.BR Before
+ and
+ .Sy After Ns = Ns Pa zfs-import.target .
+ .Pp
+@@ -132,51 +65,173 @@ of strength
+ .
+ .Ss Cache File
+ Because ZFS pools may not be available very early in the boot process,
+-information on ZFS mountpoints must be stored separately.
+-The output of
+-.Dl Nm zfs Cm list Fl Ho Ar name , Ns Aq every property above in order
+-for datasets that should be mounted by systemd should be kept at
+-.Pa @sysconfdir@/zfs/zfs-list.cache/ Ns Ar poolname ,
+-and, if writeable, will be kept synchronized for the entire pool by the
+-.Pa history_event-zfs-list-cacher.sh
+-ZEDLET, if enabled
+-.Pq see Xr zed 8 .
+-.
+-.Sh ENVIRONMENT
+-If the
+-.Sy ZFS_DEBUG
+-environment variable is nonzero
+-.Pq or unset and Pa /proc/cmdline No contains Qq Sy debug ,
+-print summary accounting information at the end.
+-.
+-.Sh EXAMPLES
++information on ZFS mountpoints must be stored separately. The output of the command
++.PP
++.RS 4
++zfs list -H -o name,mountpoint,canmount,atime,relatime,devices,exec,readonly,setuid,nbmand,encroot,keylocation,org.openzfs.systemd:requires,org.openzfs.systemd:requires-mounts-for,org.openzfs.systemd:before,org.openzfs.systemd:after,org.openzfs.systemd:wanted-by,org.openzfs.systemd:required-by,org.openzfs.systemd:nofail,org.openzfs.systemd:ignore
++
++.RE
++.PP
++for datasets that should be mounted by systemd, should be kept
++separate from the pool, at
++.PP
++.RS 4
++.RI @sysconfdir@/zfs/zfs-list.cache/ POOLNAME
++.
++.RE
++.PP
++The cache file, if writeable, will be kept synchronized with the pool
++state by the ZEDLET
++.PP
++.RS 4
++history_event-zfs-list-cacher.sh .
++.RE
++.PP
++.sp
++.SS PROPERTIES
++The behavior of the generator script can be influenced by the following dataset properties:
++.sp
++.TP 4
++.BR canmount = on | off | noauto
++If a dataset has
++.BR mountpoint
++set and
++.BR canmount
++is not
++.BR off ,
++a mount unit will be generated.
++Additionally, if
++.BR canmount
++is
++.BR on ,
++.BR local-fs.target
++will gain a dependency on the mount unit.
++
++This behavior is equal to the
++.BR auto
++and
++.BR noauto
++legacy mount options, see
++.BR systemd.mount (5).
++
++Encryption roots always generate a key-load service, even for
++.BR canmount=off .
++.TP 4
++.BR org.openzfs.systemd:requires\-mounts\-for = \fIpath\fR...
++Space\-separated list of mountpoints to require to be mounted for this mount unit
++.TP 4
++.BR org.openzfs.systemd:before = \fIunit\fR...
++The mount unit and associated key\-load service will be ordered before this space\-separated list of units.
++.TP 4
++.BR org.openzfs.systemd:after = \fIunit\fR...
++The mount unit and associated key\-load service will be ordered after this space\-separated list of units.
++.TP 4
++.BR org.openzfs.systemd:wanted\-by = \fIunit\fR...
++Space-separated list of units that will gain a
++.BR Wants
++dependency on this mount unit.
++Setting this property implies
++.BR noauto .
++.TP 4
++.BR org.openzfs.systemd:required\-by = \fIunit\fR...
++Space-separated list of units that will gain a
++.BR Requires
++dependency on this mount unit.
++Setting this property implies
++.BR noauto .
++.TP 4
++.BR org.openzfs.systemd:nofail = unset | on | off
++Toggles between a
++.BR Wants
++and
++.BR Requires
++type of dependency between the mount unit and
++.BR local-fs.target ,
++if
++.BR noauto
++isn't set or implied.
++
++.BR on :
++Mount will be
++.BR WantedBy
++local-fs.target
++
++.BR off :
++Mount will be
++.BR Before
++and
++.BR RequiredBy
++local-fs.target
++
++.BR unset :
++Mount will be
++.BR Before
++and
++.BR WantedBy
++local-fs.target
++.TP 4
++.BR org.openzfs.systemd:ignore = on | off
++If set to
++.BR on ,
++do not generate a mount unit for this dataset.
++
++See also
++.BR systemd.mount (5)
++
++.PP
++.SH EXAMPLE
+ To begin, enable tracking for the pool:
+-.Dl # Nm touch Pa @sysconfdir@/zfs/zfs-list.cache/ Ns Ar poolname
+-Then enable the tracking ZEDLET:
+-.Dl # Nm ln Fl s Pa @zfsexecdir@/zed.d/history_event-zfs-list-cacher.sh @sysconfdir@/zfs/zed.d
+-.Dl # Nm systemctl Cm enable Pa zfs-zed.service
+-.Dl # Nm systemctl Cm restart Pa zfs-zed.service
+-.Pp
+-If no history event is in the queue,
+-inject one to ensure the ZEDLET runs to refresh the cache file
+-by setting a monitored property somewhere on the pool:
+-.Dl # Nm zfs Cm set Sy relatime Ns = Ns Sy off Ar poolname/dset
+-.Dl # Nm zfs Cm inherit Sy relatime Ar poolname/dset
+-.Pp
+-To test the generator output:
+-.Dl $ Nm mkdir Pa /tmp/zfs-mount-generator
+-.Dl $ Nm @systemdgeneratordir@/zfs-mount-generator Pa /tmp/zfs-mount-generator
+-.
+-If the generated units are satisfactory, instruct
+-.Nm systemd
+-to re-run all generators:
+-.Dl # Nm systemctl daemon-reload
+-.
+-.Sh SEE ALSO
+-.Xr systemd.mount 5 ,
+-.Xr systemd.target 5 ,
+-.Xr zfs 5 ,
+-.Xr systemd.generator 7 ,
+-.Xr systemd.special 7 ,
+-.Xr zed 8 ,
+-.Xr zpool-events 8
++.PP
++.RS 4
++touch
++.RI @sysconfdir@/zfs/zfs-list.cache/ POOLNAME
++.RE
++.PP
++Then, enable the tracking ZEDLET:
++.PP
++.RS 4
++ln -s "@zfsexecdir@/zed.d/history_event-zfs-list-cacher.sh" "@sysconfdir@/zfs/zed.d"
++
++systemctl enable zfs-zed.service
++
++systemctl restart zfs-zed.service
++.RE
++.PP
++Force the running of the ZEDLET by setting a monitored property, e.g.
++.BR canmount ,
++for at least one dataset in the pool:
++.PP
++.RS 4
++zfs set canmount=on
++.I DATASET
++.RE
++.PP
++This forces an update to the stale cache file.
++
++To test the generator output, run
++.PP
++.RS 4
++@systemdgeneratordir@/zfs-mount-generator /tmp/zfs-mount-generator . .
++.RE
++.PP
++This will generate units and dependencies in
++.I /tmp/zfs-mount-generator
++for you to inspect them. The second and third argument are ignored.
++
++If you're satisfied with the generated units, instruct systemd to re-run all generators:
++.PP
++.RS 4
++systemctl daemon-reload
++.RE
++.PP
++
++.sp
++.SH SEE ALSO
++.BR zfs (5)
++.BR zfs-events (5)
++.BR zed (8)
++.BR zpool (5)
++.BR systemd (1)
++.BR systemd.target (5)
++.BR systemd.special (7)
++.BR systemd.mount (7)
diff -pruN 2.3.5-1/debian/patches/ubuntu/0003-Revert-etc-systemd-zfs-mount-generator-rewrite-in-C.patch 2.3.5-1ubuntu1/debian/patches/ubuntu/0003-Revert-etc-systemd-zfs-mount-generator-rewrite-in-C.patch
--- 2.3.5-1/debian/patches/ubuntu/0003-Revert-etc-systemd-zfs-mount-generator-rewrite-in-C.patch	2025-03-03 06:31:51.000000000 +0000
+++ 2.3.5-1ubuntu1/debian/patches/ubuntu/0003-Revert-etc-systemd-zfs-mount-generator-rewrite-in-C.patch	1970-01-01 00:00:00.000000000 +0000
@@ -1,1634 +0,0 @@
-From 7b5a6894d4b083e89c217244aee53e35aa8e4936 Mon Sep 17 00:00:00 2001
-From: Dimitri John Ledkov <dimitri.ledkov@canonical.com>
-Date: Mon, 17 Jan 2022 14:00:42 +0000
-Subject: [PATCH 3/3] Revert "etc/systemd/zfs-mount-generator: rewrite in C"
-
-This reverts commit 0382362ce06a5514a97bbbf11dfe55e7e408898a.
-
-This is to continue support for ubuntu specific zsys patch that
-currently relies on the shell based implementation of the
-generator. See https://bugs.launchpad.net/bugs/1958142
-
-Signed-off-by: Dimitri John Ledkov <dimitri.ledkov@canonical.com>
----
- Makefile.am                                   |    2 +-
- etc/systemd/system-generators/Makefile.am     |   14 +-
- .../system-generators/zfs-mount-generator.c   | 1089 -----------------
- .../system-generators/zfs-mount-generator.in  |  474 +++++++
- 4 files changed, 478 insertions(+), 1101 deletions(-)
- delete mode 100644 etc/systemd/system-generators/zfs-mount-generator.c
- create mode 100755 etc/systemd/system-generators/zfs-mount-generator.in
-
-diff --git a/Makefile.am b/Makefile.am
-index 34fe16ce41..d4e69a749c 100644
---- a/Makefile.am
-+++ b/Makefile.am
-@@ -8,7 +8,7 @@ SUBDIRS += rpm
- endif
- 
- if CONFIG_USER
--SUBDIRS += man scripts lib tests cmd etc contrib
-+SUBDIRS += etc man scripts lib tests cmd contrib
- if BUILD_LINUX
- SUBDIRS += udev
- endif
-diff --git a/etc/systemd/system-generators/Makefile.am b/etc/systemd/system-generators/Makefile.am
-index e5920bf392..fee88dad8c 100644
---- a/etc/systemd/system-generators/Makefile.am
-+++ b/etc/systemd/system-generators/Makefile.am
-@@ -1,14 +1,6 @@
--include $(top_srcdir)/config/Rules.am
-+include $(top_srcdir)/config/Substfiles.am
- 
--systemdgenerator_PROGRAMS = \
-+systemdgenerator_SCRIPTS = \
- 	zfs-mount-generator
- 
--zfs_mount_generator_SOURCES = \
--	zfs-mount-generator.c
--
--zfs_mount_generator_LDADD = \
--	$(abs_top_builddir)/lib/libzfs/libzfs.la
--
--zfs_mount_generator_LDFLAGS = -pthread
--
--include $(top_srcdir)/config/CppCheck.am
-+SUBSTFILES += $(systemdgenerator_SCRIPTS)
-diff --git a/etc/systemd/system-generators/zfs-mount-generator.c b/etc/systemd/system-generators/zfs-mount-generator.c
-deleted file mode 100644
-index 8deeed9df0..0000000000
---- a/etc/systemd/system-generators/zfs-mount-generator.c
-+++ /dev/null
-@@ -1,1089 +0,0 @@
--/*
-- * Copyright (c) 2017 Antonio Russo <antonio.e.russo@gmail.com>
-- * Copyright (c) 2020 InsanePrawn <insane.prawny@gmail.com>
-- *
-- * Permission is hereby granted, free of charge, to any person obtaining
-- * a copy of this software and associated documentation files (the
-- * "Software"), to deal in the Software without restriction, including
-- * without limitation the rights to use, copy, modify, merge, publish,
-- * distribute, sublicense, and/or sell copies of the Software, and to
-- * permit persons to whom the Software is furnished to do so, subject to
-- * the following conditions:
-- *
-- * The above copyright notice and this permission notice shall be
-- * included in all copies or substantial portions of the Software.
-- *
-- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-- * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-- * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-- * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-- */
--
--
--#include <sys/resource.h>
--#include <sys/types.h>
--#include <sys/time.h>
--#include <sys/stat.h>
--#include <sys/wait.h>
--#include <sys/mman.h>
--#include <semaphore.h>
--#include <stdbool.h>
--#include <unistd.h>
--#include <fcntl.h>
--#include <stdio.h>
--#include <time.h>
--#include <regex.h>
--#include <search.h>
--#include <dirent.h>
--#include <string.h>
--#include <stdlib.h>
--#include <limits.h>
--#include <errno.h>
--#include <libzfs.h>
--
--#define	STRCMP ((int(*)(const void *, const void *))&strcmp)
--#define	PID_T_CMP ((int(*)(const void *, const void *))&pid_t_cmp)
--
--static int
--pid_t_cmp(const pid_t *lhs, const pid_t *rhs)
--{
--	/*
--	 * This is always valid, quoth sys_types.h(7posix):
--	 * > blksize_t, pid_t, and ssize_t shall be signed integer types.
--	 */
--	return (*lhs - *rhs);
--}
--
--#define	EXIT_ENOMEM() \
--	do { \
--		fprintf(stderr, PROGNAME "[%d]: " \
--		    "not enough memory (L%d)!\n", getpid(), __LINE__); \
--		_exit(1); \
--	} while (0)
--
--
--#define	PROGNAME "zfs-mount-generator"
--#define	FSLIST SYSCONFDIR "/zfs/zfs-list.cache"
--#define	ZFS SBINDIR "/zfs"
--
--#define	OUTPUT_HEADER \
--	"# Automatically generated by " PROGNAME "\n" \
--	"\n"
--
--/*
-- * Starts like the one in libzfs_util.c but also matches "//"
-- * and captures until the end, since we actually use it for path extraxion
-- */
--#define	URI_REGEX_S "^\\([A-Za-z][A-Za-z0-9+.\\-]*\\):\\/\\/\\(.*\\)$"
--static regex_t uri_regex;
--
--static char *argv0;
--
--static const char *destdir = "/tmp";
--static int destdir_fd = -1;
--
--static void *known_pools = NULL; /* tsearch() of C strings */
--static struct {
--	sem_t noauto_not_on_sem;
--
--	sem_t noauto_names_sem;
--	size_t noauto_names_len;
--	size_t noauto_names_max;
--	char noauto_names[][NAME_MAX];
--} *noauto_files;
--
--
--static char *
--systemd_escape(const char *input, const char *prepend, const char *append)
--{
--	size_t len = strlen(input);
--	size_t applen = strlen(append);
--	size_t prelen = strlen(prepend);
--	char *ret = malloc(4 * len + prelen + applen + 1);
--	if (!ret)
--		EXIT_ENOMEM();
--
--	memcpy(ret, prepend, prelen);
--	char *out = ret + prelen;
--
--	const char *cur = input;
--	if (*cur == '.') {
--		memcpy(out, "\\x2e", 4);
--		out += 4;
--		++cur;
--	}
--	for (; *cur; ++cur) {
--		if (*cur == '/')
--			*(out++) = '-';
--		else if (strchr(
--		    "0123456789"
--		    "abcdefghijklmnopqrstuvwxyz"
--		    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
--		    ":_.", *cur))
--			*(out++) = *cur;
--		else {
--			sprintf(out, "\\x%02x", (int)*cur);
--			out += 4;
--		}
--	}
--
--	memcpy(out, append, applen + 1);
--	return (ret);
--}
--
--static void
--simplify_path(char *path)
--{
--	char *out = path;
--	for (char *cur = path; *cur; ++cur) {
--		if (*cur == '/') {
--			while (*(cur + 1) == '/')
--				++cur;
--			*(out++) = '/';
--		} else
--			*(out++) = *cur;
--	}
--
--	*(out++) = '\0';
--}
--
--static bool
--strendswith(const char *what, const char *suff)
--{
--	size_t what_l = strlen(what);
--	size_t suff_l = strlen(suff);
--
--	return ((what_l >= suff_l) &&
--	    (strcmp(what + what_l - suff_l, suff) == 0));
--}
--
--/* Assumes already-simplified path, doesn't modify input */
--static char *
--systemd_escape_path(char *input, const char *prepend, const char *append)
--{
--	if (strcmp(input, "/") == 0) {
--		char *ret;
--		if (asprintf(&ret, "%s-%s", prepend, append) == -1)
--			EXIT_ENOMEM();
--		return (ret);
--	} else {
--		/*
--		 * path_is_normalized() (flattened for absolute paths here),
--		 * required for proper escaping
--		 */
--		if (strstr(input, "/./") || strstr(input, "/../") ||
--		    strendswith(input, "/.") || strendswith(input, "/.."))
--			return (NULL);
--
--
--		if (input[0] == '/')
--			++input;
--
--		char *back = &input[strlen(input) - 1];
--		bool deslash = *back == '/';
--		if (deslash)
--			*back = '\0';
--
--		char *ret = systemd_escape(input, prepend, append);
--
--		if (deslash)
--			*back = '/';
--		return (ret);
--	}
--}
--
--static FILE *
--fopenat(int dirfd, const char *pathname, int flags,
--    const char *stream_mode, mode_t mode)
--{
--	int fd = openat(dirfd, pathname, flags, mode);
--	if (fd < 0)
--		return (NULL);
--
--	return (fdopen(fd, stream_mode));
--}
--
--static int
--line_worker(char *line, const char *cachefile)
--{
--	char *toktmp;
--	/* BEGIN CSTYLED */
--	const char *dataset                     = strtok_r(line, "\t", &toktmp);
--	      char *p_mountpoint                = strtok_r(NULL, "\t", &toktmp);
--	const char *p_canmount                  = strtok_r(NULL, "\t", &toktmp);
--	const char *p_atime                     = strtok_r(NULL, "\t", &toktmp);
--	const char *p_relatime                  = strtok_r(NULL, "\t", &toktmp);
--	const char *p_devices                   = strtok_r(NULL, "\t", &toktmp);
--	const char *p_exec                      = strtok_r(NULL, "\t", &toktmp);
--	const char *p_readonly                  = strtok_r(NULL, "\t", &toktmp);
--	const char *p_setuid                    = strtok_r(NULL, "\t", &toktmp);
--	const char *p_nbmand                    = strtok_r(NULL, "\t", &toktmp);
--	const char *p_encroot                   = strtok_r(NULL, "\t", &toktmp) ?: "-";
--	      char *p_keyloc                    = strtok_r(NULL, "\t", &toktmp) ?: strdupa("none");
--	const char *p_systemd_requires          = strtok_r(NULL, "\t", &toktmp) ?: "-";
--	const char *p_systemd_requiresmountsfor = strtok_r(NULL, "\t", &toktmp) ?: "-";
--	const char *p_systemd_before            = strtok_r(NULL, "\t", &toktmp) ?: "-";
--	const char *p_systemd_after             = strtok_r(NULL, "\t", &toktmp) ?: "-";
--	      char *p_systemd_wantedby          = strtok_r(NULL, "\t", &toktmp) ?: strdupa("-");
--	      char *p_systemd_requiredby        = strtok_r(NULL, "\t", &toktmp) ?: strdupa("-");
--	const char *p_systemd_nofail            = strtok_r(NULL, "\t", &toktmp) ?: "-";
--	const char *p_systemd_ignore            = strtok_r(NULL, "\t", &toktmp) ?: "-";
--	/* END CSTYLED */
--
--	const char *pool = dataset;
--	if ((toktmp = strchr(pool, '/')) != NULL)
--		pool = strndupa(pool, toktmp - pool);
--
--	if (p_nbmand == NULL) {
--		fprintf(stderr, PROGNAME "[%d]: %s: not enough tokens!\n",
--		    getpid(), dataset);
--		return (1);
--	}
--
--	strncpy(argv0, dataset, strlen(argv0));
--
--	/* Minimal pre-requisites to mount a ZFS dataset */
--	const char *after = "zfs-import.target";
--	const char *wants = "zfs-import.target";
--	const char *bindsto = NULL;
--	char *wantedby = NULL;
--	char *requiredby = NULL;
--	bool noauto = false;
--	bool wantedby_append = true;
--
--	/*
--	 * zfs-import.target is not needed if the pool is already imported.
--	 * This avoids a dependency loop on root-on-ZFS systems:
--	 *   systemd-random-seed.service After (via RequiresMountsFor)
--	 *   var-lib.mount After
--	 *   zfs-import.target After
--	 *   zfs-import-{cache,scan}.service After
--	 *   cryptsetup.service After
--	 *   systemd-random-seed.service
--	 */
--	if (tfind(pool, &known_pools, STRCMP)) {
--		after = "";
--		wants = "";
--	}
--
--	if (strcmp(p_systemd_after, "-") == 0)
--		p_systemd_after = NULL;
--	if (strcmp(p_systemd_before, "-") == 0)
--		p_systemd_before = NULL;
--	if (strcmp(p_systemd_requires, "-") == 0)
--		p_systemd_requires = NULL;
--	if (strcmp(p_systemd_requiresmountsfor, "-") == 0)
--		p_systemd_requiresmountsfor = NULL;
--
--
--	if (strcmp(p_encroot, "-") != 0) {
--		char *keyloadunit =
--		    systemd_escape(p_encroot, "zfs-load-key-", ".service");
--
--		if (strcmp(dataset, p_encroot) == 0) {
--			const char *keymountdep = NULL;
--			bool is_prompt = false;
--
--			regmatch_t uri_matches[3];
--			if (regexec(&uri_regex, p_keyloc,
--			    sizeof (uri_matches) / sizeof (*uri_matches),
--			    uri_matches, 0) == 0) {
--				p_keyloc[uri_matches[2].rm_eo] = '\0';
--				const char *path =
--				    &p_keyloc[uri_matches[2].rm_so];
--
--				/*
--				 * Assumes all URI keylocations need
--				 * the mount for their path;
--				 * http://, for example, wouldn't
--				 * (but it'd need network-online.target et al.)
--				 */
--				keymountdep = path;
--			} else {
--				if (strcmp(p_keyloc, "prompt") != 0)
--					fprintf(stderr, PROGNAME "[%d]: %s: "
--					    "unknown non-URI keylocation=%s\n",
--					    getpid(), dataset, p_keyloc);
--
--				is_prompt = true;
--			}
--
--
--			/* Generate the key-load .service unit */
--			FILE *keyloadunit_f = fopenat(destdir_fd, keyloadunit,
--			    O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, "w",
--			    0644);
--			if (!keyloadunit_f) {
--				fprintf(stderr, PROGNAME "[%d]: %s: "
--				    "couldn't open %s under %s: %s\n",
--				    getpid(), dataset, keyloadunit, destdir,
--				    strerror(errno));
--				return (1);
--			}
--
--			fprintf(keyloadunit_f,
--			    OUTPUT_HEADER
--			    "[Unit]\n"
--			    "Description=Load ZFS key for %s\n"
--			    "SourcePath=" FSLIST "/%s\n"
--			    "Documentation=man:zfs-mount-generator(8)\n"
--			    "DefaultDependencies=no\n"
--			    "Wants=%s\n"
--			    "After=%s\n",
--			    dataset, cachefile, wants, after);
--
--			if (p_systemd_requires)
--				fprintf(keyloadunit_f,
--				    "Requires=%s\n", p_systemd_requires);
--
--			if (p_systemd_requiresmountsfor || keymountdep) {
--				fprintf(keyloadunit_f, "RequiresMountsFor=");
--				if (p_systemd_requiresmountsfor)
--					fprintf(keyloadunit_f,
--					    "%s ", p_systemd_requiresmountsfor);
--				if (keymountdep)
--					fprintf(keyloadunit_f,
--					    "'%s'", keymountdep);
--				fprintf(keyloadunit_f, "\n");
--			}
--
--			/* BEGIN CSTYLED */
--			fprintf(keyloadunit_f,
--			    "\n"
--			    "[Service]\n"
--			    "Type=oneshot\n"
--			    "RemainAfterExit=yes\n"
--			    "# This avoids a dependency loop involving systemd-journald.socket if this\n"
--			    "# dataset is a parent of the root filesystem.\n"
--			    "StandardOutput=null\n"
--			    "StandardError=null\n"
--			    "ExecStart=/bin/sh -c '"
--			        "set -eu;"
--			        "keystatus=\"$$(" ZFS " get -H -o value keystatus \"%s\")\";"
--			        "[ \"$$keystatus\" = \"unavailable\" ] || exit 0;",
--			    dataset);
--			if (is_prompt)
--				fprintf(keyloadunit_f,
--				    "count=0;"
--				    "while [ $$count -lt 3 ]; do "
--				        "systemd-ask-password --id=\"zfs:%s\" \"Enter passphrase for %s:\" |"
--				        "" ZFS " load-key \"%s\" && exit 0;"
--				        "count=$$((count + 1));"
--				    "done;"
--				    "exit 1",
--				    dataset, dataset, dataset);
--			else
--				fprintf(keyloadunit_f,
--				    "" ZFS " load-key \"%s\"",
--				    dataset);
--
--			fprintf(keyloadunit_f,
--				"'\n"
--				"ExecStop=/bin/sh -c '"
--				    "set -eu;"
--				    "keystatus=\"$$(" ZFS " get -H -o value keystatus \"%s\")\";"
--				    "[ \"$$keystatus\" = \"available\" ] || exit 0;"
--				    "" ZFS " unload-key \"%s\""
--				"'\n",
--				dataset, dataset);
--			/* END CSTYLED */
--
--			(void) fclose(keyloadunit_f);
--		}
--
--		/* Update dependencies for the mount file to want this */
--		bindsto = keyloadunit;
--		if (after[0] == '\0')
--			after = keyloadunit;
--		else if (asprintf(&toktmp, "%s %s", after, keyloadunit) != -1)
--			after = toktmp;
--		else
--			EXIT_ENOMEM();
--	}
--
--
--	/* Skip generation of the mount unit if org.openzfs.systemd:ignore=on */
--	if (strcmp(p_systemd_ignore, "-") == 0 ||
--	    strcmp(p_systemd_ignore, "off") == 0) {
--		/* ok */
--	} else if (strcmp(p_systemd_ignore, "on") == 0)
--		return (0);
--	else {
--		fprintf(stderr, PROGNAME "[%d]: %s: "
--		    "invalid org.openzfs.systemd:ignore=%s\n",
--		    getpid(), dataset, p_systemd_ignore);
--		return (1);
--	}
--
--	/* Check for canmount */
--	if (strcmp(p_canmount, "on") == 0) {
--		/* ok */
--	} else if (strcmp(p_canmount, "noauto") == 0)
--		noauto = true;
--	else if (strcmp(p_canmount, "off") == 0)
--		return (0);
--	else {
--		fprintf(stderr, PROGNAME "[%d]: %s: invalid canmount=%s\n",
--		    getpid(), dataset, p_canmount);
--		return (1);
--	}
--
--	/* Check for legacy and blank mountpoints */
--	if (strcmp(p_mountpoint, "legacy") == 0 ||
--	    strcmp(p_mountpoint, "none") == 0)
--		return (0);
--	else if (p_mountpoint[0] != '/') {
--		fprintf(stderr, PROGNAME "[%d]: %s: invalid mountpoint=%s\n",
--		    getpid(), dataset, p_mountpoint);
--		return (1);
--	}
--
--	/* Escape the mountpoint per systemd policy */
--	simplify_path(p_mountpoint);
--	const char *mountfile = systemd_escape_path(p_mountpoint, "", ".mount");
--	if (mountfile == NULL) {
--		fprintf(stderr,
--		    PROGNAME "[%d]: %s: abnormal simplified mountpoint: %s\n",
--		    getpid(), dataset, p_mountpoint);
--		return (1);
--	}
--
--
--	/*
--	 * Parse options, cf. lib/libzfs/libzfs_mount.c:zfs_add_options
--	 *
--	 * The longest string achievable here is
--	 * ",atime,strictatime,nodev,noexec,rw,nosuid,nomand".
--	 */
--	char opts[64] = "";
--
--	/* atime */
--	if (strcmp(p_atime, "on") == 0) {
--		/* relatime */
--		if (strcmp(p_relatime, "on") == 0)
--			strcat(opts, ",atime,relatime");
--		else if (strcmp(p_relatime, "off") == 0)
--			strcat(opts, ",atime,strictatime");
--		else
--			fprintf(stderr,
--			    PROGNAME "[%d]: %s: invalid relatime=%s\n",
--			    getpid(), dataset, p_relatime);
--	} else if (strcmp(p_atime, "off") == 0) {
--		strcat(opts, ",noatime");
--	} else
--		fprintf(stderr, PROGNAME "[%d]: %s: invalid atime=%s\n",
--		    getpid(), dataset, p_atime);
--
--	/* devices */
--	if (strcmp(p_devices, "on") == 0)
--		strcat(opts, ",dev");
--	else if (strcmp(p_devices, "off") == 0)
--		strcat(opts, ",nodev");
--	else
--		fprintf(stderr, PROGNAME "[%d]: %s: invalid devices=%s\n",
--		    getpid(), dataset, p_devices);
--
--	/* exec */
--	if (strcmp(p_exec, "on") == 0)
--		strcat(opts, ",exec");
--	else if (strcmp(p_exec, "off") == 0)
--		strcat(opts, ",noexec");
--	else
--		fprintf(stderr, PROGNAME "[%d]: %s: invalid exec=%s\n",
--		    getpid(), dataset, p_exec);
--
--	/* readonly */
--	if (strcmp(p_readonly, "on") == 0)
--		strcat(opts, ",ro");
--	else if (strcmp(p_readonly, "off") == 0)
--		strcat(opts, ",rw");
--	else
--		fprintf(stderr, PROGNAME "[%d]: %s: invalid readonly=%s\n",
--		    getpid(), dataset, p_readonly);
--
--	/* setuid */
--	if (strcmp(p_setuid, "on") == 0)
--		strcat(opts, ",suid");
--	else if (strcmp(p_setuid, "off") == 0)
--		strcat(opts, ",nosuid");
--	else
--		fprintf(stderr, PROGNAME "[%d]: %s: invalid setuid=%s\n",
--		    getpid(), dataset, p_setuid);
--
--	/* nbmand */
--	if (strcmp(p_nbmand, "on") == 0)
--		strcat(opts, ",mand");
--	else if (strcmp(p_nbmand, "off") == 0)
--		strcat(opts, ",nomand");
--	else
--		fprintf(stderr, PROGNAME "[%d]: %s: invalid nbmand=%s\n",
--		    getpid(), dataset, p_setuid);
--
--	if (strcmp(p_systemd_wantedby, "-") != 0) {
--		noauto = true;
--
--		if (strcmp(p_systemd_wantedby, "none") != 0)
--			wantedby = p_systemd_wantedby;
--	}
--
--	if (strcmp(p_systemd_requiredby, "-") != 0) {
--		noauto = true;
--
--		if (strcmp(p_systemd_requiredby, "none") != 0)
--			requiredby = p_systemd_requiredby;
--	}
--
--	/*
--	 * For datasets with canmount=on, a dependency is created for
--	 * local-fs.target by default. To avoid regressions, this dependency
--	 * is reduced to "wants" rather than "requires" when nofail!=off.
--	 * **THIS MAY CHANGE**
--	 * noauto=on disables this behavior completely.
--	 */
--	if (!noauto) {
--		if (strcmp(p_systemd_nofail, "off") == 0)
--			requiredby = strdupa("local-fs.target");
--		else {
--			wantedby = strdupa("local-fs.target");
--			wantedby_append = strcmp(p_systemd_nofail, "on") != 0;
--		}
--	}
--
--	/*
--	 * Handle existing files:
--	 * 1.	We never overwrite existing files, although we may delete
--	 * 	files if we're sure they were created by us. (see 5.)
--	 * 2.	We handle files differently based on canmount.
--	 * 	Units with canmount=on always have precedence over noauto.
--	 * 	This is enforced by the noauto_not_on_sem semaphore,
--	 * 	which is only unlocked when the last canmount=on process exits.
--	 * 	It is important to use p_canmount and not noauto here,
--	 * 	since we categorise by canmount while other properties,
--	 * 	e.g. org.openzfs.systemd:wanted-by, also modify noauto.
--	 * 3.	If no unit file exists for a noauto dataset, we create one.
--	 * 	Additionally, we use noauto_files to track the unit file names
--	 * 	(which are the systemd-escaped mountpoints) of all (exclusively)
--	 * 	noauto datasets that had a file created.
--	 * 4.	If the file to be created is found in the tracking array,
--	 * 	we do NOT create it.
--	 * 5.	If a file exists for a noauto dataset,
--	 * 	we check whether the file name is in the array.
--	 * 	If it is, we have multiple noauto datasets for the same
--	 * 	mountpoint. In such cases, we remove the file for safety.
--	 * 	We leave the file name in the tracking array to avoid
--	 * 	further noauto datasets creating a file for this path again.
--	 */
--
--	{
--		sem_t *our_sem = (strcmp(p_canmount, "on") == 0) ?
--		    &noauto_files->noauto_names_sem :
--		    &noauto_files->noauto_not_on_sem;
--		while (sem_wait(our_sem) == -1 && errno == EINTR)
--			;
--	}
--
--	struct stat stbuf;
--	bool already_exists = fstatat(destdir_fd, mountfile, &stbuf, 0) == 0;
--
--	bool is_known = false;
--	for (size_t i = 0; i < noauto_files->noauto_names_len; ++i) {
--		if (strncmp(
--		    noauto_files->noauto_names[i], mountfile, NAME_MAX) == 0) {
--			is_known = true;
--			break;
--		}
--	}
--
--	if (already_exists) {
--		if (is_known) {
--			/* If it's in $noauto_files, we must be noauto too */
--
--			/* See 5 */
--			errno = 0;
--			(void) unlinkat(destdir_fd, mountfile, 0);
--
--			/* See 2 */
--			fprintf(stderr, PROGNAME "[%d]: %s: "
--			    "removing duplicate noauto unit %s%s%s\n",
--			    getpid(), dataset, mountfile,
--			    errno ? "" : " failed: ",
--			    errno ? "" : strerror(errno));
--		} else {
--			/* Don't log for canmount=noauto */
--			if (strcmp(p_canmount, "on") == 0)
--				fprintf(stderr, PROGNAME "[%d]: %s: "
--				    "%s already exists. Skipping.\n",
--				    getpid(), dataset, mountfile);
--		}
--
--		/* File exists: skip current dataset */
--		if (strcmp(p_canmount, "on") == 0)
--			sem_post(&noauto_files->noauto_names_sem);
--		return (0);
--	} else {
--		if (is_known) {
--			/* See 4 */
--			if (strcmp(p_canmount, "on") == 0)
--				sem_post(&noauto_files->noauto_names_sem);
--			return (0);
--		} else if (strcmp(p_canmount, "noauto") == 0) {
--			if (noauto_files->noauto_names_len ==
--			    noauto_files->noauto_names_max)
--				fprintf(stderr, PROGNAME "[%d]: %s: "
--				    "noauto dataset limit (%zu) reached! "
--				    "Not tracking %s. Please report this to "
--				    "https://github.com/openzfs/zfs\n",
--				    getpid(), dataset,
--				    noauto_files->noauto_names_max, mountfile);
--			else {
--				strncpy(noauto_files->noauto_names[
--				    noauto_files->noauto_names_len],
--				    mountfile, NAME_MAX);
--				++noauto_files->noauto_names_len;
--			}
--		}
--	}
--
--
--	FILE *mountfile_f = fopenat(destdir_fd, mountfile,
--	    O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, "w", 0644);
--	if (strcmp(p_canmount, "on") == 0)
--		sem_post(&noauto_files->noauto_names_sem);
--	if (!mountfile_f) {
--		fprintf(stderr,
--		    PROGNAME "[%d]: %s: couldn't open %s under %s: %s\n",
--		    getpid(), dataset, mountfile, destdir, strerror(errno));
--		return (1);
--	}
--
--	fprintf(mountfile_f,
--	    OUTPUT_HEADER
--	    "[Unit]\n"
--	    "SourcePath=" FSLIST "/%s\n"
--	    "Documentation=man:zfs-mount-generator(8)\n"
--	    "\n"
--	    "Before=",
--	    cachefile);
--
--	if (p_systemd_before)
--		fprintf(mountfile_f, "%s ", p_systemd_before);
--	fprintf(mountfile_f, "zfs-mount.service"); /* Ensures we don't race */
--	if (requiredby)
--		fprintf(mountfile_f, " %s", requiredby);
--	if (wantedby && wantedby_append)
--		fprintf(mountfile_f, " %s", wantedby);
--
--	fprintf(mountfile_f,
--	    "\n"
--	    "After=");
--	if (p_systemd_after)
--		fprintf(mountfile_f, "%s ", p_systemd_after);
--	fprintf(mountfile_f, "%s\n", after);
--
--	fprintf(mountfile_f, "Wants=%s\n", wants);
--
--	if (bindsto)
--		fprintf(mountfile_f, "BindsTo=%s\n", bindsto);
--	if (p_systemd_requires)
--		fprintf(mountfile_f, "Requires=%s\n", p_systemd_requires);
--	if (p_systemd_requiresmountsfor)
--		fprintf(mountfile_f,
--		    "RequiresMountsFor=%s\n", p_systemd_requiresmountsfor);
--
--	fprintf(mountfile_f,
--	    "\n"
--	    "[Mount]\n"
--	    "Where=%s\n"
--	    "What=%s\n"
--	    "Type=zfs\n"
--	    "Options=defaults%s,zfsutil\n",
--	    p_mountpoint, dataset, opts);
--
--	(void) fclose(mountfile_f);
--
--	if (!requiredby && !wantedby)
--		return (0);
--
--	/* Finally, create the appropriate dependencies */
--	char *linktgt;
--	if (asprintf(&linktgt, "../%s", mountfile) == -1)
--		EXIT_ENOMEM();
--
--	char *dependencies[][2] = {
--		{"wants", wantedby},
--		{"requires", requiredby},
--		{}
--	};
--	for (__typeof__(&*dependencies) dep = &*dependencies; **dep; ++dep) {
--		if (!(*dep)[1])
--			continue;
--
--		for (char *reqby = strtok_r((*dep)[1], " ", &toktmp);
--		    reqby;
--		    reqby = strtok_r(NULL, " ", &toktmp)) {
--			char *depdir;
--			if (asprintf(&depdir, "%s.%s", reqby, (*dep)[0]) == -1)
--				EXIT_ENOMEM();
--
--			(void) mkdirat(destdir_fd, depdir, 0755);
--			int depdir_fd = openat(destdir_fd, depdir,
--			    O_PATH | O_DIRECTORY | O_CLOEXEC);
--			if (depdir_fd < 0) {
--				fprintf(stderr, PROGNAME "[%d]: %s: "
--				    "couldn't open %s under %s: %s\n",
--				    getpid(), dataset, depdir, destdir,
--				    strerror(errno));
--				free(depdir);
--				continue;
--			}
--
--			if (symlinkat(linktgt, depdir_fd, mountfile) == -1)
--				fprintf(stderr, PROGNAME "[%d]: %s: "
--				    "couldn't symlink at "
--				    "%s under %s under %s: %s\n",
--				    getpid(), dataset, mountfile,
--				    depdir, destdir, strerror(errno));
--
--			(void) close(depdir_fd);
--			free(depdir);
--		}
--	}
--
--	return (0);
--}
--
--
--static int
--pool_enumerator(zpool_handle_t *pool, void *data __attribute__((unused)))
--{
--	int ret = 0;
--
--	/*
--	 * Pools are guaranteed-unique by the kernel,
--	 * no risk of leaking dupes here
--	 */
--	char *name = strdup(zpool_get_name(pool));
--	if (!name || !tsearch(name, &known_pools, STRCMP)) {
--		free(name);
--		ret = ENOMEM;
--	}
--
--	zpool_close(pool);
--	return (ret);
--}
--
--int
--main(int argc, char **argv)
--{
--	struct timespec time_init = {};
--	clock_gettime(CLOCK_MONOTONIC_RAW, &time_init);
--
--	{
--		int kmfd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC);
--		if (kmfd >= 0) {
--			(void) dup2(kmfd, STDERR_FILENO);
--			(void) close(kmfd);
--		}
--	}
--
--	uint8_t debug = 0;
--
--	argv0 = argv[0];
--	switch (argc) {
--	case 1:
--		/* Use default */
--		break;
--	case 2:
--	case 4:
--		destdir = argv[1];
--		break;
--	default:
--		fprintf(stderr,
--		    PROGNAME "[%d]: wrong argument count: %d\n",
--		    getpid(), argc - 1);
--		_exit(1);
--	}
--
--	{
--		destdir_fd = open(destdir, O_PATH | O_DIRECTORY | O_CLOEXEC);
--		if (destdir_fd < 0) {
--			fprintf(stderr, PROGNAME "[%d]: "
--			    "can't open destination directory %s: %s\n",
--			    getpid(), destdir, strerror(errno));
--			_exit(1);
--		}
--	}
--
--	DIR *fslist_dir = opendir(FSLIST);
--	if (!fslist_dir) {
--		if (errno != ENOENT)
--			fprintf(stderr,
--			    PROGNAME "[%d]: couldn't open " FSLIST ": %s\n",
--			    getpid(), strerror(errno));
--		_exit(0);
--	}
--
--	{
--		libzfs_handle_t *libzfs = libzfs_init();
--		if (libzfs) {
--			if (zpool_iter(libzfs, pool_enumerator, NULL) != 0)
--				fprintf(stderr, PROGNAME "[%d]: "
--				    "error listing pools, ignoring\n",
--				    getpid());
--			libzfs_fini(libzfs);
--		} else
--			fprintf(stderr, PROGNAME "[%d]: "
--			    "couldn't start libzfs, ignoring\n",
--			    getpid());
--	}
--
--	{
--		int regerr = regcomp(&uri_regex, URI_REGEX_S, 0);
--		if (regerr != 0) {
--			fprintf(stderr,
--			    PROGNAME "[%d]: invalid regex: %d\n",
--			    getpid(), regerr);
--			_exit(1);
--		}
--	}
--
--	{
--		/*
--		 * We could just get a gigabyte here and Not Care,
--		 * but if vm.overcommit_memory=2, then MAP_NORESERVE is ignored
--		 * and we'd try (and likely fail) to rip it out of swap
--		 */
--		noauto_files = mmap(NULL, 4 * 1024 * 1024,
--		    PROT_READ | PROT_WRITE,
--		    MAP_SHARED | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);
--		if (noauto_files == MAP_FAILED) {
--			fprintf(stderr,
--			    PROGNAME "[%d]: couldn't allocate IPC region: %s\n",
--			    getpid(), strerror(errno));
--			_exit(1);
--		}
--
--		sem_init(&noauto_files->noauto_not_on_sem, true, 0);
--		sem_init(&noauto_files->noauto_names_sem, true, 1);
--		noauto_files->noauto_names_len = 0;
--		/* Works out to 16447ish, *well* enough */
--		noauto_files->noauto_names_max =
--		    (4 * 1024 * 1024 - sizeof (*noauto_files)) / NAME_MAX;
--	}
--
--	char *line = NULL;
--	size_t linelen = 0;
--	struct timespec time_start = {};
--	{
--		const char *dbgenv = getenv("ZFS_DEBUG");
--		if (dbgenv)
--			debug = atoi(dbgenv);
--		else {
--			FILE *cmdline = fopen("/proc/cmdline", "re");
--			if (cmdline != NULL) {
--				if (getline(&line, &linelen, cmdline) >= 0)
--					debug = strstr(line, "debug") ? 2 : 0;
--				(void) fclose(cmdline);
--			}
--		}
--
--		if (debug && !isatty(STDOUT_FILENO))
--			dup2(STDERR_FILENO, STDOUT_FILENO);
--	}
--
--	size_t forked_canmount_on = 0;
--	size_t forked_canmount_not_on = 0;
--	size_t canmount_on_pids_len = 128;
--	pid_t *canmount_on_pids =
--	    malloc(canmount_on_pids_len * sizeof (*canmount_on_pids));
--	if (canmount_on_pids == NULL)
--		canmount_on_pids_len = 0;
--
--	if (debug)
--		clock_gettime(CLOCK_MONOTONIC_RAW, &time_start);
--
--	ssize_t read;
--	pid_t pid;
--	struct dirent *cachent;
--	while ((cachent = readdir(fslist_dir)) != NULL) {
--		if (strcmp(cachent->d_name, ".") == 0 ||
--		    strcmp(cachent->d_name, "..") == 0)
--			continue;
--
--		FILE *cachefile = fopenat(dirfd(fslist_dir), cachent->d_name,
--		    O_RDONLY | O_CLOEXEC, "r", 0);
--		if (!cachefile) {
--			fprintf(stderr, PROGNAME "[%d]: "
--			    "couldn't open %s under " FSLIST ": %s\n",
--			    getpid(), cachent->d_name, strerror(errno));
--			continue;
--		}
--
--		while ((read = getline(&line, &linelen, cachefile)) >= 0) {
--			line[read - 1] = '\0'; /* newline */
--
--			switch (pid = fork()) {
--			case -1:
--				fprintf(stderr,
--				    PROGNAME "[%d]: couldn't fork for %s: %s\n",
--				    getpid(), line, strerror(errno));
--				break;
--			case 0: /* child */
--				_exit(line_worker(line, cachent->d_name));
--			default: { /* parent */
--				char *tmp;
--				char *dset = strtok_r(line, "\t", &tmp);
--				strtok_r(NULL, "\t", &tmp);
--				char *canmount = strtok_r(NULL, "\t", &tmp);
--				bool canmount_on =
--				    canmount && strncmp(canmount, "on", 2) == 0;
--
--				if (debug >= 2)
--					printf(PROGNAME ": forked %d, "
--					    "canmount_on=%d, dataset=%s\n",
--					    (int)pid, canmount_on, dset);
--
--				if (canmount_on &&
--				    forked_canmount_on ==
--				    canmount_on_pids_len) {
--					size_t new_len =
--					    (canmount_on_pids_len ?: 16) * 2;
--					void *new_pidlist =
--					    realloc(canmount_on_pids,
--					    new_len *
--					    sizeof (*canmount_on_pids));
--					if (!new_pidlist) {
--						fprintf(stderr,
--						    PROGNAME "[%d]: "
--						    "out of memory! "
--						    "Mount ordering may be "
--						    "affected.\n", getpid());
--						continue;
--					}
--
--					canmount_on_pids = new_pidlist;
--					canmount_on_pids_len = new_len;
--				}
--
--				if (canmount_on) {
--					canmount_on_pids[forked_canmount_on] =
--					    pid;
--					++forked_canmount_on;
--				} else
--					++forked_canmount_not_on;
--				break;
--			}
--			}
--		}
--
--		(void) fclose(cachefile);
--	}
--	free(line);
--
--	if (forked_canmount_on == 0) {
--		/* No canmount=on processes to finish, so don't deadlock here */
--		for (size_t i = 0; i < forked_canmount_not_on; ++i)
--			sem_post(&noauto_files->noauto_not_on_sem);
--	} else {
--		/* Likely a no-op, since we got these from a narrow fork loop */
--		qsort(canmount_on_pids, forked_canmount_on,
--		    sizeof (*canmount_on_pids), PID_T_CMP);
--	}
--
--	int status, ret = 0;
--	struct rusage usage;
--	size_t forked_canmount_on_max = forked_canmount_on;
--	while ((pid = wait4(-1, &status, 0, &usage)) != -1) {
--		ret |= WEXITSTATUS(status) | WTERMSIG(status);
--
--		if (forked_canmount_on != 0) {
--			if (bsearch(&pid, canmount_on_pids,
--			    forked_canmount_on_max, sizeof (*canmount_on_pids),
--			    PID_T_CMP))
--				--forked_canmount_on;
--
--			if (forked_canmount_on == 0) {
--				/*
--				 * All canmount=on processes have finished,
--				 * let all the lower-priority ones finish now
--				 */
--				for (size_t i = 0;
--				    i < forked_canmount_not_on; ++i)
--					sem_post(
--					    &noauto_files->noauto_not_on_sem);
--			}
--		}
--
--		if (debug >= 2)
--			printf(PROGNAME ": %d done, user=%llu.%06us, "
--			    "system=%llu.%06us, maxrss=%ldB, ex=0x%x\n",
--			    (int)pid,
--			    (unsigned long long) usage.ru_utime.tv_sec,
--			    (unsigned int) usage.ru_utime.tv_usec,
--			    (unsigned long long) usage.ru_stime.tv_sec,
--			    (unsigned int) usage.ru_stime.tv_usec,
--			    usage.ru_maxrss * 1024, status);
--	}
--
--	if (debug) {
--		struct timespec time_end = {};
--		clock_gettime(CLOCK_MONOTONIC_RAW, &time_end);
--
--		getrusage(RUSAGE_SELF, &usage);
--		printf(
--		    "\n"
--		    PROGNAME ": self    : "
--		    "user=%llu.%06us, system=%llu.%06us, maxrss=%ldB\n",
--		    (unsigned long long) usage.ru_utime.tv_sec,
--		    (unsigned int) usage.ru_utime.tv_usec,
--		    (unsigned long long) usage.ru_stime.tv_sec,
--		    (unsigned int) usage.ru_stime.tv_usec,
--		    usage.ru_maxrss * 1024);
--
--		getrusage(RUSAGE_CHILDREN, &usage);
--		printf(PROGNAME ": children: "
--		    "user=%llu.%06us, system=%llu.%06us, maxrss=%ldB\n",
--		    (unsigned long long) usage.ru_utime.tv_sec,
--		    (unsigned int) usage.ru_utime.tv_usec,
--		    (unsigned long long) usage.ru_stime.tv_sec,
--		    (unsigned int) usage.ru_stime.tv_usec,
--		    usage.ru_maxrss * 1024);
--
--		if (time_start.tv_nsec > time_end.tv_nsec) {
--			time_end.tv_nsec =
--			    1000000000 + time_end.tv_nsec - time_start.tv_nsec;
--			time_end.tv_sec -= 1;
--		} else
--			time_end.tv_nsec -= time_start.tv_nsec;
--		time_end.tv_sec -= time_start.tv_sec;
--
--		if (time_init.tv_nsec > time_start.tv_nsec) {
--			time_start.tv_nsec =
--			    1000000000 + time_start.tv_nsec - time_init.tv_nsec;
--			time_start.tv_sec -= 1;
--		} else
--			time_start.tv_nsec -= time_init.tv_nsec;
--		time_start.tv_sec -= time_init.tv_sec;
--
--		time_init.tv_nsec = time_start.tv_nsec + time_end.tv_nsec;
--		time_init.tv_sec =
--		    time_start.tv_sec + time_end.tv_sec +
--		    time_init.tv_nsec / 1000000000;
--		time_init.tv_nsec %= 1000000000;
--
--		printf(PROGNAME ": wall    : "
--		    "total=%llu.%09llus = "
--		    "init=%llu.%09llus + real=%llu.%09llus\n",
--		    (unsigned long long) time_init.tv_sec,
--		    (unsigned long long) time_init.tv_nsec,
--		    (unsigned long long) time_start.tv_sec,
--		    (unsigned long long) time_start.tv_nsec,
--		    (unsigned long long) time_end.tv_sec,
--		    (unsigned long long) time_end.tv_nsec);
--	}
--
--	_exit(ret);
--}
-diff --git a/etc/systemd/system-generators/zfs-mount-generator.in b/etc/systemd/system-generators/zfs-mount-generator.in
-new file mode 100755
-index 0000000000..c276fbbce5
---- /dev/null
-+++ b/etc/systemd/system-generators/zfs-mount-generator.in
-@@ -0,0 +1,474 @@
-+#!/bin/sh
-+
-+# zfs-mount-generator - generates systemd mount units for zfs
-+# Copyright (c) 2017 Antonio Russo <antonio.e.russo@gmail.com>
-+# Copyright (c) 2020 InsanePrawn <insane.prawny@gmail.com>
-+#
-+# Permission is hereby granted, free of charge, to any person obtaining
-+# a copy of this software and associated documentation files (the
-+# "Software"), to deal in the Software without restriction, including
-+# without limitation the rights to use, copy, modify, merge, publish,
-+# distribute, sublicense, and/or sell copies of the Software, and to
-+# permit persons to whom the Software is furnished to do so, subject to
-+# the following conditions:
-+#
-+# The above copyright notice and this permission notice shall be
-+# included in all copies or substantial portions of the Software.
-+#
-+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-+
-+set -e
-+
-+FSLIST="@sysconfdir@/zfs/zfs-list.cache"
-+
-+[ -d "${FSLIST}" ] || exit 0
-+[ "$(echo "${FSLIST}"/*)" = "${FSLIST}/*" ] && exit 0
-+
-+do_fail() {
-+  printf 'zfs-mount-generator: %s\n' "$*" > /dev/kmsg
-+  exit 1
-+}
-+
-+# test if $1 is in space-separated list $2
-+is_known() {
-+  query="$1"
-+  IFS=' '
-+  for element in $2 ; do
-+    if [ "$query" = "$element" ] ; then
-+      return 0
-+    fi
-+  done
-+  return 1
-+}
-+
-+# create dependency on unit file $1
-+# of type $2, i.e. "wants" or "requires"
-+# in the target units from space-separated list $3
-+create_dependencies() {
-+  unitfile="$1"
-+  suffix="$2"
-+  IFS=' '
-+  for target in $3 ; do
-+    target_dir="${dest_norm}/${target}.${suffix}/"
-+    mkdir -p "${target_dir}"
-+    ln -s "../${unitfile}" "${target_dir}"
-+  done
-+}
-+
-+# see systemd.generator
-+if [ $# -eq 0 ] ; then
-+  dest_norm="/tmp"
-+elif [ $# -eq 3 ] ; then
-+  dest_norm="${1}"
-+else
-+  do_fail "zero or three arguments required"
-+fi
-+
-+pools=$(zpool list -H -o name || true)
-+
-+# All needed information about each ZFS is available from
-+# zfs list -H -t filesystem -o <properties>
-+# cached in $FSLIST, and each line is processed by the following function:
-+# See the list below for the properties and their order
-+
-+process_line() {
-+
-+  # zfs list -H -o name,...
-+  # fields are tab separated
-+  IFS="$(printf '\t')"
-+  # shellcheck disable=SC2086
-+  set -- $1
-+
-+  dataset="${1}"
-+  pool="${dataset%%/*}"
-+  p_mountpoint="${2}"
-+  p_canmount="${3}"
-+  p_atime="${4}"
-+  p_relatime="${5}"
-+  p_devices="${6}"
-+  p_exec="${7}"
-+  p_readonly="${8}"
-+  p_setuid="${9}"
-+  p_nbmand="${10}"
-+  p_encroot="${11}"
-+  p_keyloc="${12}"
-+  p_systemd_requires="${13}"
-+  p_systemd_requiresmountsfor="${14}"
-+  p_systemd_before="${15}"
-+  p_systemd_after="${16}"
-+  p_systemd_wantedby="${17}"
-+  p_systemd_requiredby="${18}"
-+  p_systemd_nofail="${19}"
-+  p_systemd_ignore="${20}"
-+
-+  # Minimal pre-requisites to mount a ZFS dataset
-+  # By ordering before zfs-mount.service, we avoid race conditions.
-+  after="zfs-import.target"
-+  before="zfs-mount.service"
-+  wants="zfs-import.target"
-+  requires=""
-+  requiredmounts=""
-+  bindsto=""
-+  wantedby=""
-+  requiredby=""
-+  noauto="off"
-+
-+  # If the pool is already imported, zfs-import.target is not needed.  This
-+  # avoids a dependency loop on root-on-ZFS systems:
-+  # systemd-random-seed.service After (via RequiresMountsFor) var-lib.mount
-+  # After zfs-import.target After zfs-import-{cache,scan}.service After
-+  # cryptsetup.service After systemd-random-seed.service.
-+  #
-+  # Pools are newline-separated and may contain spaces in their names.
-+  # There is no better portable way to set IFS to just a newline.  Using
-+  # $(printf '\n') doesn't work because $(...) strips trailing newlines.
-+  IFS="
-+"
-+  for p in $pools ; do
-+    if [ "$p" = "$pool" ] ; then
-+      after=""
-+      wants=""
-+      break
-+    fi
-+  done
-+
-+  if [ -n "${p_systemd_after}" ] && \
-+      [ "${p_systemd_after}" != "-" ] ; then
-+    after="${p_systemd_after} ${after}"
-+  fi
-+
-+  if [ -n "${p_systemd_before}" ] && \
-+      [ "${p_systemd_before}" != "-" ] ; then
-+    before="${p_systemd_before} ${before}"
-+  fi
-+
-+  if [ -n "${p_systemd_requires}" ] && \
-+      [ "${p_systemd_requires}" != "-" ] ; then
-+    requires="Requires=${p_systemd_requires}"
-+  fi
-+
-+  if [ -n "${p_systemd_requiresmountsfor}" ] && \
-+      [ "${p_systemd_requiresmountsfor}" != "-" ] ; then
-+    requiredmounts="RequiresMountsFor=${p_systemd_requiresmountsfor}"
-+  fi
-+
-+  # Handle encryption
-+  if [ -n "${p_encroot}" ] &&
-+      [ "${p_encroot}" != "-" ] ; then
-+    keyloadunit="zfs-load-key-$(systemd-escape "${p_encroot}").service"
-+    if [ "${p_encroot}" = "${dataset}" ] ; then
-+      keymountdep=""
-+      if [ "${p_keyloc%%://*}" = "file" ] ; then
-+        if [ -n "${requiredmounts}" ] ; then
-+          keymountdep="${requiredmounts} '${p_keyloc#file://}'"
-+        else
-+          keymountdep="RequiresMountsFor='${p_keyloc#file://}'"
-+        fi
-+        keyloadscript="@sbindir@/zfs load-key \"${dataset}\""
-+      elif [ "${p_keyloc}" = "prompt" ] ; then
-+        keyloadscript="\
-+count=0;\
-+while [ \$\$count -lt 3 ];do\
-+  systemd-ask-password --id=\"zfs:${dataset}\"\
-+    \"Enter passphrase for ${dataset}:\"|\
-+    @sbindir@/zfs load-key \"${dataset}\" && exit 0;\
-+  count=\$\$((count + 1));\
-+done;\
-+exit 1"
-+      else
-+        printf 'zfs-mount-generator: (%s) invalid keylocation\n' \
-+          "${dataset}" >/dev/kmsg
-+      fi
-+      keyloadcmd="\
-+/bin/sh -c '\
-+set -eu;\
-+keystatus=\"\$\$(@sbindir@/zfs get -H -o value keystatus \"${dataset}\")\";\
-+[ \"\$\$keystatus\" = \"unavailable\" ] || exit 0;\
-+${keyloadscript}'"
-+      keyunloadcmd="\
-+/bin/sh -c '\
-+set -eu;\
-+keystatus=\"\$\$(@sbindir@/zfs get -H -o value keystatus \"${dataset}\")\";\
-+[ \"\$\$keystatus\" = \"available\" ] || exit 0;\
-+@sbindir@/zfs unload-key \"${dataset}\"'"
-+
-+
-+
-+      # Generate the key-load .service unit
-+      #
-+      # Note: It is tempting to use a `<<EOF` style here-document for this, but
-+      #   bash requires a writable /tmp or $TMPDIR for that. This is not always
-+      #   available early during boot.
-+      #
-+      echo \
-+"# Automatically generated by zfs-mount-generator
-+
-+[Unit]
-+Description=Load ZFS key for ${dataset}
-+SourcePath=${cachefile}
-+Documentation=man:zfs-mount-generator(8)
-+DefaultDependencies=no
-+Wants=${wants}
-+After=${after}
-+${requires}
-+${keymountdep}
-+
-+[Service]
-+Type=oneshot
-+RemainAfterExit=yes
-+# This avoids a dependency loop involving systemd-journald.socket if this
-+# dataset is a parent of the root filesystem.
-+StandardOutput=null
-+StandardError=null
-+ExecStart=${keyloadcmd}
-+ExecStop=${keyunloadcmd}"   > "${dest_norm}/${keyloadunit}"
-+    fi
-+    # Update the dependencies for the mount file to want the
-+    # key-loading unit.
-+    wants="${wants}"
-+    bindsto="BindsTo=${keyloadunit}"
-+    after="${after} ${keyloadunit}"
-+  fi
-+
-+  # Prepare the .mount unit
-+
-+  # skip generation of the mount unit if org.openzfs.systemd:ignore is "on"
-+  if [ -n "${p_systemd_ignore}" ] ; then
-+    if [ "${p_systemd_ignore}" = "on" ] ; then
-+      return
-+    elif [ "${p_systemd_ignore}" = "-" ] \
-+      || [ "${p_systemd_ignore}" = "off" ] ; then
-+      : # This is OK
-+    else
-+      do_fail "invalid org.openzfs.systemd:ignore for ${dataset}"
-+    fi
-+  fi
-+
-+  # Check for canmount=off .
-+  if [ "${p_canmount}" = "off" ] ; then
-+    return
-+  elif [ "${p_canmount}" = "noauto" ] ; then
-+    noauto="on"
-+  elif [ "${p_canmount}" = "on" ] ; then
-+    : # This is OK
-+  else
-+    do_fail "invalid canmount for ${dataset}"
-+  fi
-+
-+  # Check for legacy and blank mountpoints.
-+  if [ "${p_mountpoint}" = "legacy" ] ; then
-+    return
-+  elif [ "${p_mountpoint}" = "none" ] ; then
-+    return
-+  elif [ "${p_mountpoint%"${p_mountpoint#?}"}" != "/" ] ; then
-+    do_fail "invalid mountpoint for ${dataset}"
-+  fi
-+
-+  # Escape the mountpoint per systemd policy.
-+  mountfile="$(systemd-escape --path --suffix=mount "${p_mountpoint}")"
-+
-+  # Parse options
-+  # see lib/libzfs/libzfs_mount.c:zfs_add_options
-+  opts=""
-+
-+  # atime
-+  if [ "${p_atime}" = on ] ; then
-+    # relatime
-+    if [ "${p_relatime}" = on ] ; then
-+      opts="${opts},atime,relatime"
-+    elif [ "${p_relatime}" = off ] ; then
-+      opts="${opts},atime,strictatime"
-+    else
-+      printf 'zfs-mount-generator: (%s) invalid relatime\n' \
-+        "${dataset}" >/dev/kmsg
-+    fi
-+  elif [ "${p_atime}" = off ] ; then
-+    opts="${opts},noatime"
-+  else
-+    printf 'zfs-mount-generator: (%s) invalid atime\n' \
-+      "${dataset}" >/dev/kmsg
-+  fi
-+
-+  # devices
-+  if [ "${p_devices}" = on ] ; then
-+    opts="${opts},dev"
-+  elif [ "${p_devices}" = off ] ; then
-+    opts="${opts},nodev"
-+  else
-+    printf 'zfs-mount-generator: (%s) invalid devices\n' \
-+      "${dataset}" >/dev/kmsg
-+  fi
-+
-+  # exec
-+  if [ "${p_exec}" = on ] ; then
-+    opts="${opts},exec"
-+  elif [ "${p_exec}" = off ] ; then
-+    opts="${opts},noexec"
-+  else
-+    printf 'zfs-mount-generator: (%s) invalid exec\n' \
-+      "${dataset}" >/dev/kmsg
-+  fi
-+
-+  # readonly
-+  if [ "${p_readonly}" = on ] ; then
-+    opts="${opts},ro"
-+  elif [ "${p_readonly}" = off ] ; then
-+    opts="${opts},rw"
-+  else
-+    printf 'zfs-mount-generator: (%s) invalid readonly\n' \
-+      "${dataset}" >/dev/kmsg
-+  fi
-+
-+  # setuid
-+  if [ "${p_setuid}" = on ] ; then
-+    opts="${opts},suid"
-+  elif [ "${p_setuid}" = off ] ; then
-+    opts="${opts},nosuid"
-+  else
-+    printf 'zfs-mount-generator: (%s) invalid setuid\n' \
-+      "${dataset}" >/dev/kmsg
-+  fi
-+
-+  # nbmand
-+  if [ "${p_nbmand}" = on ]  ; then
-+    opts="${opts},mand"
-+  elif [ "${p_nbmand}" = off ] ; then
-+    opts="${opts},nomand"
-+  else
-+    printf 'zfs-mount-generator: (%s) invalid nbmand\n' \
-+      "${dataset}" >/dev/kmsg
-+  fi
-+
-+  if [ -n "${p_systemd_wantedby}" ] && \
-+      [ "${p_systemd_wantedby}" != "-" ] ; then
-+    noauto="on"
-+    if [ "${p_systemd_wantedby}" = "none" ] ; then
-+      wantedby=""
-+    else
-+      wantedby="${p_systemd_wantedby}"
-+      before="${before} ${wantedby}"
-+    fi
-+  fi
-+
-+  if [ -n "${p_systemd_requiredby}" ] && \
-+      [ "${p_systemd_requiredby}" != "-" ] ; then
-+    noauto="on"
-+    if [ "${p_systemd_requiredby}" = "none" ] ; then
-+      requiredby=""
-+    else
-+      requiredby="${p_systemd_requiredby}"
-+      before="${before} ${requiredby}"
-+    fi
-+  fi
-+
-+  # For datasets with canmount=on, a dependency is created for
-+  # local-fs.target by default. To avoid regressions, this dependency
-+  # is reduced to "wants" rather than "requires" when nofail is not "off".
-+  # **THIS MAY CHANGE**
-+  # noauto=on disables this behavior completely.
-+  if [ "${noauto}" != "on" ] ; then
-+    if [ "${p_systemd_nofail}" = "off" ] ; then
-+      requiredby="local-fs.target"
-+      before="${before} local-fs.target"
-+    else
-+      wantedby="local-fs.target"
-+      if [ "${p_systemd_nofail}" != "on" ] ; then
-+        before="${before} local-fs.target"
-+      fi
-+    fi
-+  fi
-+
-+  # Handle existing files:
-+  # 1.  We never overwrite existing files, although we may delete
-+  #     files if we're sure they were created by us. (see 5.)
-+  # 2.  We handle files differently based on canmount. Units with canmount=on
-+  #     always have precedence over noauto. This is enforced by the sort pipe
-+  #     in the loop around this function.
-+  #     It is important to use $p_canmount and not $noauto here, since we
-+  #     sort by canmount while other properties also modify $noauto, e.g.
-+  #     org.openzfs.systemd:wanted-by.
-+  # 3.  If no unit file exists for a noauto dataset, we create one.
-+  #     Additionally, we use $noauto_files to track the unit file names
-+  #     (which are the systemd-escaped mountpoints) of all (exclusively)
-+  #     noauto datasets that had a file created.
-+  # 4.  If the file to be created is found in the tracking variable,
-+  #     we do NOT create it.
-+  # 5.  If a file exists for a noauto dataset, we check whether the file
-+  #     name is in the variable. If it is, we have multiple noauto datasets
-+  #     for the same mountpoint. In such cases, we remove the file for safety.
-+  #     To avoid further noauto datasets creating a file for this path again,
-+  #     we leave the file name in the tracking variable.
-+  if [ -e "${dest_norm}/${mountfile}" ] ; then
-+    if is_known "$mountfile" "$noauto_files" ; then
-+      # if it's in $noauto_files, we must be noauto too. See 2.
-+      printf 'zfs-mount-generator: removing duplicate noauto %s\n' \
-+        "${mountfile}" >/dev/kmsg
-+      # See 5.
-+      rm "${dest_norm}/${mountfile}"
-+    else
-+      # don't log for canmount=noauto
-+      if [  "${p_canmount}" = "on" ] ; then
-+        printf 'zfs-mount-generator: %s already exists. Skipping.\n' \
-+          "${mountfile}" >/dev/kmsg
-+      fi
-+    fi
-+    # file exists; Skip current dataset.
-+    return
-+  else
-+    if is_known "${mountfile}" "${noauto_files}" ; then
-+      # See 4.
-+      return
-+    elif [ "${p_canmount}" = "noauto" ] ; then
-+      noauto_files="${mountfile} ${noauto_files}"
-+    fi
-+  fi
-+
-+  # Create the .mount unit file.
-+  #
-+  # (Do not use `<<EOF`-style here-documents for this, see warning above)
-+  #
-+  echo \
-+"# Automatically generated by zfs-mount-generator
-+
-+[Unit]
-+SourcePath=${cachefile}
-+Documentation=man:zfs-mount-generator(8)
-+
-+Before=${before}
-+After=${after}
-+Wants=${wants}
-+${bindsto}
-+${requires}
-+${requiredmounts}
-+
-+[Mount]
-+Where=${p_mountpoint}
-+What=${dataset}
-+Type=zfs
-+Options=defaults${opts},zfsutil" > "${dest_norm}/${mountfile}"
-+
-+  # Finally, create the appropriate dependencies
-+  create_dependencies "${mountfile}" "wants" "$wantedby"
-+  create_dependencies "${mountfile}" "requires" "$requiredby"
-+
-+}
-+
-+for cachefile in "${FSLIST}/"* ; do
-+  # Disable glob expansion to protect against special characters when parsing.
-+  set -f
-+  # Sort cachefile's lines by canmount, "on" before "noauto"
-+  # and feed each line into process_line
-+  sort -t "$(printf '\t')" -k 3 -r "${cachefile}" | \
-+  ( # subshell is necessary for `sort|while read` and $noauto_files
-+    noauto_files=""
-+    while read -r fs ; do
-+      process_line "${fs}"
-+    done
-+  )
-+done
--- 
-2.32.0
-
diff -pruN 2.3.5-1/debian/patches/ubuntu/0003-enable-linux-experimental.patch 2.3.5-1ubuntu1/debian/patches/ubuntu/0003-enable-linux-experimental.patch
--- 2.3.5-1/debian/patches/ubuntu/0003-enable-linux-experimental.patch	1970-01-01 00:00:00.000000000 +0000
+++ 2.3.5-1ubuntu1/debian/patches/ubuntu/0003-enable-linux-experimental.patch	2025-11-19 20:31:55.000000000 +0000
@@ -0,0 +1,29 @@
+From d85062b52f77f44ce924ed542211361512df943b Mon Sep 17 00:00:00 2001
+From: John Cabaj <john.cabaj@canonical.com>
+Date: Tue, 11 Mar 2025 13:52:58 -0500
+Subject: [PATCH] Enable linux-experimental support
+
+To build against advance kernels, --enable-linux-experimental
+must be set
+
+Last-Update: 2025-03-11
+Signed-off-by: John Cabaj <john.cabaj@canonical.com>
+---
+ scripts/dkms.mkconf | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/scripts/dkms.mkconf b/scripts/dkms.mkconf
+index 046ce9edc..ec6373739 100755
+--- a/scripts/dkms.mkconf
++++ b/scripts/dkms.mkconf
+@@ -60,6 +60,7 @@ PRE_BUILD="configure
+       fi
+     }
+   )
++  --enable-linux-experimental
+ "
+ POST_BUILD="scripts/dkms.postbuild
+   -n \${PACKAGE_NAME}
+-- 
+2.43.0
+
diff -pruN 2.3.5-1/debian/patches/ubuntu/0004-Linux-6.18-replace-nth_page.patch 2.3.5-1ubuntu1/debian/patches/ubuntu/0004-Linux-6.18-replace-nth_page.patch
--- 2.3.5-1/debian/patches/ubuntu/0004-Linux-6.18-replace-nth_page.patch	1970-01-01 00:00:00.000000000 +0000
+++ 2.3.5-1ubuntu1/debian/patches/ubuntu/0004-Linux-6.18-replace-nth_page.patch	2025-11-19 20:41:31.000000000 +0000
@@ -0,0 +1,44 @@
+From 9d50ee59dc13dbb376ec738808da9d95226b44fe Mon Sep 17 00:00:00 2001
+From: Rob Norris <robn@despairlabs.com>
+Date: Fri, 12 Sep 2025 09:57:53 +1000
+Subject: [PATCH 1/7] Linux 6.18: replace nth_page()
+
+Sponsored-by: https://despairlabs.com/sponsor/
+Signed-off-by: Rob Norris <robn@despairlabs.com>
+
+Last-Update: 2025-11-19
+Signed-off-by: John Cabaj <john.cabaj@canonical.com>
+---
+ module/os/linux/zfs/abd_os.c | 9 +++++++++
+ 1 file changed, 9 insertions(+)
+
+diff --git a/module/os/linux/zfs/abd_os.c b/module/os/linux/zfs/abd_os.c
+index 8a8316f63..18f2426fb 100644
+--- a/module/os/linux/zfs/abd_os.c
++++ b/module/os/linux/zfs/abd_os.c
+@@ -23,6 +23,7 @@
+  * Copyright (c) 2014 by Chunwei Chen. All rights reserved.
+  * Copyright (c) 2019 by Delphix. All rights reserved.
+  * Copyright (c) 2023, 2024, Klara Inc.
++ * Copyright (c) 2025, Rob Norris <robn@despairlabs.com>
+  */
+ 
+ /*
+@@ -1109,6 +1110,14 @@ abd_return_buf_copy(abd_t *abd, void *buf, size_t n)
+ #define	ABD_ITER_PAGE_SIZE(page)	(PAGESIZE)
+ #endif
+ 
++#ifndef nth_page
++/*
++ * Since 6.18 nth_page() no longer exists, and is no longer required to iterate
++ * within a single SG entry, so we replace it with a simple addition.
++ */
++#define	nth_page(p, n)	((p)+(n))
++#endif
++
+ void
+ abd_iter_page(struct abd_iter *aiter)
+ {
+-- 
+2.43.0
+
diff -pruN 2.3.5-1/debian/patches/ubuntu/0005-Linux-6.18-convert-ida_simple_-calls.patch 2.3.5-1ubuntu1/debian/patches/ubuntu/0005-Linux-6.18-convert-ida_simple_-calls.patch
--- 2.3.5-1/debian/patches/ubuntu/0005-Linux-6.18-convert-ida_simple_-calls.patch	1970-01-01 00:00:00.000000000 +0000
+++ 2.3.5-1ubuntu1/debian/patches/ubuntu/0005-Linux-6.18-convert-ida_simple_-calls.patch	2025-11-19 20:41:31.000000000 +0000
@@ -0,0 +1,76 @@
+From 5de4a297e7ab0f8062b550acc4e76308c675f653 Mon Sep 17 00:00:00 2001
+From: Rob Norris <robn@despairlabs.com>
+Date: Fri, 12 Sep 2025 10:03:07 +1000
+Subject: [PATCH 1/6] Linux 6.18: convert ida_simple_* calls
+
+ida_simple_get() and ida_simple_remove() are removed in 6.18. However,
+since 4.19 they have been simple wrappers around ida_alloc() and
+ida_free(), so we can just use those directly.
+
+Sponsored-by: https://despairlabs.com/sponsor/
+Signed-off-by: Rob Norris <robn@despairlabs.com>
+
+Last-Update: 2025-11-19
+[john-cabaj: keeping only relevant copyright changes, and leaving out ida_free()
+modification in zvol_os_remove_minor() as the function doesn't exist in 2.3.4.
+Also modifying ida_simple_remove() zvol_os_free() to ida_free()]
+Signed-off-by: John Cabaj <john.cabaj@canonical.com>
+---
+ module/os/linux/zfs/zvol_os.c | 12 ++++++------
+ 1 file changed, 6 insertions(+), 6 deletions(-)
+
+--- a/module/os/linux/zfs/zvol_os.c
++++ b/module/os/linux/zfs/zvol_os.c
+@@ -22,6 +22,7 @@
+ /*
+  * Copyright (c) 2012, 2020 by Delphix. All rights reserved.
+  * Copyright (c) 2024, Rob Norris <robn@despairlabs.com>
++ * Copyright (c) 2024, 2025, Rob Norris <robn@despairlabs.com>
+  * Copyright (c) 2024, Klara, Inc.
+  */
+ 
+@@ -1531,7 +1532,7 @@
+ 	if (zv->zv_zso->use_blk_mq)
+ 		blk_mq_free_tag_set(&zv->zv_zso->tag_set);
+ 
+-	ida_simple_remove(&zvol_ida,
++	ida_free(&zvol_ida,
+ 	    MINOR(zv->zv_zso->zvo_dev) >> ZVOL_MINOR_BITS);
+ 
+ 	cv_destroy(&zv->zv_removing_cv);
+@@ -1665,7 +1666,7 @@
+ 	if (zvol_inhibit_dev)
+ 		return (0);
+ 
+-	idx = ida_simple_get(&zvol_ida, 0, 0, kmem_flags_convert(KM_SLEEP));
++	idx = ida_alloc(&zvol_ida, kmem_flags_convert(KM_SLEEP));
+ 	if (idx < 0)
+ 		return (SET_ERROR(-idx));
+ 	minor = idx << ZVOL_MINOR_BITS;
+@@ -1673,7 +1674,7 @@
+ 		/* too many partitions can cause an overflow */
+ 		zfs_dbgmsg("zvol: create minor overflow: %s, minor %u/%u",
+ 		    name, minor, MINOR(minor));
+-		ida_simple_remove(&zvol_ida, idx);
++		ida_free(&zvol_ida, idx);
+ 		return (SET_ERROR(EINVAL));
+ 	}
+ 
+@@ -1681,7 +1682,7 @@
+ 	if (zv) {
+ 		ASSERT(MUTEX_HELD(&zv->zv_state_lock));
+ 		mutex_exit(&zv->zv_state_lock);
+-		ida_simple_remove(&zvol_ida, idx);
++		ida_free(&zvol_ida, idx);
+ 		return (SET_ERROR(EEXIST));
+ 	}
+ 
+@@ -1783,7 +1784,7 @@
+ 		rw_exit(&zvol_state_lock);
+ 		error = zvol_os_add_disk(zv->zv_zso->zvo_disk);
+ 	} else {
+-		ida_simple_remove(&zvol_ida, idx);
++		ida_free(&zvol_ida, idx);
+ 	}
+ 
+ 	return (error);
diff -pruN 2.3.5-1/debian/patches/ubuntu/0006-linux-atomic-fill-out-API-for-atomic-pointer-ops.patch 2.3.5-1ubuntu1/debian/patches/ubuntu/0006-linux-atomic-fill-out-API-for-atomic-pointer-ops.patch
--- 2.3.5-1/debian/patches/ubuntu/0006-linux-atomic-fill-out-API-for-atomic-pointer-ops.patch	1970-01-01 00:00:00.000000000 +0000
+++ 2.3.5-1ubuntu1/debian/patches/ubuntu/0006-linux-atomic-fill-out-API-for-atomic-pointer-ops.patch	2025-11-19 20:41:31.000000000 +0000
@@ -0,0 +1,68 @@
+From 1aec627c60fe9efc3313e553cef389adda08e7b4 Mon Sep 17 00:00:00 2001
+From: Rob Norris <rob.norris@klarasystems.com>
+Date: Fri, 1 Aug 2025 08:51:47 +1000
+Subject: [PATCH] linux/atomic: fill out API for atomic pointer ops
+
+Sponsored-by: Klara, Inc.
+Sponsored-by: Wasabi Technology, Inc.
+Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
+Reviewed-by: Alexander Motin <alexander.motin@TrueNAS.com>
+Signed-off-by: Rob Norris <rob.norris@klarasystems.com>
+Closes #17580
+---
+ include/os/linux/spl/sys/atomic.h | 32 +++++++++++++++++++++++++++++++
+ 1 file changed, 32 insertions(+)
+
+diff --git a/include/os/linux/spl/sys/atomic.h b/include/os/linux/spl/sys/atomic.h
+index b2a39d7d6..f4bcd58bd 100644
+--- a/include/os/linux/spl/sys/atomic.h
++++ b/include/os/linux/spl/sys/atomic.h
+@@ -71,6 +71,22 @@ atomic_cas_ptr(volatile void *target,  void *cmp, void *newval)
+ 	return ((void *)atomic_cas_64((volatile uint64_t *)target,
+ 	    (uint64_t)cmp, (uint64_t)newval));
+ }
++static __inline__ void *
++atomic_swap_ptr(volatile void *target, void *newval)
++{
++	return ((void *)atomic_swap_64((volatile uint64_t *)target,
++	    (uint64_t)newval));
++}
++static __inline__ void *
++atomic_load_ptr(volatile void *target)
++{
++	return ((void *)atomic_load_64((volatile uint64_t *)target));
++}
++static __inline__ void
++atomic_store_ptr(volatile void *target, void *newval)
++{
++	atomic_store_64((volatile uint64_t *)target, (uint64_t)newval);
++}
+ #else /* _LP64 */
+ static __inline__ void *
+ atomic_cas_ptr(volatile void *target,  void *cmp, void *newval)
+@@ -78,6 +94,22 @@ atomic_cas_ptr(volatile void *target,  void *cmp, void *newval)
+ 	return ((void *)atomic_cas_32((volatile uint32_t *)target,
+ 	    (uint32_t)cmp, (uint32_t)newval));
+ }
++static __inline__ void *
++atomic_swap_ptr(volatile void *target, void *newval)
++{
++	return ((void *)atomic_swap_32((volatile uint32_t *)target,
++	    (uint32_t)newval));
++}
++static __inline__ void *
++atomic_load_ptr(volatile void *target)
++{
++	return ((void *)atomic_load_32((volatile uint32_t *)target));
++}
++static __inline__ void
++atomic_store_ptr(volatile void *target, void *newval)
++{
++	atomic_store_32((volatile uint32_t *)target, (uint32_t)newval);
++}
+ #endif /* _LP64 */
+ 
+ #endif  /* _SPL_ATOMIC_H */
+-- 
+2.43.0
+
diff -pruN 2.3.5-1/debian/patches/ubuntu/0007-Linux-6.18-block_device_operations-getgeo-takes-stru.patch 2.3.5-1ubuntu1/debian/patches/ubuntu/0007-Linux-6.18-block_device_operations-getgeo-takes-stru.patch
--- 2.3.5-1/debian/patches/ubuntu/0007-Linux-6.18-block_device_operations-getgeo-takes-stru.patch	1970-01-01 00:00:00.000000000 +0000
+++ 2.3.5-1ubuntu1/debian/patches/ubuntu/0007-Linux-6.18-block_device_operations-getgeo-takes-stru.patch	2025-11-19 20:41:31.000000000 +0000
@@ -0,0 +1,106 @@
+From 39db4bda8078eb83776ad7ac90ecb6cdcbd083eb Mon Sep 17 00:00:00 2001
+From: Rob Norris <robn@despairlabs.com>
+Date: Fri, 12 Sep 2025 10:23:28 +1000
+Subject: [PATCH 2/6] Linux 6.18: block_device_operations->getgeo takes struct
+ gendisk*
+
+Sponsored-by: https://despairlabs.com/sponsor/
+Signed-off-by: Rob Norris <robn@despairlabs.com>
+
+Last-Update: 2025-11-19
+[john-cabaj: context changes in zvol_getgeo_impl()]
+Signed-off-by: John Cabaj <john.cabaj@canonical.com>
+---
+ config/kernel-block-device-operations.m4 | 34 ++++++++++++++++++++++++
+ module/os/linux/zfs/zvol_os.c            | 20 +++++++++++---
+ 2 files changed, 51 insertions(+), 3 deletions(-)
+
+--- a/config/kernel-block-device-operations.m4
++++ b/config/kernel-block-device-operations.m4
+@@ -119,15 +119,49 @@
+ 	])
+ ])
+ 
++dnl #
++dnl # 6.18 API change
++dnl # block_device_operation->getgeo takes struct gendisk* as first arg
++dnl #
++AC_DEFUN([ZFS_AC_KERNEL_SRC_BLOCK_DEVICE_OPERATIONS_GETGEO_GENDISK], [
++	ZFS_LINUX_TEST_SRC([block_device_operations_getgeo_gendisk], [
++		#include <linux/blkdev.h>
++
++		static int blk_getgeo(struct gendisk *disk, struct hd_geometry *geo)
++		{
++			(void) disk, (void) geo;
++			return (0);
++		}
++
++		static const struct block_device_operations
++		    bops __attribute__ ((unused)) = {
++			.getgeo	= blk_getgeo,
++		};
++	], [], [])
++])
++
++AC_DEFUN([ZFS_AC_KERNEL_BLOCK_DEVICE_OPERATIONS_GETGEO_GENDISK], [
++	AC_MSG_CHECKING([whether bops->getgeo() takes gendisk as first arg])
++	ZFS_LINUX_TEST_RESULT([block_device_operations_getgeo_gendisk], [
++		AC_MSG_RESULT(yes)
++		AC_DEFINE([HAVE_BLOCK_DEVICE_OPERATIONS_GETGEO_GENDISK], [1],
++			[Define if getgeo() in block_device_operations takes struct gendisk * as its first arg])
++	],[
++		AC_MSG_RESULT(no)
++	])
++])
++
+ AC_DEFUN([ZFS_AC_KERNEL_SRC_BLOCK_DEVICE_OPERATIONS], [
+ 	ZFS_AC_KERNEL_SRC_BLOCK_DEVICE_OPERATIONS_CHECK_EVENTS
+ 	ZFS_AC_KERNEL_SRC_BLOCK_DEVICE_OPERATIONS_RELEASE_VOID
+ 	ZFS_AC_KERNEL_SRC_BLOCK_DEVICE_OPERATIONS_RELEASE_1ARG
+ 	ZFS_AC_KERNEL_SRC_BLOCK_DEVICE_OPERATIONS_REVALIDATE_DISK
++	ZFS_AC_KERNEL_SRC_BLOCK_DEVICE_OPERATIONS_GETGEO_GENDISK
+ ])
+ 
+ AC_DEFUN([ZFS_AC_KERNEL_BLOCK_DEVICE_OPERATIONS], [
+ 	ZFS_AC_KERNEL_BLOCK_DEVICE_OPERATIONS_CHECK_EVENTS
+ 	ZFS_AC_KERNEL_BLOCK_DEVICE_OPERATIONS_RELEASE_VOID
+ 	ZFS_AC_KERNEL_BLOCK_DEVICE_OPERATIONS_REVALIDATE_DISK
++	ZFS_AC_KERNEL_BLOCK_DEVICE_OPERATIONS_GETGEO_GENDISK
+ ])
+--- a/module/os/linux/zfs/zvol_os.c
++++ b/module/os/linux/zfs/zvol_os.c
+@@ -1067,10 +1067,10 @@
+  * tiny devices.  For devices over 1 Mib a standard head and sector count
+  * is used to keep the cylinders count reasonable.
+  */
+-static int
+-zvol_getgeo(struct block_device *bdev, struct hd_geometry *geo)
++static inline int
++zvol_getgeo_impl(struct gendisk *disk, struct hd_geometry *geo)
+ {
+-	zvol_state_t *zv = bdev->bd_disk->private_data;
++	zvol_state_t *zv = atomic_load_ptr(&disk->private_data);
+ 	sector_t sectors;
+ 
+ 	ASSERT3U(zv->zv_open_count, >, 0);
+@@ -1091,6 +1091,20 @@
+ 	return (0);
+ }
+ 
++#ifdef HAVE_BLOCK_DEVICE_OPERATIONS_GETGEO_GENDISK
++static int
++zvol_getgeo(struct gendisk *disk, struct hd_geometry *geo)
++{
++	return (zvol_getgeo_impl(disk, geo));
++}
++#else
++static int
++zvol_getgeo(struct block_device *bdev, struct hd_geometry *geo)
++{
++	return (zvol_getgeo_impl(bdev->bd_disk, geo));
++}
++#endif
++
+ /*
+  * Why have two separate block_device_operations structs?
+  *
diff -pruN 2.3.5-1/debian/patches/ubuntu/0008-Linux-6.18-replace-write_cache_pages.patch 2.3.5-1ubuntu1/debian/patches/ubuntu/0008-Linux-6.18-replace-write_cache_pages.patch
--- 2.3.5-1/debian/patches/ubuntu/0008-Linux-6.18-replace-write_cache_pages.patch	1970-01-01 00:00:00.000000000 +0000
+++ 2.3.5-1ubuntu1/debian/patches/ubuntu/0008-Linux-6.18-replace-write_cache_pages.patch	2025-11-19 20:41:31.000000000 +0000
@@ -0,0 +1,232 @@
+From 76c238f1ba9fbd0123cf4f93028e70ad19a0bcd2 Mon Sep 17 00:00:00 2001
+From: Rob Norris <robn@despairlabs.com>
+Date: Fri, 12 Sep 2025 09:31:35 +1000
+Subject: [PATCH 3/6] Linux 6.18: replace write_cache_pages()
+
+Linux 6.18 removed write_cache_pages() without a usable replacement.
+Here we implement a minimal zpl_write_cache_pages() that find the dirty
+pages within the mapping, gets them into the expected state and hands
+them off to zfs_putpage(), which handles the rest.
+
+Sponsored-by: https://despairlabs.com/sponsor/
+Signed-off-by: Rob Norris <robn@despairlabs.com>
+
+Last-Update: 2025-11-19
+[john-cabaj: copying only relevant copyright changes]
+Signed-off-by: John Cabaj <john.cabaj@canonical.com>
+---
+ config/kernel-writeback.m4     | 58 ++++++++++++++++++++++++++
+ config/kernel-writepage_t.m4   | 26 ------------
+ config/kernel.m4               |  4 +-
+ module/os/linux/zfs/zpl_file.c | 74 ++++++++++++++++++++++++++++++++++
+ 4 files changed, 134 insertions(+), 28 deletions(-)
+ create mode 100644 config/kernel-writeback.m4
+ delete mode 100644 config/kernel-writepage_t.m4
+
+--- /dev/null
++++ b/config/kernel-writeback.m4
+@@ -0,0 +1,58 @@
++AC_DEFUN([ZFS_AC_KERNEL_SRC_WRITEPAGE_T], [
++	dnl #
++	dnl # 6.3 API change
++	dnl # The writepage_t function type now has its first argument as
++	dnl # struct folio* instead of struct page*
++	dnl #
++	ZFS_LINUX_TEST_SRC([writepage_t_folio], [
++		#include <linux/writeback.h>
++		static int putpage(struct folio *folio,
++		    struct writeback_control *wbc, void *data)
++		{ return 0; }
++		writepage_t func = putpage;
++	],[])
++])
++
++AC_DEFUN([ZFS_AC_KERNEL_WRITEPAGE_T], [
++	AC_MSG_CHECKING([whether int (*writepage_t)() takes struct folio*])
++	ZFS_LINUX_TEST_RESULT([writepage_t_folio], [
++		AC_MSG_RESULT(yes)
++		AC_DEFINE(HAVE_WRITEPAGE_T_FOLIO, 1,
++		   [int (*writepage_t)() takes struct folio*])
++	],[
++		AC_MSG_RESULT(no)
++	])
++])
++
++AC_DEFUN([ZFS_AC_KERNEL_SRC_WRITE_CACHE_PAGES], [
++	dnl #
++	dnl # 6.18 API change
++	dnl # write_cache_pages() has been removed.
++	dnl #
++	ZFS_LINUX_TEST_SRC([write_cache_pages], [
++		#include <linux/writeback.h>
++	], [
++		(void) write_cache_pages(NULL, NULL, NULL, NULL);
++	])
++])
++
++AC_DEFUN([ZFS_AC_KERNEL_WRITE_CACHE_PAGES], [
++	AC_MSG_CHECKING([whether write_cache_pages() is available])
++	ZFS_LINUX_TEST_RESULT([write_cache_pages], [
++		AC_MSG_RESULT(yes)
++		AC_DEFINE(HAVE_WRITE_CACHE_PAGES, 1,
++		    [write_cache_pages() is available])
++	],[
++		AC_MSG_RESULT(no)
++	])
++])
++
++AC_DEFUN([ZFS_AC_KERNEL_SRC_WRITEBACK], [
++	ZFS_AC_KERNEL_SRC_WRITEPAGE_T
++	ZFS_AC_KERNEL_SRC_WRITE_CACHE_PAGES
++])
++
++AC_DEFUN([ZFS_AC_KERNEL_WRITEBACK], [
++	ZFS_AC_KERNEL_WRITEPAGE_T
++	ZFS_AC_KERNEL_WRITE_CACHE_PAGES
++])
+--- a/config/kernel-writepage_t.m4
++++ /dev/null
+@@ -1,26 +0,0 @@
+-AC_DEFUN([ZFS_AC_KERNEL_SRC_WRITEPAGE_T], [
+-	dnl #
+-	dnl # 6.3 API change
+-	dnl # The writepage_t function type now has its first argument as
+-	dnl # struct folio* instead of struct page*
+-	dnl #
+-	ZFS_LINUX_TEST_SRC([writepage_t_folio], [
+-		#include <linux/writeback.h>
+-		static int putpage(struct folio *folio,
+-		    struct writeback_control *wbc, void *data)
+-		{ return 0; }
+-		writepage_t func = putpage;
+-	],[])
+-])
+-
+-AC_DEFUN([ZFS_AC_KERNEL_WRITEPAGE_T], [
+-	AC_MSG_CHECKING([whether int (*writepage_t)() takes struct folio*])
+-	ZFS_LINUX_TEST_RESULT([writepage_t_folio], [
+-		AC_MSG_RESULT(yes)
+-		AC_DEFINE(HAVE_WRITEPAGE_T_FOLIO, 1,
+-		   [int (*writepage_t)() takes struct folio*])
+-	],[
+-		AC_MSG_RESULT(no)
+-	])
+-])
+-
+--- a/config/kernel.m4
++++ b/config/kernel.m4
+@@ -120,7 +120,7 @@
+ 	ZFS_AC_KERNEL_SRC_IDMAP_MNT_API
+ 	ZFS_AC_KERNEL_SRC_IDMAP_NO_USERNS
+ 	ZFS_AC_KERNEL_SRC_IATTR_VFSID
+-	ZFS_AC_KERNEL_SRC_WRITEPAGE_T
++	ZFS_AC_KERNEL_SRC_WRITEBACK
+ 	ZFS_AC_KERNEL_SRC_RECLAIMED
+ 	ZFS_AC_KERNEL_SRC_REGISTER_SYSCTL_TABLE
+ 	ZFS_AC_KERNEL_SRC_REGISTER_SYSCTL_SZ
+@@ -238,7 +238,7 @@
+ 	ZFS_AC_KERNEL_IDMAP_MNT_API
+ 	ZFS_AC_KERNEL_IDMAP_NO_USERNS
+ 	ZFS_AC_KERNEL_IATTR_VFSID
+-	ZFS_AC_KERNEL_WRITEPAGE_T
++	ZFS_AC_KERNEL_WRITEBACK
+ 	ZFS_AC_KERNEL_RECLAIMED
+ 	ZFS_AC_KERNEL_REGISTER_SYSCTL_TABLE
+ 	ZFS_AC_KERNEL_REGISTER_SYSCTL_SZ
+--- a/module/os/linux/zfs/zpl_file.c
++++ b/module/os/linux/zfs/zpl_file.c
+@@ -22,6 +22,7 @@
+ /*
+  * Copyright (c) 2011, Lawrence Livermore National Security, LLC.
+  * Copyright (c) 2015 by Chunwei Chen. All rights reserved.
++ * Copyright (c) 2025, Rob Norris <robn@despairlabs.com>
+  */
+ 
+ 
+@@ -444,6 +445,7 @@
+ 	return (ret);
+ }
+ 
++#ifdef HAVE_WRITE_CACHE_PAGES
+ #ifdef HAVE_WRITEPAGE_T_FOLIO
+ static int
+ zpl_putfolio(struct folio *pp, struct writeback_control *wbc, void *data)
+@@ -465,6 +467,78 @@
+ #endif
+ 	return (result);
+ }
++#else
++static inline int
++zpl_write_cache_pages(struct address_space *mapping,
++    struct writeback_control *wbc, void *data)
++{
++	pgoff_t start = wbc->range_start >> PAGE_SHIFT;
++	pgoff_t end = wbc->range_end >> PAGE_SHIFT;
++
++	struct folio_batch fbatch;
++	folio_batch_init(&fbatch);
++
++	/*
++	 * This atomically (-ish) tags all DIRTY pages in the range with
++	 * TOWRITE, allowing users to continue dirtying or undirtying pages
++	 * while we get on with writeback, without us treading on each other.
++	 */
++	tag_pages_for_writeback(mapping, start, end);
++
++	int err = 0;
++	unsigned int npages;
++
++	/*
++	 * Grab references to the TOWRITE pages just flagged. This may not get
++	 * all of them, so we do it in a loop until there are none left.
++	 */
++	while ((npages = filemap_get_folios_tag(mapping, &start, end,
++	    PAGECACHE_TAG_TOWRITE, &fbatch)) != 0) {
++
++		/* Loop over each page and write it out. */
++		struct folio *folio;
++		while ((folio = folio_batch_next(&fbatch)) != NULL) {
++			folio_lock(folio);
++
++			/*
++			 * If the folio has been remapped, or is no longer
++			 * dirty, then there's nothing to do.
++			 */
++			if (folio->mapping != mapping ||
++			    !folio_test_dirty(folio)) {
++				folio_unlock(folio);
++				continue;
++			}
++
++			/*
++			 * If writeback is already in progress, wait for it to
++			 * finish. We continue after this even if the page
++			 * ends up clean; zfs_putpage() will skip it if no
++			 * further work is required.
++			 */
++			while (folio_test_writeback(folio))
++				folio_wait_bit(folio, PG_writeback);
++
++			/*
++			 * Write it out and collect any error. zfs_putpage()
++			 * will clear the TOWRITE and DIRTY flags, and return
++			 * with the page unlocked.
++			 */
++			int ferr = zpl_putpage(&folio->page, wbc, data);
++			if (err == 0 && ferr != 0)
++				err = ferr;
++
++			/* Housekeeping for the caller. */
++			wbc->nr_to_write -= folio_nr_pages(folio);
++		}
++
++		/* Release any remaining references on the batch. */
++		folio_batch_release(&fbatch);
++	}
++
++	return (err);
++}
++#endif
+ 
+ static int
+ zpl_writepages(struct address_space *mapping, struct writeback_control *wbc)
diff -pruN 2.3.5-1/debian/patches/ubuntu/0009-Linux-6.18-namespace-type-moved-to-ns_common.patch 2.3.5-1ubuntu1/debian/patches/ubuntu/0009-Linux-6.18-namespace-type-moved-to-ns_common.patch
--- 2.3.5-1/debian/patches/ubuntu/0009-Linux-6.18-namespace-type-moved-to-ns_common.patch	1970-01-01 00:00:00.000000000 +0000
+++ 2.3.5-1ubuntu1/debian/patches/ubuntu/0009-Linux-6.18-namespace-type-moved-to-ns_common.patch	2025-11-19 20:41:31.000000000 +0000
@@ -0,0 +1,127 @@
+From 8911360a416fb3a9fe055768017e003b2fc0d3bf Mon Sep 17 00:00:00 2001
+From: Rob Norris <robn@despairlabs.com>
+Date: Mon, 29 Sep 2025 09:16:36 +1000
+Subject: [PATCH 4/6] Linux 6.18: namespace type moved to ns_common
+
+The namespace type has moved from the namespace ops struct to the
+"common" base namespace struct. Detect this and define a macro that does
+the right thing for both versions.
+
+Sponsored-by: https://despairlabs.com/sponsor/
+Signed-off-by: Rob Norris <robn@despairlabs.com>
+
+Last-Update: 2025-11-19
+[john-cabaj: kernel-userns-capabilities.m4 doesn't exist in 2.3.4, so not
+removing here]
+Signed-off-by: John Cabaj <john.cabaj@canonical.com>
+---
+ config/kernel-namespace.m4           | 31 +++++++++++
+ config/kernel.m4                     |  2 +
+ module/os/linux/spl/spl-zone.c       | 19 ++++++-
+ 4 files changed, 51 insertions(+), 80 deletions(-)
+ create mode 100644 config/kernel-namespace.m4
+
+diff --git a/config/kernel-namespace.m4 b/config/kernel-namespace.m4
+new file mode 100644
+index 000000000..9b0b12e4e
+--- /dev/null
++++ b/config/kernel-namespace.m4
+@@ -0,0 +1,31 @@
++dnl #
++dnl # 6.18 API change
++dnl # ns->ops->type was moved to ns->ns.ns_type (struct ns_common)
++dnl #
++AC_DEFUN([ZFS_AC_KERNEL_SRC_NS_COMMON_TYPE], [
++	ZFS_LINUX_TEST_SRC([ns_common_type], [
++		#include <linux/user_namespace.h>
++	],[
++		struct user_namespace ns;
++		ns.ns.ns_type = 0;
++	])
++])
++
++AC_DEFUN([ZFS_AC_KERNEL_NS_COMMON_TYPE], [
++	AC_MSG_CHECKING([whether ns_type is accessible through ns_common])
++	ZFS_LINUX_TEST_RESULT([ns_common_type], [
++		AC_MSG_RESULT(yes)
++		AC_DEFINE([HAVE_NS_COMMON_TYPE], 1,
++			[Define if ns_type is accessible through ns_common])
++	],[
++		AC_MSG_RESULT(no)
++	])
++])
++
++AC_DEFUN([ZFS_AC_KERNEL_SRC_NAMESPACE], [
++	ZFS_AC_KERNEL_SRC_NS_COMMON_TYPE
++])
++
++AC_DEFUN([ZFS_AC_KERNEL_NAMESPACE], [
++	ZFS_AC_KERNEL_NS_COMMON_TYPE
++])
+diff --git a/config/kernel.m4 b/config/kernel.m4
+index 27fe76616..8484bcfb1 100644
+--- a/config/kernel.m4
++++ b/config/kernel.m4
+@@ -136,6 +136,7 @@ AC_DEFUN([ZFS_AC_KERNEL_TEST_SRC], [
+ 	ZFS_AC_KERNEL_SRC_TIMER
+ 	ZFS_AC_KERNEL_SRC_SUPER_BLOCK_S_WB_ERR
+ 	ZFS_AC_KERNEL_SRC_SOPS_FREE_INODE
++	ZFS_AC_KERNEL_SRC_NAMESPACE
+ 	case "$host_cpu" in
+ 		powerpc*)
+ 			ZFS_AC_KERNEL_SRC_CPU_HAS_FEATURE
+@@ -256,6 +257,7 @@ AC_DEFUN([ZFS_AC_KERNEL_TEST_RESULT], [
+ 	ZFS_AC_KERNEL_TIMER
+ 	ZFS_AC_KERNEL_SUPER_BLOCK_S_WB_ERR
+ 	ZFS_AC_KERNEL_SOPS_FREE_INODE
++	ZFS_AC_KERNEL_NAMESPACE
+ 	case "$host_cpu" in
+ 		powerpc*)
+ 			ZFS_AC_KERNEL_CPU_HAS_FEATURE
+diff --git a/module/os/linux/spl/spl-zone.c b/module/os/linux/spl/spl-zone.c
+index 45c2999a4..b2eae5d00 100644
+--- a/module/os/linux/spl/spl-zone.c
++++ b/module/os/linux/spl/spl-zone.c
+@@ -25,6 +25,10 @@
+  * SUCH DAMAGE.
+  */
+ 
++/*
++ * Copyright (c) 2025, Rob Norris <robn@despairlabs.com>
++ */
++
+ #include <sys/types.h>
+ #include <sys/sysmacros.h>
+ #include <sys/kmem.h>
+@@ -56,6 +60,19 @@ typedef struct zone_dataset {
+ } zone_dataset_t;
+ 
+ #ifdef CONFIG_USER_NS
++
++/*
++ * Linux 6.18 moved the generic namespace type away from ns->ops->type onto
++ * ns_common itself.
++ */
++#ifdef HAVE_NS_COMMON_TYPE
++#define	ns_is_newuser(ns)	\
++	((ns)->ns_type == CLONE_NEWUSER)
++#else
++#define	ns_is_newuser(ns)	\
++	((ns)->ops != NULL && (ns)->ops->type == CLONE_NEWUSER)
++#endif
++
+ /*
+  * Returns:
+  * - 0 on success
+@@ -84,7 +101,7 @@ user_ns_get(int fd, struct user_namespace **userns)
+ 		goto done;
+ 	}
+ 	ns = get_proc_ns(file_inode(nsfile));
+-	if (ns->ops->type != CLONE_NEWUSER) {
++	if (!ns_is_newuser(ns)) {
+ 		error = ENOTTY;
+ 		goto done;
+ 	}
+-- 
+2.43.0
+
diff -pruN 2.3.5-1/debian/patches/ubuntu/0010-sha256_generic-make-internal-functions-a-little-more.patch 2.3.5-1ubuntu1/debian/patches/ubuntu/0010-sha256_generic-make-internal-functions-a-little-more.patch
--- 2.3.5-1/debian/patches/ubuntu/0010-sha256_generic-make-internal-functions-a-little-more.patch	1970-01-01 00:00:00.000000000 +0000
+++ 2.3.5-1ubuntu1/debian/patches/ubuntu/0010-sha256_generic-make-internal-functions-a-little-more.patch	2025-11-19 20:41:31.000000000 +0000
@@ -0,0 +1,146 @@
+From 3651888182ec381f95d90efbd564a207e5e17670 Mon Sep 17 00:00:00 2001
+From: Rob Norris <robn@despairlabs.com>
+Date: Mon, 29 Sep 2025 09:32:50 +1000
+Subject: [PATCH 5/6] sha256_generic: make internal functions a little more
+ private
+
+Linux 6.18 has conflicting prototypes for various sha256_* and sha512_*
+functions, which we get through a very long include chain. That's tough
+to fix right now; easier is just to rename our internal functions.
+
+Sponsored-by: https://despairlabs.com/sponsor/
+Signed-off-by: Rob Norris <robn@despairlabs.com>
+
+Last-Update: 2025-11-19
+Signed-off-by: John Cabaj <john.cabaj@canonical.com>
+---
+ module/icp/algs/sha2/sha2_generic.c | 41 +++++++++++++++++------------
+ 1 file changed, 24 insertions(+), 17 deletions(-)
+
+diff --git a/module/icp/algs/sha2/sha2_generic.c b/module/icp/algs/sha2/sha2_generic.c
+index d0fcca798..ad707341e 100644
+--- a/module/icp/algs/sha2/sha2_generic.c
++++ b/module/icp/algs/sha2/sha2_generic.c
+@@ -77,7 +77,8 @@ static const uint32_t SHA256_K[64] = {
+ 	h = g, g = f, f = e, e = d + T1; \
+ 	d = c, c = b, b = a, a = T1 + T2;
+ 
+-static void sha256_generic(uint32_t state[8], const void *data, size_t num_blks)
++static void
++icp_sha256_generic(uint32_t state[8], const void *data, size_t num_blks)
+ {
+ 	uint64_t blk;
+ 
+@@ -173,7 +174,8 @@ static const uint64_t SHA512_K[80] = {
+ 	0x5fcb6fab3ad6faec, 0x6c44198c4a475817
+ };
+ 
+-static void sha512_generic(uint64_t state[8], const void *data, size_t num_blks)
++static void
++icp_sha512_generic(uint64_t state[8], const void *data, size_t num_blks)
+ {
+ 	uint64_t blk;
+ 
+@@ -226,7 +228,8 @@ static void sha512_generic(uint64_t state[8], const void *data, size_t num_blks)
+ 	}
+ }
+ 
+-static void sha256_update(sha256_ctx *ctx, const uint8_t *data, size_t len)
++static void
++icp_sha256_update(sha256_ctx *ctx, const uint8_t *data, size_t len)
+ {
+ 	uint64_t pos = ctx->count[0];
+ 	uint64_t total = ctx->count[1];
+@@ -258,7 +261,8 @@ static void sha256_update(sha256_ctx *ctx, const uint8_t *data, size_t len)
+ 	ctx->count[1] = total;
+ }
+ 
+-static void sha512_update(sha512_ctx *ctx, const uint8_t *data, size_t len)
++static void
++icp_sha512_update(sha512_ctx *ctx, const uint8_t *data, size_t len)
+ {
+ 	uint64_t pos = ctx->count[0];
+ 	uint64_t total = ctx->count[1];
+@@ -290,7 +294,8 @@ static void sha512_update(sha512_ctx *ctx, const uint8_t *data, size_t len)
+ 	ctx->count[1] = total;
+ }
+ 
+-static void sha256_final(sha256_ctx *ctx, uint8_t *result, int bits)
++static void
++icp_sha256_final(sha256_ctx *ctx, uint8_t *result, int bits)
+ {
+ 	uint64_t mlen, pos = ctx->count[0];
+ 	uint8_t *m = ctx->wbuf;
+@@ -334,7 +339,8 @@ static void sha256_final(sha256_ctx *ctx, uint8_t *result, int bits)
+ 	memset(ctx, 0, sizeof (*ctx));
+ }
+ 
+-static void sha512_final(sha512_ctx *ctx, uint8_t *result, int bits)
++static void
++icp_sha512_final(sha512_ctx *ctx, uint8_t *result, int bits)
+ {
+ 	uint64_t mlen, pos = ctx->count[0];
+ 	uint8_t *m = ctx->wbuf, *r;
+@@ -461,14 +467,14 @@ SHA2Update(SHA2_CTX *ctx, const void *data, size_t len)
+ 
+ 	switch (ctx->algotype) {
+ 		case SHA256:
+-			sha256_update(&ctx->sha256, data, len);
++			icp_sha256_update(&ctx->sha256, data, len);
+ 			break;
+ 		case SHA512:
+ 		case SHA512_HMAC_MECH_INFO_TYPE:
+-			sha512_update(&ctx->sha512, data, len);
++			icp_sha512_update(&ctx->sha512, data, len);
+ 			break;
+ 		case SHA512_256:
+-			sha512_update(&ctx->sha512, data, len);
++			icp_sha512_update(&ctx->sha512, data, len);
+ 			break;
+ 	}
+ }
+@@ -479,32 +485,33 @@ SHA2Final(void *digest, SHA2_CTX *ctx)
+ {
+ 	switch (ctx->algotype) {
+ 		case SHA256:
+-			sha256_final(&ctx->sha256, digest, 256);
++			icp_sha256_final(&ctx->sha256, digest, 256);
+ 			break;
+ 		case SHA512:
+ 		case SHA512_HMAC_MECH_INFO_TYPE:
+-			sha512_final(&ctx->sha512, digest, 512);
++			icp_sha512_final(&ctx->sha512, digest, 512);
+ 			break;
+ 		case SHA512_256:
+-			sha512_final(&ctx->sha512, digest, 256);
++			icp_sha512_final(&ctx->sha512, digest, 256);
+ 			break;
+ 	}
+ }
+ 
+ /* the generic implementation is always okay */
+-static boolean_t sha2_is_supported(void)
++static boolean_t
++icp_sha2_is_supported(void)
+ {
+ 	return (B_TRUE);
+ }
+ 
+ const sha256_ops_t sha256_generic_impl = {
+ 	.name = "generic",
+-	.transform = sha256_generic,
+-	.is_supported = sha2_is_supported
++	.transform = icp_sha256_generic,
++	.is_supported = icp_sha2_is_supported
+ };
+ 
+ const sha512_ops_t sha512_generic_impl = {
+ 	.name = "generic",
+-	.transform = sha512_generic,
+-	.is_supported = sha2_is_supported
++	.transform = icp_sha512_generic,
++	.is_supported = icp_sha2_is_supported
+ };
+-- 
+2.43.0
+
diff -pruN 2.3.5-1/debian/patches/ubuntu/0011-Linux-6.18-generic_drop_inode-and-generic_delete_ino.patch 2.3.5-1ubuntu1/debian/patches/ubuntu/0011-Linux-6.18-generic_drop_inode-and-generic_delete_ino.patch
--- 2.3.5-1/debian/patches/ubuntu/0011-Linux-6.18-generic_drop_inode-and-generic_delete_ino.patch	1970-01-01 00:00:00.000000000 +0000
+++ 2.3.5-1ubuntu1/debian/patches/ubuntu/0011-Linux-6.18-generic_drop_inode-and-generic_delete_ino.patch	2025-11-19 20:41:31.000000000 +0000
@@ -0,0 +1,106 @@
+From fe8b50f09fe69d3ae672d75593ec11b6d2b3f73f Mon Sep 17 00:00:00 2001
+From: Rob Norris <robn@despairlabs.com>
+Date: Mon, 29 Sep 2025 09:51:06 +1000
+Subject: [PATCH 6/6] Linux 6.18: generic_drop_inode() and
+ generic_delete_inode() renamed
+
+Sponsored-by: https://despairlabs.com/sponsor/
+Signed-off-by: Rob Norris <robn@despairlabs.com>
+
+Last-Update: 2025-11-19
+[john-cabaj: keeping only relevant copyright changes, and leaving out
+changes to code comments in zpl_super.c as the context doesn't exist
+in 2.3.4]
+Signed-off-by: John Cabaj <john.cabaj@canonical.com>
+---
+ config/kernel-drop-inode.m4                | 24 ++++++++++++++++++++++
+ config/kernel.m4                           |  2 ++
+ include/os/linux/kernel/linux/vfs_compat.h |  7 +++++++
+ module/os/linux/zfs/zpl_super.c            |  4 +++-
+ 4 files changed, 36 insertions(+), 1 deletion(-)
+ create mode 100644 config/kernel-drop-inode.m4
+
+--- /dev/null
++++ b/config/kernel-drop-inode.m4
+@@ -0,0 +1,24 @@
++dnl #
++dnl # 6.18 API change
++dnl # - generic_drop_inode() renamed to inode_generic_drop()
++dnl # - generic_delete_inode() renamed to inode_just_drop()
++dnl #
++AC_DEFUN([ZFS_AC_KERNEL_SRC_INODE_GENERIC_DROP], [
++	ZFS_LINUX_TEST_SRC([inode_generic_drop], [
++		#include <linux/fs.h>
++	],[
++		struct inode *ip = NULL;
++		inode_generic_drop(ip);
++	])
++])
++
++AC_DEFUN([ZFS_AC_KERNEL_INODE_GENERIC_DROP], [
++	AC_MSG_CHECKING([whether inode_generic_drop() exists])
++	ZFS_LINUX_TEST_RESULT([inode_generic_drop], [
++		AC_MSG_RESULT(yes)
++		AC_DEFINE(HAVE_INODE_GENERIC_DROP, 1,
++			[inode_generic_drop() exists])
++	],[
++		AC_MSG_RESULT(no)
++	])
++])
+--- a/config/kernel.m4
++++ b/config/kernel.m4
+@@ -136,6 +136,7 @@
+ 	ZFS_AC_KERNEL_SRC_SUPER_BLOCK_S_WB_ERR
+ 	ZFS_AC_KERNEL_SRC_SOPS_FREE_INODE
+ 	ZFS_AC_KERNEL_SRC_NAMESPACE
++	ZFS_AC_KERNEL_SRC_INODE_GENERIC_DROP
+ 	case "$host_cpu" in
+ 		powerpc*)
+ 			ZFS_AC_KERNEL_SRC_CPU_HAS_FEATURE
+@@ -256,6 +257,7 @@
+ 	ZFS_AC_KERNEL_SUPER_BLOCK_S_WB_ERR
+ 	ZFS_AC_KERNEL_SOPS_FREE_INODE
+ 	ZFS_AC_KERNEL_NAMESPACE
++	ZFS_AC_KERNEL_INODE_GENERIC_DROP
+ 	case "$host_cpu" in
+ 		powerpc*)
+ 			ZFS_AC_KERNEL_CPU_HAS_FEATURE
+--- a/include/os/linux/kernel/linux/vfs_compat.h
++++ b/include/os/linux/kernel/linux/vfs_compat.h
+@@ -23,6 +23,7 @@
+ /*
+  * Copyright (C) 2011 Lawrence Livermore National Security, LLC.
+  * Copyright (C) 2015 Jörg Thalheim.
++ * Copyright (c) 2025, Rob Norris <robn@despairlabs.com>
+  */
+ 
+ #ifndef _ZFS_VFS_H
+@@ -262,4 +263,10 @@
+ #define	zpl_generic_fillattr(user_ns, ip, sp)	generic_fillattr(ip, sp)
+ #endif
+ 
++#ifdef HAVE_INODE_GENERIC_DROP
++/* 6.18 API change. These were renamed, alias the old names to the new. */
++#define	generic_delete_inode(ip)	inode_just_drop(ip)
++#define	generic_drop_inode(ip)		inode_generic_drop(ip)
++#endif
++
+ #endif /* _ZFS_VFS_H */
+--- a/module/os/linux/zfs/zpl_super.c
++++ b/module/os/linux/zfs/zpl_super.c
+@@ -22,6 +22,7 @@
+ /*
+  * Copyright (c) 2011, Lawrence Livermore National Security, LLC.
+  * Copyright (c) 2023, Datto Inc. All rights reserved.
++ * Copyright (c) 2025, Rob Norris <robn@despairlabs.com>
+  */
+ 
+ 
+@@ -32,6 +33,7 @@
+ #include <sys/zpl.h>
+ #include <linux/iversion.h>
+ #include <linux/version.h>
++#include <linux/vfs_compat.h>
+ 
+ 
+ static struct inode *
diff -pruN 2.3.5-1/debian/patches/ubuntu/4000-zsys-support.patch 2.3.5-1ubuntu1/debian/patches/ubuntu/4000-zsys-support.patch
--- 2.3.5-1/debian/patches/ubuntu/4000-zsys-support.patch	2025-03-03 06:31:51.000000000 +0000
+++ 2.3.5-1ubuntu1/debian/patches/ubuntu/4000-zsys-support.patch	2025-11-19 20:31:55.000000000 +0000
@@ -7,203 +7,12 @@ Description: Support zsys systems
  snapshots.
 Author: Jean-Baptiste Lallement <jean.baptiste@ubuntu.com>
         Didier Roche <didrocks@ubuntu.com>
-Last-Update: 2019-06-06
-Index: zfs-linux-2.1.2/etc/systemd/system-generators/zfs-mount-generator.in
+        Heitor Alves de Siqueira <halves@ubuntu.com>
+Last-Update: 2025-01-20
+Index: zfs-linux/contrib/initramfs/scripts/zfs
 ===================================================================
---- zfs-linux-2.1.2.orig/etc/systemd/system-generators/zfs-mount-generator.in
-+++ zfs-linux-2.1.2/etc/systemd/system-generators/zfs-mount-generator.in
-@@ -30,6 +30,8 @@ FSLIST="@sysconfdir@/zfs/zfs-list.cache"
- [ -d "${FSLIST}" ] || exit 0
- [ "$(echo "${FSLIST}"/*)" = "${FSLIST}/*" ] && exit 0
- 
-+OLD_IFS=$IFS
-+
- do_fail() {
-   printf 'zfs-mount-generator: %s\n' "$*" > /dev/kmsg
-   exit 1
-@@ -138,6 +140,9 @@ process_line() {
-     fi
-   done
- 
-+  # Escape the mountpoint per systemd policy.
-+  mountfile="$(systemd-escape --path --suffix=mount "${p_mountpoint}")"
-+
-   if [ -n "${p_systemd_after}" ] && \
-       [ "${p_systemd_after}" != "-" ] ; then
-     after="${p_systemd_after} ${after}"
-@@ -163,6 +168,62 @@ process_line() {
-       [ "${p_encroot}" != "-" ] ; then
-     keyloadunit="zfs-load-key-$(systemd-escape "${p_encroot}").service"
-     if [ "${p_encroot}" = "${dataset}" ] ; then
-+
-+      # Automount and unmount ZSys USERDATA datasets with keystore
-+      zsys_automount=0
-+      automount_loadkey_extra_args=""
-+      automount_pool=""
-+      automount_user=""
-+      if echo "${dataset}" | grep -q '/USERDATA/'; then
-+        automount_pool=${dataset%%/*}
-+        automount_user=${dataset##*/}
-+        # Only operate on user dataset mountpoint itself and not its children
-+        if ! echo "${automount_user}" | grep -q '/'; then
-+          automount_user=${automount_user%%_*}
-+          # Ensure we have a keystore
-+          if [ -f "/run/keystore/${automount_pool}/${automount_user}.enc" -a -x /usr/sbin/user_keystore ]; then
-+            zsys_automount=1
-+          fi
-+        fi
-+      fi
-+
-+      # Create automount unit and keystore tracker
-+      if [ ${zsys_automount} -eq 1 ]; then
-+        automountunit="$(systemd-escape --path --suffix=automount "${p_mountpoint}")"
-+      echo \
-+"# Automatically generated by zfs-mount-generator
-+
-+[Unit]
-+Description=Automount ZFS user home for ${dataset} on demand
-+
-+[Automount]
-+Where=${p_mountpoint}
-+TimeoutIdleSec=10
-+
-+[Install]
-+WantedBy=local-fs.target
-+"   > "${dest_norm}/${automountunit}"
-+        create_dependencies "${automountunit}" "wants" "local-fs.target"
-+
-+        keystoreunit="zfs-keystore-$(systemd-escape "${p_encroot}").service"
-+        automount_loadkey_extra_args="BindsTo=${mountfile}
-+BindsTo=${keystoreunit}
-+After=${keystoreunit}"
-+        echo \
-+"# Automatically generated by zfs-mount-generator
-+
-+[Unit]
-+Description=Make available ZFS encryption key for ${dataset} from keystore
-+ConditionPathExists=/run/keystore/${automount_pool}/${automount_user}.enc
-+BindsTo=${keyloadunit}
-+
-+[Service]
-+Type=oneshot
-+RemainAfterExit=yes
-+ExecStop=/usr/sbin/user_keystore lock ${automount_pool} ${automount_user}
-+"   > "${dest_norm}/${keystoreunit}"
-+      fi
-+
-       keymountdep=""
-       if [ "${p_keyloc%%://*}" = "file" ] ; then
-         if [ -n "${requiredmounts}" ] ; then
-@@ -218,6 +279,7 @@ Wants=${wants}
- After=${after}
- ${requires}
- ${keymountdep}
-+${automount_loadkey_extra_args}
- 
- [Service]
- Type=oneshot
-@@ -270,9 +332,6 @@ ExecStop=${keyunloadcmd}"   > "${dest_no
-     do_fail "invalid mountpoint for ${dataset}"
-   fi
- 
--  # Escape the mountpoint per systemd policy.
--  mountfile="$(systemd-escape --path --suffix=mount "${p_mountpoint}")"
--
-   # Parse options
-   # see lib/libzfs/libzfs_mount.c:zfs_add_options
-   opts=""
-@@ -459,6 +518,87 @@ Options=defaults${opts},zfsutil" > "${de
- 
- }
- 
-+ZPOOL_CACHE="@sysconfdir@/zfs/zpool.cache"
-+PROPS="name,mountpoint,canmount,atime,relatime,devices,exec\
-+,readonly,setuid,nbmand,encroot,keylocation\
-+,org.openzfs.systemd:requires,org.openzfs.systemd:requires-mounts-for\
-+,org.openzfs.systemd:before,org.openzfs.systemd:after\
-+,org.openzfs.systemd:wanted-by,org.openzfs.systemd:required-by\
-+,org.openzfs.systemd:nofail,org.openzfs.systemd:ignore"
-+zsys_revert_failed=0
-+errfile="/tmp/zsys-revert-out.log"
-+
-+drop_emergency_on_failure() {
-+  if [ ${zsys_revert_failed} -eq 0 ]; then
-+    return
-+  fi
-+
-+  # Drop to emergency target in case of failure after cleanup fstab mountpoints.
-+  # This avoids booting and having a mix of old and new datasets, and creating directory in the wrong
-+  # datasets, like /boot/grub in / which will prevent zfs to mount /boot dataset later on.
-+  rm -f "${dest_norm}"/*.mount
-+  ln -s /lib/systemd/system/emergency.target "${dest_norm}"/default.target
-+
-+  printf 'ERROR: zfs-mount-generator failed and you requested a revert:\n' > /dev/kmsg
-+  cat "${errfile}" > /dev/kmsg
-+  printf 'You can reboot on current master dataset to fix the issue\n' > /dev/kmsg
-+}
-+
-+# Handle revert so that zsys prepares all datasets as expected.
-+initzsys() {
-+  if [ ! -x @sbindir@/zsysd ]; then
-+    return
-+  fi
-+
-+  # Non ZFS system
-+  if ! grep -q "root=ZFS=" /proc/cmdline; then
-+    return
-+  fi
-+
-+  # If we boot on the same dataset than last time, assume we don’t need to do anything as the cache file will only
-+  # import desired pools.
-+  bootds="$(sed -e 's/.*root=ZFS=\([^ ]\+\).*/\1/' /proc/cmdline)"
-+  if grep -Eq "${bootds}\s+/\s+on" "${FSLIST}/"*; then
-+      return
-+  fi
-+
-+  # If we get here: we are reverting. Let zsys handle it
-+  trap drop_emergency_on_failure EXIT INT QUIT ABRT PIPE TERM
-+
-+  exec 3>&1 1>"${errfile}"
-+  exec 4>&2 2>&1
-+
-+  zsys_revert_failed=1
-+  # Import and list previously imported pools for zsys
-+  if [ -f "${ZPOOL_CACHE}" ]; then
-+    @sbindir@/zpool import -c "${ZPOOL_CACHE}" -aN
-+  # As a best effort, import all available pools, hoping there is no conflict.
-+  else
-+    echo "We had to search for all available pools because ${ZPOOL_CACHE} doesn't exist. To avoid this, create a zpool cache file."
-+    @sbindir@/zpool import -aN
-+  fi
-+
-+  @sbindir@/zsysd boot-prepare >"${errfile}"
-+
-+  # If FSLIST is empty, populate with all imported pools
-+  if [ -z "$(ls -A ${FSLIST})" ]; then
-+    @sbindir@/zpool list -H -o name | xargs -I{} touch ${FSLIST}/{}
-+  fi
-+
-+  # Refresh zfs list cache
-+  for cachefile in "${FSLIST}/"* ; do
-+    pool=`basename ${cachefile}`
-+    @sbindir@/zfs list -H -t filesystem -o "${PROPS}" -r "${pool}" >"${cachefile}"
-+  done
-+
-+  exec 1>&3 3>&-
-+  exec 2>&4 4>&-
-+  zsys_revert_failed=0
-+  rm "${errfile}"
-+}
-+
-+initzsys
-+
- for cachefile in "${FSLIST}/"* ; do
-   # Disable glob expansion to protect against special characters when parsing.
-   set -f
-Index: zfs-linux-2.1.2/contrib/initramfs/scripts/zfs
-===================================================================
---- zfs-linux-2.1.2.orig/contrib/initramfs/scripts/zfs
-+++ zfs-linux-2.1.2/contrib/initramfs/scripts/zfs
+--- zfs-linux.orig/contrib/initramfs/scripts/zfs
++++ zfs-linux/contrib/initramfs/scripts/zfs
 @@ -66,6 +66,20 @@ get_fs_value()
  	"${ZFS}" get -H -ovalue "$value" "$fs" 2> /dev/null
  }
@@ -225,7 +34,7 @@ Index: zfs-linux-2.1.2/contrib/initramfs
  # Find the 'bootfs' property on pool $1.
  # If the property does not contain '/', then ignore this
  # pool by exporting it again.
-@@ -483,16 +497,17 @@ clone_snap()
+@@ -505,16 +519,17 @@ clone_snap()
  	snap="$1"
  	destfs="$2"
  	mountpoint="$3"
@@ -249,7 +58,7 @@ Index: zfs-linux-2.1.2/contrib/initramfs
  	ZFS_CMD="${ZFS_CMD} $snap $destfs"
  	ZFS_STDERR="$(${ZFS_CMD} 2>&1)"
  	ZFS_ERROR="$?"
-@@ -611,6 +626,15 @@ setup_snapshot_booting()
+@@ -633,6 +648,15 @@ setup_snapshot_booting()
  	snapname="${snap##*@}"
  	ZFS_BOOTFS="${rootfs}_${snapname}"
  
@@ -265,7 +74,7 @@ Index: zfs-linux-2.1.2/contrib/initramfs
  	if ! grep -qiE '(^|[^\\](\\\\)* )(rollback)=(on|yes|1)( |$)' /proc/cmdline
  	then
  		# If the destination dataset for the clone
-@@ -640,10 +664,18 @@ setup_snapshot_booting()
+@@ -664,10 +688,18 @@ setup_snapshot_booting()
  			#       rpool/ROOT/debian/boot@snap2	=> rpool/ROOT/debian_snap2/boot
  			#       rpool/ROOT/debian/usr@snap2	=> rpool/ROOT/debian_snap2/usr
  			#       rpool/ROOT/debian/var@snap2	=> rpool/ROOT/debian_snap2/var
@@ -274,8 +83,8 @@ Index: zfs-linux-2.1.2/contrib/initramfs
 +			#       rpool/ROOT/debian_uid1@snap2		=> rpool/ROOT/debian_uid2
 +			#       rpool/ROOT/debian_uid1/boot@snap2	=> rpool/ROOT/debian_uid2/boot
 +
- 			subfs="${s##$rootfs}"
- 			subfs="${subfs%%@$snapname}"
+ 			subfs="${s##"$rootfs"}"
+ 			subfs="${subfs%%@"$snapname"}"
  
  			destfs="${rootfs}_${snapname}" # base fs.
 +			if [ "${use_zsys}" = "yes" ]; then
@@ -284,7 +93,7 @@ Index: zfs-linux-2.1.2/contrib/initramfs
  			[ -n "$subfs" ] && destfs="${destfs}$subfs" # + sub fs.
  
  			# Get the mountpoint of the filesystem, to be used
-@@ -660,9 +692,38 @@ setup_snapshot_booting()
+@@ -684,9 +716,38 @@ setup_snapshot_booting()
  				fi
  			fi
  
@@ -324,7 +133,7 @@ Index: zfs-linux-2.1.2/contrib/initramfs
  			    retval=$((retval + 1))
  		fi
  	done
-@@ -887,6 +948,38 @@ mountroot()
+@@ -911,6 +972,38 @@ mountroot()
  		shell
  	fi
  
@@ -361,9 +170,9 @@ Index: zfs-linux-2.1.2/contrib/initramfs
 +	fi
 +
  	# In case the pool was specified as guid, resolve guid to name
- 	pool="$("${ZPOOL}" get name,guid -o name,value -H | \
+ 	pool="$("${ZPOOL}" get -H -o name,value name,guid | \
  	    awk -v pool="${ZFS_RPOOL}" '$2 == pool { print $1 }')"
-@@ -906,6 +999,8 @@ mountroot()
+@@ -930,6 +1023,8 @@ mountroot()
  		# Booting from a snapshot?
  		# Will overwrite the ZFS_BOOTFS variable like so:
  		#   rpool/ROOT/debian@snap2 => rpool/ROOT/debian_snap2
@@ -372,7 +181,7 @@ Index: zfs-linux-2.1.2/contrib/initramfs
  		echo "${ZFS_BOOTFS}" | grep -q '@' && \
  		    setup_snapshot_booting "${ZFS_BOOTFS}"
  	fi
-@@ -943,13 +1038,23 @@ mountroot()
+@@ -967,13 +1062,23 @@ mountroot()
  	# Go through the complete list (recursively) of all filesystems below
  	# the real root dataset
  	filesystems="$("${ZFS}" list -oname -tfilesystem -H -r "${ZFS_BOOTFS}")"
@@ -396,7 +205,7 @@ Index: zfs-linux-2.1.2/contrib/initramfs
  		mount_fs "$fs"
  	done
  
-@@ -995,3 +1100,8 @@ mountroot()
+@@ -1019,3 +1124,8 @@ mountroot()
  		fi
  	fi
  }
@@ -405,3 +214,195 @@ Index: zfs-linux-2.1.2/contrib/initramfs
 +{
 +	grep -a -m10 -E "\*" /dev/urandom 2>/dev/null | tr -dc 'a-z0-9' | cut -c-6
 +}
+Index: zfs-linux/etc/systemd/system-generators/zfs-mount-generator.in
+===================================================================
+--- zfs-linux.orig/etc/systemd/system-generators/zfs-mount-generator.in
++++ zfs-linux/etc/systemd/system-generators/zfs-mount-generator.in
+@@ -30,6 +30,8 @@ FSLIST="@sysconfdir@/zfs/zfs-list.cache"
+ [ -d "${FSLIST}" ] || exit 0
+ [ "$(echo "${FSLIST}"/*)" = "${FSLIST}/*" ] && exit 0
+ 
++OLD_IFS=$IFS
++
+ do_fail() {
+   printf 'zfs-mount-generator: %s\n' "$*" > /dev/kmsg
+   exit 1
+@@ -138,6 +140,9 @@ process_line() {
+     fi
+   done
+ 
++  # Escape the mountpoint per systemd policy.
++  mountfile="$(systemd-escape --path --suffix=mount "${p_mountpoint}")"
++
+   if [ -n "${p_systemd_after}" ] && \
+       [ "${p_systemd_after}" != "-" ] ; then
+     after="${p_systemd_after} ${after}"
+@@ -163,6 +168,62 @@ process_line() {
+       [ "${p_encroot}" != "-" ] ; then
+     keyloadunit="zfs-load-key-$(systemd-escape "${p_encroot}").service"
+     if [ "${p_encroot}" = "${dataset}" ] ; then
++
++      # Automount and unmount ZSys USERDATA datasets with keystore
++      zsys_automount=0
++      automount_loadkey_extra_args=""
++      automount_pool=""
++      automount_user=""
++      if echo "${dataset}" | grep -q '/USERDATA/'; then
++        automount_pool=${dataset%%/*}
++        automount_user=${dataset##*/}
++        # Only operate on user dataset mountpoint itself and not its children
++        if ! echo "${automount_user}" | grep -q '/'; then
++          automount_user=${automount_user%%_*}
++          # Ensure we have a keystore
++          if [ -f "/run/keystore/${automount_pool}/${automount_user}.enc" -a -x /usr/sbin/user_keystore ]; then
++            zsys_automount=1
++          fi
++        fi
++      fi
++
++      # Create automount unit and keystore tracker
++      if [ ${zsys_automount} -eq 1 ]; then
++        automountunit="$(systemd-escape --path --suffix=automount "${p_mountpoint}")"
++      echo \
++"# Automatically generated by zfs-mount-generator
++
++[Unit]
++Description=Automount ZFS user home for ${dataset} on demand
++
++[Automount]
++Where=${p_mountpoint}
++TimeoutIdleSec=10
++
++[Install]
++WantedBy=local-fs.target
++"   > "${dest_norm}/${automountunit}"
++        create_dependencies "${automountunit}" "wants" "local-fs.target"
++
++        keystoreunit="zfs-keystore-$(systemd-escape "${p_encroot}").service"
++        automount_loadkey_extra_args="BindsTo=${mountfile}
++BindsTo=${keystoreunit}
++After=${keystoreunit}"
++        echo \
++"# Automatically generated by zfs-mount-generator
++
++[Unit]
++Description=Make available ZFS encryption key for ${dataset} from keystore
++ConditionPathExists=/run/keystore/${automount_pool}/${automount_user}.enc
++BindsTo=${keyloadunit}
++
++[Service]
++Type=oneshot
++RemainAfterExit=yes
++ExecStop=/usr/sbin/user_keystore lock ${automount_pool} ${automount_user}
++"   > "${dest_norm}/${keystoreunit}"
++      fi
++
+       keymountdep=""
+       if [ "${p_keyloc%%://*}" = "file" ] ; then
+         if [ -n "${requiredmounts}" ] ; then
+@@ -218,6 +279,7 @@ Wants=${wants}
+ After=${after}
+ ${requires}
+ ${keymountdep}
++${automount_loadkey_extra_args}
+ 
+ [Service]
+ Type=oneshot
+@@ -270,9 +332,6 @@ ExecStop=${keyunloadcmd}"   > "${dest_no
+     do_fail "invalid mountpoint for ${dataset}"
+   fi
+ 
+-  # Escape the mountpoint per systemd policy.
+-  mountfile="$(systemd-escape --path --suffix=mount "${p_mountpoint}")"
+-
+   # Parse options
+   # see lib/libzfs/libzfs_mount.c:zfs_add_options
+   opts=""
+@@ -459,6 +518,87 @@ Options=defaults${opts},zfsutil" > "${de
+ 
+ }
+ 
++ZPOOL_CACHE="@sysconfdir@/zfs/zpool.cache"
++PROPS="name,mountpoint,canmount,atime,relatime,devices,exec\
++,readonly,setuid,nbmand,encroot,keylocation\
++,org.openzfs.systemd:requires,org.openzfs.systemd:requires-mounts-for\
++,org.openzfs.systemd:before,org.openzfs.systemd:after\
++,org.openzfs.systemd:wanted-by,org.openzfs.systemd:required-by\
++,org.openzfs.systemd:nofail,org.openzfs.systemd:ignore"
++zsys_revert_failed=0
++errfile="/tmp/zsys-revert-out.log"
++
++drop_emergency_on_failure() {
++  if [ ${zsys_revert_failed} -eq 0 ]; then
++    return
++  fi
++
++  # Drop to emergency target in case of failure after cleanup fstab mountpoints.
++  # This avoids booting and having a mix of old and new datasets, and creating directory in the wrong
++  # datasets, like /boot/grub in / which will prevent zfs to mount /boot dataset later on.
++  rm -f "${dest_norm}"/*.mount
++  ln -s /lib/systemd/system/emergency.target "${dest_norm}"/default.target
++
++  printf 'ERROR: zfs-mount-generator failed and you requested a revert:\n' > /dev/kmsg
++  cat "${errfile}" > /dev/kmsg
++  printf 'You can reboot on current master dataset to fix the issue\n' > /dev/kmsg
++}
++
++# Handle revert so that zsys prepares all datasets as expected.
++initzsys() {
++  if [ ! -x @sbindir@/zsysd ]; then
++    return
++  fi
++
++  # Non ZFS system
++  if ! grep -q "root=ZFS=" /proc/cmdline; then
++    return
++  fi
++
++  # If we boot on the same dataset than last time, assume we don’t need to do anything as the cache file will only
++  # import desired pools.
++  bootds="$(sed -e 's/.*root=ZFS=\([^ ]\+\).*/\1/' /proc/cmdline)"
++  if grep -Eq "${bootds}\s+/\s+on" "${FSLIST}/"*; then
++      return
++  fi
++
++  # If we get here: we are reverting. Let zsys handle it
++  trap drop_emergency_on_failure EXIT INT QUIT ABRT PIPE TERM
++
++  exec 3>&1 1>"${errfile}"
++  exec 4>&2 2>&1
++
++  zsys_revert_failed=1
++  # Import and list previously imported pools for zsys
++  if [ -f "${ZPOOL_CACHE}" ]; then
++    @sbindir@/zpool import -c "${ZPOOL_CACHE}" -aN
++  # As a best effort, import all available pools, hoping there is no conflict.
++  else
++    echo "We had to search for all available pools because ${ZPOOL_CACHE} doesn't exist. To avoid this, create a zpool cache file."
++    @sbindir@/zpool import -aN
++  fi
++
++  @sbindir@/zsysd boot-prepare >"${errfile}"
++
++  # If FSLIST is empty, populate with all imported pools
++  if [ -z "$(ls -A ${FSLIST})" ]; then
++    @sbindir@/zpool list -H -o name | xargs -I{} touch ${FSLIST}/{}
++  fi
++
++  # Refresh zfs list cache
++  for cachefile in "${FSLIST}/"* ; do
++    pool=`basename ${cachefile}`
++    @sbindir@/zfs list -H -t filesystem -o "${PROPS}" -r "${pool}" >"${cachefile}"
++  done
++
++  exec 1>&3 3>&-
++  exec 2>&4 4>&-
++  zsys_revert_failed=0
++  rm "${errfile}"
++}
++
++initzsys
++
+ for cachefile in "${FSLIST}/"* ; do
+   # Disable glob expansion to protect against special characters when parsing.
+   set -f
diff -pruN 2.3.5-1/debian/patches/ubuntu/4001-dracut-Open-and-mount-luks-keystore.patch 2.3.5-1ubuntu1/debian/patches/ubuntu/4001-dracut-Open-and-mount-luks-keystore.patch
--- 2.3.5-1/debian/patches/ubuntu/4001-dracut-Open-and-mount-luks-keystore.patch	1970-01-01 00:00:00.000000000 +0000
+++ 2.3.5-1ubuntu1/debian/patches/ubuntu/4001-dracut-Open-and-mount-luks-keystore.patch	2025-11-19 20:31:55.000000000 +0000
@@ -0,0 +1,68 @@
+From: Benjamin Drung <benjamin.drung@canonical.com>
+Date: Thu, 4 Sep 2025 21:11:51 +0200
+Subject: dracut: Open and mount luks keystore
+
+Booting an encrypted ZFS system with dracut fails:
+
+```
+dracut-pre-mount[817]: Warning: ZFS: Key /run/keystore/rpool/system.key for rpool hasn't appeared. Trying anyway.
+dracut-pre-mount[863]: Key load error: Failed to open key material file: No such file or directory
+[FAILED] Failed to mount sysroot.mount - /sysroot.
+```
+
+4000-zsys-support.patch enhances `contrib/initramfs/scripts/zfs` to open
+and mount luks keystore for any pools using one. Port this Ubuntu
+keystore convention to Dracut.
+
+Bug-Ubuntu: https://launchpad.net/bugs/2070066
+---
+ contrib/dracut/90zfs/zfs-load-key.sh.in | 29 +++++++++++++++++++++++++++++
+ 1 file changed, 29 insertions(+)
+
+diff --git a/contrib/dracut/90zfs/zfs-load-key.sh.in b/contrib/dracut/90zfs/zfs-load-key.sh.in
+index 8e68468..1932100 100755
+--- a/contrib/dracut/90zfs/zfs-load-key.sh.in
++++ b/contrib/dracut/90zfs/zfs-load-key.sh.in
+@@ -22,6 +22,29 @@ fi
+ 
+ [ "$(zpool get -Ho value feature@encryption "${BOOTFS%%/*}")" = 'active' ] || return 0
+ 
++_open_and_mount_luks_keystore() {
++    pool="$1"
++    keyfile="$2"
++
++    ks="/dev/zvol/$pool/keystore"
++    if [ ! -e "$ks" ]; then
++        echo "Error: $ks does not exist." >&2
++        return 1
++    fi
++
++    systemd-cryptsetup attach "keystore-${pool}" "${ks}"
++
++    dev="/dev/mapper/keystore-${pool}"
++    if [ ! -e "$dev" ]; then
++        echo "Error: $dev does not exist." >&2
++        return 1
++    fi
++
++    keypath="${keyfile%/*}"
++    mkdir -p "${keypath}"
++    mount -o discard "${dev}" "${keypath}"
++}
++
+ _load_key_cb() {
+     dataset="$1"
+ 
+@@ -31,6 +54,12 @@ _load_key_cb() {
+     [ "$(zfs get -Ho value keystatus "${ENCRYPTIONROOT}")" = "unavailable" ] || return 0
+ 
+     KEYLOCATION="$(zfs get -Ho value keylocation "${ENCRYPTIONROOT}")"
++    case "$KEYLOCATION" in
++        "file:///run/keystore/${ENCRYPTIONROOT}/"*)
++            _open_and_mount_luks_keystore "${ENCRYPTIONROOT}" "${KEYLOCATION#file://}"
++            ;;
++    esac
++
+     case "${KEYLOCATION%%://*}" in
+         prompt)
+             for _ in 1 2 3; do
diff -pruN 2.3.5-1/debian/patches/ubuntu/4510-silently-ignore-modprobe-failure.patch 2.3.5-1ubuntu1/debian/patches/ubuntu/4510-silently-ignore-modprobe-failure.patch
--- 2.3.5-1/debian/patches/ubuntu/4510-silently-ignore-modprobe-failure.patch	2025-03-03 06:31:51.000000000 +0000
+++ 2.3.5-1ubuntu1/debian/patches/ubuntu/4510-silently-ignore-modprobe-failure.patch	2025-11-19 20:31:55.000000000 +0000
@@ -5,29 +5,25 @@ Origin: ubuntu
 Forwarded: no
 Last-Update: 2020-06-04
 
-Index: zfs-linux-2.0.1/etc/systemd/system/zfs-load-module.service.in
-===================================================================
---- zfs-linux-2.0.1.orig/etc/systemd/system/zfs-load-module.service.in
-+++ zfs-linux-2.0.1/etc/systemd/system/zfs-load-module.service.in
-@@ -10,7 +10,7 @@ After=systemd-remount-fs.service
+--- a/etc/systemd/system/zfs-load-module.service.in
++++ b/etc/systemd/system/zfs-load-module.service.in
+@@ -10,7 +10,7 @@
  [Service]
  Type=oneshot
  RemainAfterExit=yes
--ExecStart=/sbin/modprobe zfs
-+ExecStart=-/sbin/modprobe zfs
+-ExecStart=/usr/sbin/modprobe zfs
++ExecStart=-/usr/sbin/modprobe zfs
  
  [Install]
  WantedBy=zfs-mount.service
-Index: zfs-linux-2.0.1/etc/systemd/system/zfs-share.service.in
-===================================================================
---- zfs-linux-2.0.1.orig/etc/systemd/system/zfs-share.service.in
-+++ zfs-linux-2.0.1/etc/systemd/system/zfs-share.service.in
-@@ -13,7 +13,7 @@ ConditionPathIsDirectory=/sys/module/zfs
- [Service]
+--- a/etc/systemd/system/zfs-share.service.in
++++ b/etc/systemd/system/zfs-share.service.in
+@@ -14,7 +14,7 @@
  Type=oneshot
  RemainAfterExit=yes
--ExecStart=@sbindir@/zfs share -a
-+ExecStart=-@sbindir@/zfs share -a
+ EnvironmentFile=-@initconfdir@/zfs
+-ExecStart=zfs share -a
++ExecStart=-zfs share -a
  
  [Install]
  WantedBy=zfs.target
diff -pruN 2.3.5-1/debian/patches/ubuntu/fixup-abi.patch 2.3.5-1ubuntu1/debian/patches/ubuntu/fixup-abi.patch
--- 2.3.5-1/debian/patches/ubuntu/fixup-abi.patch	1970-01-01 00:00:00.000000000 +0000
+++ 2.3.5-1ubuntu1/debian/patches/ubuntu/fixup-abi.patch	2025-11-19 20:31:55.000000000 +0000
@@ -0,0 +1,38 @@
+Description: fixup abi check files
+Author: Dimitri John Ledkov <dimitri.ledkov@canonical.com>
+Bug: https://github.com/openzfs/zfs/issues/15196
+
+
+--- zfs-linux-2.2.0~rc3.orig/lib/libuutil/libuutil.abi
++++ zfs-linux-2.2.0~rc3/lib/libuutil/libuutil.abi
+@@ -176,8 +176,6 @@
+     <elf-symbol name='mkdirp' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+     <elf-symbol name='print_timestamp' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+     <elf-symbol name='spl_pagesize' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+-    <elf-symbol name='strlcat' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+-    <elf-symbol name='strlcpy' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+     <elf-symbol name='uu_avl_create' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+     <elf-symbol name='uu_avl_destroy' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+     <elf-symbol name='uu_avl_find' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+--- zfs-linux-2.2.0~rc3.orig/lib/libzfs/libzfs.abi
++++ zfs-linux-2.2.0~rc3/lib/libzfs/libzfs.abi
+@@ -248,8 +248,6 @@
+     <elf-symbol name='sa_validate_shareopts' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+     <elf-symbol name='snapshot_namecheck' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+     <elf-symbol name='spl_pagesize' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+-    <elf-symbol name='strlcat' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+-    <elf-symbol name='strlcpy' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+     <elf-symbol name='tpool_abandon' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+     <elf-symbol name='tpool_create' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+     <elf-symbol name='tpool_destroy' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+--- zfs-linux-2.2.0~rc3.orig/lib/libzfs_core/libzfs_core.abi
++++ zfs-linux-2.2.0~rc3/lib/libzfs_core/libzfs_core.abi
+@@ -213,8 +213,6 @@
+     <elf-symbol name='mkdirp' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+     <elf-symbol name='print_timestamp' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+     <elf-symbol name='spl_pagesize' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+-    <elf-symbol name='strlcat' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+-    <elf-symbol name='strlcpy' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+   </elf-function-symbols>
+   <abi-instr address-size='64' path='lib/libspl/assert.c' language='LANG_C99'>
+     <class-decl name='__va_list_tag' size-in-bits='192' is-struct='yes' visibility='default' id='d5027220'>
diff -pruN 2.3.5-1/debian/rules 2.3.5-1ubuntu1/debian/rules
--- 2.3.5-1/debian/rules	2025-11-17 08:28:58.000000000 +0000
+++ 2.3.5-1ubuntu1/debian/rules	2025-11-19 20:21:43.000000000 +0000
@@ -81,7 +81,7 @@ override_dh_auto_build:
 override_dh_auto_test:
 ifeq (amd64,$(DEB_HOST_ARCH))
 	# Upstream provides an ABI guarantee that we validate here
-	-$(MAKE) checkabi
+	$(MAKE) checkabi
 endif
 
 	# The dh_auto_test rule is disabled because
diff -pruN 2.3.5-1/debian/tests/control 2.3.5-1ubuntu1/debian/tests/control
--- 2.3.5-1/debian/tests/control	2025-03-03 06:31:51.000000000 +0000
+++ 2.3.5-1ubuntu1/debian/tests/control	2025-11-19 20:29:48.000000000 +0000
@@ -1,46 +1,8 @@
-Tests: kernel-smoke-test, kernel-ztest
+Tests: kernel-smoke-test
 Architecture: !i386
-Restrictions: needs-root, allow-stderr, isolation-machine
+Restrictions: Restrictions: needs-root, allow-stderr, isolation-machine, skippable
 Depends: zfs-dkms [ amd64 arm64 ppc64el s390x ],
          zfs-initramfs [ amd64 arm64 ppc64el s390x ],
          zfs-test [ amd64 arm64 ppc64el s390x ],
          zfs-zed [ amd64 arm64 ppc64el s390x ],
-         zfsutils-linux [ amd64 arm64 ppc64el s390x ],
-         linux-headers-amd64 [amd64],
-         linux-headers-arm64 [arm64],
-         linux-headers-armmp [armhf],
-         linux-headers-rpi [armel],
-         linux-headers-powerpc64le [ppc64el],
-         linux-headers-s390x [s390x],
-         linux-headers-riscv64 [riscv64],
-         @recommends@
-
-Tests: zfs-test-suite-1, zfs-test-suite-2, zfs-test-suite-3, zfs-test-suite-4
-Architecture: !i386
-Restrictions: needs-root, allow-stderr, isolation-machine, breaks-testbed, flaky-and-slow
-Depends: zfs-dkms [ amd64 arm64 ppc64el s390x ],
-         zfs-initramfs [ amd64 arm64 ppc64el s390x ],
-         zfs-test [ amd64 arm64 ppc64el s390x ],
-         zfs-zed [ amd64 arm64 ppc64el s390x ],
-         zfsutils-linux [ amd64 arm64 ppc64el s390x ],
-         linux-headers-amd64 [amd64],
-         linux-headers-arm64 [arm64],
-         linux-headers-armmp [armhf],
-         linux-headers-rpi [armel],
-         linux-headers-powerpc64le [ppc64el],
-         linux-headers-s390x [s390x],
-         linux-headers-riscv64 [riscv64],
-         @recommends@
-
-Tests: binary-debs-modules, binary-debs-modules-udeb
-Architecture: !i386
-Restrictions: needs-root, allow-stderr
-Depends: fakeroot,
-         linux-headers-amd64 [amd64],
-         linux-headers-arm64 [arm64],
-         linux-headers-armmp [armhf],
-         linux-headers-rpi [armel],
-         linux-headers-powerpc64le [ppc64el],
-         linux-headers-s390x [s390x],
-         linux-headers-riscv64 [riscv64],
-         @builddeps@
+         zfsutils-linux [ amd64 arm64 ppc64el s390x ]
diff -pruN 2.3.5-1/debian/tests/kernel-smoke-test-pool-draid 2.3.5-1ubuntu1/debian/tests/kernel-smoke-test-pool-draid
--- 2.3.5-1/debian/tests/kernel-smoke-test-pool-draid	2025-03-03 06:31:51.000000000 +0000
+++ 2.3.5-1ubuntu1/debian/tests/kernel-smoke-test-pool-draid	2025-11-19 20:30:02.000000000 +0000
@@ -2,6 +2,11 @@
 
 set -e
 
+if [ "$(uname -i)" = "s390x" ]; then
+       echo "skip draid tests that are expected to fail on s390x arch - LP: #2097378"
+       exit 0
+fi
+
 ./debian/tests/kernel-smoke-test-pool "draid1"
 ./debian/tests/kernel-smoke-test-pool "draid2"
 ./debian/tests/kernel-smoke-test-pool "draid3"
