Whamcloud - gitweb
e2scrub: create online fsck tool of sorts
[tools/e2fsprogs.git] / scrub / e2scrub.in
1 #!/bin/bash
2
3 #  Copyright (C) 2018 Oracle.  All Rights Reserved.
4 #
5 #  Author: Darrick J. Wong <darrick.wong@oracle.com>
6 #
7 #  This program is free software; you can redistribute it and/or
8 #  modify it under the terms of the GNU General Public License
9 #  as published by the Free Software Foundation; either version 2
10 #  of the License, or (at your option) any later version.
11 #
12 #  This program is distributed in the hope that it would be useful,
13 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #  GNU General Public License for more details.
16 #
17 #  You should have received a copy of the GNU General Public License
18 #  along with this program; if not, write the Free Software Foundation,
19 #  Inc.,  51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA.
20
21 # Automatically check a LVM-managed filesystem online.
22 # We use lvm snapshots to do this, which means that we can only
23 # check filesystems in VGs that have at least 256MB (or so) of
24 # free space.
25
26 snap_size_mb=256
27 fstrim=0
28 reap=0
29 e2fsck_opts=""
30 conffile="@root_sysconfdir@/e2scrub.conf"
31
32 test -f "${conffile}" && . "${conffile}"
33
34 print_help() {
35         echo "Usage: $0 [OPTIONS] mountpoint | device"
36         echo
37         echo "mountpoint must be on a LVM-managed block device"
38         echo "-r: Remove e2scrub snapshot and exit, do not check anything."
39         echo "-t: Run fstrim if successful."
40         echo "-V: Print version information and exit."
41 }
42
43 print_version() {
44         echo "e2scrub @E2FSPROGS_VERSION@ (@E2FSPROGS_DATE@)"
45 }
46
47 while getopts "rtV" opt; do
48         case "${opt}" in
49         "r") reap=1;;
50         "t") fstrim=1;;
51         "V") print_version; exit 0;;
52         *) print_help; exit 2;;
53         esac
54 done
55 shift "$((OPTIND - 1))"
56
57 arg="$1"
58 if [ -z "${arg}" ]; then
59         print_help
60         exit 1
61 fi
62
63 # Find the device for a given mountpoint
64 dev_from_mount() {
65         local mountpt="$(realpath "$1")"
66
67         lsblk -o NAME,FSTYPE,MOUNTPOINT -p -P -n 2> /dev/null | while read vars; do
68                 eval "${vars}"
69                 if [ "${mountpt}" != "${MOUNTPOINT}" ]; then
70                         continue
71                 fi
72                 case "${FSTYPE}" in
73                 ext[234])
74                         echo "${NAME}"
75                         return 0
76                         ;;
77                 esac
78         done
79         return 1
80 }
81
82 # Check a device argument
83 dev_from_arg() {
84         local dev="$1"
85         local fstype="$(lsblk -o FSTYPE -n "${dev}" 2> /dev/null)"
86
87         case "${fstype}" in
88         ext[234])
89                 echo "${dev}"
90                 return 0
91                 ;;
92         esac
93         return 1
94 }
95
96 mnt_from_dev() {
97         local dev="$1"
98
99         if [ -n "${dev}" ]; then
100                 lsblk -o MOUNTPOINT -n "${dev}"
101         fi
102 }
103
104 # Construct block device path and mountpoint from argument
105 if [ -b "${arg}" ]; then
106         dev="$(dev_from_arg "${arg}")"
107         mnt="$(mnt_from_dev "${dev}")"
108 else
109         dev="$(dev_from_mount "${arg}")"
110         mnt="${arg}"
111 fi
112 if [ ! -e "${dev}" ]; then
113         echo "${arg}: Not an ext[234] filesystem."
114         print_help
115         exit 16
116 fi
117
118 # Make sure this is an LVM device we can snapshot
119 lvm_vars="$(lvs --nameprefixes -o name,vgname,lv_role --noheadings "${dev}" 2> /dev/null)"
120 eval "${lvm_vars}"
121 if [ -z "${LVM2_VG_NAME}" ] || [ -z "${LVM2_LV_NAME}" ] ||
122    echo "${LVM2_LV_ROLE}" | grep -q "snapshot"; then
123         echo "${arg}: Not connnected to a LVM logical volume."
124         print_help
125         exit 16
126 fi
127 start_time="$(date +'%Y%m%d%H%M%S')"
128 snap="${LVM2_LV_NAME}.e2scrub"
129 snap_dev="/dev/${LVM2_VG_NAME}/${snap}"
130
131 teardown() {
132         # Remove and wait for removal to succeed.
133         ${DBG} lvremove -f "${LVM2_VG_NAME}/${snap}" 3>&-
134         while [ -e "${snap_dev}" ] && [ "$?" -eq "5" ]; do
135                 sleep 0.5
136                 ${DBG} lvremove -f "${LVM2_VG_NAME}/${snap}" 3>&-
137         done
138 }
139
140 check() {
141         # First we recover the journal, then we see if e2fsck tries any
142         # non-optimization repairs.  If either of these two returns a
143         # non-zero status (errors fixed or remaining) then this fs is bad.
144         E2FSCK_FIXES_ONLY=1
145         export E2FSCK_FIXES_ONLY
146         ${DBG} "@root_sbindir@/e2fsck" -E journal_only -p ${e2fsck_opts} "${snap_dev}" || return $?
147         ${DBG} "@root_sbindir@/e2fsck" -f -y ${e2fsck_opts} "${snap_dev}"
148 }
149
150 mark_clean() {
151         ${DBG} "@root_sbindir@/tune2fs" -C 0 -T "${start_time}" "${dev}"
152 }
153
154 mark_corrupt() {
155         ${DBG} "@root_sbindir@/tune2fs" -E force_fsck "${dev}"
156 }
157
158 setup() {
159         # Try to remove snapshot for 30s, bail out if we can't remove it.
160         lveremove_deadline="$(( $(date "+%s") + 30))"
161         ${DBG} lvremove -f "${LVM2_VG_NAME}/${snap}" 3>&- 2>/dev/null
162         while [ -e "${snap_dev}" ] && [ "$?" -eq "5" ] &&
163               [ "$(date "+%s")" -lt "${lvremove_deadline}" ]; do
164                 sleep 0.5
165                 ${DBG} lvremove -f "${LVM2_VG_NAME}/${snap}" 3>&-
166         done
167         if [ -e "${snap_dev}" ]; then
168                 echo "${arg}: e2scrub snapshot is in use, cannot check!"
169                 return 1
170         fi
171         # Create the snapshot, wait for device to appear.
172         ${DBG} lvcreate -s -L "${snap_size_mb}m" -n "${snap}" "${LVM2_VG_NAME}/${LVM2_LV_NAME}" 3>&-
173         if [ $? -ne 0 ]; then
174                 echo "${arg}: e2scrub snapshot FAILED, will not check!"
175                 return 1
176         fi
177         ${DBG} udevadm settle 2> /dev/null
178         return 0
179 }
180
181 if [ "${reap}" -gt 0 ]; then
182         if [ -e "${snap_dev}" ]; then
183                 teardown 2> /dev/null
184         fi
185         exit 0
186 fi
187 if ! setup; then
188         exit 8
189 fi
190 trap "teardown; exit 1" EXIT INT QUIT TERM
191
192 # Check and react
193 check
194 case "$?" in
195 "0")
196         # Clean check!
197         echo "${arg}: Scrub succeeded."
198         mark_clean
199         teardown
200         trap '' EXIT
201
202         # Trim the free space, which requires the snapshot be deleted.
203         if [ "${fstrim}" -eq 1 ] && [ -d "${mnt}" ] && type fstrim > /dev/null 2>&1; then
204                 echo "${arg}: Trimming free space."
205                 fstrim -v "${mnt}"
206         fi
207
208         ret=0
209         ;;
210 "8")
211         # Operational error, what now?
212         echo "${arg}: e2fsck operational error."
213         teardown
214         trap '' EXIT
215         ret=8
216         ;;
217 *)
218         # fsck failed.  Check if the snapshot is invalid; if so, make a
219         # note of that at the end of the log.  This isn't necessarily a
220         # failure because the mounted fs could have overflowed the
221         # snapshot with regular disk writes /or/ our repair process
222         # could have done it by repairing too much.
223         #
224         # If it's really corrupt we ought to fsck at next boot.
225         is_invalid="$(lvs -o lv_snapshot_invalid --noheadings "${snap_dev}" | awk '{print $1}')"
226         if [ -n "${is_invalid}" ]; then
227                 echo "${arg}: Scrub FAILED due to invalid snapshot."
228                 ret=8
229         else
230                 echo "${arg}: Scrub FAILED due to corruption!  Unmount and run e2fsck -y."
231                 mark_corrupt
232                 ret=6
233         fi
234         teardown
235         trap '' EXIT
236         ;;
237 esac
238
239 exit "${ret}"