]>
git.ipfire.org Git - thirdparty/xfsprogs-dev.git/blob - scrub/xfs_scrub_all.in
3 # Run online scrubbers in parallel, but avoid thrashing.
5 # Copyright (C) 2018 Oracle. All rights reserved.
7 # Author: Darrick J. Wong <darrick.wong@oracle.com>
9 # This program is free software; you can redistribute it and/or
10 # modify it under the terms of the GNU General Public License
11 # as published by the Free Software Foundation; either version 2
12 # of the License, or (at your option) any later version.
14 # This program is distributed in the hope that it would be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with this program; if not, write the Free Software Foundation,
21 # Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
35 '''Return /dev/null in subprocess writable format.'''
37 from subprocess
import DEVNULL
40 return open(os
.devnull
, 'wb')
43 '''Map mountpoints to physical disks.'''
46 cmd
=['lsblk', '-o', 'KNAME,TYPE,FSTYPE,MOUNTPOINT', '-J']
47 result
= subprocess
.Popen(cmd
, stdout
=subprocess
.PIPE
)
49 if result
.returncode
!= 0:
51 sarray
= [x
.decode('utf-8') for x
in result
.stdout
.readlines()]
52 output
= ' '.join(sarray
)
53 bdevdata
= json
.loads(output
)
54 # The lsblk output had better be in disks-then-partitions order
55 for bdev
in bdevdata
['blockdevices']:
56 if bdev
['type'] in ('disk', 'loop'):
57 lastdisk
= bdev
['kname']
58 if bdev
['fstype'] == 'xfs':
59 mnt
= bdev
['mountpoint']
65 fs
[mnt
] = set([lastdisk
])
68 def kill_systemd(unit
, proc
):
69 '''Kill systemd unit.'''
71 cmd
=['systemctl', 'stop', unit
]
72 x
= subprocess
.Popen(cmd
)
75 def run_killable(cmd
, stdout
, killfuncs
, kill_fn
):
76 '''Run a killable program. Returns program retcode or -1 if we can't start it.'''
78 proc
= subprocess
.Popen(cmd
, stdout
= stdout
)
79 real_kill_fn
= lambda: kill_fn(proc
)
80 killfuncs
.add(real_kill_fn
)
83 killfuncs
.remove(real_kill_fn
)
86 return proc
.returncode
90 # systemd doesn't like unit instance names with slashes in them, so it
91 # replaces them with dashes when it invokes the service. However, it's not
92 # smart enough to convert the dashes to something else, so when it unescapes
93 # the instance name to feed to xfs_scrub, it turns all dashes into slashes.
94 # "/moo-cow" becomes "-moo-cow" becomes "/moo/cow", which is wrong. systemd
95 # actually /can/ escape the dashes correctly if it is told that this is a path
96 # (and not a unit name), but it didn't do this prior to January 2017, so fix
98 def systemd_escape(path
):
99 '''Escape a path to avoid mangled systemd mangling.'''
103 cmd
= ['systemd-escape', '--path', path
]
105 proc
= subprocess
.Popen(cmd
, stdout
= subprocess
.PIPE
)
107 for line
in proc
.stdout
:
108 return '-' + line
.decode(sys
.stdout
.encoding
).strip()
112 def run_scrub(mnt
, cond
, running_devs
, mntdevs
, killfuncs
):
113 '''Run a scrub process.'''
114 global retcode
, terminate
116 print("Scrubbing %s..." % mnt
)
123 # Try it the systemd way
124 cmd
=['systemctl', 'start', 'xfs_scrub@%s' % systemd_escape(mnt
)]
125 ret
= run_killable(cmd
, DEVNULL(), killfuncs
, \
126 lambda proc
: kill_systemd('xfs_scrub@%s' % mnt
, proc
))
127 if ret
== 0 or ret
== 1:
128 print("Scrubbing %s done, (err=%d)" % (mnt
, ret
))
136 # Invoke xfs_scrub manually
137 cmd
=['@sbindir@/xfs_scrub', '@scrub_args@', mnt
]
138 ret
= run_killable(cmd
, None, killfuncs
, \
139 lambda proc
: proc
.terminate())
141 print("Scrubbing %s done, (err=%d)" % (mnt
, ret
))
149 print("Unable to start scrub tool.")
152 running_devs
-= mntdevs
158 '''Find mounts, schedule scrub runs.'''
160 a
= (mnt
, cond
, running_devs
, devs
, killfuncs
)
161 thr
= threading
.Thread(target
= run_scrub
, args
= a
)
163 global retcode
, terminate
165 parser
= argparse
.ArgumentParser( \
166 description
= "Scrub all mounted XFS filesystems.")
167 parser
.add_argument("-V", help = "Report version and exit.", \
168 action
= "store_true")
169 args
= parser
.parse_args()
172 print("xfs_scrub_all version @pkg_version@")
177 # Tail the journal if we ourselves aren't a service...
179 if 'SERVICE_MODE' not in os
.environ
:
181 cmd
=['journalctl', '--no-pager', '-q', '-S', 'now', \
182 '-f', '-u', 'xfs_scrub@*', '-o', \
184 journalthread
= subprocess
.Popen(cmd
)
188 # Schedule scrub jobs...
191 cond
= threading
.Condition()
193 if len(running_devs
) == 0:
194 mnt
, devs
= fs
.popitem()
195 running_devs
.update(devs
)
202 if dev
in running_devs
:
206 running_devs
.update(devs
)
214 except KeyboardInterrupt:
216 print("Terminating...")
218 while len(killfuncs
) > 0:
224 if journalthread
is not None:
225 journalthread
.terminate()
227 # See the service mode comments in xfs_scrub.c for why we do this.
228 if 'SERVICE_MODE' in os
.environ
:
235 if __name__
== '__main__':