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

