From: Darrick J. Wong <darrick.wong@xxxxxxxxxx> Add the ability to run the e2scrub utilities as a periodically scheduled system service. Signed-off-by: Darrick J. Wong <darrick.wong@xxxxxxxxxx> --- MCONFIG.in | 5 + configure | 179 ++++++++++++++++++++++++++++++++++++++++ configure.ac | 74 ++++++++++++++++- debian/e2fsprogs.files | 3 + debian/e2fsprogs.postinst | 19 ++++ debian/e2fsprogs.postrm | 35 ++++++++ scrub/Makefile.in | 90 ++++++++++++++++++++ scrub/e2scrub.in | 38 +++++++- scrub/e2scrub@xxxxxxxxxxx | 20 ++++ scrub/e2scrub_all.cron.in | 2 scrub/e2scrub_all.in | 38 ++++++++ scrub/e2scrub_all.service.in | 10 ++ scrub/e2scrub_all.timer.in | 11 ++ scrub/e2scrub_all_cron.in | 68 +++++++++++++++ scrub/e2scrub_fail.in | 25 ++++++ scrub/e2scrub_fail@xxxxxxxxxxx | 10 ++ scrub/e2scrub_reap.in | 48 +++++++++++ scrub/e2scrub_reap.service.in | 21 +++++ util/subst.conf.in | 3 + 19 files changed, 684 insertions(+), 15 deletions(-) create mode 100644 debian/e2fsprogs.postrm create mode 100644 scrub/e2scrub@xxxxxxxxxxx create mode 100644 scrub/e2scrub_all.cron.in create mode 100644 scrub/e2scrub_all.service.in create mode 100644 scrub/e2scrub_all.timer.in create mode 100644 scrub/e2scrub_all_cron.in create mode 100644 scrub/e2scrub_fail.in create mode 100644 scrub/e2scrub_fail@xxxxxxxxxxx create mode 100644 scrub/e2scrub_reap.in create mode 100644 scrub/e2scrub_reap.service.in diff --git a/MCONFIG.in b/MCONFIG.in index adeb5bd..ee83554 100644 --- a/MCONFIG.in +++ b/MCONFIG.in @@ -32,9 +32,14 @@ man8dir = $(mandir)/man8 infodir = @infodir@ datadir = @datadir@ pkgconfigdir = $(libdir)/pkgconfig +pkglibdir = $(libdir)/e2fsprogs HAVE_UDEV = @have_udev@ UDEV_RULES_DIR = @pkg_udev_rules_dir@ +HAVE_CROND = @have_crond@ +CROND_DIR = @crond_dir@ +HAVE_SYSTEMD = @have_systemd@ +SYSTEMD_SYSTEM_UNIT_DIR = @systemd_system_unit_dir@ @SET_MAKE@ diff --git a/configure b/configure index 986e057..f6a0dea 100755 --- a/configure +++ b/configure @@ -625,6 +625,12 @@ gl_use_threads_default= ac_func_list= ac_subst_vars='LTLIBOBJS LIBOBJS +systemd_system_unit_dir +have_systemd +systemd_LIBS +systemd_CFLAGS +crond_dir +have_crond pkg_udev_rules_dir have_udev udev_LIBS @@ -899,6 +905,8 @@ with_libintl_prefix enable_fuse2fs with_multiarch with_udev_rules_dir +with_crond_dir +with_systemd_unit_dir ' ac_precious_vars='build_alias host_alias @@ -913,7 +921,9 @@ PKG_CONFIG PKG_CONFIG_PATH PKG_CONFIG_LIBDIR udev_CFLAGS -udev_LIBS' +udev_LIBS +systemd_CFLAGS +systemd_LIBS' # Initialize some variables set by options. @@ -1591,6 +1601,9 @@ Optional Packages: --with-multiarch=ARCH specify the multiarch triplet --with-udev-rules-dir[=DIR] Install udev rules into DIR. + --with-crond-dir[=DIR] Install system crontabs into DIR. + --with-systemd-unit-dir[=DIR] + Install systemd system units into DIR. Some influential environment variables: CC C compiler command @@ -1608,6 +1621,10 @@ Some influential environment variables: path overriding pkg-config's built-in search path udev_CFLAGS C compiler flags for udev, overriding pkg-config udev_LIBS linker flags for udev, overriding pkg-config + systemd_CFLAGS + C compiler flags for systemd, overriding pkg-config + systemd_LIBS + linker flags for systemd, overriding pkg-config Use these variables to override the choices made by `configure' or to help it to find libraries and programs with nonstandard names/locations. @@ -13803,6 +13820,7 @@ else fi fi + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we can link with -static" >&5 $as_echo_n "checking whether we can link with -static... " >&6; } if ${ac_cv_e2fsprogs_use_static+:} false; then : @@ -14009,6 +14027,165 @@ fi + +# Check whether --with-crond_dir was given. +if test "${with_crond_dir+set}" = set; then : + withval=$with_crond_dir; +else + with_crond_dir=yes +fi + +if test "x${with_crond_dir}" != "xno"; then : + + if test "x${with_crond_dir}" = "xyes"; then : + + if test -d "/etc/cron.d"; then : + with_crond_dir="/etc/cron.d" +fi + +fi + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for system crontab dir" >&5 +$as_echo_n "checking for system crontab dir... " >&6; } + crond_dir="${with_crond_dir}" + if test -n "${crond_dir}"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: ${crond_dir}" >&5 +$as_echo "${crond_dir}" >&6; } + have_crond="yes" + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + have_crond="no" + +fi + +else + + have_crond="disabled" + +fi + + + + +# Check whether --with-systemd_unit_dir was given. +if test "${with_systemd_unit_dir+set}" = set; then : + withval=$with_systemd_unit_dir; +else + with_systemd_unit_dir=yes +fi + +if test "x${with_systemd_unit_dir}" != "xno"; then : + + if test "x${with_systemd_unit_dir}" = "xyes"; then : + + +pkg_failed=no +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for systemd" >&5 +$as_echo_n "checking for systemd... " >&6; } + +if test -n "$systemd_CFLAGS"; then + pkg_cv_systemd_CFLAGS="$systemd_CFLAGS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"systemd\""; } >&5 + ($PKG_CONFIG --exists --print-errors "systemd") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_systemd_CFLAGS=`$PKG_CONFIG --cflags "systemd" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi +if test -n "$systemd_LIBS"; then + pkg_cv_systemd_LIBS="$systemd_LIBS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"systemd\""; } >&5 + ($PKG_CONFIG --exists --print-errors "systemd") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_systemd_LIBS=`$PKG_CONFIG --libs "systemd" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi + + + +if test $pkg_failed = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then + _pkg_short_errors_supported=yes +else + _pkg_short_errors_supported=no +fi + if test $_pkg_short_errors_supported = yes; then + systemd_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "systemd" 2>&1` + else + systemd_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "systemd" 2>&1` + fi + # Put the nasty error message in config.log where it belongs + echo "$systemd_PKG_ERRORS" >&5 + + + with_systemd_unit_dir="" + +elif test $pkg_failed = untried; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + + with_systemd_unit_dir="" + +else + systemd_CFLAGS=$pkg_cv_systemd_CFLAGS + systemd_LIBS=$pkg_cv_systemd_LIBS + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + with_systemd_unit_dir="$($PKG_CONFIG --variable=systemdsystemunitdir systemd)" + +fi + + +fi + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for systemd system unit dir" >&5 +$as_echo_n "checking for systemd system unit dir... " >&6; } + systemd_system_unit_dir="${with_systemd_unit_dir}" + if test -n "${systemd_system_unit_dir}"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: ${systemd_system_unit_dir}" >&5 +$as_echo "${systemd_system_unit_dir}" >&6; } + have_systemd="yes" + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + have_systemd="no" + +fi + +else + + have_systemd="disabled" + +fi + + + test -d lib || mkdir lib test -d include || mkdir include test -d include/linux || mkdir include/linux diff --git a/configure.ac b/configure.ac index 6e549c5..5a2c8be 100644 --- a/configure.ac +++ b/configure.ac @@ -1392,7 +1392,8 @@ else libdir=$libdir/$withval root_libdir=$root_libdir/$withval fi -)dnl +) +dnl dnl dnl See if -static works. This could fail if the linker does not dnl support -static, or if required external libraries are not available @@ -1514,6 +1515,77 @@ AC_SUBST(have_udev) AC_SUBST(pkg_udev_rules_dir) dnl +dnl Where do cron jobs go? +dnl +AC_ARG_WITH([crond_dir], + [AS_HELP_STRING([--with-crond-dir@<:@=DIR@:>@], + [Install system crontabs into DIR.])], + [], + [with_crond_dir=yes]) +AS_IF([test "x${with_crond_dir}" != "xno"], + [ + AS_IF([test "x${with_crond_dir}" = "xyes"], + [ + AS_IF([test -d "/etc/cron.d"], + [with_crond_dir="/etc/cron.d"]) + ]) + AC_MSG_CHECKING([for system crontab dir]) + crond_dir="${with_crond_dir}" + AS_IF([test -n "${crond_dir}"], + [ + AC_MSG_RESULT(${crond_dir}) + have_crond="yes" + ], + [ + AC_MSG_RESULT(no) + have_crond="no" + ]) + ], + [ + have_crond="disabled" + ]) +AC_SUBST(have_crond) +AC_SUBST(crond_dir) + +dnl +dnl Where do systemd services go? +dnl +AC_ARG_WITH([systemd_unit_dir], + [AS_HELP_STRING([--with-systemd-unit-dir@<:@=DIR@:>@], + [Install systemd system units into DIR.])], + [], + [with_systemd_unit_dir=yes]) +AS_IF([test "x${with_systemd_unit_dir}" != "xno"], + [ + AS_IF([test "x${with_systemd_unit_dir}" = "xyes"], + [ + PKG_CHECK_MODULES([systemd], [systemd], + [ + with_systemd_unit_dir="$($PKG_CONFIG --variable=systemdsystemunitdir systemd)" + ], [ + with_systemd_unit_dir="" + ]) + m4_pattern_allow([^PKG_(MAJOR|MINOR|BUILD|REVISION)$]) + ]) + AC_MSG_CHECKING([for systemd system unit dir]) + systemd_system_unit_dir="${with_systemd_unit_dir}" + AS_IF([test -n "${systemd_system_unit_dir}"], + [ + AC_MSG_RESULT(${systemd_system_unit_dir}) + have_systemd="yes" + ], + [ + AC_MSG_RESULT(no) + have_systemd="no" + ]) + ], + [ + have_systemd="disabled" + ]) +AC_SUBST(have_systemd) +AC_SUBST(systemd_system_unit_dir) + +dnl dnl Make our output files, being sure that we create the some miscellaneous dnl directories dnl diff --git a/debian/e2fsprogs.files b/debian/e2fsprogs.files index 78720fe..e0e49ce 100644 --- a/debian/e2fsprogs.files +++ b/debian/e2fsprogs.files @@ -1,6 +1,9 @@ sbin usr/bin +usr/lib usr/sbin usr/share/man etc lib/udev/rules.d +lib/systemd/system +usr/lib diff --git a/debian/e2fsprogs.postinst b/debian/e2fsprogs.postinst index 00ac363..e7acb0e 100644 --- a/debian/e2fsprogs.postinst +++ b/debian/e2fsprogs.postinst @@ -10,4 +10,23 @@ fi #DEBHELPER# +# debhelper doesn't know what timers are... +update_svc() { + deb-systemd-helper unmask "$1" >/dev/null || true + + if deb-systemd-helper --quiet was-enabled "$1"; then + deb-systemd-helper enable "$1" >/dev/null || true + else + deb-systemd-helper update-state "$1" >/dev/null || true + fi +} +update_svc e2scrub_all.timer +update_svc e2scrub_reap.service + +# Start our new services +if [ -d /run/systemd/system ]; then + systemctl --system daemon-reload >/dev/null || true + deb-systemd-invoke start e2scrub_all.timer >/dev/null || true +fi + exit 0 diff --git a/debian/e2fsprogs.postrm b/debian/e2fsprogs.postrm new file mode 100644 index 0000000..32cb642 --- /dev/null +++ b/debian/e2fsprogs.postrm @@ -0,0 +1,35 @@ +#!/bin/sh + +update_svc() { + deb-systemd-helper mask "$1" >/dev/null || true + + if deb-systemd-helper --quiet was-enabled "$1"; then + # Enables the unit on first installation, creates new + # symlinks on upgrades if the unit file has changed. + deb-systemd-helper disable "$1" >/dev/null || true + fi +} + +if [ "$1" != "upgrade" ]; then + # Abort on error. + set -e + + if [ -x /usr/sbin/update-initramfs -a \ + -e /etc/initramfs-tools/initramfs.conf ]; then + update-initramfs -u + fi + + #DEBHELPER# + + # debhelper doesn't know what timers are... + update_svc e2scrub_all.timer + update_svc e2scrub_reap.service + + # Start our new services + if [ -d /run/systemd/system ]; then + deb-systemd-invoke stop e2scrub_all.timer >/dev/null || true + fi +fi + +exit 0 + diff --git a/scrub/Makefile.in b/scrub/Makefile.in index 1744941..cc2e9ce 100644 --- a/scrub/Makefile.in +++ b/scrub/Makefile.in @@ -22,7 +22,23 @@ INSTALL_TGT += install-udev UNINSTALL_TGT += uninstall-udev endif -all:: $(PROGS) $(MANPAGES) $(CONFFILES) $(UDEV_RULES) +ifeq ($(HAVE_CROND),yes) +CRONTABS = e2scrub_all.cron +LIBPROGS += e2scrub_reap e2scrub_all_cron +INSTALLDIRS_TGT += installdirs-crond installdirs-libprogs +INSTALL_TGT += install-crond install-libprogs +UNINSTALL_TGT += uninstall-crond uninstall-libprogs +endif + +ifeq ($(HAVE_SYSTEMD),yes) +SERVICE_FILES = e2scrub@.service e2scrub_all.service e2scrub_all.timer e2scrub_fail@.service e2scrub_reap.service +LIBPROGS += e2scrub_fail e2scrub_reap +INSTALLDIRS_TGT += installdirs-systemd installdirs-libprogs +INSTALL_TGT += install-systemd install-libprogs +UNINSTALL_TGT += uninstall-systemd uninstall-libprogs +endif + +all:: $(PROGS) $(MANPAGES) $(CONFFILES) $(UDEV_RULES) $(SERVICE_FILES) $(CRONTABS) $(LIBPROGS) e2scrub: $(DEP_SUBSTITUTE) e2scrub.in $(E) " SUBST $@" @@ -34,6 +50,21 @@ e2scrub_all: e2scrub_all.in $(Q) $(SUBSTITUTE_UPTIME) $(srcdir)/e2scrub_all.in $@ $(Q) chmod a+x $@ +e2scrub_fail: e2scrub_fail.in + $(E) " SUBST $@" + $(Q) $(SUBSTITUTE_UPTIME) $(srcdir)/e2scrub_fail.in $@ + $(Q) chmod a+x $@ + +e2scrub_reap: e2scrub_reap.in + $(E) " SUBST $@" + $(Q) $(SUBSTITUTE_UPTIME) $(srcdir)/e2scrub_reap.in $@ + $(Q) chmod a+x $@ + +e2scrub_all_cron: e2scrub_all_cron.in + $(E) " SUBST $@" + $(Q) $(SUBSTITUTE_UPTIME) $(srcdir)/e2scrub_all_cron.in $@ + $(Q) chmod a+x $@ + %.8: %.8.in $(DEP_SUBSTITUTE) $(E) " SUBST $@" $(Q) $(SUBSTITUTE_UPTIME) $< $@ @@ -46,10 +77,34 @@ e2scrub_all: e2scrub_all.in $(E) " SUBST $@" $(Q) $(SUBSTITUTE_UPTIME) $< $@ +%.service: %.service.in $(DEP_SUBSTITUTE) + $(E) " SUBST $@" + $(Q) $(SUBSTITUTE_UPTIME) $< $@ + +%.cron: %.cron.in $(DEP_SUBSTITUTE) + $(E) " SUBST $@" + $(Q) $(SUBSTITUTE_UPTIME) $< $@ + +%.timer: %.timer.in $(DEP_SUBSTITUTE) + $(E) " SUBST $@" + $(Q) $(SUBSTITUTE_UPTIME) $< $@ + installdirs-udev: $(E) " MKINSTALLDIRS $(UDEV_RULES_DIR)" $(Q) $(MKINSTALLDIRS) $(DESTDIR)$(UDEV_RULES_DIR) +installdirs-crond: + $(E) " MKINSTALLDIRS $(CROND_DIR)" + $(Q) $(MKINSTALLDIRS) $(DESTDIR)$(CROND_DIR) + +installdirs-libprogs: + $(E) " MKINSTALLDIRS $(pkglibdir)" + $(Q) $(MKINSTALLDIRS) $(DESTDIR)$(pkglibdir) + +installdirs-systemd: + $(E) " MKINSTALLDIRS $(SYSTEMD_SYSTEM_UNIT_DIR)" + $(Q) $(MKINSTALLDIRS) $(DESTDIR)$(SYSTEMD_SYSTEM_UNIT_DIR) + installdirs: $(INSTALLDIRS_TGT) $(E) " MKINSTALLDIRS $(root_sbindir) $(man8dir) $(root_sysconfdir)" $(Q) $(MKINSTALLDIRS) $(DESTDIR)$(root_sbindir) \ @@ -61,6 +116,24 @@ install-udev: $(INSTALL_PROGRAM) $$i $(DESTDIR)$(UDEV_RULES_DIR)/96-$$i; \ done +install-crond: + $(Q) for i in $(CRONTABS); do \ + $(ES) " INSTALL $(CROND_DIR)/$$i"; \ + $(INSTALL_PROGRAM) $$i $(DESTDIR)$(CROND_DIR)/$$i; \ + done + +install-libprogs: $(LIBPROGS) + $(Q) for i in $(LIBPROGS); do \ + $(ES) " INSTALL $(pkglibdir)/$$i"; \ + $(INSTALL_PROGRAM) $$i $(DESTDIR)$(pkglibdir)/$$i; \ + done + +install-systemd: $(SERVICE_FILES) + $(Q) for i in $(SERVICE_FILES); do \ + $(ES) " INSTALL_DATA $(SYSTEMD_SYSTEM_UNIT_DIR)/$$i"; \ + $(INSTALL_DATA) $$i $(DESTDIR)$(SYSTEMD_SYSTEM_UNIT_DIR)/$$i; \ + done + install: $(PROGS) $(MANPAGES) $(FMANPAGES) installdirs $(INSTALL_TGT) $(Q) for i in $(PROGS); do \ $(ES) " INSTALL $(root_sbindir)/$$i"; \ @@ -83,6 +156,21 @@ uninstall-udev: $(RM) -f $(DESTDIR)$(UDEV_RULES_DIR)/96-$$i; \ done +uninstall-crond: + for i in $(CRONTABS); do \ + $(RM) -f $(DESTDIR)$(CROND_DIR)/$$i; \ + done + +uninstall-libprogs: + for i in $(LIBPROGS); do \ + $(RM) -f $(DESTDIR)$(pkglibdir)/$$i; \ + done + +uninstall-systemd: + for i in $(SERVICE_FILES); do \ + $(RM) -f $(DESTDIR)$(SYSTEMD_SYSTEM_UNIT_DIR)/$$i; \ + done + uninstall: $(UNINSTALL_TGT) for i in $(PROGS); do \ $(RM) -f $(DESTDIR)$(root_sbindir)/$$i; \ diff --git a/scrub/e2scrub.in b/scrub/e2scrub.in index e26b449..6a077fe 100644 --- a/scrub/e2scrub.in +++ b/scrub/e2scrub.in @@ -41,11 +41,33 @@ print_version() { echo "e2scrub @E2FSPROGS_VERSION@ (@E2FSPROGS_DATE@)" } +exitcode() { + ret="$1" + + # If we're being run as a service, the return code must fit the LSB + # init script action error guidelines, which is to say that we + # compress all errors to 1 ("generic or unspecified error", LSB 5.0 + # section 22.2) and hope the admin will scan the log for what + # actually happened. + + # We have to sleep 2 seconds here because journald uses the pid to + # connect our log messages to the systemd service. This is critical + # for capturing all the log messages if the scrub fails, because the + # fail service uses the service name to gather log messages for the + # error report. + if [ -n "${SERVICE_MODE}" ]; then + test "${ret}" -ne 0 && ret=1 + sleep 2 + fi + + exit "${ret}" +} + while getopts "tV" opt; do case "${opt}" in "t") fstrim=1;; - "V") print_version; exit 0;; - *) print_help; exit 2;; + "V") print_version; exitcode 0;; + *) print_help; exitcode 2;; esac done shift "$((OPTIND - 1))" @@ -53,11 +75,11 @@ shift "$((OPTIND - 1))" dev="$1" if [ -z "${dev}" ]; then print_help - exit 1 + exitcode 1 elif [ ! -b "${dev}" ]; then echo "${dev}: Not a block device?" print_help - exit 16 + exitcode 16 fi # Make sure this is an LVM device we can snapshot @@ -65,7 +87,7 @@ vg="$(lvs --noheadings -o vg_name "${dev}" 2> /dev/null | sed -e 's/^ //g')" lv="$(lvs --noheadings -o lv_name "${dev}" 2> /dev/null | sed -e 's/^ //g')" if [ -z "${vg}" ] || [ -z "${lv}" ]; then echo "${dev}: Not a LVM device." - exit 16 + exitcode 16 fi start_time="$(date +'%Y%m%d%H%M%S')" snap="${lv}.e2scrub" @@ -77,7 +99,7 @@ case "${fstype}" in ;; *) echo "${dev}: Filesystem of type ${fstype} not supported." - exit 16 + exitcode 16 ;; esac @@ -121,7 +143,7 @@ setup() { trap "teardown; exit 1" EXIT INT QUIT TERM if ! setup; then echo "Snapshot of ${dev} FAILED, will not check!" - exit 1 + exitcode 1 fi # Check and react @@ -159,4 +181,4 @@ else fi fi -exit "${ret}" +exitcode "${ret}" diff --git a/scrub/e2scrub@xxxxxxxxxxx b/scrub/e2scrub@xxxxxxxxxxx new file mode 100644 index 0000000..496f894 --- /dev/null +++ b/scrub/e2scrub@xxxxxxxxxxx @@ -0,0 +1,20 @@ +[Unit] +Description=Online ext4 Metadata Check for %I +OnFailure=e2scrub_fail@%i.service +Documentation=man:e2scrub(8) + +[Service] +Type=oneshot +WorkingDirectory=/ +PrivateNetwork=true +ProtectSystem=true +ProtectHome=read-only +PrivateTmp=yes +AmbientCapabilities=CAP_SYS_ADMIN CAP_SYS_RAWIO +NoNewPrivileges=yes +User=root +IOSchedulingClass=idle +CPUSchedulingPolicy=idle +Environment=SERVICE_MODE=1 +ExecStart=@root_sbindir@/e2scrub -t %I +SyslogIdentifier=%N diff --git a/scrub/e2scrub_all.cron.in b/scrub/e2scrub_all.cron.in new file mode 100644 index 0000000..0cb5f1f --- /dev/null +++ b/scrub/e2scrub_all.cron.in @@ -0,0 +1,2 @@ +30 3 * * 0 root test -e /run/systemd/system || @pkglibdir@/e2scrub_all_cron +10 3 * * * root test -e /run/systemd/system || @pkglibdir@/e2scrub_reap diff --git a/scrub/e2scrub_all.in b/scrub/e2scrub_all.in index 414a519..23ccbd2 100644 --- a/scrub/e2scrub_all.in +++ b/scrub/e2scrub_all.in @@ -29,10 +29,32 @@ print_version() { echo "e2scrub_all @E2FSPROGS_VERSION@ (@E2FSPROGS_DATE@)" } +exitcode() { + ret="$1" + + # If we're being run as a service, the return code must fit the LSB + # init script action error guidelines, which is to say that we + # compress all errors to 1 ("generic or unspecified error", LSB 5.0 + # section 22.2) and hope the admin will scan the log for what + # actually happened. + + # We have to sleep 2 seconds here because journald uses the pid to + # connect our log messages to the systemd service. This is critical + # for capturing all the log messages if the scrub fails, because the + # fail service uses the service name to gather log messages for the + # error report. + if [ -n "${SERVICE_MODE}" ]; then + test "${ret}" -ne 0 && ret=1 + sleep 2 + fi + + exit "${ret}" +} + while getopts "V" opt; do case "${opt}" in - "V") print_version; exit 0;; - *) print_help; exit 2;; + "V") print_version; exitcode 0;; + *) print_help; exitcode 2;; esac done shift "$((OPTIND - 1))" @@ -48,7 +70,15 @@ lvs -o vg_name,lv_name,lv_role --noheadings 2> /dev/null | while read vg lv role # Skip non-ext[234] blkid -p -n "${types}" "${dev}" > /dev/null 2>&1 || continue - ${DBG} "@root_sbindir@/e2scrub" "${dev}" + if type systemctl > /dev/null 2>&1; then + ${DBG} systemctl start "e2scrub@${dev}" 2> /dev/null + res=$? + if [ "${res}" -ne 0 ] && [ "${res}" -ne 1 ]; then + ${DBG} "@root_sbindir@/e2scrub" "${dev}" + fi + else + ${DBG} "@root_sbindir@/e2scrub" "${dev}" + fi done -exit 0 +exitcode 0 diff --git a/scrub/e2scrub_all.service.in b/scrub/e2scrub_all.service.in new file mode 100644 index 0000000..bc05184 --- /dev/null +++ b/scrub/e2scrub_all.service.in @@ -0,0 +1,10 @@ +[Unit] +Description=Online ext4 Metadata Check for All Filesystems +ConditionACPower=true +Documentation=man:e2scrub_all(8) + +[Service] +Type=oneshot +Environment=SERVICE_MODE=1 +ExecStart=@root_sbindir@/e2scrub_all +SyslogIdentifier=e2scrub_all diff --git a/scrub/e2scrub_all.timer.in b/scrub/e2scrub_all.timer.in new file mode 100644 index 0000000..3d558bb --- /dev/null +++ b/scrub/e2scrub_all.timer.in @@ -0,0 +1,11 @@ +[Unit] +Description=Periodic ext4 Online Metadata Check for All Filesystems + +[Timer] +# Run on Sunday at 3:10am, to avoid running afoul of DST changes +OnCalendar=Sun *-*-* 03:10:00 +RandomizedDelaySec=60 +Persistent=true + +[Install] +WantedBy=timers.target diff --git a/scrub/e2scrub_all_cron.in b/scrub/e2scrub_all_cron.in new file mode 100644 index 0000000..f9cff87 --- /dev/null +++ b/scrub/e2scrub_all_cron.in @@ -0,0 +1,68 @@ +#!/bin/bash + +# Copyright (C) 2018 Oracle. All Rights Reserved. +# +# Author: Darrick J. Wong <darrick.wong@xxxxxxxxxx> +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it would be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +# Run e2scrub_all from a cronjob if we don't have systemd and we're not +# running on AC power. + +on_ac_power() { + local any_known=no + + # try sysfs power class first + if [ -d /sys/class/power_supply ]; then + for psu in /sys/class/power_supply/*; do + if [ -r "$psu/type" ]; then + type=$(cat "$psu/type") + + # ignore batteries + [ "$type" = "Battery" ] && continue + + online=$(cat "$psu/online") + + [ "$online" = 1 ] && return 0 + [ "$online" = 0 ] && any_known=yes + fi + done + + [ "$any_known" = "yes" ] && return 1 + fi + + # else fall back to AC adapters in /proc + if [ -d /proc/acpi/ac_adapter ]; then + for ac in /proc/acpi/ac_adapter/*; do + if [ -r "$ac/state" ]; then + grep -q on-line "$ac/state" && return 0 + grep -q off-line "$ac/state" && any_known=yes + elif [ -r "$ac/status" ]; then + grep -q on-line "$ac/status" && return 0 + grep -q off-line "$ac/status" && any_known=yes + fi + done + + [ "$any_known" = "yes" ] && return 1 + fi + + # Can't tell, just assume we're on AC. + return 0 +} + +test -e /run/systemd/system && exit 0 +on_ac_power || exit 0 + +exec @root_sbindir@/e2scrub_all diff --git a/scrub/e2scrub_fail.in b/scrub/e2scrub_fail.in new file mode 100644 index 0000000..f27197a --- /dev/null +++ b/scrub/e2scrub_fail.in @@ -0,0 +1,25 @@ +#!/bin/bash + +# Email logs of failed e2scrub unit runs when the systemd service fails. + +recipient="$1" +test -z "${recipient}" && exit 0 +device="$2" +test -z "${device}" && exit 0 +hostname="$(hostname -f 2>/dev/null)" +test -z "${hostname}" && hostname="${HOSTNAME}" +if ! type sendmail > /dev/null 2>&1; then + echo "$0: sendmail program not found." + exit 1 +fi + +(cat << ENDL +To: $1 +From: <e2scrub@${hostname}> +Subject: e2scrub failure on ${device} + +So sorry, the automatic e2scrub of ${device} on ${hostname} failed. + +A log of what happened follows: +ENDL +systemctl status --full --lines 4294967295 "e2scrub@${device}") | sendmail -t -i diff --git a/scrub/e2scrub_fail@xxxxxxxxxxx b/scrub/e2scrub_fail@xxxxxxxxxxx new file mode 100644 index 0000000..df87949 --- /dev/null +++ b/scrub/e2scrub_fail@xxxxxxxxxxx @@ -0,0 +1,10 @@ +[Unit] +Description=Online ext4 Metadata Check Failure Reporting for %I + +[Service] +Type=oneshot +Environment=EMAIL_ADDR=root +ExecStart=@pkglibdir@/e2scrub_fail "${EMAIL_ADDR}" %I +User=mail +Group=mail +SupplementaryGroups=systemd-journal diff --git a/scrub/e2scrub_reap.in b/scrub/e2scrub_reap.in new file mode 100644 index 0000000..0fde0e6 --- /dev/null +++ b/scrub/e2scrub_reap.in @@ -0,0 +1,48 @@ +#!/bin/bash + +# Copyright (C) 2018 Oracle. All Rights Reserved. +# +# Author: Darrick J. Wong <darrick.wong@xxxxxxxxxx> +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it would be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +# Clean up leftover e2scrub snapshots + +# Find any ${lvname}.e2scrub snapshot and delete it +lvs -o vg_name,lv_name,lv_role --noheadings 2> /dev/null | while read vg lv role extra; do + # Parse error? + test -n "${extra}" || continue + # Is this really a snapshot? + echo "${role}" | grep -q "snapshot" || continue + # An e2scrub snapshot? + echo "${lv}" | grep -q ".e2scrub$" || continue + + # Check that the e2scrub snap still exists after waiting a brief period. + # This is how try to avoid deleting a snap from underneath a running + # scrub. + dev="/dev/${vg}/${lv}" + test -e "${dev}" || continue + sleep 2 + test -e "${dev}" || continue + + # Remove and wait for removal to succeed. + lvremove -f "${vg}/${lv}" 3>&- + while [ -e "${dev}" ] && [ "$?" -eq "5" ]; do + sleep 0.5 + lvremove -f "${vg}/${lv}" 3>&- + done +done + +exitcode 0 diff --git a/scrub/e2scrub_reap.service.in b/scrub/e2scrub_reap.service.in new file mode 100644 index 0000000..38eefc4 --- /dev/null +++ b/scrub/e2scrub_reap.service.in @@ -0,0 +1,21 @@ +[Unit] +Description=Remove Stale Online ext4 Metadata Check Snapshots + +[Service] +Type=oneshot +WorkingDirectory=/ +PrivateNetwork=true +ProtectSystem=true +ProtectHome=read-only +PrivateTmp=yes +AmbientCapabilities=CAP_SYS_ADMIN CAP_SYS_RAWIO +NoNewPrivileges=yes +User=root +IOSchedulingClass=idle +CPUSchedulingPolicy=idle +ExecStart=@pkglibdir@/e2scrub_reap +SyslogIdentifier=%N +RemainAfterExit=no + +[Install] +WantedBy=default.target diff --git a/util/subst.conf.in b/util/subst.conf.in index 6bf658d..0da4554 100644 --- a/util/subst.conf.in +++ b/util/subst.conf.in @@ -21,3 +21,6 @@ JDEV TDB_MAN_COMMENT @TDB_MAN_COMMENT@ root_sbindir @root_sbindir@ root_bindir @root_bindir@ +libdir @libdir@ +$exec_prefix @exec_prefix@ +pkglibdir @libdir@/e2fsprogs