summaryrefslogtreecommitdiff
path: root/etc/systemd/system-generators/zfs-mount-generator.in
diff options
context:
space:
mode:
Diffstat (limited to 'etc/systemd/system-generators/zfs-mount-generator.in')
-rwxr-xr-xetc/systemd/system-generators/zfs-mount-generator.in450
1 files changed, 450 insertions, 0 deletions
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 000000000000..fdef13cfa95a
--- /dev/null
+++ b/etc/systemd/system-generators/zfs-mount-generator.in
@@ -0,0 +1,450 @@
+#!/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
+
+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=' '
+ # protect against special characters
+ set -f
+ 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"
+ # protect against special characters
+ set -f
+ 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
+
+
+# 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')"
+ # protect against special characters in, e.g., mountpoints
+ set -f
+ # shellcheck disable=SC2086
+ set -- $1
+ dataset="${1}"
+ 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 [ -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
+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
+ # 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