]>
git.ipfire.org Git - thirdparty/xfsprogs-dev.git/blob - scrub/xfs_scrub_all.in
3 # SPDX-License-Identifier: GPL-2.0+
4 # Copyright (C) 2018 Oracle. All rights reserved.
6 # Author: Darrick J. Wong <darrick.wong@oracle.com>
8 # Run online scrubbers in parallel, but avoid thrashing.
22 '''Return /dev/null in subprocess writable format.'''
24 from subprocess
import DEVNULL
27 return open(os
.devnull
, 'wb')
30 '''Map mountpoints to physical disks.'''
31 def find_xfs_mounts(bdev
, fs
, lastdisk
):
32 '''Attach lastdisk to each fs found under bdev.'''
33 if bdev
['fstype'] == 'xfs' and bdev
['mountpoint'] is not None:
34 mnt
= bdev
['mountpoint']
38 fs
[mnt
] = set([lastdisk
])
39 if 'children' not in bdev
:
41 for child
in bdev
['children']:
42 find_xfs_mounts(child
, fs
, lastdisk
)
45 cmd
=['lsblk', '-o', 'NAME,KNAME,TYPE,FSTYPE,MOUNTPOINT', '-J']
46 result
= subprocess
.Popen(cmd
, stdout
=subprocess
.PIPE
)
48 if result
.returncode
!= 0:
50 sarray
= [x
.decode(sys
.stdout
.encoding
) for x
in result
.stdout
.readlines()]
51 output
= ' '.join(sarray
)
52 bdevdata
= json
.loads(output
)
54 # The lsblk output had better be in disks-then-partitions order
55 for bdev
in bdevdata
['blockdevices']:
56 lastdisk
= bdev
['kname']
57 find_xfs_mounts(bdev
, fs
, lastdisk
)
61 def kill_systemd(unit
, proc
):
62 '''Kill systemd unit.'''
64 cmd
=['systemctl', 'stop', unit
]
65 x
= subprocess
.Popen(cmd
)
68 def run_killable(cmd
, stdout
, killfuncs
, kill_fn
):
69 '''Run a killable program. Returns program retcode or -1 if we can't start it.'''
71 proc
= subprocess
.Popen(cmd
, stdout
= stdout
)
72 real_kill_fn
= lambda: kill_fn(proc
)
73 killfuncs
.add(real_kill_fn
)
76 killfuncs
.remove(real_kill_fn
)
79 return proc
.returncode
83 # systemd doesn't like unit instance names with slashes in them, so it
84 # replaces them with dashes when it invokes the service. However, it's not
85 # smart enough to convert the dashes to something else, so when it unescapes
86 # the instance name to feed to xfs_scrub, it turns all dashes into slashes.
87 # "/moo-cow" becomes "-moo-cow" becomes "/moo/cow", which is wrong. systemd
88 # actually /can/ escape the dashes correctly if it is told that this is a path
89 # (and not a unit name), but it didn't do this prior to January 2017, so fix
92 # systemd path escaping also drops the initial slash so we add that back in so
93 # that log messages from the service units preserve the full path and users can
94 # look up log messages using full paths. However, for "/" the escaping rules
95 # do /not/ drop the initial slash, so we have to special-case that here.
96 def systemd_escape(path
):
97 '''Escape a path to avoid mangled systemd mangling.'''
101 cmd
= ['systemd-escape', '--path', path
]
103 proc
= subprocess
.Popen(cmd
, stdout
= subprocess
.PIPE
)
105 for line
in proc
.stdout
:
106 return '-' + line
.decode(sys
.stdout
.encoding
).strip()
110 def run_scrub(mnt
, cond
, running_devs
, mntdevs
, killfuncs
):
111 '''Run a scrub process.'''
112 global retcode
, terminate
114 print("Scrubbing %s..." % mnt
)
121 # Try it the systemd way
122 cmd
=['systemctl', 'start', 'xfs_scrub@%s' % systemd_escape(mnt
)]
123 ret
= run_killable(cmd
, DEVNULL(), killfuncs
, \
124 lambda proc
: kill_systemd('xfs_scrub@%s' % mnt
, proc
))
125 if ret
== 0 or ret
== 1:
126 print("Scrubbing %s done, (err=%d)" % (mnt
, ret
))
134 # Invoke xfs_scrub manually
135 cmd
=['@sbindir@/xfs_scrub', '@scrub_args@', mnt
]
136 ret
= run_killable(cmd
, None, killfuncs
, \
137 lambda proc
: proc
.terminate())
139 print("Scrubbing %s done, (err=%d)" % (mnt
, ret
))
147 print("Unable to start scrub tool.")
150 running_devs
-= mntdevs
156 '''Find mounts, schedule scrub runs.'''
158 a
= (mnt
, cond
, running_devs
, devs
, killfuncs
)
159 thr
= threading
.Thread(target
= run_scrub
, args
= a
)
161 global retcode
, terminate
163 parser
= argparse
.ArgumentParser( \
164 description
= "Scrub all mounted XFS filesystems.")
165 parser
.add_argument("-V", help = "Report version and exit.", \
166 action
= "store_true")
167 args
= parser
.parse_args()
170 print("xfs_scrub_all version @pkg_version@")
175 # Tail the journal if we ourselves aren't a service...
177 if 'SERVICE_MODE' not in os
.environ
:
179 cmd
=['journalctl', '--no-pager', '-q', '-S', 'now', \
180 '-f', '-u', 'xfs_scrub@*', '-o', \
182 journalthread
= subprocess
.Popen(cmd
)
186 # Schedule scrub jobs...
189 cond
= threading
.Condition()
191 if len(running_devs
) == 0:
192 mnt
, devs
= fs
.popitem()
193 running_devs
.update(devs
)
200 if dev
in running_devs
:
204 running_devs
.update(devs
)
212 except KeyboardInterrupt:
214 print("Terminating...")
216 while len(killfuncs
) > 0:
222 if journalthread
is not None:
223 journalthread
.terminate()
225 # See the service mode comments in xfs_scrub.c for why we do this.
226 if 'SERVICE_MODE' in os
.environ
:
233 if __name__
== '__main__':