@@ -38,7 +38,8 @@ configure_options = \
--disable-ubsan \
--disable-addrsan \
--disable-threadsan \
- --enable-lto
+ --enable-lto \
+ --localstatedir=/var
options = export DEBUG=-DNDEBUG DISTRIBUTION=debian \
INSTALL_USER=root INSTALL_GROUP=root \
@@ -57,6 +57,9 @@ PKG_DOC_DIR = @datadir@/doc/@pkg_name@
PKG_LOCALE_DIR = @datadir@/locale
PKG_DATA_DIR = @datadir@/@pkg_name@
MKFS_CFG_DIR = @datadir@/@pkg_name@/mkfs
+PKG_STATE_DIR = @localstatedir@/lib/@pkg_name@
+
+XFS_SCRUB_ALL_AUTO_MEDIA_SCAN_STAMP=$(PKG_STATE_DIR)/xfs_scrub_all_media.stamp
CC = @cc@
BUILD_CC = @BUILD_CC@
@@ -11,11 +11,12 @@ ifneq ("$(ENABLE_SCRUB)","yes")
MAN_PAGES = $(filter-out xfs_scrub%,$(shell echo *.$(MAN_SECTION)))
else
MAN_PAGES = $(shell echo *.$(MAN_SECTION))
+ MAN_PAGES += xfs_scrub_all.8
endif
MAN_PAGES += mkfs.xfs.8
MAN_DEST = $(PKG_MAN_DIR)/man$(MAN_SECTION)
LSRCFILES = $(MAN_PAGES)
-DIRT = mkfs.xfs.8
+DIRT = mkfs.xfs.8 xfs_scrub_all.8
default : $(MAN_PAGES)
@@ -29,4 +30,8 @@ mkfs.xfs.8: mkfs.xfs.8.in
@echo " [SED] $@"
$(Q)$(SED) -e 's|@mkfs_cfg_dir@|$(MKFS_CFG_DIR)|g' < $^ > $@
+xfs_scrub_all.8: xfs_scrub_all.8.in
+ @echo " [SED] $@"
+ $(Q)$(SED) -e 's|@stampfile@|$(XFS_SCRUB_ALL_AUTO_MEDIA_SCAN_STAMP)|g' < $^ > $@
+
install-dev :
similarity index 63%
rename from man/man8/xfs_scrub_all.8
rename to man/man8/xfs_scrub_all.8.in
@@ -18,6 +18,21 @@ operations can be run in parallel so long as no two scrubbers access
the same device simultaneously.
.SH OPTIONS
.TP
+.B \--auto-media-scan-interval
+Automatically enable the file data scan (i.e. the
+.B -x
+flag) if it has not been run in the specified interval.
+The interval must be a floating point number with an optional unit suffix.
+Supported unit suffixes are
+.IR y ", " q ", " mo ", " w ", " d ", " h ", " m ", and " s
+for years, 90-day quarters, 30-day months, weeks, days, hours, minutes, and
+seconds, respectively.
+If no units are specified, the default is seconds.
+.TP
+.B \--auto-media-scan-stamp
+Path to a file that will record the last time the media scan was run.
+Defaults to @stampfile@.
+.TP
.B \-h
Display help.
.TP
@@ -118,6 +118,7 @@ xfs_scrub_all: xfs_scrub_all.in $(builddefs)
-e "s|@scrub_svcname@|$(scrub_svcname)|g" \
-e "s|@scrub_media_svcname@|$(scrub_media_svcname)|g" \
-e "s|@pkg_version@|$(PKG_VERSION)|g" \
+ -e "s|@stampfile@|$(XFS_SCRUB_ALL_AUTO_MEDIA_SCAN_STAMP)|g" \
-e "s|@scrub_service_args@|$(XFS_SCRUB_SERVICE_ARGS)|g" \
-e "s|@scrub_args@|$(XFS_SCRUB_ARGS)|g" < $< > $@
$(Q)chmod a+x $@
@@ -141,6 +142,7 @@ install: $(INSTALL_SCRUB)
-e "s|@scrub_service_args@|$(XFS_SCRUB_SERVICE_ARGS)|g" \
-e "s|@scrub_args@|$(XFS_SCRUB_ARGS)|g" \
-e "s|@pkg_libexec_dir@|$(PKG_LIBEXEC_DIR)|g" \
+ -e "s|@pkg_state_dir@|$(PKG_STATE_DIR)|g" \
< $< > $@
%.cron: %.cron.in $(builddefs)
@@ -161,6 +163,7 @@ install-scrub: default
$(INSTALL) -m 755 -d $(PKG_SBIN_DIR)
$(LTINSTALL) -m 755 $(LTCOMMAND) $(PKG_SBIN_DIR)
$(INSTALL) -m 755 $(XFS_SCRUB_ALL_PROG) $(PKG_SBIN_DIR)
+ $(INSTALL) -m 755 -d $(PKG_STATE_DIR)
install-udev: $(UDEV_RULES)
$(INSTALL) -m 755 -d $(UDEV_RULE_DIR)
@@ -16,6 +16,10 @@ import os
import argparse
import signal
from io import TextIOWrapper
+from pathlib import Path
+from datetime import timedelta
+from datetime import datetime
+from datetime import timezone
retcode = 0
terminate = False
@@ -248,6 +252,65 @@ def wait_for_termination(cond, killfuncs):
fn()
return True
+def scan_interval(string):
+ '''Convert a textual scan interval argument into a time delta.'''
+
+ if string.endswith('y'):
+ year = timedelta(seconds = 31556952)
+ return year * float(string[:-1])
+ if string.endswith('q'):
+ return timedelta(days = 90 * float(string[:-1]))
+ if string.endswith('mo'):
+ return timedelta(days = 30 * float(string[:-2]))
+ if string.endswith('w'):
+ return timedelta(weeks = float(string[:-1]))
+ if string.endswith('d'):
+ return timedelta(days = float(string[:-1]))
+ if string.endswith('h'):
+ return timedelta(hours = float(string[:-1]))
+ if string.endswith('m'):
+ return timedelta(minutes = float(string[:-1]))
+ if string.endswith('s'):
+ return timedelta(seconds = float(string[:-1]))
+ return timedelta(seconds = int(string))
+
+def utcnow():
+ '''Create a representation of the time right now, in UTC.'''
+
+ dt = datetime.utcnow()
+ return dt.replace(tzinfo = timezone.utc)
+
+def enable_automatic_media_scan(args):
+ '''Decide if we enable media scanning automatically.'''
+ already_enabled = args.x
+
+ try:
+ interval = scan_interval(args.auto_media_scan_interval)
+ except Exception as e:
+ raise Exception('%s: Invalid media scan interval.' % \
+ args.auto_media_scan_interval)
+
+ p = Path(args.auto_media_scan_stamp)
+ if already_enabled:
+ res = True
+ else:
+ try:
+ last_run = p.stat().st_mtime
+ now = utcnow().timestamp()
+ res = last_run + interval.total_seconds() < now
+ except FileNotFoundError:
+ res = True
+
+ if res:
+ # Truncate the stamp file to update its mtime
+ with p.open('w') as f:
+ pass
+ if not already_enabled:
+ print('Automatically enabling file data scrub.')
+ sys.stdout.flush()
+
+ return res
+
def main():
'''Find mounts, schedule scrub runs.'''
def thr(mnt, devs):
@@ -262,13 +325,24 @@ def main():
action = "store_true")
parser.add_argument("-x", help = "Scrub file data after filesystem metadata.", \
action = "store_true")
+ parser.add_argument("--auto-media-scan-interval", help = "Automatically scrub file data at this interval.", \
+ default = None)
+ parser.add_argument("--auto-media-scan-stamp", help = "Stamp file for automatic file data scrub.", \
+ default = '@stampfile@')
args = parser.parse_args()
if args.V:
print("xfs_scrub_all version @pkg_version@")
sys.exit(0)
- scrub_media = args.x
+ if args.auto_media_scan_interval is not None:
+ try:
+ scrub_media = enable_automatic_media_scan(args)
+ except Exception as e:
+ print(e)
+ sys.exit(16)
+ else:
+ scrub_media = args.x
fs = find_mounts()
@@ -34,11 +34,13 @@ CapabilityBoundingSet=
NoNewPrivileges=true
RestrictSUIDSGID=true
-# Make the entire filesystem readonly. We don't want to hide anything because
-# we need to find all mounted XFS filesystems in the host.
+# Make the entire filesystem readonly except for the media scan stamp file
+# directory. We don't want to hide anything because we need to find all
+# mounted XFS filesystems in the host.
ProtectSystem=strict
ProtectHome=read-only
PrivateTmp=false
+BindPaths=@pkg_state_dir@
# No network access except to the systemd control socket
PrivateNetwork=true