#!/bin/bash set -eu set -o pipefail backup_id="" filesystem_directory="" save_directory="" skip_tar_diff="False" filesystem_components_to_strip=0 backup_components_to_strip=0 if [ -t 2 ] then on_tty=True else on_tty=False fi function usage { retval="$1" case "$retval" in 0) ;; *) exec 1>&2 ;; esac echo "This script checks for:" echo " 1) filenames in the backup but not in the filesystem" echo " 2) filenames in the filesystem but not in the backup" echo " 3) files in both, but with different content (optional - turn off with --skip-tar-diff)" echo echo "Usage: $0" echo " --backup-id bi" echo " --filesystem-directory /dir/name" echo " --save-directory /backshift/save-directory" echo " --skip-tar-diff" echo " --filesystem-strip-components number" echo " --backup-strip-components number" echo echo "This script assumes that you:" echo " 1) Have no filenames with newlines in them" echo " 2) Haven't gotten fancy with the find -print0 you piped in while creating the backup" echo echo "tar --diff (which this script uses by default) is pretty slow, so --skip-tar-diff says to skip this part" echo "of the verification." echo echo "--filesystem-directory /dir indicates that /dir should be fully recursed for comparison with the" echo "specified backup (by --backup-id). This recursive traversal is limited only by -xdev, so it" echo "will not traverse into other filesystems." echo echo "Note that --filesystem-directory does Not restrict which part of the backup is examined; the entire" echo "backup is always inspected, irrespective of --filesystem-directory" exit "$retval" } while [ "$#" -ge 1 ] do case "$1" in --backup-id) backup_id="$2" shift ;; --filesystem-directory) filesystem_directory="$2" shift ;; --save-directory) save_directory="$2" shift ;; --filesystem-strip-components) filesystem_components_to_strip="$2" shift ;; --backup-strip-components) backup_components_to_strip="$2" shift ;; --skip-tar-diff) skip_tar_diff="True" ;; --help|-h) usage 0 ;; *) echo "$0: Unrecognized option: $1" 1>&2 usage 1 ;; esac shift done case "$backup_id" in "") echo "$0: --backup-id is a required option" 1>&2 usage 1 ;; esac case "$filesystem_directory" in "") echo "$0: --filesystem-directory is a required option" 1>&2 usage 1 ;; "/"*) # shellcheck disable=SC2001 # filesystem_directory=".$filesystem_directory" : ;; *) echo "$0: --filesystem-directory must start with a leading /" 1>&2 usage 1 ;; esac case "$save_directory" in "") echo "$0: --save-directory is a required option" 1>&2 usage 1 ;; esac case "$skip_tar_diff" in True) ;; False) ;; *) echo "$0: Internal error: (1) \$skip_tar_diff is not True or False: $skip_tar_diff" 1>&2 exit 1 ;; esac preflight_good=True if ! type backshift > /dev/null 2>&1 then preflight_good=False echo "$0: Error: backshift not found, will be needed" 1>&2 fi if ! type gprog-backshift-extract > /dev/null 2>&1 then preflight_good=False echo "$0: Error: gprog-backshift-extract not found, will be needed" 1>&2 fi if ! type set-arithmetic > /dev/null 2>&1 then preflight_good=False echo "$0: Error: set-arithmetic not found, will be needed" 1>&2 fi if ! type count > /dev/null 2>&1 then preflight_good=False echo "$0: Error: count not found, will be needed" 1>&2 fi if ! type strip-components > /dev/null 2>&1 then preflight_good=False echo "$0: Error: strip-components not found, will be needed" 1>&2 fi if ! tar --help > /dev/null 2>&1 then preflight_good=False echo "$0: Error: tar is probably not GNU tar, will be needed" 1>&2 fi case "$preflight_good" in True) # Cool, nothing missing ;; False) echo "$0: Error: One or more shell commands missing in preflight check" exit 1 ;; *) echo "$0: Internal error: \$preflight_good not True or False: $preflight_good" exit 1 ;; esac basename="$(basename "$0")" tempdir="/tmp/$basename.$$" echo "$0: Writing temporary files to $tempdir" 1>&2 # shellcheck disable=SC2064 trap "rm -rf \"$tempdir\"" $(seq 0 15) function optional_count { case "$on_tty" in True) count -c ;; False) cat ;; *) echo "$0: Internal error: \$on_tty has a strange value: $on_tty" 1>&2 exit 1 ;; esac } mkdir "$tempdir" backshift \ --save-directory "$save_directory" \ --list-backup-simply \ --backup-id "$backup_id" 2>&1 \ | optional_count \ | sed -e 's#/$##' -e '/^$/d' \ | strip-components --count "$backup_components_to_strip" > "$tempdir/in-backup" in_backup_count=$(wc -l "$tempdir/in-backup") echo "Got $in_backup_count files in backup" cd "$filesystem_directory" find . -xdev -print | sed -e 's#^\./##' -e '#^\.$#d' -e '#^$#d' \ | strip-components --count "$filesystem_components_to_strip" > "$tempdir/currently-in-filesystem" in_filesystem_count=$(wc -l "$tempdir/currently-in-filesystem") echo "Got $in_filesystem_count files currently in filesystem" echo "Files in backup but not currently in filesystem:" set-arithmetic --difference "$tempdir/in-backup" "$tempdir/currently-in-filesystem" | sort | sed 's/^/ /' echo echo "Files currently in filesystem but not in backup:" set-arithmetic --difference "$tempdir/currently-in-filesystem" "$tempdir/in-backup" | sort | sed 's/^/ /' case "$skip_tar_diff" in True) ;; False) echo echo "Verifying file content of all files in backup:" if [ "${DISPLAY:-}" = "" ] then # Just do the comparison; do not try to give progress info. # We could use reblock-backshift-extract, but then stderr would get clobbered by progress info. backshift \ --save-directory "$save_directory" \ --tar-format gnu \ --produce-tar \ --backup-id "$backup_id" else # This pops up a GUI for how long the backshift --produce-tar | tar --diff is going to take. gprog-backshift-extract \ --save-directory "$save_directory" \ --tar-format gnu \ --backup-id "$backup_id" fi \ | ( # See https://www.gnu.org/software/tar/manual/html_section/tar_51.html for what's going on here. case "$backup_components_to_strip" in 0) ;; *) echo "$0: Do not become concerned if you see 'tar: : Warning: Cannot stat: No such file or directory'" ;; esac cd "$filesystem_directory" \ && tar --show-transformed-names --strip-components="$backup_components_to_strip" --diff 2>&1 \ | sed 's/^/ /' ) ;; *) echo "$0: Internal error: (2) \$skip_tar_diff is not True or False: $skip_tar_diff" 1>&2 exit 1 ;; esac