aboutsummaryrefslogtreecommitdiffstats
path: root/scrub
diff options
context:
space:
mode:
authorDarrick J. Wong <darrick.wong@oracle.com>2018-03-23 18:57:09 -0700
committerTheodore Ts'o <tytso@mit.edu>2018-08-05 14:43:12 -0400
commit5ce368f07cb2e53d8b6d5aaeaa3ca47029c8a1d6 (patch)
treecf6074d9cf26d040103b805beef3e6f63da65182 /scrub
parent2b17b98e41180d17b4916a9e13a2a891906653a7 (diff)
downloadandroid_external_e2fsprogs-5ce368f07cb2e53d8b6d5aaeaa3ca47029c8a1d6.tar.gz
android_external_e2fsprogs-5ce368f07cb2e53d8b6d5aaeaa3ca47029c8a1d6.tar.bz2
android_external_e2fsprogs-5ce368f07cb2e53d8b6d5aaeaa3ca47029c8a1d6.zip
e2scrub: create online fsck tool of sorts
Implement online fsck for ext* filesystems which live on LVM-managed logical volumes. The basic strategy mirrors that of e2croncheck -- create a snapshot, fsck the snapshot, report whatever errors appear, remove snapshot. Unlike e2croncheck, this utility accepts any LVM device path, knows about snapshots running out of space, and can call fstrim having validated that the fs metadata is ok. Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com> Signed-off-by: Theodore Ts'o <tytso@mit.edu>
Diffstat (limited to 'scrub')
-rw-r--r--scrub/Makefile.in97
-rw-r--r--scrub/e2scrub.8.in60
-rw-r--r--scrub/e2scrub.conf.in10
-rw-r--r--scrub/e2scrub.in239
-rw-r--r--scrub/e2scrub.rules.in2
5 files changed, 408 insertions, 0 deletions
diff --git a/scrub/Makefile.in b/scrub/Makefile.in
new file mode 100644
index 00000000..a8bb06bc
--- /dev/null
+++ b/scrub/Makefile.in
@@ -0,0 +1,97 @@
+#
+# Makefile for e2scrub
+#
+
+srcdir = @srcdir@
+top_srcdir = @top_srcdir@
+VPATH = @srcdir@
+top_builddir = ..
+my_dir = scrub
+INSTALL = @INSTALL@
+
+@MCONFIG@
+
+PROGS= e2scrub
+MANPAGES= e2scrub.8
+CONFFILES= e2scrub.conf
+
+ifeq ($(HAVE_UDEV),yes)
+UDEV_RULES = e2scrub.rules
+INSTALLDIRS_TGT += installdirs-udev
+INSTALL_TGT += install-udev
+UNINSTALL_TGT += uninstall-udev
+endif
+
+all:: $(PROGS) $(MANPAGES) $(CONFFILES) $(UDEV_RULES)
+
+e2scrub: $(DEP_SUBSTITUTE) e2scrub.in
+ $(E) " SUBST $@"
+ $(Q) $(SUBSTITUTE_UPTIME) $(srcdir)/e2scrub.in $@
+ $(Q) chmod a+x $@
+
+%.8: %.8.in $(DEP_SUBSTITUTE)
+ $(E) " SUBST $@"
+ $(Q) $(SUBSTITUTE_UPTIME) $< $@
+
+%.conf: %.conf.in $(DEP_SUBSTITUTE)
+ $(E) " SUBST $@"
+ $(Q) $(SUBSTITUTE_UPTIME) $< $@
+
+%.rules: %.rules.in $(DEP_SUBSTITUTE)
+ $(E) " SUBST $@"
+ $(Q) $(SUBSTITUTE_UPTIME) $< $@
+
+installdirs-udev:
+ $(E) " MKINSTALLDIRS $(UDEV_RULES_DIR)"
+ $(Q) $(MKINSTALLDIRS) $(DESTDIR)$(UDEV_RULES_DIR)
+
+installdirs: $(INSTALLDIRS_TGT)
+ $(E) " MKINSTALLDIRS $(root_sbindir) $(man8dir) $(root_sysconfdir)"
+ $(Q) $(MKINSTALLDIRS) $(DESTDIR)$(root_sbindir) \
+ $(DESTDIR)$(man8dir) $(DESTDIR)$(root_sysconfdir)
+
+install-udev:
+ $(Q) for i in $(UDEV_RULES); do \
+ $(ES) " INSTALL $(UDEV_RULES_DIR)/$$i"; \
+ $(INSTALL_PROGRAM) $$i $(DESTDIR)$(UDEV_RULES_DIR)/96-$$i; \
+ done
+
+install: $(PROGS) $(MANPAGES) $(FMANPAGES) installdirs $(INSTALL_TGT)
+ $(Q) for i in $(PROGS); do \
+ $(ES) " INSTALL $(root_sbindir)/$$i"; \
+ $(INSTALL_PROGRAM) $$i $(DESTDIR)$(root_sbindir)/$$i; \
+ done
+ $(Q) for i in $(MANPAGES); do \
+ for j in $(COMPRESS_EXT); do \
+ $(RM) -f $(DESTDIR)$(man8dir)/$$i.$$j; \
+ done; \
+ $(ES) " INSTALL_DATA $(man8dir)/$$i"; \
+ $(INSTALL_DATA) $$i $(DESTDIR)$(man8dir)/$$i; \
+ done
+ $(Q) for i in $(CONFFILES); do \
+ $(ES) " INSTALL_DATA $(root_sysconfdir)/$$i"; \
+ $(INSTALL_DATA) $$i $(DESTDIR)$(root_sysconfdir)/$$i; \
+ done
+
+uninstall-udev:
+ for i in $(UDEV_RULES); do \
+ $(RM) -f $(DESTDIR)$(UDEV_RULES_DIR)/96-$$i; \
+ done
+
+uninstall: $(UNINSTALL_TGT)
+ for i in $(PROGS); do \
+ $(RM) -f $(DESTDIR)$(root_sbindir)/$$i; \
+ done
+ for i in $(MANPAGES); do \
+ $(RM) -f $(DESTDIR)$(man8dir)/$$i; \
+ done
+ for i in $(CONFFILES); do \
+ $(RM) -f $(DESTDIR)$(root_sysconfdir)/$$i; \
+ done
+
+clean::
+ $(RM) -f $(PROGS)
+
+mostlyclean: clean
+distclean: clean
+ $(RM) -f .depend Makefile $(srcdir)/TAGS $(srcdir)/Makefile.in.old
diff --git a/scrub/e2scrub.8.in b/scrub/e2scrub.8.in
new file mode 100644
index 00000000..ff03523e
--- /dev/null
+++ b/scrub/e2scrub.8.in
@@ -0,0 +1,60 @@
+.TH E2SCRUB 8 "@E2FSPROGS_MONTH@ @E2FSPROGS_YEAR@" "E2fsprogs version @E2FSPROGS_VERSION@"
+.SH NAME
+e2scrub - check the contents of a mounted ext[234] filesystem
+.SH SYNOPSYS
+.B
+e2scrub [OPTION] MOUNTPOINT | DEVICE
+.SH DESCRIPTION
+.B e2scrub
+attempts to check (but not repair) all metadata in a mounted ext[234]
+filesystem if the filesystem resides on a LVM logical volume.
+The block device of the LVM logical volume can also be passed in.
+
+This program snapshots the volume and runs a file system check on the snapshot
+to look for corruption errors.
+The LVM volume group must have at least 256MiB of unallocated space to
+dedicate to the snapshot or the logical volume will be skipped.
+The snapshot will be named
+.IR lvname ".e2scrub"
+and
+.B udev
+will not create symbolic links to it under
+.IR /dev/disk .
+Every attempt will be made to remove the snapshots prior to running
+.BR e2scrub ,
+but in a dire situation it may be necessary to remove the snapshot manually.
+
+If no errors are found,
+.B fstrim
+can be called on the file system if it is mounted.
+If errors are found, the file system will be marked as having errors.
+The filesystem should be taken offline and
+.B e2fsck
+run as soon as possible, because
+.B e2scrub
+does not fix corruptions.
+If the filesystem is not repaired,
+.B e2fsck
+will be run before the next mount.
+.SH OPTIONS
+.TP
+\fB-r\fR
+Remove the e2scrub snapshot and exit without checking anything.
+.TP
+\fB-t\fR
+Run
+.B
+fstrim(1)
+on the mounted filesystem if no errors are found.
+.TP
+\fB-V\fR
+Print version information and exit.
+.SH EXIT CODE
+The exit codes are the same as in
+.BR e2fsck (8)
+.SH SEE ALSO
+.BR e2fsck (8)
+.SH AUTHOR
+Darrick J. Wong <darrick.wong@oracle.com>
+.SH COPYRIGHT
+Copyright ©2018 Oracle. License is GPLv2+. <http://www.gnu.org/licenses/gpl-2.0.html>
diff --git a/scrub/e2scrub.conf.in b/scrub/e2scrub.conf.in
new file mode 100644
index 00000000..d578cc72
--- /dev/null
+++ b/scrub/e2scrub.conf.in
@@ -0,0 +1,10 @@
+# e2scrub configuration file
+
+# Snapshots will be created to run fsck; the snapshot will be of this size.
+# snap_size_mb=256
+
+# Set this to 1 to enable fstrim for everyone.
+# fstrim=0
+
+# Arguments passed into e2fsck.
+# e2fsck_opts="-vtt"
diff --git a/scrub/e2scrub.in b/scrub/e2scrub.in
new file mode 100644
index 00000000..08b36002
--- /dev/null
+++ b/scrub/e2scrub.in
@@ -0,0 +1,239 @@
+#!/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.
+
+# Automatically check a LVM-managed filesystem online.
+# We use lvm snapshots to do this, which means that we can only
+# check filesystems in VGs that have at least 256MB (or so) of
+# free space.
+
+snap_size_mb=256
+fstrim=0
+reap=0
+e2fsck_opts=""
+conffile="@root_sysconfdir@/e2scrub.conf"
+
+test -f "${conffile}" && . "${conffile}"
+
+print_help() {
+ echo "Usage: $0 [OPTIONS] mountpoint | device"
+ echo
+ echo "mountpoint must be on a LVM-managed block device"
+ echo "-r: Remove e2scrub snapshot and exit, do not check anything."
+ echo "-t: Run fstrim if successful."
+ echo "-V: Print version information and exit."
+}
+
+print_version() {
+ echo "e2scrub @E2FSPROGS_VERSION@ (@E2FSPROGS_DATE@)"
+}
+
+while getopts "rtV" opt; do
+ case "${opt}" in
+ "r") reap=1;;
+ "t") fstrim=1;;
+ "V") print_version; exit 0;;
+ *) print_help; exit 2;;
+ esac
+done
+shift "$((OPTIND - 1))"
+
+arg="$1"
+if [ -z "${arg}" ]; then
+ print_help
+ exit 1
+fi
+
+# Find the device for a given mountpoint
+dev_from_mount() {
+ local mountpt="$(realpath "$1")"
+
+ lsblk -o NAME,FSTYPE,MOUNTPOINT -p -P -n 2> /dev/null | while read vars; do
+ eval "${vars}"
+ if [ "${mountpt}" != "${MOUNTPOINT}" ]; then
+ continue
+ fi
+ case "${FSTYPE}" in
+ ext[234])
+ echo "${NAME}"
+ return 0
+ ;;
+ esac
+ done
+ return 1
+}
+
+# Check a device argument
+dev_from_arg() {
+ local dev="$1"
+ local fstype="$(lsblk -o FSTYPE -n "${dev}" 2> /dev/null)"
+
+ case "${fstype}" in
+ ext[234])
+ echo "${dev}"
+ return 0
+ ;;
+ esac
+ return 1
+}
+
+mnt_from_dev() {
+ local dev="$1"
+
+ if [ -n "${dev}" ]; then
+ lsblk -o MOUNTPOINT -n "${dev}"
+ fi
+}
+
+# Construct block device path and mountpoint from argument
+if [ -b "${arg}" ]; then
+ dev="$(dev_from_arg "${arg}")"
+ mnt="$(mnt_from_dev "${dev}")"
+else
+ dev="$(dev_from_mount "${arg}")"
+ mnt="${arg}"
+fi
+if [ ! -e "${dev}" ]; then
+ echo "${arg}: Not an ext[234] filesystem."
+ print_help
+ exit 16
+fi
+
+# Make sure this is an LVM device we can snapshot
+lvm_vars="$(lvs --nameprefixes -o name,vgname,lv_role --noheadings "${dev}" 2> /dev/null)"
+eval "${lvm_vars}"
+if [ -z "${LVM2_VG_NAME}" ] || [ -z "${LVM2_LV_NAME}" ] ||
+ echo "${LVM2_LV_ROLE}" | grep -q "snapshot"; then
+ echo "${arg}: Not connnected to a LVM logical volume."
+ print_help
+ exit 16
+fi
+start_time="$(date +'%Y%m%d%H%M%S')"
+snap="${LVM2_LV_NAME}.e2scrub"
+snap_dev="/dev/${LVM2_VG_NAME}/${snap}"
+
+teardown() {
+ # Remove and wait for removal to succeed.
+ ${DBG} lvremove -f "${LVM2_VG_NAME}/${snap}" 3>&-
+ while [ -e "${snap_dev}" ] && [ "$?" -eq "5" ]; do
+ sleep 0.5
+ ${DBG} lvremove -f "${LVM2_VG_NAME}/${snap}" 3>&-
+ done
+}
+
+check() {
+ # First we recover the journal, then we see if e2fsck tries any
+ # non-optimization repairs. If either of these two returns a
+ # non-zero status (errors fixed or remaining) then this fs is bad.
+ E2FSCK_FIXES_ONLY=1
+ export E2FSCK_FIXES_ONLY
+ ${DBG} "@root_sbindir@/e2fsck" -E journal_only -p ${e2fsck_opts} "${snap_dev}" || return $?
+ ${DBG} "@root_sbindir@/e2fsck" -f -y ${e2fsck_opts} "${snap_dev}"
+}
+
+mark_clean() {
+ ${DBG} "@root_sbindir@/tune2fs" -C 0 -T "${start_time}" "${dev}"
+}
+
+mark_corrupt() {
+ ${DBG} "@root_sbindir@/tune2fs" -E force_fsck "${dev}"
+}
+
+setup() {
+ # Try to remove snapshot for 30s, bail out if we can't remove it.
+ lveremove_deadline="$(( $(date "+%s") + 30))"
+ ${DBG} lvremove -f "${LVM2_VG_NAME}/${snap}" 3>&- 2>/dev/null
+ while [ -e "${snap_dev}" ] && [ "$?" -eq "5" ] &&
+ [ "$(date "+%s")" -lt "${lvremove_deadline}" ]; do
+ sleep 0.5
+ ${DBG} lvremove -f "${LVM2_VG_NAME}/${snap}" 3>&-
+ done
+ if [ -e "${snap_dev}" ]; then
+ echo "${arg}: e2scrub snapshot is in use, cannot check!"
+ return 1
+ fi
+ # Create the snapshot, wait for device to appear.
+ ${DBG} lvcreate -s -L "${snap_size_mb}m" -n "${snap}" "${LVM2_VG_NAME}/${LVM2_LV_NAME}" 3>&-
+ if [ $? -ne 0 ]; then
+ echo "${arg}: e2scrub snapshot FAILED, will not check!"
+ return 1
+ fi
+ ${DBG} udevadm settle 2> /dev/null
+ return 0
+}
+
+if [ "${reap}" -gt 0 ]; then
+ if [ -e "${snap_dev}" ]; then
+ teardown 2> /dev/null
+ fi
+ exit 0
+fi
+if ! setup; then
+ exit 8
+fi
+trap "teardown; exit 1" EXIT INT QUIT TERM
+
+# Check and react
+check
+case "$?" in
+"0")
+ # Clean check!
+ echo "${arg}: Scrub succeeded."
+ mark_clean
+ teardown
+ trap '' EXIT
+
+ # Trim the free space, which requires the snapshot be deleted.
+ if [ "${fstrim}" -eq 1 ] && [ -d "${mnt}" ] && type fstrim > /dev/null 2>&1; then
+ echo "${arg}: Trimming free space."
+ fstrim -v "${mnt}"
+ fi
+
+ ret=0
+ ;;
+"8")
+ # Operational error, what now?
+ echo "${arg}: e2fsck operational error."
+ teardown
+ trap '' EXIT
+ ret=8
+ ;;
+*)
+ # fsck failed. Check if the snapshot is invalid; if so, make a
+ # note of that at the end of the log. This isn't necessarily a
+ # failure because the mounted fs could have overflowed the
+ # snapshot with regular disk writes /or/ our repair process
+ # could have done it by repairing too much.
+ #
+ # If it's really corrupt we ought to fsck at next boot.
+ is_invalid="$(lvs -o lv_snapshot_invalid --noheadings "${snap_dev}" | awk '{print $1}')"
+ if [ -n "${is_invalid}" ]; then
+ echo "${arg}: Scrub FAILED due to invalid snapshot."
+ ret=8
+ else
+ echo "${arg}: Scrub FAILED due to corruption! Unmount and run e2fsck -y."
+ mark_corrupt
+ ret=6
+ fi
+ teardown
+ trap '' EXIT
+ ;;
+esac
+
+exit "${ret}"
diff --git a/scrub/e2scrub.rules.in b/scrub/e2scrub.rules.in
new file mode 100644
index 00000000..5e1b35ba
--- /dev/null
+++ b/scrub/e2scrub.rules.in
@@ -0,0 +1,2 @@
+# Try to hide our fsck snapshots from udev's /dev/disk linking...
+ACTION=="add|change", ENV{DM_LV_NAME}=="*.e2scrub", OPTIONS="link_priority=-100"