]>
git.ipfire.org Git - thirdparty/xfsprogs-dev.git/blob - scrub/xfs_scrub_all.in
3 # SPDX-License-Identifier: GPL-2.0-or-later
4 # Copyright (C) 2018-2024 Oracle. All rights reserved.
6 # Author: Darrick J. Wong <djwong@kernel.org>
8 # Run online scrubbers in parallel, but avoid thrashing.
18 from io
import TextIOWrapper
24 '''Return /dev/null in subprocess writable format.'''
26 from subprocess
import DEVNULL
29 return open(os
.devnull
, 'wb')
32 '''Map mountpoints to physical disks.'''
33 def find_xfs_mounts(bdev
, fs
, lastdisk
):
34 '''Attach lastdisk to each fs found under bdev.'''
35 if bdev
['fstype'] == 'xfs' and bdev
['mountpoint'] is not None:
36 mnt
= bdev
['mountpoint']
40 fs
[mnt
] = set([lastdisk
])
41 if 'children' not in bdev
:
43 for child
in bdev
['children']:
44 find_xfs_mounts(child
, fs
, lastdisk
)
47 cmd
=['lsblk', '-o', 'NAME,KNAME,TYPE,FSTYPE,MOUNTPOINT', '-J']
48 result
= subprocess
.Popen(cmd
, stdout
=subprocess
.PIPE
)
50 if result
.returncode
!= 0:
52 sarray
= [x
.decode(sys
.stdout
.encoding
) for x
in result
.stdout
.readlines()]
53 output
= ' '.join(sarray
)
54 bdevdata
= json
.loads(output
)
56 # The lsblk output had better be in disks-then-partitions order
57 for bdev
in bdevdata
['blockdevices']:
58 lastdisk
= bdev
['kname']
59 find_xfs_mounts(bdev
, fs
, lastdisk
)
64 '''Generator function that yields lines of a program's stdout.'''
65 p
= subprocess
.Popen(cmd
, stdout
= subprocess
.PIPE
)
66 for line
in TextIOWrapper(p
.stdout
, encoding
="utf-8"):
69 def remove_killfunc(killfuncs
, fn
):
70 '''Ensure fn is not in killfuncs.'''
76 def run_killable(cmd
, stdout
, killfuncs
):
77 '''Run a killable program. Returns program retcode or -1 if we can't
80 proc
= subprocess
.Popen(cmd
, stdout
= stdout
)
81 killfuncs
.add(proc
.terminate
)
83 remove_killfunc(killfuncs
, proc
.terminate
)
84 return proc
.returncode
88 # systemd doesn't like unit instance names with slashes in them, so it
89 # replaces them with dashes when it invokes the service. Filesystem paths
90 # need a special --path argument so that dashes do not get mangled.
91 def path_to_serviceunit(path
):
92 '''Convert a pathname into a systemd service unit name.'''
94 cmd
= ['systemd-escape', '--template', '@scrub_svcname@',
97 proc
= subprocess
.Popen(cmd
, stdout
= subprocess
.PIPE
)
99 for line
in proc
.stdout
:
100 return line
.decode(sys
.stdout
.encoding
).strip()
104 def systemctl_stop(unitname
):
105 '''Stop a systemd unit.'''
106 cmd
= ['systemctl', 'stop', unitname
]
107 x
= subprocess
.Popen(cmd
)
110 def systemctl_start(unitname
, killfuncs
):
111 '''Start a systemd unit and wait for it to complete.'''
113 cmd
= ['systemctl', 'start', unitname
]
115 proc
= subprocess
.Popen(cmd
, stdout
= DEVNULL())
116 stop_fn
= lambda: systemctl_stop(unitname
)
117 killfuncs
.add(stop_fn
)
119 ret
= proc
.returncode
121 if stop_fn
is not None:
122 remove_killfunc(killfuncs
, stop_fn
)
126 remove_killfunc(killfuncs
, stop_fn
)
129 # If systemctl-start returns 1, it's possible that the service failed
130 # or that dbus/systemd restarted and the client program lost its
131 # connection -- according to the systemctl man page, 1 means "unit not
134 # Either way, we switch to polling the service status to try to wait
135 # for the service to end. As of systemd 249, the is-active command
136 # returns any of the following states: active, reloading, inactive,
137 # failed, activating, deactivating, or maintenance. Apparently these
138 # strings are not localized.
141 for l
in backtick(['systemctl', 'is-active', unitname
]):
143 remove_killfunc(killfuncs
, stop_fn
)
146 remove_killfunc(killfuncs
, stop_fn
)
149 remove_killfunc(killfuncs
, stop_fn
)
154 def run_scrub(mnt
, cond
, running_devs
, mntdevs
, killfuncs
):
155 '''Run a scrub process.'''
156 global retcode
, terminate
158 print("Scrubbing %s..." % mnt
)
165 # Try it the systemd way
166 unitname
= path_to_serviceunit(path
)
167 if unitname
is not None:
168 ret
= systemctl_start(unitname
, killfuncs
)
169 if ret
== 0 or ret
== 1:
170 print("Scrubbing %s done, (err=%d)" % (mnt
, ret
))
178 # Invoke xfs_scrub manually
179 cmd
= ['@sbindir@/xfs_scrub']
180 cmd
+= '@scrub_args@'.split()
182 ret
= run_killable(cmd
, None, killfuncs
)
184 print("Scrubbing %s done, (err=%d)" % (mnt
, ret
))
192 print("Unable to start scrub tool.")
195 running_devs
-= mntdevs
200 def signal_scrubs(signum
, cond
):
201 '''Handle termination signals by killing xfs_scrub children.'''
202 global debug
, terminate
205 print('Signal handler called with signal', signum
)
213 def wait_for_termination(cond
, killfuncs
):
214 '''Wait for a child thread to terminate. Returns True if we should
215 abort the program, False otherwise.'''
216 global debug
, terminate
219 print('waiting for threads to terminate')
225 except KeyboardInterrupt:
232 print("Terminating...")
234 while len(killfuncs
) > 0:
240 '''Find mounts, schedule scrub runs.'''
242 a
= (mnt
, cond
, running_devs
, devs
, killfuncs
)
243 thr
= threading
.Thread(target
= run_scrub
, args
= a
)
245 global retcode
, terminate
247 parser
= argparse
.ArgumentParser( \
248 description
= "Scrub all mounted XFS filesystems.")
249 parser
.add_argument("-V", help = "Report version and exit.", \
250 action
= "store_true")
251 args
= parser
.parse_args()
254 print("xfs_scrub_all version @pkg_version@")
259 # Tail the journal if we ourselves aren't a service...
261 if 'SERVICE_MODE' not in os
.environ
:
263 cmd
=['journalctl', '--no-pager', '-q', '-S', 'now', \
264 '-f', '-u', 'xfs_scrub@*', '-o', \
266 journalthread
= subprocess
.Popen(cmd
)
270 # Schedule scrub jobs...
273 cond
= threading
.Condition()
275 signal
.signal(signal
.SIGINT
, lambda s
, f
: signal_scrubs(s
, cond
))
276 signal
.signal(signal
.SIGTERM
, lambda s
, f
: signal_scrubs(s
, cond
))
279 if len(running_devs
) == 0:
280 mnt
, devs
= fs
.popitem()
281 running_devs
.update(devs
)
288 if dev
in running_devs
:
292 running_devs
.update(devs
)
298 # Wait for one thread to finish
299 if wait_for_termination(cond
, killfuncs
):
302 # Wait for the rest of the threads to finish
303 while len(killfuncs
) > 0:
304 wait_for_termination(cond
, killfuncs
)
306 if journalthread
is not None:
307 journalthread
.terminate()
309 # See the service mode comments in xfs_scrub.c for why we do this.
310 if 'SERVICE_MODE' in os
.environ
:
317 if __name__
== '__main__':