summaryrefslogtreecommitdiffstats
path: root/scrub/e2scrub_all.in
blob: f0593d8cb1912ffe93ca905b7567743b96f4e415 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
#!/bin/bash

#  Copyright (C) 2018 Oracle.  All Rights Reserved.
#
#  Author: Darrick J. Wong <darrick.wong@oracle.com>
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it would be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write the Free Software Foundation,
#  Inc.,  51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA.

PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

if (( $EUID != 0 )); then
    echo "e2scrub_all must be run as root"
    exit 1
fi

periodic_e2scrub=0
scrub_all=0
snap_size_mb=256
reap=0
conffile="@root_sysconfdir@/e2scrub.conf"

test -f "${conffile}" && . "${conffile}"

scrub_args=""

print_help() {
	echo "Usage: $0 [OPTIONS]"
	echo " -n: Show what commands e2scrub_all would execute."
	echo " -r: Remove e2scrub snapshots."
	echo " -A: Scrub all ext[234] filesystems even if not mounted."
	echo " -V: Print version information and exit."
}

print_version() {
	echo "e2scrub_all @E2FSPROGS_VERSION@ (@E2FSPROGS_DATE@)"
}

exitcode() {
	ret="$1"

	# If we're being run as a service, the return code must fit the LSB
	# init script action error guidelines, which is to say that we
	# compress all errors to 1 ("generic or unspecified error", LSB 5.0
	# section 22.2) and hope the admin will scan the log for what
	# actually happened.

	if [ -n "${SERVICE_MODE}" -a "${ret}" -ne 0 ]; then
		test "${ret}" -ne 0 && ret=1
	fi

	exit "${ret}"
}

while getopts "nrAV" opt; do
	case "${opt}" in
	"n") DBG="echo Would execute: " ;;
	"r") scrub_args="${scrub_args} -r"; reap=1;;
	"A") scrub_all=1;;
	"V") print_version; exitcode 0;;
	*) print_help; exitcode 2;;
	esac
done
shift "$((OPTIND - 1))"

# If we're in service mode and the service is not enabled via config file...
if [ -n "${SERVICE_MODE}" -a "${periodic_e2scrub}" -ne 1 ]; then
	# ...don't start e2scrub processes.
	if [ "${reap}" -eq 0 ]; then
		exitcode 0
	fi

	# ...and if we don't see any leftover e2scrub snapshots, don't
	# run the reaping process either, because lvs can be slow.
	if ! readlink -q -s -e /dev/mapper/*.e2scrub* > /dev/null; then
		exitcode 0
	fi
fi

# close file descriptor 3 (from cron) since it causes lvm to kvetch
exec 3<&-

# If some prerequisite packages are not installed, exit with a code
# indicating success to avoid spamming the sysadmin with fail messages
# when e2scrub_all is run out of cron or a systemd timer.

if ! type mapfile >& /dev/null ; then
    test -n "${SERVICE_MODE}" && exitcode 0
    echo "e2scrub_all: can't find mapfile --- is bash 4.xx installed?"
    exitcode 1
fi

if ! type lsblk >& /dev/null ; then
    test -n "${SERVICE_MODE}" && exitcode 0
    echo "e2scrub_all: can't find lsblk --- is util-linux installed?"
    exitcode 1
fi

if ! type lvcreate >& /dev/null ; then
    test -n "${SERVICE_MODE}" && exitcode 0
    echo "e2scrub_all: can't find lvcreate --- is lvm2 installed?"
    exitcode 1
fi

# Find scrub targets, make sure we only do this once.
ls_scan_targets() {
    local devices=$(lvs -o lv_path --noheadings -S "lv_active=active,lv_role=public,lv_role!=snapshot,vg_free>=${snap_size_mb}")

    if [ -z "$devices" ]; then
	return 0;
    fi
    lsblk -o NAME,MOUNTPOINT,FSTYPE,TYPE -P -n -p $devices | \
	grep FSTYPE=\"ext\[234\]\" | grep TYPE=\"lvm\" | \
	while read vars ; do
		eval "${vars}"

		# Skip unjournalled filesystems; they are inconsistent when
		# mounted
		dumpe2fs -h "${NAME}" | grep -q 'has_journal' || continue

		if [ "${scrub_all}" -eq 1 ] || [ -n "${MOUNTPOINT}" ]; then
		    echo ${MOUNTPOINT:-${NAME}}
		fi
	done
}

# Find leftover scrub snapshots
ls_reap_targets() {
    lvs -o lv_path -S lv_role=snapshot -S lv_name=~\(e2scrub$\) \
	--noheadings | sed -e 's/.e2scrub$//'
}

# Figure out what we're targeting
ls_targets() {
	if [ "${reap}" -eq 1 ]; then
		ls_reap_targets
	else
		ls_scan_targets
	fi
}

# Turn our mount path into a service name that systemd will recognize
escape_path_for_systemd() {
	local path="$1"
	systemd-escape --template 'e2scrub@.service' --path "${path}"
}

# Scrub any mounted fs on lvm by creating a snapshot and fscking that.
mapfile -t targets < <(ls_targets)
for tgt in "${targets[@]}"; do
	# If we're not reaping and systemd is present, try invoking the
	# systemd service.
	if [ "${reap}" -ne 1 ] && [ -e /run/systemd/system ]; then
		svcname="$(escape_path_for_systemd "${tgt}")"
		${DBG} systemctl start "${svcname}" 2> /dev/null
		res=$?
		if [ "${res}" -eq 0 ] || [ "${res}" -eq 1 ]; then
			continue;
		fi
	fi

	# Otherwise use direct invocation
	${DBG} "@root_sbindir@/e2scrub" ${scrub_args} "${tgt}"
done

exitcode 0