diff options
Diffstat (limited to 'etc/systemd/system-generators/zfs-mount-generator.in')
| -rwxr-xr-x | etc/systemd/system-generators/zfs-mount-generator.in | 450 |
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 |
