#!/bin/sh ############################## ## Default Options # The default device should be a non-rewinding tape, so that we can store # the volume list there. DEVICE="/dev/nst0" # Files matching these glob patterns will not be backed up. EXCLUDE_TYPES="*.wav *.ogg *.mp3 *.rm *.mpg *.mpeg *.asf *.avi *.mov *.qt *.exe *.iso" # Used to compress the backup before writing to tape. Use 'cat' if you # want no compression COMPRESS="gzip --best" ############################## ## Setup environment BACKUP_LIST=`mktemp /tmp/backup.XXXXXX` for type in $EXCLUDE_TYPES; do EXCLUDE_ARG="$EXCLUDE_ARG -not -iname $type " done ############################## ## Backup functions ## find_files generates a list of null terminated filenames and stores ## it in the temp file, "$BACKUP_LIST". This requires GNU find. find_files () { LIST_TYPE=$1 case $LIST_TYPE in full) for fs in "${FSS[@]}"; do find_files_on_fs $fs done ;; partial) # Look for a file under /var/run/backup that will indicate # the last time this fs had a full backup. for fs in "${FSS[@]}"; do cleanfs=`slash_out $fs` unset FIND_ARGS [ -f /var/run/backup/full:$cleanfs ] && \ FIND_ARGS="-newer /var/run/backup/full:$cleanfs" find_files_on_fs $fs "$FIND_ARGS" done ;; esac } find_files_on_fs () { SOURCE=$1 FIND_ARGS=$2 echo Finding files on $SOURCE... find $SOURCE $EXCLUDE_ARG $FIND_ARGS -xdev -print0 >> $BACKUP_LIST } mark_fss_full_backup () { # Touch a file in /var/run/backup to mark the time an fs # last had a full backup. Partial backups can look for # files newer than this one. [ -d /var/run/backup ] || mkdir /var/run/backup for fs in "${FSS[@]}"; do cleanfs=`slash_out $fs` touch /var/run/backup/full:$cleanfs done } ## slash_out is a helper function that removes slash characters from a path, ## converting them to dots. slash_out () { echo "$1" | sed -e s:/:.:g } ## do_backup will store the file list if $DEVICE appears to be a tape. ## It expects $BACKUP_LIST to be a null terminated list of filenames, as generated ## by GNU find. do_backup () { echo -n "$TYPE backup of ${FSS[@]} began on " date [ -c $DEVICE ] && { # Set the tape device parameters: variable block size, compression off mt -f $DEVICE setblk 0 mt -f $DEVICE compression 0 # Write the file list to the beginning of the tape, as a volume list dd of=$DEVICE if=$BACKUP_LIST bs=5k } # Long cpio options are: # cpio -o --format=newc --null --reset-access-time --verbose cpio -oc0av < $BACKUP_LIST | $COMPRESS | dd of=$DEVICE bs=5k [ -c $DEVICE ] && { # Rewind if this is a tape device mt -f $DEVICE rewind } rm -f $BACKUP_LIST echo -n "$TYPE backup of ${FSS[@]} finished on " date } ## Print the number of bytes, uncompressed, this backup will probably ## compose. estimate_size_bytes () { xargs --null ls -ld < $BACKUP_LIST | \ awk 'BEGIN { EST=0 } { EST=EST + $5 } END { print EST }' } ## Print readable information based on estimate_size_bytes estimate_size () { ESIZE=`estimate_size_bytes` echo "Backup of ${FSS[@]} will be about $ESIZE bytes, uncompressed." rm -f $BACKUP_LIST } ############################## ## View volume list function view_volume_list () { if [ -c $DEVICE ]; then # For a tape device, rewind to the beginning, where the volume # list will be stored, read it out to the pager, and rewind again. mt -f $DEVICE rewind dd if=$DEVICE bs=5k | tr '\000' '\n' mt -f $DEVICE rewind else # For non-tape devices, get the list from cpio dd if=$DEVICE bs=5k | $COMPRESS -d | cpio -it fi } ############################## ## Restore function restore_files () { # If the backup was uncompressed, then use "cat" to "decompress". This # special case is required, because cat does not accept the -d flag. # Everything else has to. if [ "$COMPRESS" = "cat" ] ; then UNCOMPRESS=cat else UNCOMPRESS="$COMPRESS -d" fi echo -n "restore of ${FSS[@]} began on " date [ -c $DEVICE ] && { # For a tape device, move forward, beyond the volume list. mt -f $DEVICE rewind mt -f $DEVICE fsf 1 } [ -n "$RESTORE_ROOT" ] && { # Restore files to alternate root directory [ -d $RESTORE_ROOT ] || mkdir $RESTORE_ROOT cd $RESTORE_ROOT } # Long cpio options are: # cpio -o --format=newc --make-directories --preserve-modification-time --verbose dd if=$DEVICE bs=5k | $UNCOMPRESS | cpio -icdmv --no-absolute-filenames "${FSS[@]}" echo -n "restore of ${FSS[@]} finished on " date [ -c $DEVICE ] && { mt -f $DEVICE rewind } } ############################## ## General functions print_help () { cat < <-comp compressor> <-prune dir> filesystem ... Estimate the uncompressed size of a backup: backup [estimate_full|estimate_partial] <-prune dir> filesystem ... View the volume list on a tape: backup view <-dev device> Restore files from tape: backup restore <-dev device> <-comp compressor> <-root alt-restore-root> pattern ... EOF } ############################## ## Parse command line options TYPE=$1 shift unset FSS while [ -n "$*" ]; do case "$1" in -comp*) shift COMPRESS="$1" shift ;; -dev*) shift DEVICE="$1" shift ;; -prune) shift EXCLUDE_ARG="-path $1 -prune -o $EXCLUDE_ARG" shift ;; -root) shift RESTORE_ROOT="$1" shift ;; *) FSS[${#FSS[@]}]="$1" shift ;; esac done ############################## ## Validate options case $TYPE in full|partial|estimate_full|estimate_partial) if [ -z "$FSS" ]; then print_help exit fi ;; view) ;; restore) ;; *) print_help exit ;; esac ############################## ## Do it case $TYPE in full) find_files full mark_fss_full_backup do_backup ;; partial) find_files partial do_backup ;; estimate_full) find_files full estimate_size ;; estimate_partial) find_files partial estimate_size ;; view) view_volume_list ;; restore) restore_files ;; esac