diff --git a/cmd/zed/Makefile.am b/cmd/zed/Makefile.am index 37739696e..e9e38d8e5 100644 --- a/cmd/zed/Makefile.am +++ b/cmd/zed/Makefile.am @@ -4,7 +4,8 @@ DEFAULT_INCLUDES += \ -I$(top_srcdir)/include \ -I$(top_srcdir)/lib/libspl/include -EXTRA_DIST = zed.d/README +EXTRA_DIST = zed.d/README \ + zed.d/history_event-zfs-list-cacher.sh.in sbin_PROGRAMS = zed @@ -60,6 +61,7 @@ dist_zedexec_SCRIPTS = \ zed.d/all-syslog.sh \ zed.d/data-notify.sh \ zed.d/generic-notify.sh \ + zed.d/history_event-zfs-list-cacher.sh \ zed.d/resilver_finish-notify.sh \ zed.d/scrub_finish-notify.sh \ zed.d/statechange-led.sh \ @@ -69,6 +71,13 @@ dist_zedexec_SCRIPTS = \ zed.d/pool_import-led.sh \ zed.d/resilver_finish-start-scrub.sh +zed.d/history_event-zfs-list-cacher.sh: %: %.in + -$(SED) -e 's,@bindir\@,$(bindir),g' \ + -e 's,@runstatedir\@,$(runstatedir),g' \ + -e 's,@sbindir\@,$(sbindir),g' \ + -e 's,@sysconfdir\@,$(sysconfdir),g' \ + $< >'$@' + zedconfdefaults = \ all-syslog.sh \ data-notify.sh \ diff --git a/cmd/zed/zed.d/.gitignore b/cmd/zed/zed.d/.gitignore new file mode 100644 index 000000000..46a00945a --- /dev/null +++ b/cmd/zed/zed.d/.gitignore @@ -0,0 +1 @@ +history_event-zfs-list-cacher.sh diff --git a/cmd/zed/zed.d/history_event-zfs-list-cacher.sh.in b/cmd/zed/zed.d/history_event-zfs-list-cacher.sh.in new file mode 100755 index 000000000..348c8d67a --- /dev/null +++ b/cmd/zed/zed.d/history_event-zfs-list-cacher.sh.in @@ -0,0 +1,73 @@ +#!/bin/sh +# +# Track changes to enumerated pools for use in early-boot +set -ef + +FSLIST_DIR="@sysconfdir@/zfs/zfs-list.cache" +FSLIST_TMP="@runstatedir@/zfs-list.cache.new" +FSLIST="${FSLIST_DIR}/${ZEVENT_POOL}" + +# If the pool specific cache file is not writeable, abort +[ -w "${FSLIST}" ] || exit 0 + +[ -f "${ZED_ZEDLET_DIR}/zed.rc" ] && . "${ZED_ZEDLET_DIR}/zed.rc" +. "${ZED_ZEDLET_DIR}/zed-functions.sh" + +zed_exit_if_ignoring_this_event +zed_check_cmd "${ZFS}" sort diff grep + +# If we are acting on a snapshot, we have nothing to do +printf '%s' "${ZEVENT_HISTORY_DSNAME}" | grep '@' && exit 0 + +# We obtain a lock on zfs-list to avoid any simultaneous writes. +# If we run into trouble, log and drop the lock +abort_alter() { + zed_log_msg "Error updating zfs-list.cache!" + zed_unlock zfs-list +} + +finished() { + zed_unlock zfs-list + trap - EXIT + exit 0 +} + +case "${ZEVENT_HISTORY_INTERNAL_NAME}" in + create|"finish receiving"|import|destroy|rename) + ;; + + export) + zed_lock zfs-list + trap abort_alter EXIT + echo > "${FSLIST}" + finished + ;; + + set|inherit) + # Only act if the mountpoint or canmount setting is altered. + case "${ZEVENT_HISTORY_INTERNAL_STR}" in + canmount=*|mountpoint=*) ;; + *) exit 0 ;; + esac + ;; + + *) + # Ignore all other events. + exit 0 + ;; +esac + +zed_lock zfs-list +trap abort_alter EXIT + +"${ZFS}" list -H -tfilesystem -oname,mountpoint,canmount -r "${ZEVENT_POOL}" \ + >"${FSLIST_TMP}" + +# Sort the output so that it is stable +sort "${FSLIST_TMP}" -o "${FSLIST_TMP}" + +# Don't modify the file if it hasn't changed +diff -q "${FSLIST_TMP}" "${FSLIST}" || mv "${FSLIST_TMP}" "${FSLIST}" +rm -f "${FSLIST_TMP}" + +finished diff --git a/config/user-systemd.m4 b/config/user-systemd.m4 index de2a44f10..5d1f5618a 100644 --- a/config/user-systemd.m4 +++ b/config/user-systemd.m4 @@ -20,6 +20,11 @@ AC_DEFUN([ZFS_AC_CONFIG_USER_SYSTEMD], [ [install systemd module load files into dir [[/usr/lib/modules-load.d]]]), systemdmoduleloaddir=$withval,systemdmodulesloaddir=/usr/lib/modules-load.d) + AC_ARG_WITH(systemdgeneratordir, + AC_HELP_STRING([--with-systemdgeneratordir=DIR], + [install systemd generators in dir [[/usr/lib/systemd/system-generators]]]), + systemdgeneratordir=$withval,systemdgeneratordir=/usr/lib/systemd/system-generators) + AS_IF([test "x$enable_systemd" = xcheck], [ AS_IF([systemctl --version >/dev/null 2>&1], [enable_systemd=yes], @@ -32,7 +37,7 @@ AC_DEFUN([ZFS_AC_CONFIG_USER_SYSTEMD], [ AS_IF([test "x$enable_systemd" = xyes], [ ZFS_INIT_SYSTEMD=systemd ZFS_MODULE_LOAD=modules-load.d - DEFINE_SYSTEMD='--with systemd --define "_unitdir $(systemdunitdir)" --define "_presetdir $(systemdpresetdir)"' + DEFINE_SYSTEMD='--with systemd --define "_unitdir $(systemdunitdir)" --define "_presetdir $(systemdpresetdir)" --define "_generatordir $(systemdgeneratordir)"' modulesloaddir=$systemdmodulesloaddir ],[ DEFINE_SYSTEMD='--without systemd' @@ -43,5 +48,6 @@ AC_DEFUN([ZFS_AC_CONFIG_USER_SYSTEMD], [ AC_SUBST(DEFINE_SYSTEMD) AC_SUBST(systemdunitdir) AC_SUBST(systemdpresetdir) + AC_SUBST(systemdgeneratordir) AC_SUBST(modulesloaddir) ]) diff --git a/configure.ac b/configure.ac index 5a84ffcbf..0893af42e 100644 --- a/configure.ac +++ b/configure.ac @@ -67,6 +67,7 @@ AC_CONFIG_FILES([ etc/zfs/Makefile etc/systemd/Makefile etc/systemd/system/Makefile + etc/systemd/system-generators/Makefile etc/sudoers.d/Makefile etc/modules-load.d/Makefile man/Makefile diff --git a/etc/systemd/Makefile.am b/etc/systemd/Makefile.am index d4008c0dd..7b47b93fc 100644 --- a/etc/systemd/Makefile.am +++ b/etc/systemd/Makefile.am @@ -1 +1 @@ -SUBDIRS = system +SUBDIRS = system system-generators diff --git a/etc/systemd/system-generators/.gitignore b/etc/systemd/system-generators/.gitignore new file mode 100644 index 000000000..fc2ebc1a2 --- /dev/null +++ b/etc/systemd/system-generators/.gitignore @@ -0,0 +1 @@ +zfs-mount-generator diff --git a/etc/systemd/system-generators/Makefile.am b/etc/systemd/system-generators/Makefile.am new file mode 100644 index 000000000..c730982a5 --- /dev/null +++ b/etc/systemd/system-generators/Makefile.am @@ -0,0 +1,15 @@ +systemdgenerator_SCRIPTS = \ + zfs-mount-generator + +EXTRA_DIST = \ + $(top_srcdir)/etc/systemd/system-generators/zfs-mount-generator.in + +$(systemdgenerator_SCRIPTS): %: %.in + -$(SED) -e 's,@bindir\@,$(bindir),g' \ + -e 's,@runstatedir\@,$(runstatedir),g' \ + -e 's,@sbindir\@,$(sbindir),g' \ + -e 's,@sysconfdir\@,$(sysconfdir),g' \ + $< >'$@' + +distclean-local:: + -$(RM) $(systemdgenerator_SCRIPTS) diff --git a/etc/systemd/system-generators/zfs-mount-generator.in b/etc/systemd/system-generators/zfs-mount-generator.in new file mode 100755 index 000000000..e39a03036 --- /dev/null +++ b/etc/systemd/system-generators/zfs-mount-generator.in @@ -0,0 +1,115 @@ +#!/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 -ef + +FSLIST="@sysconfdir@/zfs/zfs-list.cache" + +[ -d "${FSLIST}" ] || exit 0 + +do_fail() { + printf 'zfs-mount-generator.sh: %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 -oname,mountpoint,canmount +# cached in $FSLIST, and each line is processed by the following function: + +process_line() { + + # Check for canmount=off . + if [ "${3}" = "off" ] ; then + return + elif [ "${3}" = "noauto" ] ; then + # Don't let a noauto marked mountpoint block an "auto" market mountpoint + return + elif [ "${3}" = "on" ] ; then + : # This is OK + else + do_fail "invalid canmount" + fi + + # Check for legacy and blank mountpoints. + if [ "${2}" = "legacy" ] ; then + return + elif [ "${2}" = "none" ] ; then + return + elif [ "${2%"${2#?}"}" != "/" ] ; then + do_fail "invalid mountpoint $*" + fi + + # Escape the mountpoint per systemd policy. + mountfile="$(systemd-escape "${2#?}").mount" + + # If the mountpoint has already been created, give it precedence. + if [ -e "${dest_norm}/${mountfile}" ] ; then + printf 'zfs-mount-generator.sh: %s.mount already exists\n' "${2}" \ + >/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=${FSLIST}/${cachefile} +Documentation=man:zfs-mount-generator(8) +Before=local-fs.target zfs-mount.service +After=zfs-import.target +Wants=zfs-import.target + +[Mount] +Where=${2} +What=${1} +Type=zfs +Options=zfsutil,auto +EOF + + # Finally, create the appropriate dependencies based on the ZFS properties. + [ "$3" = "on" ] & ln -s "../${mountfile}" "${req_dir}" +} + +# Feed each line into process_line +for cachefile in $(ls "${FSLIST}") ; do + while read -r fs ; do + process_line $fs + done < "${FSLIST}/${cachefile}" +done diff --git a/man/man8/Makefile.am b/man/man8/Makefile.am index 5c60bdfa7..b6408ddf2 100644 --- a/man/man8/Makefile.am +++ b/man/man8/Makefile.am @@ -4,6 +4,7 @@ dist_man_MANS = \ vdev_id.8 \ zdb.8 \ zfs.8 \ + zfs-mount-generator.8 \ zfs-program.8 \ zgenhostid.8 \ zinject.8 \ diff --git a/man/man8/zfs-mount-generator.8 b/man/man8/zfs-mount-generator.8 new file mode 100644 index 000000000..af471e7c9 --- /dev/null +++ b/man/man8/zfs-mount-generator.8 @@ -0,0 +1,56 @@ +.TH "ZFS\-MOUNT\-GENERATOR" "8" "ZFS" "zfs-mount-generator" "\"" +.SH "NAME" +zfs\-mount\-generator \- generates systemd mount units for zfs +.SH SYNOPSIS +.B /lib/systemd/system-generators/zfs\-mount\-generator +.sp +.SH DESCRIPTION +The zfs\-mount\-generator implements the \fBGenerators Specification\fP +of +.BR systemd (1), +and is called during early boot to generate +.BR systemd.mount (5) +units for automatically mounted datasets. Mount ordering and dependencies +are created for all tracked pools (see below). If a dataset has +.BR canmount=on +and +.BR mountpoint +set, the +.BR auto +mount option will be set, and a dependency for +.BR local-fs.target +on the mount will be created. + +Because zfs pools may not be available very early in the boot process, +information on ZFS mountpoints must be stored separately. The output +of the command +.PP +.RS 4 +zfs list -H -oname,mountpoint,canmount +.RE +.PP +for datasets that should be mounted by systemd, should be kept +separate from the pool, at +.PP +.RS 4 +.RI @sysconfdir@/zfs/zfs-list.cache/ POOLNAME +. +.RE +.PP +The cache file, if writeable, will be kept synchronized with the pool +state by the ZEDLET +.PP +.RS 4 +history_event-zfs-list-cacher.sh . +.RE +.PP +.sp +.SH SEE ALSO +.BR zfs (5) +.BR zfs-events (5) +.BR zed (8) +.BR zpool (5) +.BR systemd (1) +.BR systemd.target (5) +.BR systemd.special (7) +.BR systemd.mount (7) diff --git a/rpm/generic/zfs.spec.in b/rpm/generic/zfs.spec.in index beb7671a1..b06cf3002 100644 --- a/rpm/generic/zfs.spec.in +++ b/rpm/generic/zfs.spec.in @@ -338,6 +338,7 @@ systemctl --system daemon-reload >/dev/null || true /usr/lib/modules-load.d/* %{_unitdir}/* %{_presetdir}/* +%{_generatordir}/* %else %config(noreplace) %{_sysconfdir}/init.d/* %config(noreplace) %{_initconfdir}/zfs