From ecbbdac799e0fd33f9d8b5fd6315008e3b4c9a50 Mon Sep 17 00:00:00 2001 From: InsanePrawn Date: Wed, 12 Feb 2020 18:01:15 +0100 Subject: Systemd mount generator: Generate noauto units; add control properties This commit refactors the systemd mount generators and makes the following major changes: - The generator now generates units for datasets marked canmount=noauto, too. These units are NOT WantedBy local-fs.target. If there are multiple noauto datasets for a path, no noauto unit will be created. Datasets with canmount=on are prioritized. - Introduces handling of new user properties which are now included in the zfs-list.cache files: - org.openzfs.systemd:requires: List of units to require for this mount unit - org.openzfs.systemd:requires-mounts-for: List of mounts to require by this mount unit - org.openzfs.systemd:before: List of units to order after this mount unit - org.openzfs.systemd:after: List of units to order before this mount unit - org.openzfs.systemd:wanted-by: List of units to add a Wants dependency on this mount unit to - org.openzfs.systemd:required-by: List of units to add a Requires dependency on this mount unit to - org.openzfs.systemd:nofail: Toggles between a wants and a requires dependency. - org.openzfs.systemd:ignore: Do not generate a mount unit for this dataset. Consult the updated man page for detailed documentation. - Restructures and extends the zfs-mount-generator(8) man page with the above properties, information on unit ordering and a license header. Reviewed-by: Richard Laager Reviewed-by: Antonio Russo Reviewed-by: Brian Behlendorf Signed-off-by: InsanePrawn Closes #9649 --- .../system-generators/zfs-mount-generator.in | 217 ++++++++++++++++++--- 1 file changed, 191 insertions(+), 26 deletions(-) (limited to 'etc/systemd/system-generators') diff --git a/etc/systemd/system-generators/zfs-mount-generator.in b/etc/systemd/system-generators/zfs-mount-generator.in index 411b3f955..bb735112d 100755 --- a/etc/systemd/system-generators/zfs-mount-generator.in +++ b/etc/systemd/system-generators/zfs-mount-generator.in @@ -2,6 +2,7 @@ # zfs-mount-generator - generates systemd mount units for zfs # Copyright (c) 2017 Antonio Russo +# Copyright (c) 2020 InsanePrawn # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -33,6 +34,35 @@ do_fail() { 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" @@ -42,11 +72,6 @@ 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 @@ -74,18 +99,58 @@ process_line() { 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 - pathdep="" + keymountdep="" if [ "${p_keyloc%%://*}" = "file" ] ; then - pathdep="RequiresMountsFor='${p_keyloc#file://}'" + if [ -n "${requiredmounts}" ] ; then + keymountdep="${requiredmounts} '${p_keyloc#file://}'" + else + keymountdep="RequiresMountsFor='${p_keyloc#file://}'" + fi keyloadcmd="@sbindir@/zfs load-key '${dataset}'" elif [ "${p_keyloc}" = "prompt" ] ; then keyloadcmd="\ @@ -121,8 +186,10 @@ SourcePath=${cachefile} Documentation=man:zfs-mount-generator(8) DefaultDependencies=no Wants=${wants} -After=${wants} -${pathdep} +After=${after} +Before=${before} +${requires} +${keymountdep} [Service] Type=oneshot @@ -130,23 +197,35 @@ RemainAfterExit=yes ExecStart=${keyloadcmd} ExecStop=@sbindir@/zfs unload-key '${dataset}'" > "${dest_norm}/${keyloadunit}" fi - # Update the dependencies for the mount file to require the + # 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 - # Don't let a noauto marked mountpoint block an "auto" marked mountpoint - return + noauto="on" elif [ "${p_canmount}" = "on" ] ; then : # This is OK else - do_fail "invalid canmount" + do_fail "invalid canmount for ${dataset}" fi # Check for legacy and blank mountpoints. @@ -155,7 +234,7 @@ ExecStop=@sbindir@/zfs unload-key '${dataset}'" > "${dest_norm}/${keyloadunit} elif [ "${p_mountpoint}" = "none" ] ; then return elif [ "${p_mountpoint%"${p_mountpoint#?}"}" != "/" ] ; then - do_fail "invalid mountpoint $*" + do_fail "invalid mountpoint for ${dataset}" fi # Escape the mountpoint per systemd policy. @@ -233,15 +312,91 @@ ExecStop=@sbindir@/zfs unload-key '${dataset}'" > "${dest_norm}/${keyloadunit} "${dataset}" >/dev/kmsg fi - # If the mountpoint has already been created, give it precedence. + 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 - printf 'zfs-mount-generator: %s already exists\n' "${mountfile}" \ - >/dev/kmsg + 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. - # By ordering before zfs-mount.service, we avoid race conditions. # # (Do not use `< "${dest_norm}/${keyloadunit} [Unit] SourcePath=${cachefile} Documentation=man:zfs-mount-generator(8) -Before=local-fs.target zfs-mount.service -After=${wants} + +Before=${before} +After=${after} Wants=${wants} +${requires} +${requiredmounts} [Mount] Where=${p_mountpoint} @@ -261,13 +419,20 @@ What=${dataset} Type=zfs Options=defaults${opts},zfsutil" > "${dest_norm}/${mountfile}" - # Finally, create the appropriate dependency - ln -s "../${mountfile}" "${req_dir}" + # Finally, create the appropriate dependencies + create_dependencies "${mountfile}" "wants" "$wantedby" + create_dependencies "${mountfile}" "requires" "$requiredby" + } -# Feed each line into process_line for cachefile in "${FSLIST}/"* ; do - while read -r fs ; do - process_line "${fs}" - done < "${cachefile}" + # 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 -- cgit v1.2.3