mirror_zfs/etc/systemd/system-generators/zfs-mount-generator.in
Antonio Russo f88d069cbb systemd encryption key support
Modify zfs-mount-generator to produce a dependency on new
zfs-import-key-*.service units, dynamically created at boot to call
zfs load-key for the encryption root, before attempting to mount any
encrypted datasets.

These units are created by zfs-mount-generator, and RequiresMountsFor on
the keyfile, if present, or call systemd-ask-password if a passphrase is
requested.

This patch includes suggestions from @Fabian-Gruenbichler, @ryanjaeb and
@rlaager, as well an adaptation of @rlaager's script to retry on
incorrect password entry.

Reviewed-by: Richard Laager <rlaager@wiktel.com>
Reviewed-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Signed-off-by: Antonio Russo <antonio.e.russo@gmail.com>
Closes #8750
Closes #8848
2019-07-15 16:31:47 -07:00

256 lines
7.0 KiB
Bash
Executable File

#!/bin/sh
# zfs-mount-generator - generates systemd mount units for zfs
# Copyright (c) 2017 Antonio Russo <antonio.e.russo@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
}
# 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
# For ZFSs marked "auto", a dependency is created for local-fs.target. To
# avoid regressions, this dependency is reduced to "wants" rather than
# "requires". **THIS MAY CHANGE**
req_dir="${dest_norm}/local-fs.target.wants/"
mkdir -p "${req_dir}"
# 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
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}"
# Check for canmount=off .
if [ "${p_canmount}" = "off" ] ; then
return
elif [ "${p_canmount}" = "noauto" ] ; then
# Don't let a noauto marked mountpoint block an "auto" market mountpoint
return
elif [ "${p_canmount}" = "on" ] ; then
: # This is OK
else
do_fail "invalid canmount"
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 $*"
fi
# Escape the mountpoint per systemd policy.
mountfile="$(systemd-escape "${p_mountpoint#?}").mount"
# 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
# Minimal pre-requisites to mount a ZFS dataset
wants="zfs-import.target"
if [ -n "${p_encroot}" ] &&
[ "${p_encroot}" != "-" ] ; then
keyloadunit="zfs-load-key-$(systemd-escape "${p_encroot}").service"
if [ "${p_encroot}" = "${dataset}" ] ; then
pathdep=""
if [ "${p_keyloc%%://*}" = "file" ] ; then
pathdep="RequiresMountsFor='${p_keyloc#file://}'"
keyloadcmd="@sbindir@/zfs load-key '${dataset}'"
elif [ "${p_keyloc}" = "prompt" ] ; then
keyloadcmd="sh -c 'set -eu;"\
"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
cat > "${dest_norm}/${keyloadunit}" << EOF
# 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=${wants}
${pathdep}
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=${keyloadcmd}
ExecStop=@sbindir@/zfs unload-key '${dataset}'
EOF
fi
# Update the dependencies for the mount file to require the
# key-loading unit.
wants="${wants},${keyloadunit}"
fi
# If the mountpoint has already been created, give it precedence.
if [ -e "${dest_norm}/${mountfile}" ] ; then
printf 'zfs-mount-generator: %s already exists\n' "${mountfile}" \
>/dev/kmsg
return
fi
# By ordering before zfs-mount.service, we avoid race conditions.
cat > "${dest_norm}/${mountfile}" << EOF
# Automatically generated by zfs-mount-generator
[Unit]
SourcePath=${cachefile}
Documentation=man:zfs-mount-generator(8)
Before=local-fs.target zfs-mount.service
After=${wants}
Wants=${wants}
[Mount]
Where=${p_mountpoint}
What=${dataset}
Type=zfs
Options=defaults${opts},zfsutil
EOF
# Finally, create the appropriate dependency
ln -s "../${mountfile}" "${req_dir}"
}
# Feed each line into process_line
for cachefile in "${FSLIST}/"* ; do
while read -r fs ; do
process_line "${fs}"
done < "${cachefile}"
done