From 96e20d3f61225165d806cdd3f65c802e00a3be8f Mon Sep 17 00:00:00 2001
From: Heitor Alves de Siqueira <halves@canonical.com>
Date: Mon, 20 Jan 2025 11:37:00 -0300
Subject: [PATCH] Revert "etc/systemd/zfs-mount-generator: rewrite in C"

This reverts commit 0382362ce06a5514a97bbbf11dfe55e7e408898a.

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

Last-Update: 2025-01-20
Signed-off-by: Heitor Alves de Siqueira <halves@canonical.com>
---
 etc/systemd/system-generators/Makefile.am     |   6 +
 .../system-generators/zfs-mount-generator.in  | 474 ++++++++++++++++++
 man/man8/zfs-mount-generator.8.in             | 331 +++++++-----
 3 files changed, 673 insertions(+), 138 deletions(-)
 create mode 100644 etc/systemd/system-generators/Makefile.am
 create mode 100755 etc/systemd/system-generators/zfs-mount-generator.in

Index: zfs-linux/etc/systemd/system-generators/Makefile.am
===================================================================
--- /dev/null
+++ zfs-linux/etc/systemd/system-generators/Makefile.am
@@ -0,0 +1,6 @@
+include $(top_srcdir)/config/Substfiles.am
+
+systemdgenerator_SCRIPTS = \
+	zfs-mount-generator
+
+SUBSTFILES += $(systemdgenerator_SCRIPTS)
Index: zfs-linux/etc/systemd/system-generators/zfs-mount-generator.in
===================================================================
--- /dev/null
+++ zfs-linux/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
Index: zfs-linux/man/man8/zfs-mount-generator.8.in
===================================================================
--- zfs-linux.orig/man/man8/zfs-mount-generator.8.in
+++ zfs-linux/man/man8/zfs-mount-generator.8.in
@@ -21,98 +21,31 @@
 .\" LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 .\" OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 .\" WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-.\"
-.Dd November 30, 2021
-.Dt ZFS-MOUNT-GENERATOR 8
-.Os
-.
-.Sh NAME
-.Nm zfs-mount-generator
-.Nd generate systemd mount units for ZFS filesystems
-.Sh SYNOPSIS
-.Pa @systemdgeneratordir@/zfs-mount-generator
-.
-.Sh DESCRIPTION
-.Nm
-is a
-.Xr systemd.generator 7
-that generates native
-.Xr systemd.mount 5
-units for configured ZFS datasets.
-.
-.Ss Properties
-.Bl -tag -compact -width "org.openzfs.systemd:required-by=unit[ unit]…"
-.It Sy mountpoint Ns =
-.No Skipped if Sy legacy No or Sy none .
-.
-.It Sy canmount Ns =
-.No Skipped if Sy off .
-.No Skipped if only Sy noauto
-datasets exist for a given mountpoint and there's more than one.
-.No Datasets with Sy yes No take precedence over ones with Sy noauto No for the same mountpoint .
-.No Sets logical Em noauto No flag if Sy noauto .
-Encryption roots always generate
-.Sy zfs-load-key@ Ns Ar root Ns Sy .service ,
-even if
-.Sy off .
-.
-.It Sy atime Ns = , Sy relatime Ns = , Sy devices Ns = , Sy exec Ns = , Sy readonly Ns = , Sy setuid Ns = , Sy nbmand Ns =
-Used to generate mount options equivalent to
-.Nm zfs Cm mount .
-.
-.It Sy encroot Ns = , Sy keylocation Ns =
-If the dataset is an encryption root, its mount unit will bind to
-.Sy zfs-load-key@ Ns Ar root Ns Sy .service ,
-with additional dependencies as follows:
-.Bl -tag -compact -offset Ds -width "keylocation=https://URL (et al.)"
-.It Sy keylocation Ns = Ns Sy prompt
-None, uses
-.Xr systemd-ask-password 1
-.It Sy keylocation Ns = Ns Sy https:// Ns Ar URL Pq et al.\&
-.Sy Wants Ns = , Sy After Ns = : Pa network-online.target
-.It Sy keylocation Ns = Ns Sy file:// Ns < Ns Ar path Ns >
-.Sy RequiresMountsFor Ns = Ns Ar path
-.El
-.
-The service also uses the same
-.Sy Wants Ns = ,
-.Sy After Ns = ,
-.Sy Requires Ns = , No and
-.Sy RequiresMountsFor Ns = ,
-as the mount unit.
-.
-.It Sy org.openzfs.systemd:requires Ns = Ns Pa path Ns Oo " " Ns Pa path Oc Ns …
-.No Sets Sy Requires Ns = for the mount- and key-loading unit.
-.
-.It Sy org.openzfs.systemd:requires-mounts-for Ns = Ns Pa path Ns Oo " " Ns Pa path Oc Ns …
-.No Sets Sy RequiresMountsFor Ns = for the mount- and key-loading unit.
-.
-.It Sy org.openzfs.systemd:before Ns = Ns Pa unit Ns Oo " " Ns Pa unit Oc Ns …
-.No Sets Sy Before Ns = for the mount unit.
-.
-.It Sy org.openzfs.systemd:after Ns = Ns Pa unit Ns Oo " " Ns Pa unit Oc Ns …
-.No Sets Sy After Ns = for the mount unit.
-.
-.It Sy org.openzfs.systemd:wanted-by Ns = Ns Pa unit Ns Oo " " Ns Pa unit Oc Ns …
-.No Sets logical Em noauto No flag (see below) .
-.No If not Sy none , No sets Sy WantedBy Ns = for the mount unit.
-.It Sy org.openzfs.systemd:required-by Ns = Ns Pa unit Ns Oo " " Ns Pa unit Oc Ns …
-.No Sets logical Em noauto No flag (see below) .
-.No If not Sy none , No sets Sy RequiredBy Ns = for the mount unit.
-.
-.It Sy org.openzfs.systemd:nofail Ns = Ns (unset) Ns | Ns Sy on Ns | Ns Sy off
-Waxes or wanes strength of default reverse dependencies of the mount unit, see
-below.
-.
-.It Sy org.openzfs.systemd:ignore Ns = Ns Sy on Ns | Ns Sy off
-.No Skip if Sy on .
-.No Defaults to Sy off .
-.El
-.
-.Ss Unit Ordering And Dependencies
-Additionally, unless the pool the dataset resides on
-is imported at generation time, both units gain
-.Sy Wants Ns = Ns Pa zfs-import.target
+
+.TH ZFS-MOUNT-GENERATOR 8 "Aug 24, 2020" OpenZFS
+
+.SH "NAME"
+zfs\-mount\-generator \- generates systemd mount units for ZFS
+.SH SYNOPSIS
+.B @systemdgeneratordir@/zfs\-mount\-generator
+.sp
+.SH DESCRIPTION
+zfs\-mount\-generator implements the \fBGenerators Specification\fP
+of
+.BR systemd (1),
+and is called during early boot to generate
+.BR systemd.mount (5)
+units for automatically mounted datasets. Mount ordering and dependencies
+are created for all tracked pools (see below).
+
+.SS ENCRYPTION KEYS
+If the dataset is an encryption root, a service that loads the associated key (either from file or through a
+.BR systemd\-ask\-password (1)
+prompt) will be created. This service
+. BR RequiresMountsFor
+the path of the key (if file-based) and also copies the mount unit's
+.BR After ,
+.BR Before
 and
 .Sy After Ns = Ns Pa zfs-import.target .
 .Pp
@@ -132,51 +65,173 @@ of strength
 .
 .Ss Cache File
 Because ZFS pools may not be available very early in the boot process,
-information on ZFS mountpoints must be stored separately.
-The output of
-.Dl Nm zfs Cm list Fl Ho Ar name , Ns Aq every property above in order
-for datasets that should be mounted by systemd should be kept at
-.Pa @sysconfdir@/zfs/zfs-list.cache/ Ns Ar poolname ,
-and, if writeable, will be kept synchronized for the entire pool by the
-.Pa history_event-zfs-list-cacher.sh
-ZEDLET, if enabled
-.Pq see Xr zed 8 .
-.
-.Sh ENVIRONMENT
-If the
-.Sy ZFS_DEBUG
-environment variable is nonzero
-.Pq or unset and Pa /proc/cmdline No contains Qq Sy debug ,
-print summary accounting information at the end.
-.
-.Sh EXAMPLES
+information on ZFS mountpoints must be stored separately. The output of the command
+.PP
+.RS 4
+zfs list -H -o name,mountpoint,canmount,atime,relatime,devices,exec,readonly,setuid,nbmand,encroot,keylocation,org.openzfs.systemd:requires,org.openzfs.systemd:requires-mounts-for,org.openzfs.systemd:before,org.openzfs.systemd:after,org.openzfs.systemd:wanted-by,org.openzfs.systemd:required-by,org.openzfs.systemd:nofail,org.openzfs.systemd:ignore
+
+.RE
+.PP
+for datasets that should be mounted by systemd, should be kept
+separate from the pool, at
+.PP
+.RS 4
+.RI @sysconfdir@/zfs/zfs-list.cache/ POOLNAME
+.
+.RE
+.PP
+The cache file, if writeable, will be kept synchronized with the pool
+state by the ZEDLET
+.PP
+.RS 4
+history_event-zfs-list-cacher.sh .
+.RE
+.PP
+.sp
+.SS PROPERTIES
+The behavior of the generator script can be influenced by the following dataset properties:
+.sp
+.TP 4
+.BR canmount = on | off | noauto
+If a dataset has
+.BR mountpoint
+set and
+.BR canmount
+is not
+.BR off ,
+a mount unit will be generated.
+Additionally, if
+.BR canmount
+is
+.BR on ,
+.BR local-fs.target
+will gain a dependency on the mount unit.
+
+This behavior is equal to the
+.BR auto
+and
+.BR noauto
+legacy mount options, see
+.BR systemd.mount (5).
+
+Encryption roots always generate a key-load service, even for
+.BR canmount=off .
+.TP 4
+.BR org.openzfs.systemd:requires\-mounts\-for = \fIpath\fR...
+Space\-separated list of mountpoints to require to be mounted for this mount unit
+.TP 4
+.BR org.openzfs.systemd:before = \fIunit\fR...
+The mount unit and associated key\-load service will be ordered before this space\-separated list of units.
+.TP 4
+.BR org.openzfs.systemd:after = \fIunit\fR...
+The mount unit and associated key\-load service will be ordered after this space\-separated list of units.
+.TP 4
+.BR org.openzfs.systemd:wanted\-by = \fIunit\fR...
+Space-separated list of units that will gain a
+.BR Wants
+dependency on this mount unit.
+Setting this property implies
+.BR noauto .
+.TP 4
+.BR org.openzfs.systemd:required\-by = \fIunit\fR...
+Space-separated list of units that will gain a
+.BR Requires
+dependency on this mount unit.
+Setting this property implies
+.BR noauto .
+.TP 4
+.BR org.openzfs.systemd:nofail = unset | on | off
+Toggles between a
+.BR Wants
+and
+.BR Requires
+type of dependency between the mount unit and
+.BR local-fs.target ,
+if
+.BR noauto
+isn't set or implied.
+
+.BR on :
+Mount will be
+.BR WantedBy
+local-fs.target
+
+.BR off :
+Mount will be
+.BR Before
+and
+.BR RequiredBy
+local-fs.target
+
+.BR unset :
+Mount will be
+.BR Before
+and
+.BR WantedBy
+local-fs.target
+.TP 4
+.BR org.openzfs.systemd:ignore = on | off
+If set to
+.BR on ,
+do not generate a mount unit for this dataset.
+
+See also
+.BR systemd.mount (5)
+
+.PP
+.SH EXAMPLE
 To begin, enable tracking for the pool:
-.Dl # Nm touch Pa @sysconfdir@/zfs/zfs-list.cache/ Ns Ar poolname
-Then enable the tracking ZEDLET:
-.Dl # Nm ln Fl s Pa @zfsexecdir@/zed.d/history_event-zfs-list-cacher.sh @sysconfdir@/zfs/zed.d
-.Dl # Nm systemctl Cm enable Pa zfs-zed.service
-.Dl # Nm systemctl Cm restart Pa zfs-zed.service
-.Pp
-If no history event is in the queue,
-inject one to ensure the ZEDLET runs to refresh the cache file
-by setting a monitored property somewhere on the pool:
-.Dl # Nm zfs Cm set Sy relatime Ns = Ns Sy off Ar poolname/dset
-.Dl # Nm zfs Cm inherit Sy relatime Ar poolname/dset
-.Pp
-To test the generator output:
-.Dl $ Nm mkdir Pa /tmp/zfs-mount-generator
-.Dl $ Nm @systemdgeneratordir@/zfs-mount-generator Pa /tmp/zfs-mount-generator
-.
-If the generated units are satisfactory, instruct
-.Nm systemd
-to re-run all generators:
-.Dl # Nm systemctl daemon-reload
-.
-.Sh SEE ALSO
-.Xr systemd.mount 5 ,
-.Xr systemd.target 5 ,
-.Xr zfs 5 ,
-.Xr systemd.generator 7 ,
-.Xr systemd.special 7 ,
-.Xr zed 8 ,
-.Xr zpool-events 8
+.PP
+.RS 4
+touch
+.RI @sysconfdir@/zfs/zfs-list.cache/ POOLNAME
+.RE
+.PP
+Then, enable the tracking ZEDLET:
+.PP
+.RS 4
+ln -s "@zfsexecdir@/zed.d/history_event-zfs-list-cacher.sh" "@sysconfdir@/zfs/zed.d"
+
+systemctl enable zfs-zed.service
+
+systemctl restart zfs-zed.service
+.RE
+.PP
+Force the running of the ZEDLET by setting a monitored property, e.g.
+.BR canmount ,
+for at least one dataset in the pool:
+.PP
+.RS 4
+zfs set canmount=on
+.I DATASET
+.RE
+.PP
+This forces an update to the stale cache file.
+
+To test the generator output, run
+.PP
+.RS 4
+@systemdgeneratordir@/zfs-mount-generator /tmp/zfs-mount-generator . .
+.RE
+.PP
+This will generate units and dependencies in
+.I /tmp/zfs-mount-generator
+for you to inspect them. The second and third argument are ignored.
+
+If you're satisfied with the generated units, instruct systemd to re-run all generators:
+.PP
+.RS 4
+systemctl daemon-reload
+.RE
+.PP
+
+.sp
+.SH SEE ALSO
+.BR zfs (5)
+.BR zfs-events (5)
+.BR zed (8)
+.BR zpool (5)
+.BR systemd (1)
+.BR systemd.target (5)
+.BR systemd.special (7)
+.BR systemd.mount (7)
