"Fossies" - the Fresh Open Source Software Archive

Member "backup2l-1.5/backup2l" (13 Dec 2009, 39544 Bytes) of package /linux/privat/backup2l_1.5.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Bash source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file.

    1 #!/bin/bash
    2 
    3 # backup2l --- low-maintenance backup tool
    4 
    5 # Copyright (c) 2001-2009 Gundolf Kiefer <gundolf.kiefer@web.de>
    6 
    7 # This program is free software; you can redistribute it and/or modify
    8 # it under the terms of the GNU General Public License as published by
    9 # the Free Software Foundation; either version 2, or (at your option)
   10 # any later version.
   11 #
   12 # This program is distributed in the hope that it will be useful,
   13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
   14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   15 # GNU General Public License for more details.
   16 #
   17 # You should have received a copy of the GNU General Public License
   18 # along with this program; if not, write to the Free Software
   19 # Foundation, Inc.; 59 Temple Place, Suite 330;
   20 # Boston, MA 02111-1307, USA.
   21 
   22 
   23 
   24 
   25 
   26 ##################################################
   27 # Set global variables
   28 
   29 VER=1.5
   30 
   31 
   32 # The following variable defines the commonly required tools. Additional tools
   33 # may be required by special functions, e. g. md5sum by the check functions.
   34 # Tool requirements are check in the do_* and show_* functions.
   35 
   36 COMMON_TOOLS="date find grep gzip gunzip sed awk mount umount"
   37 
   38 
   39 # The following variables define the format of *.list files. Modify at your own risk.
   40 
   41 FORMAT="%8s %TD %.8TT %04U.%04G %04m %p"     # format for *.list files
   42 
   43 FILTER_NAME="sed 's#^\([-:. 0-9]*/\)\{3\}#/#'"
   44     # sed command for extracting names from .list, .new, ... files
   45     # (removes everything up to the 3rd "/"; two are contained in the time field)
   46 FILTER_CHOWN="sed 's#^\( *[^ ]\+\)\{3\} *0\{0,3\}\([0-9]\+\)\.0\{0,3\}\([0-9]\+\) \+\([0-9]*\)\{4\} \+/\(.*\)\$#\2:\3 \"\5\"#'"
   47     # sed command for extracting ownerships and names from .list files
   48 FILTER_CHMOD="sed 's#^\( *[^ ]\+\)\{4\} *\([0-9]\{4\}\) \+/\(.*\)\$#\2 \"\3\"#'"
   49     # sed command for extracting permissions and names from .list files
   50 #FILTER_UNIFY_NAME="sed 's#\\\\[0-7]\{3\}#?#g;   s#[^a-zA-Z0-9_ .$%:~/=+\#\-]#?#g'"
   51 FILTER_UNIFY_NAME="sed 's#\\\\[0-7]\{3\}#?#g;   s#[^a-zA-Z0-9_ .$%:~/=+\#\-]#?#g'"
   52     # replaces special and escaped characters by '?';
   53     # only used when checking TOCs of fresh backup archives in order to avoid false alarms
   54 
   55 # The following alternative (donated by Christian Ludwig <Ludwig_C@gmx.de>) is slightly more precise,
   56 # but requires perl to be installed:
   57 #FILTER_UNIFY_NAME="perl -n -e 's{\\\\(\d{3})}{chr(oct(\$1))}eg; print;'"
   58 
   59 # On systems without GNU sed >= 3.0.2 such as Mac OS X, try the following alternatives...
   60 # ... settings (donated by Joe Auricchio <avarame@ml1.net>).
   61 
   62 #FILTER_CHOWN="perl -pe 's#^( *[^ ]+){3} *0{0,3}([0-9]+).0{0,3}([0-9]+) +([0-9]*){4} +/(.*)\$#\$2.\$3 \"\$5\"#'"
   63 #FILTER_CHMOD="perl -pe 's#^( *[^ ]+){4} *([0-9]{4}) +/(.*)\$#\$2 \"\$3\"#'"
   64 
   65 #COMMON_TOOLS="$COMMON_TOOLS perl"
   66 
   67 
   68 
   69 
   70 
   71 ##################################################
   72 # Misc. helpers
   73 
   74 
   75 require_tools ()
   76 {
   77     local NOT_AVAIL=""
   78     for TOOL in $@; do
   79         if [ "`which $TOOL 2> /dev/null`" == "" ]; then NOT_AVAIL="$NOT_AVAIL $TOOL"; fi
   80     done
   81     if [[ "$NOT_AVAIL" != "" ]]; then
   82         echo "ERROR: The following required tool(s) cannot be found: $NOT_AVAIL"
   83         exit 3
   84     fi
   85 }
   86 
   87 
   88 get_bid_of_name ()
   89 {
   90     local SUFFIX=${1#$VOLNAME.}
   91     echo ${SUFFIX%%.*}
   92 }
   93 
   94 
   95 get_last_bid ()
   96 {
   97     if [ -f $VOLNAME.1.list.gz ]; then
   98         REV_ARCH_LIST=(`ls $VOLNAME.*.list.gz | sort -r`)
   99         get_bid_of_name ${REV_ARCH_LIST[0]}
  100     else
  101         echo "0"  # default if no archives exist yet
  102     fi
  103 }
  104 
  105 
  106 get_base_bid ()
  107 {
  108     local BID=$(( $1 - 1 ))
  109     echo ${BID%%+(0)}
  110 }
  111 
  112 
  113 expand_bid_list ()
  114 {
  115     local SUFFIX="$1"
  116     shift
  117     local ARCH_LIST=""
  118     local BID ARCH
  119     for BID in "$@"; do
  120         ARCH_LIST="$ARCH_LIST `ls -1 $BACKUP_DIR/$VOLNAME.$BID.$SUFFIX 2> /dev/null`"
  121     done
  122     local BID_LIST=""
  123     local ARCH
  124     for ARCH in $ARCH_LIST; do
  125         BID_LIST="$BID_LIST `get_bid_of_name ${ARCH#$BACKUP_DIR/}`"
  126     done
  127     echo $BID_LIST
  128 }
  129 
  130 
  131 do_symlink ()
  132 {
  133     # tries to symlink & copies if not possible (e. g. on FAT32/Samba file systems)
  134     ln -s $@ &> /dev/null ||
  135     cp -af $@
  136 }
  137 
  138 
  139 readable_bytes_sum ()
  140 {
  141   awk -v UNIT=$1 '
  142     { B += $1 }
  143     END {
  144       KB=B / 1024.0;
  145       MB=KB / 1024.0;
  146       GB=MB / 1024.0;
  147       if ((GB>=1.0 && UNIT=="") || UNIT=="G" || UNIT=="g") print sprintf("%.1fG", GB);
  148       else if ((MB>=1.0 && UNIT=="") || UNIT=="M" || UNIT=="m") print sprintf("%.1fM", MB);
  149       else if ((KB>=1.0 && UNIT=="") || UNIT=="K" || UNIT=="k") print sprintf("%.0fK", KB);
  150       else print sprintf("%i ", B);
  151     }
  152   '
  153 }
  154 
  155 
  156 
  157 
  158 
  159 ##################################################
  160 # Archive drivers & helpers
  161 
  162 
  163 BUILTIN_DRIVER_LIST="DRIVER_TAR DRIVER_TAR_GZ DRIVER_TAR_BZ2 DRIVER_AFIOZ"
  164 
  165 
  166 DRIVER_TAR ()
  167 {
  168     case $1 in
  169         -test)
  170             require_tools tar
  171             echo "ok"
  172             ;;
  173         -suffix)
  174             echo "tar"
  175             ;;
  176         -create)        # Arguments: $2 = BID, $3 = archive file name, $4 = file list file
  177             tar cf $3 -T $4 --numeric-owner --no-recursion 2>&1 \
  178                 | grep -v 'tar: Removing leading .* from .* names'
  179             ;;
  180         -toc)           # Arguments: $2 = BID, $3 = archive file name
  181             tar tf $3 | sed 's#^#/#'
  182             ;;
  183         -extract)       # Arguments: $2 = BID, $3 = archive file name, $4 = file list file
  184             tar x --same-permission --same-owner --numeric-owner -f $3 -T $4 2>&1
  185             ;;
  186     esac
  187 }
  188 
  189 
  190 DRIVER_TAR_GZ ()
  191 {
  192     case $1 in
  193         -test)
  194             require_tools tar
  195             echo "ok"
  196             ;;
  197         -suffix)
  198             echo "tar.gz"
  199             ;;
  200         -create)        # Arguments: $2 = BID, $3 = archive file name, $4 = file list file
  201             tar czf $3 -T $4 --no-recursion 2>&1 \
  202                 | grep -v 'tar: Removing leading .* from .* names'
  203             ;;
  204         -toc)           # Arguments: $2 = BID, $3 = archive file name
  205             tar tzf $3 | sed 's#^#/#'
  206             ;;
  207         -extract)       # Arguments: $2 = BID, $3 = archive file name, $4 = file list file
  208             tar zx --same-permission --same-owner -f $3 -T $4 2>&1
  209             ;;
  210     esac
  211 }
  212 
  213 
  214 DRIVER_TAR_BZ2 ()
  215 {
  216     case $1 in
  217         -test)
  218             require_tools tar bzip2
  219             echo "ok"
  220             ;;
  221         -suffix)
  222             echo "tar.bz2"
  223             ;;
  224         -create)        # Arguments: $2 = BID, $3 = archive file name, $4 = file list file
  225             tar cjf $3 -T $4 --no-recursion 2>&1 \
  226                 | grep -v 'tar: Removing leading .* from .* names'
  227             ;;
  228         -toc)           # Arguments: $2 = BID, $3 = archive file name
  229             tar tjf $3 | sed 's#^#/#'
  230             ;;
  231         -extract)       # Arguments: $2 = BID, $3 = archive file name, $4 = file list file
  232             tar jx --same-permission --same-owner -f $3 -T $4 2>&1
  233             ;;
  234     esac
  235 }
  236 
  237 
  238 DRIVER_AFIOZ ()
  239 {
  240     case $1 in
  241         -test)
  242             require_tools afio
  243             echo "ok"
  244             ;;
  245         -suffix)
  246             echo "afioz"
  247             ;;
  248         -create)        # Arguments: $2 = BID, $3 = archive file name, $4 = file list file
  249             afio -Zo $3 < $4 2>&1
  250 #            afio -Zo - < $4 > $3 2>&1
  251             ;;
  252         -toc)           # Arguments: $2 = BID, $3 = archive file name
  253             afio -Zt $3 | sed 's#^#/#' # 's#^#/#;s#\.z$##'
  254 #            afio -Zt - < $3 | sed 's#^#/#' # 's#^#/#;s#\.z$##'
  255             ;;
  256         -extract)       # Arguments: $2 = BID, $3 = archive file name, $4 = file list file
  257             afio -Zinw $4 $3 2>&1
  258 #            afio -Zinw $4 - < $3 2>&1
  259             ;;
  260     esac
  261 }
  262 
  263 
  264 
  265 # Helpers
  266 
  267 
  268 require_drivers ()
  269 {
  270     for DRIVER in $@; do
  271         local STATUS=`$DRIVER -test`
  272         if [ "$STATUS" != "ok" ]; then
  273             echo "ERROR: Archive driver not ready: $DRIVER"
  274             echo $STATUS
  275             exit 3
  276         fi
  277     done
  278 }
  279 
  280 
  281 get_driver_and_file ()           # Argument: <path>/<volname>.<bid>
  282 {
  283     local DRIVER
  284     for DRIVER in $USER_DRIVER_LIST $BUILTIN_DRIVER_LIST; do
  285         local SUFFIX=`$DRIVER -suffix`
  286         if [ "$SUFFIX" != "" -a -e "$1.$SUFFIX" ]; then
  287             echo "$DRIVER:$1.$SUFFIX"
  288             break
  289         fi
  290     done
  291 }
  292 
  293 
  294 get_driver ()                    # Argument: <path><volname><bid>
  295 {
  296     local DRIVER_AND_FILE=`get_driver_and_file $1`
  297     echo ${DRIVER_AND_FILE%%:*}
  298 }
  299 
  300 
  301 get_archive ()                   # Argument: <path><volname><bid>
  302 {
  303     local DRIVER_AND_FILE=`get_driver_and_file $1`
  304     echo ${DRIVER_AND_FILE##*:}
  305 }
  306 
  307 
  308 
  309 
  310 
  311 ##################################################
  312 # (Un)mount backup disk and (un)lock volume
  313 
  314 
  315 MUST_BE_UNMOUNTED=0
  316 
  317 
  318 mount_dev ()
  319 {
  320     if [ ! -d $BACKUP_DIR -a "$BACKUP_DEV" != "" ]; then
  321         echo "Mounting $BACKUP_DEV..."
  322         echo
  323         mount $BACKUP_DEV
  324         MUST_BE_UNMOUNTED=1
  325     fi
  326     if [ ! -d $BACKUP_DIR ]; then
  327         echo "ERROR: $BACKUP_DIR not present (mount failed?)"
  328         umount $BACKUP_DEV >& /dev/null
  329         exit 3
  330     fi
  331     if [ -e $BACKUP_DIR/$VOLNAME.lock ]; then
  332         if [ `ps -a | grep -af $BACKUP_DIR/$VOLNAME.lock | grep ${0##*/} | wc -l` -gt 0 ]; then
  333             echo "ERROR: Backup volume is locked."
  334             echo
  335             echo "Another instance is currently running. If you are sure that this is not"
  336             echo "the case, then remove the lock file '$BACKUP_DIR/$VOLNAME.lock' manually."
  337             exit 3
  338         fi
  339     fi
  340     echo $$ > $BACKUP_DIR/$VOLNAME.lock
  341 }
  342 
  343 
  344 umount_dev ()
  345 {
  346     rm -f $BACKUP_DIR/$VOLNAME.lock
  347     if [ $MUST_BE_UNMOUNTED == "1" ]; then
  348         echo
  349         echo "Unmounting $BACKUP_DEV..."
  350         umount $BACKUP_DEV
  351         MUST_BE_UNMOUNTED=0
  352     fi
  353 }
  354 
  355 
  356 
  357 
  358 
  359 ##################################################
  360 # Purging
  361 
  362 
  363 purge ()
  364 {
  365     local LV=${#1}
  366     : $[LV-=1]
  367     local LASTDIG=${1:LV:1}
  368     local PREFIX=${1%?}
  369     if [[ $LV == 0 ]]; then
  370         local CAND=$VOLNAME.$1*.list.gz
  371     else
  372         local CAND=$VOLNAME.$PREFIX[$LASTDIG-9]*.list.gz
  373     fi
  374     local ARCH BASE
  375     for ARCH in $CAND; do
  376         BASE=${ARCH%.list.gz}
  377         echo "  removing <$BASE>"
  378         rm -fr $BASE.*
  379     done
  380 }
  381 
  382 
  383 name_cleanup ()
  384 {
  385     local DST=0
  386     local SRC=0
  387     local SUFFIX SRC_FILE DST_FILE CHK
  388     while [[ $SRC -lt 9 ]]; do
  389         : $[SRC+=1]
  390         if [[ -f $VOLNAME.$SRC.list.gz ]]; then
  391             : $[DST+=1]
  392             if [[ "$SRC" != "$DST" ]]; then
  393                 for SRC_FILE in $VOLNAME.$SRC* ; do
  394                     local SUFFIX=${SRC_FILE##$VOLNAME.$SRC}
  395                     local DST_FILE=$VOLNAME.$DST$SUFFIX
  396                     if [ ${SUFFIX#*.} == "list.gz" ]; then
  397                         echo "  moving <${SRC_FILE%.list.gz}> to <${DST_FILE%.list.gz}>"
  398                     fi
  399                     if [ ${SUFFIX#*.} == "new.gz" -a -h $SRC_FILE ]; then
  400                         rm -fr $SRC_FILE
  401                         do_symlink ${DST_FILE%.new.gz}.list.gz $DST_FILE
  402                     else
  403                         mv $SRC_FILE $DST_FILE
  404                     fi
  405                 done
  406                 for CHK in $VOLNAME.$DST*.check ; do
  407                     sed "s/ $VOLNAME.$SRC/ $VOLNAME.$DST/" < $CHK > $TMP.check
  408                     mv $TMP.check $CHK
  409                 done
  410             fi
  411         fi
  412     done
  413 }
  414 
  415 
  416 do_purge ()
  417 {
  418     if [[ "$1" == "" ]]; then
  419         echo "No archive specified - not purging anything!"
  420     else
  421         mount_dev
  422         cd $BACKUP_DIR
  423         BID_LIST=`expand_bid_list list.gz "$@"`
  424         if [[ "$BID_LIST" == "" ]]; then
  425             echo "No archive(s) matching '$@' found - nothing to purge."
  426         else
  427             echo "Purging <$BID_LIST>..."
  428             for BID in $BID_LIST; do
  429                 purge $BID
  430             done
  431             name_cleanup
  432          fi
  433     fi
  434 }
  435 
  436 
  437 
  438 
  439 
  440 ##################################################
  441 # Checking
  442 
  443 
  444 create_check ()
  445 {
  446     # Parameter: single BID
  447     local BID=$1
  448     if [[ "${#BID}" == "1" ]]; then
  449         local BASE_FILE=""
  450     else
  451         local BASE_FILE=$VOLNAME.`get_base_bid $BID`.list.gz
  452     fi
  453     echo "Creating check file for <$VOLNAME.$BID>..."
  454     rm -f $VOLNAME.$BID.check
  455     md5sum `find . -follow -path "*/$VOLNAME.$BID.*" -type f` $BASE_FILE > $TMP.check
  456     mv $TMP.check $VOLNAME.$BID.check
  457 }
  458 
  459 
  460 do_create_check ()
  461 {
  462     require_tools $COMMON_TOOLS md5sum
  463 
  464     mount_dev
  465     cd $BACKUP_DIR
  466     rm -fr $TMP.*
  467 
  468     if [[ "$1" == "" ]]; then
  469         BID_LIST=`expand_bid_list list.gz "*"`
  470     else
  471         BID_LIST=`expand_bid_list list.gz "$@"`
  472     fi
  473     for BID in $BID_LIST; do
  474         if [[ "$1" != "" || ! -f $VOLNAME.$BID.check ]]; then
  475             create_check $BID
  476         fi
  477     done
  478 }
  479 
  480 
  481 check_arch ()
  482 {
  483     # Parameter: single BID
  484 
  485     local BID=$1
  486     echo "Checking archive <$VOLNAME.$BID>..."
  487 
  488     # Check the plausibility by file existence...
  489     local SUFFIX
  490     for SUFFIX in list.gz skipped.gz new.gz obsolete.gz error.gz; do
  491         if [[ ! -f $VOLNAME.$BID.$SUFFIX ]]; then
  492             echo "  ERROR: File '$VOLNAME.$BID.$SUFFIX' does not exist."
  493         fi
  494     done
  495     if [[ ${#BID} -gt 1 ]]; then
  496         if [[ ! -f $VOLNAME.`get_base_bid $BID`.list.gz ]]; then
  497             echo "  ERROR: Base archive <$VOLNAME.`get_base_bid $BID`> does not exist."
  498         fi
  499     fi
  500 
  501     # Check check sum file...
  502     if [[ -f $VOLNAME.$BID.check ]]; then
  503         md5sum -c $VOLNAME.$BID.check 2>&1 | sed 's/^/  /'
  504         if [[ $(( `grep $VOLNAME.$BID. $VOLNAME.$BID.check | wc -l` )) -lt 6 ]]; then
  505             echo "  ERROR: Check file seems to be corrupted."
  506         fi
  507     else
  508         echo "  Information: no check file"
  509     fi
  510 }
  511 
  512 
  513 do_check ()
  514 {
  515     require_tools $COMMON_TOOLS md5sum
  516 
  517     mount_dev
  518     cd $BACKUP_DIR
  519     rm -fr $TMP.*
  520 
  521     if [[ "$1" == "" ]]; then
  522         BID_LIST=`expand_bid_list list.gz "*"`
  523     else
  524         BID_LIST=`expand_bid_list list.gz "$@"`
  525     fi
  526     for BID in $BID_LIST; do
  527         check_arch $BID
  528     done
  529 }
  530 
  531 
  532 
  533 
  534 
  535 ##################################################
  536 # Print summary
  537 
  538 
  539 show_summary ()
  540 {
  541     require_tools $COMMON_TOOLS
  542 
  543     echo "Summary"
  544     echo "======="
  545     echo
  546     cd $BACKUP_DIR
  547     if [[ `echo $VOLNAME.*.list.gz` == "" ]]; then
  548         echo "No backup archives present."
  549     else
  550         echo "Backup       Date       Time  |  Size   | Skipped  Files+D |  New  Obs. | Err."
  551         echo "------------------------------------------------------------------------------"
  552         for f in `ls $VOLNAME.*.list.gz` ; do
  553             p=${f%%.list.gz}
  554             size="`du -sbL $p.* | readable_bytes_sum $SIZE_UNITS`"
  555             skipped=$( gunzip -c $p.skipped.gz | wc -l )
  556             total=$( gunzip -c $p.list.gz | wc -l )
  557             new_files=$( gunzip -c $p.new.gz | wc -l )
  558             obsolete=$( gunzip -c $p.obsolete.gz | wc -l )
  559             errors=$( gunzip -c $p.error.gz | grep '<' | wc -l )
  560             printf "%-12s %s %s |%8s |%8i %8i |%5i %5i |%5i\n" \
  561                    $p $(date -r $p.list.gz +"%Y-%m-%d %H:%M") "$size" \
  562                    $skipped $total $new_files $obsolete $errors
  563         done
  564     fi
  565     echo
  566     df -h $BACKUP_DIR
  567 }
  568 
  569 
  570 
  571 
  572 
  573 ##################################################
  574 # backup
  575 
  576 
  577 compute_level_and_bids ()
  578 {
  579     # Determine level and base BID for new backup...
  580     if [ ! -f $VOLNAME.1.list.gz ]; then
  581         LEVEL="0";
  582     else
  583         if [[ "$1" -gt 0 || "$1" == "0" ]]; then
  584             LEVEL=$1
  585         else
  586             LEVEL=$MAX_LEVEL
  587         fi
  588         BASE_BID=`get_last_bid`
  589         while [[ "$LEVEL" -gt 0 && "${BASE_BID:$LEVEL:1}" -gt "$[MAX_PER_LEVEL-1]" ]]; do
  590             : $[LEVEL-=1]
  591         done
  592         BASE_BID=${BASE_BID:0:$[LEVEL+1]}
  593         BASE_BID=${BASE_BID%%+(0)}
  594     fi
  595 
  596     # Determine new archive's name
  597     if [ "$LEVEL" != "0" ]; then
  598         NEW_BID=$BASE_BID
  599         while [ ${#NEW_BID} -le $LEVEL ]; do
  600             NEW_BID=${NEW_BID}"0"
  601         done
  602         : $[NEW_BID+=1]
  603     else
  604         NEW_BID="1"
  605         while [ -f $VOLNAME.$NEW_BID.list.gz ]; do
  606             : $[NEW_BID+=1]
  607         done
  608     fi
  609 }
  610 
  611 
  612 prepare_backup ()
  613 {
  614     # Input: Comment ("Preparing"/"Estimating"), selected level (optional)
  615     # Output: $LEVEL, $BASE_BID, $NEW_BID
  616     #         $TMP.list $TMP.skipped $TMP.new $TMP.obsolete $TMP.files
  617 
  618     # Determine level, base BID and new BID
  619     compute_level_and_bids $2
  620     if [ "$LEVEL" != "0" ]; then
  621         echo "$1 differential level-$LEVEL backup <$VOLNAME.$NEW_BID> based on <$VOLNAME.$BASE_BID>..."
  622     else
  623         echo "$1 full backup <$VOLNAME.$NEW_BID>..."
  624     fi
  625 
  626     # Determine main list & which files are new or obsolete...
  627     set -f  # pathname expansion off as it may destroy $SKIPCOND
  628     OLDIFS=$IFS
  629     IFS=""
  630     find ${SRCLIST[*]} \( \( ${SKIPCOND[*]} \) \
  631         \( -type d -fprintf $TMP.skipped.dirs "$FORMAT/\n" -o -fprintf $TMP.skipped.files "$FORMAT\n" \) \) \
  632         -o \( -not -type d -printf "$FORMAT\n" -o -printf "$FORMAT/\n" \) \
  633         | sed -e 's#   \([0-9]*\..* /.*\)# 00\1#' -e 's#  \([0-9]*\..* /\)# 0\1#' \
  634               -e 's#\(\. *\)  \([0-9 ]* /\)#\100\2#' -e 's#\. \([0-9 ]* /\)#\.0\1#' \
  635         | sort -k 6 \
  636         > $TMP.list
  637     IFS=$OLDIFS
  638     cat $TMP.skipped.dirs $TMP.skipped.files \
  639         | sed -e 's#   \([0-9]*\..* /.*\)# 00\1#' -e 's#  \([0-9]*\..* /\)# 0\1#' \
  640               -e 's#\(\. *\)  \([0-9 ]* /\)#\100\2#' -e 's#\. \([0-9 ]* /\)#\.0\1#' \
  641         | sort -k 6 \
  642         > $TMP.skipped
  643           # WORKAROUND: The reason for the two 2-line sed's above is a bug in find 4.1.7, where the format
  644           #             directives %04x do not produce leading 0's.
  645     set +f
  646     if [ $LEVEL != 0 ]; then
  647         gunzip -c $VOLNAME.$BASE_BID.list.gz | sort -k 6 | diff - $TMP.list > $TMP.diff
  648           # WORKAROUND: 'sort -k 6' can be removed if sort uses the same options for every user,
  649           #             which seems to be not the case!!
  650         if [[ $(( `grep "<" $TMP.diff | tail -n 1 | sed 's# /.*##' | wc -w` )) == 5 ]]; then
  651             echo "  file '$VOLNAME.$BASE_BID.list.gz' has an old format - using compatibility mode"
  652             sed 's#[0-9]*\.[0-9 ]* /#- /#' < $TMP.list > $TMP.list.old
  653             gunzip -c $VOLNAME.$BASE_BID.list.gz | sort -k 5 | diff - $TMP.list.old > $TMP.diff
  654         fi
  655         grep "<" $TMP.diff | sed 's/^< //' > $TMP.obsolete
  656         grep ">" $TMP.diff | sed 's/^> //' > $TMP.new
  657     else
  658         # by convention, the *.new and *.obsolete files always exist, although redundant for level-0 backups
  659         do_symlink $TMP.list $TMP.new
  660         touch $TMP.obsolete
  661     fi
  662     eval "$FILTER_NAME" < $TMP.new | grep -v "/$" > $TMP.files   # extract real files
  663 
  664     # Print statistics...
  665     echo " " `wc -l < $TMP.files` / `grep -v '/$' $TMP.list | wc -l` "file(s)," \
  666         `grep '/$' $TMP.new | wc -l` / `grep '/$' $TMP.list | wc -l` "dir(s)," \
  667         `grep -v '/$' $TMP.new | readable_bytes_sum`"B /" \
  668         `grep -v '/$' $TMP.list | readable_bytes_sum`"B (uncompressed)"
  669     echo "  skipping:" `grep -v '/$' $TMP.skipped | wc -l` "file(s)," \
  670         `grep '/$' $TMP.skipped | wc -l` "dir(s)," \
  671         `grep -v '/$' $TMP.skipped | readable_bytes_sum`"B (uncompressed)"
  672 }
  673 
  674 
  675 show_backup_estimates ()
  676 {
  677     require_tools $COMMON_TOOLS
  678 
  679     mount_dev
  680     cd $BACKUP_DIR
  681     rm -fr $TMP.*
  682     prepare_backup "Estimating" $1
  683     rm -f $TMP.*
  684 }
  685 
  686 
  687 do_backup ()
  688 {
  689     require_tools $COMMON_TOOLS
  690     require_drivers $CREATE_DRIVER
  691 
  692     # Print time stamp & mount backup drive...
  693     date
  694     echo
  695     mount_dev
  696 
  697     # Run pre-backup
  698     echo "Running pre-backup procedure..."
  699     PRE_BACKUP
  700 
  701     # Operate in destination directory
  702     cd $BACKUP_DIR
  703     rm -fr $TMP.*
  704 
  705     # Remove old backups...
  706     echo
  707     echo "Removing old backups..."
  708     name_cleanup  # should not do anything in normal cases
  709     compute_level_and_bids $1
  710     SAVED_LEVEL=$LEVEL
  711 
  712     # Rotate level-0 backups if necessary...
  713     TOO_MANY=$(( ${NEW_BID:0:1} - $MAX_FULL ))
  714     if [[ $TOO_MANY -gt 0 ]]; then
  715         N=0
  716         while [[ $N -lt $TOO_MANY ]]; do
  717             : $[N+=1]
  718             purge $N
  719         done
  720         name_cleanup
  721         compute_level_and_bids $SAVED_LEVEL
  722     fi
  723 
  724     # Remove old differential backups...
  725     if [[ $BASE_BID -gt 0 ]]; then
  726         ARCH_LIST=(`ls $VOLNAME.*1.list.gz`)
  727         LV=0
  728         while [ $LV -le 10 ]; do
  729             : $[LV+=1]
  730             MATCHCNT=0
  731             N=${#ARCH_LIST[*]}
  732             while [[ $N -gt 0 ]]; do
  733                 : $[N-=1]
  734                 BID=`get_bid_of_name ${ARCH_LIST[$N]}`
  735                 if [[ ${#BID} == $[LV+1] && ${BID:0:$LV} != ${NEW_BID:0:$LV} ]]; then
  736                     : $[MATCHCNT+=1]
  737                     if [[ $MATCHCNT -gt $GENERATIONS ]]; then
  738                         purge $BID
  739                     fi
  740                 fi
  741             done
  742         done
  743     fi
  744 
  745     # Prepare backup...
  746     echo
  747     prepare_backup "Preparing" $SAVED_LEVEL
  748 
  749     # Create and verify archive file...
  750     echo
  751     echo "Creating archive using '"$CREATE_DRIVER"'..."
  752     ARCH_SUFFIX=`$CREATE_DRIVER -suffix`
  753     $CREATE_DRIVER -create $NEW_BID $TMP.$ARCH_SUFFIX $TMP.files 2>&1 | sed 's/^/  /'
  754     if [ "$ARCH_SUFFIX" != "" ]; then
  755         echo "Checking TOC of archive file (< real file, > archive entry)..."
  756         $CREATE_DRIVER -toc $NEW_BID $TMP.$ARCH_SUFFIX | eval "$FILTER_UNIFY_NAME" > $TMP.toc
  757         eval "$FILTER_UNIFY_NAME" < $TMP.files | diff - $TMP.toc | tee $TMP.error | sed 's/^/  /'
  758     fi
  759 
  760     # Move files in place...
  761     gzip -9 $TMP.list $TMP.skipped $TMP.obsolete $TMP.error
  762     if [ $LEVEL != 0 ]; then
  763         gzip -9 $TMP.new
  764     else
  765         rm -f $TMP.new
  766             # Here we don't use the do_symlink function because we need to copy or
  767             # symlink different files, depending on wether we copy or symlink
  768         ln -s $VOLNAME.$NEW_BID.list.gz $TMP.new.gz &> /dev/null ||
  769         cp -af $TMP.list.gz $TMP.new.gz
  770     fi
  771     for SUFFIX in skipped.gz new.gz obsolete.gz error.gz $ARCH_SUFFIX list.gz ; do
  772         # *.list.gz has to be the last for transaction safety
  773         mv $TMP.$SUFFIX $VOLNAME.$NEW_BID.$SUFFIX
  774     done
  775 
  776     # Create check file if requested...
  777     if [[ "$CREATE_CHECK_FILE" == "1" ]]; then
  778         create_check $NEW_BID
  779     fi
  780 
  781     # Run post-backup
  782     echo
  783     echo "Running post-backup procedure..."
  784     POST_BACKUP
  785 
  786     # print summary and finish...
  787     rm -f $TMP.*
  788     echo
  789     date
  790     echo
  791     echo
  792     show_summary
  793 }
  794 
  795 
  796 
  797 
  798 
  799 ##################################################
  800 # show_availability
  801 
  802 
  803 show_availability ()
  804 {
  805     # parameters: masks
  806 
  807     require_tools $COMMON_TOOLS
  808 
  809     mount_dev 1>&2
  810     cd $BACKUP_DIR
  811     rm -fr $TMP.*
  812 
  813     MASK="$@"
  814     if [ "$MASK" = "" ]; then
  815         MASK="/"
  816     fi
  817 
  818     echo "Listing available files..." 1>&2
  819     for F in $VOLNAME.*.list.gz; do
  820         BID=`get_bid_of_name $F`
  821         FBID=$BID
  822         while [[ ${#FBID} -lt 5 ]]; do
  823             FBID=$FBID" "
  824         done
  825         for X in "$MASK"; do
  826            gunzip -c $VOLNAME.$BID.obsolete.gz | grep "$X" | sed "s/^/$VOLNAME.$FBID - /"
  827            gunzip -c $VOLNAME.$BID.new.gz | grep "$X" | sed "s/^/$VOLNAME.$FBID + /"
  828         done
  829     done
  830     cd /
  831     umount_dev 1>&2
  832 }
  833 
  834 
  835 
  836 
  837 
  838 ##################################################
  839 # show_location
  840 
  841 
  842 get_location ()
  843 {
  844     # parameter 1: BID of snapshot
  845     # other parameters: masks
  846 
  847     if [[ $1 == "head" ]]; then
  848         BID=`get_last_bid`
  849     else
  850         BID=$1
  851     fi
  852     if [[ ! -f $VOLNAME.$BID.list.gz ]]; then
  853         echo "ERROR: Specified backup archive <$VOLNAME.$BID> does not exist!"
  854         exit 3
  855     fi
  856     shift
  857 
  858     MASK_LIST="$@"
  859     if [ "$MASK_LIST" = "" ]; then
  860         MASK_LIST="/"
  861     fi
  862 
  863     # determine active files...
  864     for MASK in "$MASK_LIST"; do
  865         gunzip -c $VOLNAME.$BID.list.gz | grep "$MASK" | tee $TMP.found | grep '/$' >> $TMP.dirs
  866             # dirs go to $TMP.dirs WITH attributes
  867         grep -v '/$' $TMP.found | eval "$FILTER_NAME" >> $TMP.left
  868             # files go to $TMP.left WITHOUT attributes
  869     done
  870     echo "Active files in <$VOLNAME.$BID>:" `wc -l < $TMP.left`
  871 
  872     sort < $TMP.left > $TMP.nowleft
  873     mv $TMP.nowleft $TMP.left
  874 
  875     # generate location list
  876     LAST_BID="xxx"
  877     touch $TMP.located
  878     touch $TMP.archlist
  879     touch $TMP.noarch
  880     while [ ${#LAST_BID} -gt 1 -a `wc -l < $TMP.left` -gt 0 ]; do
  881         gunzip -c $VOLNAME.$BID.new.gz | eval "$FILTER_NAME" | sort | comm -1 -2 - $TMP.left | tee $TMP.found \
  882             | comm -1 -3 - $TMP.left > $TMP.nowleft
  883         local FOUND=`wc -l < $TMP.found`
  884         printf "  found in %-12s%5i   (%5i left)\n" \
  885             "$VOLNAME.$BID:" $FOUND `wc -l < $TMP.nowleft`
  886         if [ "$FOUND" -gt 0 ]; then
  887             sed "s/^/$VOLNAME.$BID: /" < $TMP.found >> $TMP.located
  888             mv $TMP.nowleft $TMP.left
  889             DRIVER=`get_driver $VOLNAME.$BID`
  890             if [ "$DRIVER" = "" ]; then
  891                 echo $VOLNAME.$BID >> $TMP.noarch
  892             else
  893                 echo $VOLNAME.$BID:$DRIVER >> $TMP.archlist
  894             fi
  895         fi
  896         LAST_BID=$BID
  897         BID=`get_base_bid $LAST_BID`
  898     done
  899     echo
  900     # leaves $TMP.left, $TMP.found, $TMP.located, $TMP.dirs, $TMP.archlist, $TMP.noarch
  901 }
  902 
  903 
  904 show_location ()
  905 {
  906     # parameter 1: BID of snapshot
  907     # other parameters: masks
  908 
  909     require_tools $COMMON_TOOLS comm
  910 
  911     mount_dev 1>&2
  912     cd $BACKUP_DIR
  913     rm -fr $TMP.*
  914 
  915     get_location "$@" 1>&2
  916     echo "Listing locations..." 1>&2
  917     cat $TMP.located
  918     sed 's#^#NOT FOUND: #' < $TMP.left
  919 
  920     if [ `wc -l < $TMP.noarch` -gt 0 ]; then
  921         echo -e "\nTo restore, the archive files of the following backups are missing and" \
  922                 "\nhave to be copied or linked into $BACKUP_DIR:" 1>&2
  923         sed 's#^#  #' < $TMP.noarch 1>&2
  924     else
  925         echo -e "\nAll required archive files are present in $BACKUP_DIR." 1>&2
  926     fi
  927 
  928     rm -f $TMP.*
  929     cd /
  930     umount_dev 1>&2
  931 }
  932 
  933 
  934 
  935 
  936 
  937 ##################################################
  938 # do_restore
  939 
  940 
  941 do_restore ()
  942 {
  943     # parameter 1: <level>.<version> of snapshot
  944     # other parameters: masks
  945 
  946     require_tools $COMMON_TOOLS comm
  947 
  948     mount_dev
  949     pushd $BACKUP_DIR > /dev/null
  950     rm -fr $TMP.*
  951 
  952     # determine which file to get from which archive
  953     get_location "$@"
  954 
  955     popd > /dev/null
  956 
  957     if [ `wc -l < $BACKUP_DIR/$TMP.noarch` -gt 0 ]; then
  958         echo "Cannot access archive file(s) of the following backup(s):"
  959         sed 's#^#  #' < $BACKUP_DIR/$TMP.noarch
  960         echo -e "\nNothing has been restored."
  961     else
  962 
  963         # check availability of all required drivers in advance...
  964         require_drivers `sed 's#^.*:##' < $BACKUP_DIR/$TMP.archlist`
  965 
  966         # create directories...
  967         DIRS=`wc -l < $BACKUP_DIR/$TMP.dirs`
  968         if [ $DIRS -gt 0 ]; then
  969             echo "Restoring" $DIRS "directories..."
  970             eval "$FILTER_NAME" < $BACKUP_DIR/$TMP.dirs | sed 's#^/\(.*\)$#"\1"#' | xargs -l1 mkdir -p
  971             eval "$FILTER_CHMOD" < $BACKUP_DIR/$TMP.dirs | xargs -l1 chmod
  972             eval "$FILTER_CHOWN" < $BACKUP_DIR/$TMP.dirs | xargs -l1 chown
  973         fi
  974 
  975         # process all archives...
  976         echo "Restoring files..."
  977         for ARCH_AND_DRIVER in `cat $BACKUP_DIR/$TMP.archlist`; do
  978             ARCH=${ARCH_AND_DRIVER%%:*}
  979             BID=${ARCH#$VOLNAME.}
  980             DRIVER=${ARCH_AND_DRIVER##*:}
  981             SUFFIX=`$DRIVER -suffix`
  982             grep "^$ARCH:" $BACKUP_DIR/$TMP.located \
  983                 | sed -e "s#^$ARCH: /##" -e 's#\([][*?]\)#\\\1#g' \
  984                 > $BACKUP_DIR/$TMP.curlist
  985                 # The second sed expression escapes special glob(7) characters ([]*?).
  986             FILES=`wc -l < $BACKUP_DIR/$TMP.curlist`
  987             echo "  $ARCH.$SUFFIX:" $FILES "file(s) using '"$DRIVER"'"
  988             $DRIVER -extract $BID $BACKUP_DIR/$ARCH.$SUFFIX $BACKUP_DIR/$TMP.curlist | sed "s/^/    /"
  989         done
  990     fi
  991 
  992     # Cleanup...
  993     rm -f $BACKUP_DIR/$TMP.*
  994 }
  995 
  996 
  997 
  998 ##################################################
  999 # External archiving
 1000 
 1001 
 1002 do_external ()
 1003 {
 1004     require_tools $COMMON_TOOLS split tail md5sum
 1005 
 1006     CTRL_EXPR="$VOLNAME\.[0-9]*\.((list)|(new)|(obsolete)|(skipped)|(error)|(check))"
 1007     mount_dev
 1008 
 1009     CAPACITY=$(( $1 * 1024 ))
 1010     shift
 1011     MAXFREE=$(( $1 * 1024 ))
 1012     shift
 1013 
 1014     # determine BID_LIST...
 1015     BID_LIST=`expand_bid_list list.gz "$@"`
 1016 
 1017     # determine distribution...
 1018     LAST_DISK=1
 1019     REMAINING=$CAPACITY
 1020     rm -f TMP.xdist 2> /dev/null
 1021     touch TMP.xdist
 1022     for BID in $BID_LIST; do
 1023         CTRL_SIZE=`ls -1s $BACKUP_DIR/$VOLNAME.$BID.* | grep -E "$CTRL_EXPR" \
 1024             | awk '{ sum += $1 } END { print sum }'`
 1025         if [ $CTRL_SIZE -gt $REMAINING -a $REMAINING -lt $CAPACITY ]; then
 1026             : $[ LAST_DISK += 1 ]
 1027             REMAINING=$CAPACITY
 1028         fi
 1029         LAST_DISK_DIR=`printf "data-%02d" $LAST_DISK`
 1030         ls -1s $BACKUP_DIR/$VOLNAME.$BID.* | grep -E "$CTRL_EXPR" \
 1031             | sed "s#$BACKUP_DIR#$LAST_DISK_DIR#" >> TMP.xdist
 1032         : $[ REMAINING -= $CTRL_SIZE ]
 1033         for ARCHIVE_PATH in `ls -1d $BACKUP_DIR/$VOLNAME.$BID.* | grep -vE "$CTRL_EXPR"`; do
 1034             ARCHIVE=${ARCHIVE_PATH##*/}
 1035             if [ -f $BACKUP_DIR/$ARCHIVE ]; then
 1036                 DATA_SIZE=`ls -1s $BACKUP_DIR/$ARCHIVE | sed 's/ [^ ]*$//'`
 1037                 if [ $DATA_SIZE -gt $REMAINING -a $REMAINING -lt $MAXFREE ]; then
 1038                     : $[ LAST_DISK += 1 ]
 1039                     REMAINING=$CAPACITY
 1040                 fi
 1041                 if [ $DATA_SIZE -le $REMAINING ]; then
 1042                     LAST_DISK_DIR=`printf "data-%02i" $LAST_DISK`
 1043                     ls -1s $BACKUP_DIR/$ARCHIVE | sed "s#$BACKUP_DIR#$LAST_DISK_DIR#" >> TMP.xdist
 1044                     : $[ REMAINING -= $DATA_SIZE ]
 1045                 else
 1046                     SPLIT_NO=1
 1047                     while [ $DATA_SIZE -gt $REMAINING ]; do
 1048                         LAST_DISK_DIR=`printf "data-%02i" $LAST_DISK`
 1049                         printf "%4i $LAST_DISK_DIR/$ARCHIVE.%02i\n" $REMAINING $SPLIT_NO >> TMP.xdist
 1050                         : $[ DATA_SIZE -= $REMAINING ]
 1051                         : $[ SPLIT_NO += 1 ]
 1052                         : $[ LAST_DISK += 1 ]
 1053                         REMAINING=$CAPACITY
 1054                     done
 1055                     LAST_DISK_DIR=`printf "data-%02i" $LAST_DISK`
 1056                     printf "%4i $LAST_DISK_DIR/$ARCHIVE.%02i\n" $DATA_SIZE $SPLIT_NO >> TMP.xdist
 1057                     : $[ REMAINING -= $DATA_SIZE ]
 1058                 fi
 1059             else
 1060                 echo "WARNING: Cannot handle directory: $ARCHIVE (skipping)"
 1061                 echo
 1062             fi
 1063         done
 1064     done
 1065 
 1066     SPACE=$(( (`grep "\.[0-9]*$" TMP.xdist | awk '{ sum += $1 } END { print sum }'` + 1023) / 1024 ))
 1067     cat << EOT
 1068 I am about to split and combine the selected backup archives into directories
 1069 of equal size, so that they can be stored on a set of removable media
 1070 (e. g. CDs). If 'cdlabelgen' is installed, I will create CD covers.
 1071 It is up to you to burn the CDs or store the data in whichever way you like.
 1072 
 1073 All files are generated in the current working directory ($PWD).
 1074 Make sure it is empty!
 1075 
 1076 In order to save disk space, only symbolic links will be generated wherever
 1077 possible. Make sure that they are followed by your CD-burn/storage tool and
 1078 that the backup device is mounted!
 1079 
 1080 You have selected a medium capacity of $(( $CAPACITY / 1024 )) MB with a maximum waste of $(( $MAXFREE /1024 )) MB
 1081 per medium. The selected BIDs are:
 1082 
 1083   $BID_LIST
 1084 
 1085 I need about $SPACE MB of disk space in the current directory.
 1086 You will get $LAST_DISK volume(s).
 1087 
 1088 EOT
 1089     read -p "Do you want to see details? [y/N] " ANSWER
 1090     if [[ "$ANSWER" == "y" ]]; then
 1091         echo -e "\nSize  Volume/File\n======================================="
 1092         cat TMP.xdist
 1093     fi
 1094     echo
 1095     read -p "Do you want to continue? [y/N] " ANSWER
 1096     if [[ "$ANSWER" != "y" ]]; then
 1097         rm -f TMP.xdist
 1098         return
 1099     fi
 1100 
 1101     echo -e "\nCreating links..."
 1102     rm -fr data-??
 1103 
 1104     # create directories...
 1105     sed -e 's#^[ 0-9]*##' -e 's#/.*$##' < TMP.xdist | sort -u | xargs -l1 mkdir -p
 1106 
 1107     # create links...
 1108     for FILE in `grep -v '\.[0-9]*$' TMP.xdist | sed 's#^[ 0-9]*##'`; do
 1109         do_symlink $BACKUP_DIR/${FILE#data-??/} $FILE
 1110     done
 1111 
 1112     echo "Splitting large files..."
 1113     for FILE in `grep '\.01$' TMP.xdist | sed -e 's#^[ 0-9]*##' -e 's#.01$##'`; do
 1114         ORG_FILE=${FILE#data-??/}
 1115         echo "  $ORG_FILE"
 1116         HEAD_SIZE=$(( `grep $FILE TMP.xdist | sed 's/ [^ ]*$//'` * 1024 ))
 1117         head $BACKUP_DIR/$ORG_FILE -c $HEAD_SIZE > $FILE.01
 1118         tail $BACKUP_DIR/$ORG_FILE -c +$(( $HEAD_SIZE + 1 )) | split -b $(( $CAPACITY * 1024 )) - TMP.split.
 1119         SPLIT_NO=2
 1120         for SPLIT in `ls -1 TMP.split.*`; do
 1121             DST_FILE=`printf "$ORG_FILE.%02i" $SPLIT_NO`
 1122             DST=`grep $DST_FILE TMP.xdist | sed 's#^[ 0-9]*##'`
 1123             mv $SPLIT $DST
 1124             : $[ SPLIT_NO += 1 ];
 1125         done
 1126     done
 1127 
 1128     # create self-check files...
 1129     cat << EOT
 1130 
 1131 I can now generate check scripts for each volume that can later be used to
 1132 verify the integrity of all files. This is e.g. useful if, in a couple of
 1133 years, you want to know whether your backup media are still readable.
 1134 Then simply mount your media and type '. check_these_files.sh' inside the
 1135 media's main directory.
 1136 
 1137 EOT
 1138     read -p "Create self-check scripts? [Y/n] " ANSWER
 1139     if [[ "$ANSWER" != "n" ]]; then
 1140         echo
 1141         echo "Creating self-check scripts..."
 1142         for DISK in data-??; do
 1143             echo "  $DISK"
 1144             cd $DISK
 1145 
 1146             DST_FILE="check_these_files.sh"
 1147             rm -f $DST_FILE
 1148             FILES=`find . -follow -type f`
 1149             echo "#!/bin/sh" > $DST_FILE
 1150             echo >> $DST_FILE
 1151             echo "echo \"This script has been auto-generated by backup2l v$VER.\"" >> $DST_FILE
 1152             echo "echo \"Verifying file(s) using md5sum(1)...\"" >> $DST_FILE
 1153             echo >> $DST_FILE
 1154             echo "md5sum -v -c << EOF" >> $DST_FILE
 1155             md5sum $FILES >> $DST_FILE
 1156             echo "EOF" >> $DST_FILE
 1157 
 1158             cd ..
 1159         done
 1160     fi
 1161 
 1162     # create CD labels
 1163     if which cdlabelgen > /dev/null; then
 1164       echo
 1165       echo "Creating CD labels..."
 1166       read -p "  Enter CD title [Backup]: " ANSWER
 1167       if [[ "$ANSWER" == "" ]]; then
 1168           ANSWER="Backup"
 1169       fi
 1170       if [ -r /usr/share/cdlabelgen/penguin.eps ]; then
 1171           TRAYPIC="-e /usr/share/cdlabelgen/penguin.eps -S 0.5"
 1172       else
 1173           TRAYPIC=""
 1174       fi
 1175       for DISK in data-??; do
 1176           DISK_NO=${DISK#data-}
 1177           echo -e "\nContents\n========\n" > contents-$DISK_NO.txt
 1178           grep $DISK TMP.xdist | sed -e 's#^.*/##' -e 's#\.list\.gz# - control files#' \
 1179               | grep -vE "$CTRL_EXPR" >> contents-$DISK_NO.txt
 1180           cdlabelgen -c "$ANSWER" -s "${DISK_NO#0} of $LAST_DISK" -d `date -I` \
 1181               -f contents-$DISK_NO.txt $TRAYPIC -o cd-cover-$DISK_NO.ps
 1182       done
 1183     fi
 1184 
 1185     # clean up
 1186     rm -f TMP.xdist
 1187 }
 1188 
 1189 
 1190 
 1191 
 1192 
 1193 ##################################################
 1194 # Usage & banner
 1195 
 1196 
 1197 banner ()
 1198 {
 1199     echo backup2l v$VER by Gundolf Kiefer
 1200     echo
 1201 }
 1202 
 1203 
 1204 usage ()
 1205 {
 1206     banner
 1207     cat << EOF
 1208 Usage: backup2l [-c <conffile>] [-t <BID>] <command>
 1209 Where
 1210     -c | --conf <conffile>         : specifies configuration file [/etc/backup2l.conf]
 1211     -t | --time <BID>              : specifies backup ID as a point-in-time for --locate and --restore
 1212 
 1213   <command>:
 1214     -h | --help                    : Help
 1215     -b | --backup [<level>]        : Create new backup
 1216     -e | --estimate [<level>]      : Like -b, but nothing is really done
 1217     -s | --get-summary             : Show backup summary
 1218 
 1219     -a | --get-available <pattern> : Show all files in all backups containing <pattern> in their path names
 1220     -l | --locate [<pattern>]      : Show most recent backup location of all active files matching <pattern>
 1221     -r | --restore [<pattern>]     : Restore active files matching <pattern> into current directory
 1222 
 1223     -p | --purge <BID-list>        : Remove the specified backup archive(s) and all depending backups
 1224 
 1225     -v | --verify [<BID-list>]     : Verify the specified / all backup archive(s)
 1226     -m | --make-check [<BID-list>] : Create md5 checksum file for the specified archive(s) / wherever missing
 1227 
 1228     -x | --extract <volume size> <max free> <BID-list> :
 1229                                      Split and collect files to be stored on removable media (e. g. CDs)
 1230 
 1231 EOF
 1232     echo "Built-in archive drivers:" $BUILTIN_DRIVER_LIST
 1233     if [ "$USER_DRIVER_LIST" != "" ]; then
 1234         echo "User-defined drivers:    " $USER_DRIVER_LIST
 1235     fi
 1236 }
 1237 
 1238 
 1239 
 1240 
 1241 
 1242 ##################################################
 1243 # Main
 1244 
 1245 
 1246 shopt -s nullglob
 1247 shopt -s extglob
 1248 unset LANG LC_ALL LC_COLLATE # otherwise: unpredictable sort order in sort, ls
 1249 
 1250 
 1251 # Set defaults...
 1252 CREATE_DRIVER="DRIVER_TAR_GZ"  # works with last stable version 1.01
 1253 
 1254 # Read & validate setup...
 1255 CONF_FILE="/etc/backup2l.conf"
 1256 if [ "$1" = "-c" -o "$1" = "--conf" ]; then
 1257     shift
 1258     CONF_FILE=$1
 1259     shift
 1260 fi
 1261 if [ -f "$CONF_FILE" ]; then
 1262     . $CONF_FILE
 1263 else
 1264   echo "Could not open configuration file '$CONF_FILE'. Aborting."
 1265   exit 3
 1266 fi
 1267 
 1268 if [ "$UNCONFIGURED" = "1" ]; then
 1269     banner
 1270     echo -e "The configuration file '$CONF_FILE' has to be edited before using ${0##*/}.\n"
 1271     echo -e "For help, look into the comments or read the man page.\n"
 1272     exit 3
 1273 fi
 1274 if [ "$VOLNAME" = "" -o "$SRCLIST" = "" -o "${SKIPCOND[*]}" = "" -o "$BACKUP_DIR" = "" -o \
 1275      "$MAX_LEVEL" = "" -o "$MAX_PER_LEVEL" = "" -o "$MAX_FULL" = "" ]; then
 1276     echo "ERROR: The configuration file '$CONF_FILE' is missing or incomplete."
 1277     exit 3
 1278 fi
 1279 
 1280 if [ "$FOR_VERSION" = "" ]; then
 1281     FOR_VERSION="0.9"
 1282 fi
 1283 if [[ "$FOR_VERSION" < "1.1" || "$FOR_VERSION" > "${VER%-*}" ]]; then
 1284     banner
 1285     cat << EOF
 1286 The configuration file '$CONF_FILE' seems to be written for
 1287 version $FOR_VERSION and may be incompatible with this version of backup2l.
 1288 
 1289 The following variables have been added, removed or their syntax may have
 1290 changed. Details can be found in first_time.conf and the man page.
 1291 
 1292   1.1 : CREATE_DRIVER, USER_DRIVER_LIST
 1293   0.93: POST_BACKUP
 1294   0.91: SRCLIST, SKIPCOND, FOR_VERSION
 1295 
 1296 If you think your configuration file is correct, please change the value
 1297 of FOR_VERSION.
 1298 EOF
 1299     exit 3
 1300 fi
 1301 TMP="TMP.$VOLNAME"
 1302 
 1303 # Read time point if given
 1304 if [ "$1" = "-t" -o "$1" = "--time" ]; then
 1305     shift
 1306     BID=${1#$VOLNAME.}
 1307     shift
 1308 else
 1309     BID="head"
 1310 fi
 1311 
 1312 # Go ahead...
 1313 case $1 in
 1314     -h | --help)
 1315         usage
 1316         ;;
 1317     -e | --estimate)
 1318         banner
 1319         show_backup_estimates "$2"
 1320         ;;
 1321     -b | --backup)
 1322         banner
 1323         do_backup "$2"
 1324         ;;
 1325     -s | --get-summary)
 1326         banner
 1327         mount_dev
 1328         show_summary
 1329         ;;
 1330     -a | --get-available)
 1331         banner 1>&2
 1332         shift
 1333         show_availability "$@"
 1334         ;;
 1335     -l | --locate)
 1336         banner 1>&2
 1337         shift
 1338         show_location $BID "$@"
 1339         ;;
 1340     -r | --restore)
 1341         banner
 1342         shift
 1343         do_restore $BID "$@"
 1344         ;;
 1345     -p | --purge)
 1346         banner
 1347         shift
 1348         do_purge "$@"
 1349         ;;
 1350     -v | --verify)
 1351         banner
 1352         shift
 1353         do_check "$@"
 1354         ;;
 1355     -m | --make-check)
 1356         banner
 1357         shift
 1358         do_create_check "$@"
 1359         ;;
 1360     -x | --extract)
 1361         banner
 1362         shift
 1363         if [[ "$3" == "" ]]; then
 1364             usage
 1365         else
 1366             do_external "$@"
 1367         fi
 1368         ;;
 1369     *)
 1370         if [ "$AUTORUN" = "1" ]; then
 1371             banner
 1372             do_backup
 1373         else
 1374             usage
 1375         fi
 1376         ;;
 1377 esac
 1378 
 1379 # Unmount backup device if it was mounted
 1380 cd /            # avoid "device is busy" during unmount
 1381 umount_dev