aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorнаб <[email protected]>2021-04-19 20:56:10 +0200
committerBrian Behlendorf <[email protected]>2021-05-27 08:45:51 -0700
commit5a1fb060fd6ccd8bf22a49028f3eb677d5978a94 (patch)
tree8e7d4cb104ff692896267c723036a45a6a134ce8
parent0bb736ce0b59d6851dff0914a98a4ff90bba15b3 (diff)
etc/systemd/zfs-mount-generator: rewrite in C
A plain rewrite of the shell version, and generates identical units, save for replacing some empty lines with nothing, having fewer meaningless spaces in After=s and different spacing in the lock scripts, for a clean git diff -w This is a gain of anywhere from 0m0.336s vs 0m0.022s (15.27x) to 0m0.202s vs 0m0.006s (33.67x), depending on the hardware, a.k.a. from "absolutely unusable" to "perfectly fine" This also properly deals with canmount=noauto units across multiple pools See PR for detailed timings (of an early version) and diffs Reviewed-by: Antonio Russo <[email protected]> Reviewed-by: Richard Laager <[email protected]> Reviewed-by: InsanePrawn <[email protected]> Reviewed-by: Brian Behlendorf <[email protected]> Signed-off-by: Ahelenia Ziemiańska <[email protected]> Issue #11915 Closes #11917
-rw-r--r--Makefile.am2
-rw-r--r--etc/systemd/system-generators/Makefile.am14
-rw-r--r--etc/systemd/system-generators/zfs-mount-generator.c1089
-rwxr-xr-xetc/systemd/system-generators/zfs-mount-generator.in474
-rw-r--r--man/man8/zfs-mount-generator.8.in31
5 files changed, 1119 insertions, 491 deletions
diff --git a/Makefile.am b/Makefile.am
index efe99142e..ca9b9053c 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -6,7 +6,7 @@ SUBDIRS += rpm
endif
if CONFIG_USER
-SUBDIRS += etc man scripts lib tests cmd contrib
+SUBDIRS += man scripts lib tests cmd etc contrib
if BUILD_LINUX
SUBDIRS += udev
endif
diff --git a/etc/systemd/system-generators/Makefile.am b/etc/systemd/system-generators/Makefile.am
index fee88dad8..e5920bf39 100644
--- a/etc/systemd/system-generators/Makefile.am
+++ b/etc/systemd/system-generators/Makefile.am
@@ -1,6 +1,14 @@
-include $(top_srcdir)/config/Substfiles.am
+include $(top_srcdir)/config/Rules.am
-systemdgenerator_SCRIPTS = \
+systemdgenerator_PROGRAMS = \
zfs-mount-generator
-SUBSTFILES += $(systemdgenerator_SCRIPTS)
+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
diff --git a/etc/systemd/system-generators/zfs-mount-generator.c b/etc/systemd/system-generators/zfs-mount-generator.c
new file mode 100644
index 000000000..8deeed9df
--- /dev/null
+++ b/etc/systemd/system-generators/zfs-mount-generator.c
@@ -0,0 +1,1089 @@
+/*
+ * Copyright (c) 2017 Antonio Russo <[email protected]>
+ * Copyright (c) 2020 InsanePrawn <[email protected]>
+ *
+ * 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
deleted file mode 100755
index c276fbbce..000000000
--- a/etc/systemd/system-generators/zfs-mount-generator.in
+++ /dev/null
@@ -1,474 +0,0 @@
-#!/bin/sh
-
-# zfs-mount-generator - generates systemd mount units for zfs
-# Copyright (c) 2017 Antonio Russo <[email protected]>
-# Copyright (c) 2020 InsanePrawn <[email protected]>
-#
-# 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
diff --git a/man/man8/zfs-mount-generator.8.in b/man/man8/zfs-mount-generator.8.in
index 96fee4291..6484c0f94 100644
--- a/man/man8/zfs-mount-generator.8.in
+++ b/man/man8/zfs-mount-generator.8.in
@@ -22,7 +22,7 @@
.\" OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
.\" WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-.TH ZFS-MOUNT-GENERATOR 8 "Aug 24, 2020" OpenZFS
+.TH ZFS-MOUNT-GENERATOR 8 "Apr 19, 2021" OpenZFS
.SH "NAME"
zfs\-mount\-generator \- generates systemd mount units for ZFS
@@ -83,19 +83,13 @@ zfs list -H -o name,mountpoint,canmount,atime,relatime,devices,exec,readonly,set
.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
+separate from the pool at
+.RI @sysconfdir@/zfs/zfs-list.cache/ POOLNAME .
.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
+state by the
+.I history_event-zfs-list-cacher.sh
+ZEDLET.
.PP
.sp
.SS PROPERTIES
@@ -189,6 +183,17 @@ See also
.BR systemd.mount (5)
.PP
+.SH ENVIRONMENT
+The
+.BR $ZFS_DEBUG
+environment variable, which can either be 0 (default),
+1 (print summary accounting information at the end),
+or at least 2 (print accounting information for each subprocess as it finishes).
+
+If not present, /proc/cmdline is additionally checked for
+.BR debug ,
+in which case the debug level is set to 2.
+
.SH EXAMPLE
To begin, enable tracking for the pool:
.PP
@@ -221,7 +226,7 @@ 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 . .
+@systemdgeneratordir@/zfs-mount-generator /tmp/zfs-mount-generator
.RE
.PP
This will generate units and dependencies in