diff --git a/MCONFIG.in b/MCONFIG.in
index 22b74eb..a244728 100644
--- a/MCONFIG.in
+++ b/MCONFIG.in
@@ -33,6 +33,9 @@ infodir = @infodir@
 datadir = @datadir@
 pkgconfigdir = $(libdir)/pkgconfig
 
+HAVE_UDEV = @have_udev@
+UDEVRULESDIR = @udevrulesdir@
+
 @SET_MAKE@
 
 @ifGNUmake@ V =
diff --git a/Makefile.in b/Makefile.in
index 37b6069..ddd94ec 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -13,10 +13,11 @@ INSTALL = @INSTALL@
 @DEBUGFS_CMT@DEBUGFS_DIR= debugfs
 @UUID_CMT@UUID_LIB_SUBDIR= lib/uuid
 @BLKID_CMT@BLKID_LIB_SUBDIR= lib/blkid
+@E2SCRUB_CMT@E2SCRUB_DIR= scrub
 SUPPORT_LIB_SUBDIR= lib/support
 
 LIB_SUBDIRS=lib/et lib/ss lib/e2p $(UUID_LIB_SUBDIR) $(BLKID_LIB_SUBDIR) $(SUPPORT_LIB_SUBDIR) lib/ext2fs intl
-PROG_SUBDIRS=e2fsck $(DEBUGFS_DIR) misc $(RESIZE_DIR) tests/progs po
+PROG_SUBDIRS=e2fsck $(DEBUGFS_DIR) misc $(RESIZE_DIR) tests/progs po $(E2SCRUB_DIR)
 SUBDIRS=util $(LIB_SUBDIRS) $(PROG_SUBDIRS) tests
 
 SUBS= util/subst.conf lib/config.h $(top_builddir)/lib/dirpaths.h \
diff --git a/configure b/configure
index b62da1b..c911469 100755
--- a/configure
+++ b/configure
@@ -625,6 +625,8 @@ gl_use_threads_default=
 ac_func_list=
 ac_subst_vars='LTLIBOBJS
 LIBOBJS
+udevrulesdir
+have_udev
 LDFLAGS_SHLIB
 CFLAGS_STLIB
 CFLAGS_SHLIB
@@ -639,6 +641,7 @@ root_libdir
 root_sbindir
 root_bindir
 root_prefix
+E2SCRUB_CMT
 UNIX_CMT
 CYGWIN_CMT
 LINUX_CMT
@@ -13711,6 +13714,8 @@ esac
 
 
 
+E2SCRUB_CMT="$LINUX_CMT"
+
 case "$host_os" in
 linux* | gnu* | k*bsd*-gnu)
 	if test "$prefix" = NONE -a "$root_prefix" = NONE ; then
@@ -13876,6 +13881,20 @@ LDFLAGS_SHLIB=${LDFLAGS_SHLIB:-$LDFLAGS}
 
 
 
+
+pkg_udevrulesdir="$(pkg-config --variable=udevdir udev 2>/dev/null)/rules"
+case "${pkg_udevrulesdir}" in
+"")
+	udevrulesdir=""
+	have_udev=no
+	;;
+*)
+	udevrulesdir="${pkg_udevrulesdir}"
+	have_udev=yes
+	;;
+esac
+
+
 test -d lib || mkdir lib
 test -d include || mkdir include
 test -d include/linux || mkdir include/linux
@@ -13897,7 +13916,7 @@ for i in MCONFIG Makefile e2fsprogs.spec \
 	misc/Makefile ext2ed/Makefile e2fsck/Makefile \
 	debugfs/Makefile tests/Makefile tests/progs/Makefile \
 	resize/Makefile doc/Makefile intl/Makefile \
-	intl/libgnuintl.h po/Makefile.in ; do
+	intl/libgnuintl.h po/Makefile.in scrub/Makefile; do
 	if test -d `dirname ${srcdir}/$i` ; then
 		outlist="$outlist $i"
 	fi
diff --git a/configure.ac b/configure.ac
index 4ec4617..ad2884d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1311,6 +1311,11 @@ AC_SUBST(LINUX_CMT)
 AC_SUBST(CYGWIN_CMT)
 AC_SUBST(UNIX_CMT)
 dnl
+dnl e2scrub only builds on linux
+dnl
+E2SCRUB_CMT="$LINUX_CMT"
+AC_SUBST(E2SCRUB_CMT)
+dnl
 dnl Linux and Hurd places root files in the / by default
 dnl
 case "$host_os" in
@@ -1468,6 +1473,23 @@ LDFLAGS_SHLIB=${LDFLAGS_SHLIB:-$LDFLAGS}
 AC_SUBST(CFLAGS_SHLIB)
 AC_SUBST(CFLAGS_STLIB)
 AC_SUBST(LDFLAGS_SHLIB)
+
+dnl
+dnl Where do udev rules go?
+dnl
+pkg_udevrulesdir="$(pkg-config --variable=udevdir udev 2>/dev/null)/rules"
+case "${pkg_udevrulesdir}" in
+"")
+	udevrulesdir=""
+	have_udev=no
+	;;
+*)
+	udevrulesdir="${pkg_udevrulesdir}"
+	have_udev=yes
+	;;
+esac
+AC_SUBST([have_udev])
+AC_SUBST([udevrulesdir])
 dnl
 dnl Make our output files, being sure that we create the some miscellaneous 
 dnl directories
@@ -1493,7 +1515,7 @@ for i in MCONFIG Makefile e2fsprogs.spec \
 	misc/Makefile ext2ed/Makefile e2fsck/Makefile \
 	debugfs/Makefile tests/Makefile tests/progs/Makefile \
 	resize/Makefile doc/Makefile intl/Makefile \
-	intl/libgnuintl.h po/Makefile.in ; do
+	intl/libgnuintl.h po/Makefile.in scrub/Makefile; do
 	if test -d `dirname ${srcdir}/$i` ; then
 		outlist="$outlist $i"
 	fi
diff --git a/debian/control.in b/debian/control.in
index 89e8801..6efaef9 100644
--- a/debian/control.in
+++ b/debian/control.in
@@ -155,7 +155,7 @@ Essential: yes
 Pre-Depends: ${shlibs:Depends}, ${misc:Depends}, libblkid1, libuuid1
 Multi-Arch: foreign
 Suggests: gpart, parted, fuse2fs, e2fsck-static
-Recommends: e2fsprogs-l10n
+Recommends: e2fsprogs-l10n, lvm2, util-linux, coreutils
 Architecture: any
 Description: ext2/ext3/ext4 file system utilities
  The ext2, ext3 and ext4 file systems are successors of the original ext
diff --git a/debian/e2fsprogs.files b/debian/e2fsprogs.files
index 37e54da..7dd64ac 100644
--- a/debian/e2fsprogs.files
+++ b/debian/e2fsprogs.files
@@ -4,3 +4,4 @@ usr/sbin
 usr/share/man
 usr/share/locale
 etc
+lib/udev/rules
diff --git a/scrub/Makefile.in b/scrub/Makefile.in
new file mode 100644
index 0000000..1f3253d
--- /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)
+UDEVRULES	= e2scrub.rules
+INSTALLDIRS_TGT	+= installdirs-udev
+INSTALL_TGT	+= install-udev
+UNINSTALL_TGT	+= uninstall-udev
+endif
+
+all:: $(PROGS) $(MANPAGES) $(CONFFILES) $(UDEVRULES)
+
+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 $(udevdir)"
+	$(Q) $(MKINSTALLDIRS) $(DESTDIR)$(UDEVRULESDIR)
+
+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 $(UDEVRULES); do \
+		$(ES) "	INSTALL $(UDEVRULESDIR)/$$i"; \
+		$(INSTALL_PROGRAM) $$i $(DESTDIR)$(UDEVRULESDIR)/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 $(UDEVRULES); do \
+		$(RM) -f $(DESTDIR)$(UDEVRULESDIR)/$$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 0000000..e3d037b
--- /dev/null
+++ b/scrub/e2scrub.8.in
@@ -0,0 +1,31 @@
+.TH E2SCRUB 8 "@E2FSPROGS_MONTH@ @E2FSPROGS_YEAR@" "E2fsprogs version @E2FSPROGS_VERSION@"
+.SH NAME
+e2scrub - check a mounted ext2/ext3/ext4 file system on an LVM volume for errors.
+.SH SYNOPSYS
+.B
+e2scrub [OPTION] [LVM DEVICE PATH]
+.SH DESCRIPTION
+Given a live file system on a LVM volume, this program snapshots the
+logical volume and runs a file system check to look for serious errors.
+If no errors are found, fstrim can be called on the mounted file system.
+However, if errors are found, the file system should be unmounted and
+fixed.
+.SH OPTIONS
+.TP
+\fB-t\fR
+Run
+.B
+fstrim(1)
+on the mounted filesystem if no errors are found.
+.SH EXIT CODE
+The exit codes are the same as in
+.BR e2fsck (8)
+.SH BUGS
+This utility is capable of checking any ext* filesystem on an LVM volume,
+regardless of whether it is mounted.
+.SH SEE ALSO
+.BR e2fsck (8)
+.SH AUTHOR
+Darrick J. Wong <darrick.wong@oracle.com>
+.SH COPYRIGHT
+Copyright ©2017 Darrick J. Wong.  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 0000000..fec828a
--- /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 0000000..75e0639
--- /dev/null
+++ b/scrub/e2scrub.in
@@ -0,0 +1,182 @@
+#!/bin/bash
+
+#  Copyright (C) 2017 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
+e2fsck_opts=""
+conffile="@root_sysconfdir@/e2scrub.conf"
+
+test -f "${conffile}" && . "${conffile}"
+
+print_help() {
+	echo "Usage: $0 [-t] device"
+	echo
+	echo "device must be a LVM-managed block device"
+	echo "-t: Run fstrim if successful."
+}
+
+exitcode() {
+	ret="$1"
+
+	exit "${ret}"
+}
+
+prog_path() {
+	path="$1"
+	displayname="$2"
+
+	if ! type -P "${path}" && [ -n "${displayname}" ]; then
+		echo "${displayname}: Command not found."
+		exitcode 8
+	fi
+}
+
+LVS_PROG="$(prog_path "@root_sbindir@/lvs" "lvs")"
+BLKID_PROG="$(prog_path "@root_sbindir@/blkid" "blkid")"
+LVCREATE_PROG="$(prog_path "@root_sbindir@/lvcreate" "lvcreate")"
+LVREMOVE_PROG="$(prog_path "@root_sbindir@/lvremove" "lvremove")"
+FSTRIM_PROG="$(prog_path "@root_sbindir@/fstrim")"
+UDEVADM_PROG="$(prog_path "@root_sbindir@/udevadm")"
+SLEEP_PROG="$(prog_path "@root_bindir@/sleep")"
+
+while getopts "t" opt; do
+	case "${opt}" in
+	"t") fstrim=1;;
+	*) print_help; exitcode 2;;
+	esac
+done
+shift "$((OPTIND - 1))"
+
+dev="$1"
+if [ -z "${dev}" ]; then
+	print_help
+	exitcode 1
+elif [ ! -b "${dev}" ]; then
+	echo "${dev}: Not a block device?"
+	print_help
+	exitcode 16
+fi
+
+# Make sure this is an LVM device we can snapshot
+vg="$("${LVS_PROG}" --noheadings -o vg_name "${dev}" 2> /dev/null | sed -e 's/^  //g')"
+lv="$("${LVS_PROG}" --noheadings -o lv_name "${dev}" 2> /dev/null | sed -e 's/^  //g')"
+if [ -z "${vg}" ] || [ -z "${lv}" ]; then
+	echo "${dev}: Not a LVM device."
+	exitcode 16
+fi
+start_time="$(date +'%Y%m%d%H%M%S')"
+snap="${lv}.e2scrub"
+snap_dev="/dev/${vg}/${snap}"
+fstype="$("${BLKID_PROG}" -p -s TYPE "${dev}" | sed -e 's/^.*TYPE="\(.*\)".*$/\1/g')"
+
+case "${fstype}" in
+"ext2"|"ext3"|"ext4")
+	;;
+*)
+	echo "${dev}: Filesystem of type ${fstype} not supported."
+	exitcode 16
+	;;
+esac
+
+teardown() {
+	# Remove and wait for removal to succeed.
+	"${LVREMOVE_PROG}" -f "${vg}/${snap}" 3>&-
+	while [ -b "${snap_dev}" ] && [ "$?" -eq "5" ]; do
+		/bin/sleep 0.5
+		"${LVREMOVE_PROG}" -f "${vg}/${snap}" 3>&-
+	done
+}
+
+check() {
+	# First we preen the filesystem to 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" -p ${e2fsck_opts} "${snap_dev}" || return 1
+	${DBG} "@root_sbindir@/e2fsck" -fy ${e2fsck_opts} "${snap_dev}" || return 1
+	return 0
+}
+
+mark_clean() {
+	${DBG} "@root_sbindir@/tune2fs" -C 0 -T "${start_time}" "${dev}"
+}
+
+mark_corrupt() {
+	${DBG} "@root_sbindir@/tune2fs" -C 16000 -T "19000101" "${dev}"
+}
+
+setup() {
+	# Create the snapshot, wait for device to appear
+	teardown > /dev/null 2> /dev/null
+	"${LVCREATE_PROG}" -s -L "${snap_size_mb}m" -n "${snap}" "${vg}/${lv}" 3>&-
+	test $? -ne 0 && return 1
+	test -x "${UDEVADM_PROG}" && "${UDEVADM_PROG}" settle
+	return 0
+}
+
+trap "teardown" EXIT INT QUIT TERM
+if ! setup; then
+	echo "Snapshot of ${dev} FAILED, will not check!"
+	exitcode 1
+fi
+
+# Check and react
+if check; then
+	echo "Scrub of ${dev} succeeded."
+	mark_clean
+
+	if [ "${fstrim}" -eq 1 ] && [ -x "${FSTRIM_PROG}" ]; then
+		dir="$(lsblk -o MOUNTPOINT -n "${dev}")"
+		if [ -d "${dir}" ]; then
+			# NB: fstrim fails with snapshot present
+			trap '' EXIT
+			teardown
+			"${FSTRIM_PROG}" -v "${dir}"
+		fi
+	fi
+
+	ret=0
+else
+	# 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_PROG}" -o lv_snapshot_invalid --noheadings "${snapdev}")"
+	if [ -n "${is_invalid}" ]; then
+		echo "Scrub of ${dev} FAILED due to invalid snapshot."
+		ret=8
+	else
+		echo "Scrub of ${dev} FAILED!  Reboot soon to fsck."
+		mark_corrupt
+		ret=6
+	fi
+fi
+
+exitcode "${ret}"
diff --git a/scrub/e2scrub.rules.in b/scrub/e2scrub.rules.in
new file mode 100644
index 0000000..5e1b35b
--- /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"
diff --git a/util/subst.conf.in b/util/subst.conf.in
index fbc044d..effac78 100644
--- a/util/subst.conf.in
+++ b/util/subst.conf.in
@@ -18,3 +18,5 @@ $prefix			@prefix@
 JDEV			
 # Enable the documentation for the tdb profile in e2fsck.conf's man page
 TDB_MAN_COMMENT		@TDB_MAN_COMMENT@
+root_sbindir		@root_sbindir@
+root_bindir		@root_bindir@
