#!/bin/sh # zfs-mount-generator - generates systemd mount units for zfs # Copyright (c) 2017 Antonio Russo # # 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 # 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