#!/bin/ksh # # Copyright (c) 2022 Nick Holland # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # # Additional (non-license) usage notes: # Really, using this code "as is" without carefully examining the assumptions # made by me about your needs is highly discouraged. # # I would love to hear if you are using this in your world in any way # (even if as a counter example!). Drop me an email and tell me about it # at nick@holland-consulting.net # Cheap and easy error checking. Use if something shouldn't fail, but might # and you don't feel like real error handling. yell() { echo "$0: $*" >&2; } die() { yell "$*"; rm -f $TMPDIR exit 111; } try() { "$@" || die "cannot $*"; } COMMANDLINE="$*" # Strings to match the start and end of the RSYNC file list RSYNCSTART="^receiving incremental file" RSYNCEND="^$" LINESMAX=20 TMPDIR=$(try mktemp -dt fart.XXXXXX) CONFIG=/etc/ibs TODAY=$(date "+%Y-%m-%d") . $CONFIG/ibs.config GLOBALFILTERS="/etc/ibs/GLOBAL.fart" function help { cat <<-__ENDHELP usage: $0 [options] [-f filter [ -f filter ...]] backuplog ... options -a no limits on output -d show diff commands for copy/paste -D show actual diff between files -f filter files. Can be repeated. -h this help screen -l show "ls -l" for changed files -n no cmp check (show touched but unaltered files) -v verbose operation (diagnostic info) -s Standard Run -- $LOGS/*-$TODAY -t retain /tmp output Options have to come before filters, and filters before backuplogs __ENDHELP exit } # no parameters? just show help and exit. if [[ -z $1 ]]; then help fi # If you want fart to look for "touched" but unchanged files, # it has to be run as root...but that's the only reason. if [[ $(whoami) != "root" ]]; then print "==>> not running as root, so -n (no cmp) is implied." print NOFILECMP="y" fi while [[ $1 = -* ]]; do case $1 in -a ) NOLIMITS="y" ;; -d ) SHOWDIFFCMD="y" ;; -D ) SHOWDIFF="y" ;; -f ) break ;; -l ) SHOWLS="y" ;; -h ) help ;; -n ) NOFILECMP="y" ;; -s ) STDRUN="y" ;; -t ) RETAINTMP="y" ;; -v ) DIAG="y" ;; * ) print "Invalid option $1" help ;; esac shift done # IF any special options are given, print out the entire # option list. Probably can go away eventually. if [[ -n "$NOLIMITS$SHOWDIFFCMD$SHOWDIFF$SHOWLS$NOFILECMP$RETAINTMP$DIAG" ]]; then print "NOLIMITS=$NOLIMITS " \ "SHOWDIFCMD=$SHOWDIFFCMD " \ "SHOWDIFF=$SHOWDIFF " \ "SHOWLS=$SHOWLS" \ "NOFILECMP=$NOFILECMP " \ "RETAINTMP=$RETAINTMP " \ "DIAG=$DIAG" fi # Build a list of the "this run" -f FART filters. # They are applied to all reports in this run. while [[ $1 = "-f" ]]; do if [[ -f $2 ]]; then GLOBALFILTERS="$GLOBALFILTERS $2" elif [[ -f $CONFIG/$2 ]]; then GLOBALFILTERS="$GLOBALFILTERS $CONFIG/$2" else print "Could not find filter $2" exit fi shift; shift done if [[ -z "$STDRUN" ]]; then RUNLIST="$*" else RUNLIST="$(ls -1 $LOGS/*-$TODAY)" fi # Print headers. Change as desired. echo "GLOBALFILTERS="$GLOBALFILTERS echo "Processing: " for A in $RUNLIST; do print " * $(basename $A)" done #echo "TMPDIR=$TMPDIR" # The files on the command line can either be absolute # paths or files in the default IBSLOG directory. for IBSLOG in $RUNLIST; do test -n "$DIAG" && print "\nprocessing $IBSLOG" if [[ ! -f $IBSLOG ]]; then if [[ -f $LOGS/$IBSLOG ]]; then IBSLOG="$LOGS/$IBSLOG" else print "No log file: $IBSLOG" continue fi fi # Parse out the host name and date from the file. IBSHOST=$(basename $(print $IBSLOG|sed 's/-2...-..-..$'//)) CURRDATE=$(print $IBSLOG|sed "s/.*-\(2...-..-..$\)/\1/") IBSHOSTFILTER=$CONFIG/$IBSHOST.fart # CURRDIR, PREVDIR are the complete path to the backup and # the previous for comparison. CURRDIR=$(ls -1d $IBSBASE/$IBSHOST/$CURRDATE*) PREVDIR=$IBSBASE/$IBSHOST/$(ls -1 $IBSBASE/$IBSHOST | grep -B1 $CURRDATE|head -1) if [[ ! -d "$CURRDIR" || ! -d "$PREVDIR" ]]; then # This is flawed in that once NOFILECMP is set, it stays set. print "$CURRDIR or $PREVDIR do not exist, so can't do file comparisons" NOFILECMP="y" fi FARTOUT="$TMPDIR/$(basename "$IBSLOG")" test -n "$DIAG" && echo "CURRDIR=$CURRDIR PREVDIR=$PREVDIR" GREPFILE=$TMPDIR/$IBSHOST.grep # Make the actual grep RE file print "$RSYNCSTART" >$GREPFILE print "$RSYNCEND" >>$GREPFILE print " [=-]> " >>$GREPFILE # rsync noise print '^.*/$' >>$GREPFILE # ignore directories -- end with / cat $GLOBALFILTERS $IBSHOSTFILTER |\ sed -e 's/#.*$//' -e 's/ *$//' -e 's/^\//^/' |\ grep -v "^$" >>$GREPFILE LINESOUT=0 # Parse through log file { sed -n "/$RSYNCSTART/,/^$/p" $IBSLOG|egrep -vf /$TMPDIR/$IBSHOST.grep |\ while read F; do if [[ ($LINESOUT -ge $LINESMAX) && (-z "$NOLIMITS") ]]; then print -- " ----> $LINESOUT line output limit reached, rerun." print -- " $0 -a $IBSLOG" break fi if [[ -z $NOFILECMP ]]; then if cmp -s "$CURRDIR/$F" "$PREVDIR/$F" ; then continue fi fi ((LINESOUT=LINESOUT+1)) DIFFCMD=" --> diff -u \"$CURRDIR/$F\" \"$PREVDIR/$F\"" print " $F" if [[ -n "$SHOWLS" ]]; then ls -Tnl "$PREVDIR/$F" "$CURRDIR/$F" 2>/dev/null | sed "s/^/ /" fi if [[ -n "$SHOWDIFFCMD" ]] ; then print " --> diff -u \"$PREVDIR/$F\" \"$CURRDIR/$F\"" fi if [[ -n "$SHOWDIFF" ]] ; then diff -u "$PREVDIR/$F" "$CURRDIR/$F" 2>&1 | sed "s/^/ /" fi done } >>$FARTOUT if [[ -s $FARTOUT ]]; then # if there was any output print print "======== $IBSLOG " cat $FARTOUT fi done if [[ -z $RETAINTMP ]]; then rm -r $TMPDIR else print "TMPDIR=$TMPDIR left for you" fi print print "EOT generated from $COMMANDLINE by $(whoami) on $(hostname)"