264 lines
7.2 KiB
Bash
264 lines
7.2 KiB
Bash
#!/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="/usr/local/etc/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}"
|
|
|
|
# Minimal pre-requisites to mount a ZFS dataset
|
|
wants="zfs-import.target"
|
|
|
|
# Handle encryption
|
|
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="/usr/local/sbin/zfs load-key '${dataset}'"
|
|
elif [ "${p_keyloc}" = "prompt" ] ; then
|
|
keyloadcmd="/bin/sh -c 'set -eu;"\
|
|
"keystatus=\"\$\$(/usr/local/sbin/zfs get -H -o value keystatus \"${dataset}\")\";"\
|
|
"[ \"\$\$keystatus\" = \"unavailable\" ] || exit 0;"\
|
|
"count=0;"\
|
|
"while [ \$\$count -lt 3 ];do"\
|
|
" systemd-ask-password --id=\"zfs:${dataset}\""\
|
|
" \"Enter passphrase for ${dataset}:\"|"\
|
|
" /usr/local/sbin/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
|
|
|
|
# Generate the key-load .service unit
|
|
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=/usr/local/sbin/zfs unload-key '${dataset}'
|
|
EOF
|
|
fi
|
|
# Update the dependencies for the mount file to require the
|
|
# key-loading unit.
|
|
wants="${wants} ${keyloadunit}"
|
|
fi
|
|
|
|
# Prepare the .mount unit
|
|
|
|
# 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" marked 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
|
|
|
|
# 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
|
|
|
|
# Create the .mount unit file.
|
|
# 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
|