#!/bin/sh # # e2check # Released under the GNU General Public License, either version 2 or # (at your option) any later version. # Overview: # # Run this from cron each night. If the machine is on AC power, it # will run the checks; otherwise they will all be skipped. (If the # script can't tell whether the machine is on AC power, a setting in # the configuration file (/etc/e2check.conf) decides whether it will # continue or abort.) # # The script will then decide which filesystems in /etc/fstab are on # logical volumes, and can therefore be checked via an LVM snapshot. # Each of these filesystems will be queried to find its last check # day, and if that was more than $INTERVAL days ago (where INTERVAL # is set in the configuration file as well), then the script will # take an LVM snapshot of the filesystem and run e2fsck on the # snapshot. The snapshot's size can be set via either the SNAPSIZE # option in the options field in /etc/fstab, or the DEFAULT_SNAPSIZE # option in /etc/e2check.conf -- but make sure it's set large enough. # After e2fsck finishes, the snapshot is destroyed. # # Any filesystem that passes e2fsck will have its last-check time # updated (in the real superblock, not the snapshot); any filesystem # that fails will send an email notification to a configurable user # ($EMAIL). This $EMAIL setting is optional, but its use is highly # recommended, since if any filesystem fails, it will need to be # checked manually offline. function on_ac_power() { local any_known=no # try sysfs power class first if [ -d /sys/class/power_supply ] ; then for psu in /sys/class/power_supply/* ; do if [ -r "${psu}/type" ] ; then type="`cat "${psu}/type"`" # ignore batteries [ "${type}" = "Battery" ] && continue online="`cat "${psu}/online"`" [ "${online}" = 1 ] && return 0 [ "${online}" = 0 ] && any_known=yes fi done [ "${any_known}" = "yes" ] && return 1 fi # else fall back to AC adapters in /proc if [ -d /proc/acpi/ac_adapter ] ; then for ac in /proc/acpi/ac_adapter/* ; do if [ -r "${ac}/state" ] ; then grep -q on-line "${ac}/state" && return 0 grep -q off-line "${ac}/state" && any_known=yes elif [ -r "${ac}/status" ] ; then grep -q on-line "${ac}/status" && return 0 grep -q off-line "${ac}/status" && any_known=yes fi done [ "${any_known}" = "yes" ] && return 1 fi if [ "$AC_UNKNOWN" == "CONTINUE" ] ; then return 0 # assume on AC power elif [ "$AC_UNKNOWN" == "ABORT" ] ; then return 1 # assume on battery else echo "Invalid value for AC_UNKNOWN in the config file" >&2 exit 1 fi } function check_fs() { local vg="$1" local lv="$2" local opts="$3" local snapsize="${DEFAULT_SNAPSIZE}" case "$opts" in *SNAPSIZE=*) # parse out just the SNAPSIZE option's value snapsize="${opts##*SNAPSIZE=}" snapsize="${snapsize%%,*}" ;; esac # else leave it at DEFAULT_SNAPSIZE [ -z "$snapsize" ] && return 1 local tmpfile=`mktemp -t e2fsck.log.XXXXXXXXXX` trap "rm $tmpfile ; trap - RETURN" RETURN local start="$(date +'%Y%m%d%H%M%S')" lvcreate -s -L "${snapsize}" -n "${lv}-snap" "${vg}/${lv}" if nice logsave -as $tmpfile e2fsck -p -C 0 "/dev/${vg}/${lv}-snap" && \ nice logsave -as $tmpfile e2fsck -fy -C 0 "/dev/${vg}/${lv}-snap" ; then echo 'Background scrubbing succeeded!' tune2fs -C 0 -T "${start}" "/dev/${vg}/${lv}" else echo 'Background scrubbing failed! Reboot to fsck soon!' tune2fs -C 16000 -T "19000101" "/dev/${vg}/${lv}" if test -n "$EMAIL"; then mail -s "E2fsck of /dev/${vg}/${lv} failed!" $EMAIL < $tmpfile fi fi lvremove -f "${vg}/${lv}-snap" } set -e # pull in configuration -- don't bother with a parser, just use the shell's . /etc/e2check.conf # check whether the machine is on AC power: if not, skip the e2fsck on_ac_power || exit 0 # parse up fstab grep -v '^#' /etc/fstab | grep -v '^$' | awk '$6!=0 {print $1,$3,$4;}' | \ while read FS FSTYPE OPTIONS ; do # Use of tune2fs in check_fs, and dumpe2fs below, means we can # only handle ext2/ext3 FSes [ "$FSTYPE" != "ext3" || "$FSTYPE" != "ext2" ] && continue # get the volume group (or an error message) VG="`lvs --noheadings -o vg_name "$FS" 2>&1`" # skip non-LVM devices (hopefully LVM VGs don't have spaces) [ "`echo "$VG" | awk '{print NF;}'`" -ne 1 ] && continue # get the logical volume name LV="`lvs --noheadings -o lv_name "$FS"`" # get the last check time plus $INTERVAL days check_date=`dumpe2fs -h "/dev/${VG}/${LV}" 2>/dev/null | grep 'Last checked:' | \ sed -e 's/Last checked:[[:space:]]*//'` check_day=`date --date="${check_date} $INTERVAL days" +"%Y%m%d"` # get today's date, and skip LVs that don't need to be checked yet today=`date +"%Y%m%d"` [ "$check_day" -gt "$today" ] && continue # else, check it check_fs "$VG" "$LV" "$OPTIONS" done