mirror of
https://git.proxmox.com/git/mirror_zfs.git
synced 2024-12-28 20:09:35 +03:00
ff2f960b24
Previously the generated keyload units for encryption roots with keylocation=file://* didn't contain the code to detect if the key was already loaded and would be marked failed in such situations. Move the code to check whether the key is already loaded from keylocation=prompt handling to general key loading code. Reviewed-by: Richard Laager <rlaager@wiktel.com> Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov> Signed-off-by: InsanePrawn <insane.prawny@gmail.com> Closes #10103
443 lines
13 KiB
Bash
Executable File
443 lines
13 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>
|
|
# 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=""
|
|
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}'"
|
|
|
|
|
|
|
|
# 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}
|
|
Before=${before}
|
|
${requires}
|
|
${keymountdep}
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
RemainAfterExit=yes
|
|
ExecStart=${keyloadcmd}
|
|
ExecStop=@sbindir@/zfs unload-key '${dataset}'" > "${dest_norm}/${keyloadunit}"
|
|
fi
|
|
# Update the dependencies for the mount file to want the
|
|
# key-loading unit.
|
|
wants="${wants} ${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}
|
|
${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
|