2 # I, Ian Kelling, follow the GNU license recommendations at
3 # https://www.gnu.org/licenses/license-recommendations.en.html. They
4 # recommend that small programs, < 300 lines, be licensed under the
5 # Apache License 2.0. This file contains or is part of one or more small
6 # programs. If a small program grows beyond 300 lines, I plan to switch
9 # Copyright 2024 Ian Kelling
11 # Licensed under the Apache License, Version 2.0 (the "License");
12 # you may not use this file except in compliance with the License.
13 # You may obtain a copy of the License at
15 # http://www.apache.org/licenses/LICENSE-2.0
17 # Unless required by applicable law or agreed to in writing, software
18 # distributed under the License is distributed on an "AS IS" BASIS,
19 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 # See the License for the specific language governing permissions and
21 # limitations under the License.
25 [[ $EUID == 0 ]] ||
exec sudo
-E "${BASH_SOURCE[0]}" "$@"
27 set -e; .
/usr
/local
/lib
/bash-bear
; set +e
30 # https://github.com/kdave/btrfsmaintenance
32 if [[ $INVOCATION_ID ]]; then
34 exim
-odf -i root
<<EOF
35 From: root@$(hostname -f)
36 To: root@$(hostname -f)
37 Subject: btrfsmaint automatically exited on command error
39 journalctl -u btrfsmaint -n 50:
40 $(journalctl -u btrfsmaint -n 50)
56 type -p xscreensaver-command
&>/dev
/null ||
return 0
57 export XAUTHORITY
=/home
/iank
/.Xauthority
60 if lock_info
=$
(xscreensaver-command
-time); then
61 if [[ $lock_info != *non-blanked
* ]]; then
71 Usage: ${0##*/} [OPTIONS]
72 Do btrfs maintence or stop if we have X and xprintidle shows a user
74 Normally, no options are needed.
76 --check Only check if an existing maintence should be cancelled due to
77 nonidle user and run in a loop every 20 seconds for 10
80 --dryrun Just print out what we would do.
82 --force Run regardless of user idle status on all disks and do scrub
83 regardless of when it was last run.
84 --no-stats Avoid checking error statistics. Use this to avoid a rare race
85 condition when running --check concurrently with normal run.
90 Note: Uses util-linux getopt option parsing: spaces between args and
91 options, short options can be combined, options before args.
96 ##### begin command line parsing ########
98 # ensure we can handle args with spaces or empty.
99 ret
=0; getopt
-T || ret
=$?
100 [[ $ret == 4 ]] ||
{ echo "Install util-linux for enhanced getopt" >&2; exit 1; }
107 temp
=$
(getopt
-l help,check
,dryrun
,force
,no-stats h
"$@") || usage
1
111 --check) check
=true
;;
112 --dryrun) dryrun
=true
;;
113 --force) force
=true
;;
114 --no-stats) stats
=false
;;
117 *) echo "$0: unexpected args: $*" >&2 ; usage
1 ;;
121 readonly check dryrun force stats
122 ##### end command line parsing ########
131 # When the cron kicks in, we may not be idle (physically sleeping) yet, so
133 while ! $locked && (( min
< max_min
)); do
138 # If we've waited a really long time for idle, just give up.
139 if (( min
== max_min
)); then
146 fnd
="findmnt --types btrfs --noheading"
147 for x
in $
($fnd --output "SOURCE" --nofsroot |
sort -u); do
148 mnt
=$
($fnd --output "TARGET" --first-only --source $x)
149 [[ $mnt ]] ||
continue
151 #### begin look for diff in stats, eg: increasing error count ####
154 # ${mnt%/} so that if mnt is / we avoid making a buggy looking path
155 stats_path
=${mnt%/}/btrfs-dev-stats
156 if [[ ! -e $stats_path ]]; then
157 btrfs dev stats
-c $mnt >$stats_path ||
: # populate initial reading
158 elif ! btrfs dev stats
-c $mnt >$tmp; then
159 if ! diff -q $stats_path $tmp; then
160 mv $stats_path $stats_path.1
161 cat $tmp >$stats_path
162 diff=$
(diff -u $stats_path $tmp 2>&1 ||
:)
163 printf "diff of: btrfs dev stats -c %s\n%s\n" "$mnt" "$diff"
164 exim
-odf -i root
<<EOF
165 From: root@$(hostname -f)
166 To: root@$(hostname -f)
167 Subject: btrfsmaint: device stats changed for $mnt
169 diff of: btrfs dev stats -c $mnt
176 #### end look for diff in stats, eg: increasing error count ####
181 echo "$0: not idle. if this wasnt a dry run, btrfs scrub cancel $mnt"
183 btrfs scrub cancel
$mnt &>/dev
/null ||
:
189 # for comparing before and after balance.
190 # the log is already fairly verbose, so commented.
191 # e btrfs filesystem df $mnt
193 if btrfs filesystem df
$mnt |
grep -q "Data+Metadata"; then
194 for usage
in $dusage; do
195 e ionice
-c 3 btrfs balance start
-dusage=$usage -musage=$usage $mnt
198 e ionice
-c 3 btrfs balance start
-dusage=0 $mnt
199 for usage
in $dusage; do
200 e ionice
-c 3 btrfs balance start
-dusage=$usage $mnt
202 e ionice
-c 3 btrfs balance start
-musage=0 $mnt
203 for usage
in $musage; do
204 e ionice
-c 3 btrfs balance start
-musage=$usage $mnt
208 scrub_status
=$
(btrfs scrub status
$mnt)
209 if printf "%s\n" "$scrub_status" |
grep -i '^status:[[:space:]]*finished$' &>/dev
/null
; then
210 date=$
(printf "%s\n" "$scrub_status" |
sed -rn 's/^Scrub started:[[:space:]]*(.*)/\1/p')
212 if [[ ! $date ]]; then
213 # output from older versions, at least btrfs v4.15.1
215 printf "%s\n" "$scrub_status" | \
216 sed -rn 's/^\s*scrub started at (.*) and finished.*/\1/p'
219 if ! $force && [[ $date ]]; then
221 echo "$0: last scrub finish for $mnt: $date"
223 date=$
(date --date="$date" +%s
)
224 # if date is sooner than 60 days ago
225 # the wiki recommends 30 days or so, but
226 # I'm going with 60 days.
227 if (( date > EPOCHSECONDS
- 60*60*24*60 )); then
229 echo "$0: skiping scrub of $mnt, last was $(( (EPOCHSECONDS - date) / 60/60/24 )) days ago, < 30 days"
234 # btrfsmaintenance does -c 2 -n 4, but I want lowest pri.
235 e btrfs scrub start
-Bd -c 3 $mnt
237 # We normally only do one disk since this is meant to be run in
238 # downtime and if we try to do all disks, we invariably end up doing
239 # a scrub after downtime. So, just do one disk per day.