]> git.ipfire.org Git - thirdparty/xfsprogs-dev.git/commitdiff
xfs_scrub_all: enable periodic file data scrubs automatically
authorDarrick J. Wong <djwong@kernel.org>
Mon, 29 Jul 2024 23:23:16 +0000 (16:23 -0700)
committerDarrick J. Wong <djwong@kernel.org>
Tue, 30 Jul 2024 00:01:10 +0000 (17:01 -0700)
Enhance xfs_scrub_all with the ability to initiate a file data scrub
periodically.  The user must specify the period, and they may optionally
specify the path to a file that will record the last time the file data
was scrubbed.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
debian/rules
include/builddefs.in
man/man8/Makefile
man/man8/xfs_scrub_all.8.in [moved from man/man8/xfs_scrub_all.8 with 63% similarity]
scrub/Makefile
scrub/xfs_scrub_all.in
scrub/xfs_scrub_all.service.in

index 0db0ed8e7543bcab117632cd3e445f2722a26966..69a79fc6740526b8130dbe35bfb269091b1e742b 100755 (executable)
@@ -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 \
index 6ac36c149238f5fc09444951c477766b013309f7..734bd95ecb0b339e9d0ccf3409978912fdf8f870 100644 (file)
@@ -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@
index 272e45aebc20b1c4815c7a0f81b19512123b988d..5be76ab727a1fe2e476ee4c0fd80a138be8977a9 100644 (file)
@@ -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
index 86a9b3eced2886c6189e080c3643084d89b034bf..0aa87e23716579f54c6bd89a83ee71a035366572 100644 (file)
@@ -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
index 5567ec061e82eb56327c474ac54d6a1b169cecfe..091a5c94baa160f4a7259a0bb8bcc096b63698cd 100644 (file)
@@ -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)
index c1fd109867f2e41dc470e493e2ece9738524f645..9dd6347fbd9c4f05b0f60df3c34b0ac28514434a 100644 (file)
@@ -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
@@ -250,6 +254,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):
@@ -266,13 +329,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()
 
index 478cd8d057a2f8211bac5ad019be2e4180e867da..9ecc3af0c1f6511a404c70d9fbe512dcda58888c 100644 (file)
@@ -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