Advertisement
v1ral_ITS

amazing easy terminal backup program

Aug 11th, 2018
315
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Bash 40.75 KB | None | 0 0
  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. PROG=backup2l
  30. VER=1.6
  31.  
  32.  
  33. # The following variable defines the commonly required tools. Additional tools
  34. # may be required by special functions, e. g. md5sum by the check functions.
  35. # Tool requirements are check in the do_* and show_* functions.
  36.  
  37. COMMON_TOOLS="date find grep gzip gunzip sed awk mount umount"
  38.  
  39.  
  40. # The following variables define the format of *.list files. Modify at your own risk.
  41.  
  42. FORMAT="%8s %TD %.8TT %04U.%04G %04m %p"     # format for *.list files
  43.  
  44. FILTER_NAME="sed 's#^\([-:. 0-9]*/\)\{3\}#/#'"
  45.     # sed command for extracting names from .list, .new, ... files
  46.     # (removes everything up to the 3rd "/"; two are contained in the time field)
  47. FILTER_CHOWN="sed 's#^\( *[^ ]\+\)\{3\} *0\{0,3\}\([0-9]\+\)\.0\{0,3\}\([0-9]\+\) \+\([0-9]*\)\{4\} \+/\(.*\)\$#\2:\3 \"\5\"#'"
  48.     # sed command for extracting ownerships and names from .list files
  49. FILTER_CHMOD="sed 's#^\( *[^ ]\+\)\{4\} *\([0-9]\{4\}\) \+/\(.*\)\$#\2 \"\3\"#'"
  50.     # sed command for extracting permissions and names from .list files
  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.             ;;
  251.         -toc)           # Arguments: $2 = BID, $3 = archive file name
  252.             afio -Zt $3 | sed 's#^#/#' # 's#^#/#;s#\.z$##'
  253.             ;;
  254.         -extract)       # Arguments: $2 = BID, $3 = archive file name, $4 = file list file
  255.             afio -Zinw $4 $3 2>&1
  256.             ;;
  257.     esac
  258. }
  259.  
  260.  
  261.  
  262. # Helpers
  263.  
  264.  
  265. require_drivers ()
  266. {
  267.     for DRIVER in $@; do
  268.         local STATUS=`$DRIVER -test`
  269.         if [ "$STATUS" != "ok" ]; then
  270.             echo "ERROR: Archive driver not ready: $DRIVER"
  271.             echo $STATUS
  272.             exit 3
  273.         fi
  274.     done
  275. }
  276.  
  277.  
  278. get_driver_and_file ()           # Argument: <path>/<volname>.<bid>
  279. {
  280.     local DRIVER
  281.     for DRIVER in $USER_DRIVER_LIST $BUILTIN_DRIVER_LIST; do
  282.         local SUFFIX=`$DRIVER -suffix`
  283.         if [ "$SUFFIX" != "" -a -e "$1.$SUFFIX" ]; then
  284.             echo "$DRIVER:$1.$SUFFIX"
  285.             break
  286.         fi
  287.     done
  288. }
  289.  
  290.  
  291. get_driver ()                    # Argument: <path><volname><bid>
  292. {
  293.     local DRIVER_AND_FILE=`get_driver_and_file $1`
  294.     echo ${DRIVER_AND_FILE%%:*}
  295. }
  296.  
  297.  
  298. get_archive ()                   # Argument: <path><volname><bid>
  299. {
  300.     local DRIVER_AND_FILE=`get_driver_and_file $1`
  301.     echo ${DRIVER_AND_FILE##*:}
  302. }
  303.  
  304.  
  305.  
  306.  
  307.  
  308. ##################################################
  309. # (Un)mount backup disk and (un)lock volume
  310.  
  311. # Note: The locking mechanism is not completely safe, since in 'mount_dev'
  312. # the existence check and the setting of the lock are seperate operation
  313. # instead of one atomic one (such as "test-and-set"). Atomic operations
  314. # would require support by the underlying file system, the type of which
  315. # we do not know and do not want to dictate. So we consider this little
  316. # synchronization hole tolerable.
  317.  
  318.  
  319. MUST_BE_UNMOUNTED=0   # did we mount a device?
  320. VOL_IS_LOCKED=0       # did we already lock (aka "mount") the volume?
  321.  
  322.  
  323. mount_dev ()
  324. {
  325.     if [ $VOL_IS_LOCKED -eq 1 ]; then return 0; fi   # do not lock twice
  326.  
  327.     if [ ! -d $BACKUP_DIR -a "$BACKUP_DEV" != "" ]; then
  328.         echo "Mounting $BACKUP_DEV..."
  329.         echo
  330.         mount $BACKUP_DEV
  331.         MUST_BE_UNMOUNTED=1
  332.     fi
  333.     if [ ! -d $BACKUP_DIR ]; then
  334.         echo "ERROR: $BACKUP_DIR not present (mount failed?)"
  335.         umount $BACKUP_DEV >& /dev/null
  336.         exit 3
  337.     fi
  338.     # Locking: Begin
  339.     if [ -e $BACKUP_DIR/$VOLNAME.lock ]; then
  340.         if [ `ps -a | grep -af $BACKUP_DIR/$VOLNAME.lock | grep -a ${0##*/} | wc -l` -gt 0 ]; then
  341.             echo "ERROR: Backup volume is locked."
  342.             echo
  343.             echo "Another instance is currently running. If you are sure that this is not"
  344.             echo "the case, then remove the lock file '$BACKUP_DIR/$VOLNAME.lock' manually."
  345.             exit 3
  346.         fi
  347.     fi
  348.     echo $$ > $BACKUP_DIR/$VOLNAME.lock
  349.     # Locking: End
  350.     VOL_IS_LOCKED=1
  351. }
  352.  
  353.  
  354. umount_dev ()
  355. {
  356.     if [ $VOL_IS_LOCKED -eq 0 ]; then return 0; fi   # do not unlock twice
  357.  
  358.     if [[ "`cat $BACKUP_DIR/$VOLNAME.lock 2>/dev/null`" == "$$" ]]; then
  359.         # A lock exists and comes from this instance => everything is ok
  360.         # Note: This test of a (rare) locking failure is also not perfect
  361.         # and may itself fail, e.g. if the backup devices is on a network
  362.         # and the lock files are cached locally. However, a (very very rare)
  363.         # failure here is tolerable: The user would receive an (obscure)
  364.         # error message and can repeat the backup / his operation.
  365.         rm -f $BACKUP_DIR/$VOLNAME.lock
  366.         if [ $MUST_BE_UNMOUNTED -eq 1 ]; then
  367.             echo
  368.             echo "Unmounting $BACKUP_DEV..."
  369.             umount $BACKUP_DEV
  370.             MUST_BE_UNMOUNTED=0
  371.         fi
  372.         VOL_IS_LOCKED=0
  373.     else
  374.         # No lock file exists, or it comes from another process => we have a race problem
  375.         echo
  376.         echo "WARNING: Volume is or was locked by another instance of '$PROG'."
  377.         echo
  378.         echo "It appears that another instance of 'backup2l' is or was running concurrently."
  379.         echo "This should not happen, but cannot be avoided completely with the present"
  380.         echo "implementation of locking in 'backup2l'. I am sorry!"
  381.         echo
  382.         echo "The following steps should bring everything back into order:"
  383.         echo "1. Stop all instances of '$PROG'."
  384.         echo "2. Remove the lock file '$BACKUP_DIR/$VOLNAME.lock' manually."
  385.         echo "3. Re-run the backup."
  386.         echo "4. Unmount the backup device (if desired)."
  387.     fi
  388. }
  389.  
  390.  
  391.  
  392.  
  393.  
  394. ##################################################
  395. # Purging
  396.  
  397.  
  398. purge ()
  399. {
  400.     local LV=${#1}
  401.     : $[LV-=1]
  402.     local LASTDIG=${1:LV:1}
  403.     local PREFIX=${1%?}
  404.     if [[ $LV == 0 ]]; then
  405.         local CAND=$VOLNAME.$1*.list.gz
  406.     else
  407.         local CAND=$VOLNAME.$PREFIX[$LASTDIG-9]*.list.gz
  408.     fi
  409.     local ARCH BASE
  410.     for ARCH in $CAND; do
  411.         BASE=${ARCH%.list.gz}
  412.         echo "  removing <$BASE>"
  413.         rm -fr $BASE.*
  414.     done
  415. }
  416.  
  417.  
  418. name_cleanup ()
  419. {
  420.     local DST=0
  421.     local SRC=0
  422.     local SUFFIX SRC_FILE DST_FILE CHK
  423.     while [[ $SRC -lt 9 ]]; do
  424.         : $[SRC+=1]
  425.         if [[ -f $VOLNAME.$SRC.list.gz ]]; then
  426.             : $[DST+=1]
  427.             if [[ "$SRC" != "$DST" ]]; then
  428.                 for SRC_FILE in $VOLNAME.$SRC* ; do
  429.                     local SUFFIX=${SRC_FILE##$VOLNAME.$SRC}
  430.                     local DST_FILE=$VOLNAME.$DST$SUFFIX
  431.                     if [ ${SUFFIX#*.} == "list.gz" ]; then
  432.                         echo "  moving <${SRC_FILE%.list.gz}> to <${DST_FILE%.list.gz}>"
  433.                     fi
  434.                     if [ ${SUFFIX#*.} == "new.gz" -a -h $SRC_FILE ]; then
  435.                         rm -fr $SRC_FILE
  436.                         do_symlink ${DST_FILE%.new.gz}.list.gz $DST_FILE
  437.                     else
  438.                         mv $SRC_FILE $DST_FILE
  439.                     fi
  440.                 done
  441.                 for CHK in $VOLNAME.$DST*.check ; do
  442.                     cat $CHK | sed "s/ \.\/$VOLNAME.$SRC/ \.\/$VOLNAME.$DST/" |\
  443.                       sed "s/ $VOLNAME.$SRC/ $VOLNAME.$DST/" > $TMP.check
  444.                     mv $TMP.check $CHK
  445.                 done
  446.             fi
  447.         fi
  448.     done
  449. }
  450.  
  451.  
  452. do_purge ()
  453. {
  454.     if [[ "$1" == "" ]]; then
  455.         echo "No archive specified - not purging anything!"
  456.     else
  457.         mount_dev
  458.         cd $BACKUP_DIR
  459.         BID_LIST=`expand_bid_list list.gz "$@"`
  460.         if [[ "$BID_LIST" == "" ]]; then
  461.             echo "No archive(s) matching '$@' found - nothing to purge."
  462.         else
  463.             echo "Purging <$BID_LIST>..."
  464.             for BID in $BID_LIST; do
  465.                 purge $BID
  466.             done
  467.             name_cleanup
  468.          fi
  469.     fi
  470. }
  471.  
  472.  
  473.  
  474.  
  475.  
  476. ##################################################
  477. # Checking
  478.  
  479.  
  480. create_check ()
  481. {
  482.     # Parameter: single BID
  483.     local BID=$1
  484.     if [[ "${#BID}" == "1" ]]; then
  485.         local BASE_FILE=""
  486.     else
  487.         local BASE_FILE=$VOLNAME.`get_base_bid $BID`.list.gz
  488.     fi
  489.     echo "Creating check file for <$VOLNAME.$BID>..."
  490.     rm -f $VOLNAME.$BID.check
  491.     md5sum `find . -follow -path "*/$VOLNAME.$BID.*" -type f` $BASE_FILE > $TMP.check
  492.     mv $TMP.check $VOLNAME.$BID.check
  493. }
  494.  
  495.  
  496. do_create_check ()
  497. {
  498.     require_tools $COMMON_TOOLS md5sum
  499.  
  500.     mount_dev
  501.     cd $BACKUP_DIR
  502.     rm -fr $TMP.*
  503.  
  504.     if [[ "$1" == "" ]]; then
  505.         BID_LIST=`expand_bid_list list.gz "*"`
  506.     else
  507.         BID_LIST=`expand_bid_list list.gz "$@"`
  508.     fi
  509.     for BID in $BID_LIST; do
  510.         if [[ "$1" != "" || ! -f $VOLNAME.$BID.check ]]; then
  511.             create_check $BID
  512.         fi
  513.     done
  514. }
  515.  
  516.  
  517. check_arch ()
  518. {
  519.     # Parameter: single BID
  520.  
  521.     local BID=$1
  522.     echo "Checking archive <$VOLNAME.$BID>..."
  523.  
  524.     # Check the plausibility by file existence...
  525.     local SUFFIX
  526.     for SUFFIX in list.gz skipped.gz new.gz obsolete.gz error.gz; do
  527.         if [[ ! -f $VOLNAME.$BID.$SUFFIX ]]; then
  528.             echo "  ERROR: File '$VOLNAME.$BID.$SUFFIX' does not exist."
  529.         fi
  530.     done
  531.     if [[ ${#BID} -gt 1 ]]; then
  532.         if [[ ! -f $VOLNAME.`get_base_bid $BID`.list.gz ]]; then
  533.             echo "  ERROR: Base archive <$VOLNAME.`get_base_bid $BID`> does not exist."
  534.         fi
  535.     fi
  536.  
  537.     # Check check sum file...
  538.     if [[ -f $VOLNAME.$BID.check ]]; then
  539.         md5sum -c $VOLNAME.$BID.check 2>&1 | sed 's/^/  /'
  540.         if [[ $(( `grep -a $VOLNAME.$BID. $VOLNAME.$BID.check | wc -l` )) -lt 6 ]]; then
  541.             echo "  ERROR: Check file seems to be corrupted."
  542.         fi
  543.     else
  544.         echo "  Information: no check file"
  545.     fi
  546. }
  547.  
  548.  
  549. do_check ()
  550. {
  551.     require_tools $COMMON_TOOLS md5sum
  552.  
  553.     mount_dev
  554.     cd $BACKUP_DIR
  555.     rm -fr $TMP.*
  556.  
  557.     if [[ "$1" == "" ]]; then
  558.         BID_LIST=`expand_bid_list list.gz "*"`
  559.     else
  560.         BID_LIST=`expand_bid_list list.gz "$@"`
  561.     fi
  562.     for BID in $BID_LIST; do
  563.         check_arch $BID
  564.     done
  565. }
  566.  
  567.  
  568.  
  569.  
  570.  
  571. ##################################################
  572. # Print summary
  573.  
  574.  
  575. show_summary ()
  576. {
  577.     require_tools $COMMON_TOOLS
  578.  
  579.     echo "Summary"
  580.     echo "======="
  581.     echo
  582.     cd $BACKUP_DIR
  583.     if [[ `echo $VOLNAME.*.list.gz` == "" ]]; then
  584.         echo "No backup archives present."
  585.     else
  586.         echo "Backup       Date       Time  |  Size   | Skipped  Files+D |  New  Obs. | Err."
  587.         echo "------------------------------------------------------------------------------"
  588.         for f in `ls $VOLNAME.*.list.gz` ; do
  589.             p=${f%%.list.gz}
  590.             size="`du -sbL $p.* | readable_bytes_sum $SIZE_UNITS`"
  591.             skipped=$( gunzip -c $p.skipped.gz | wc -l )
  592.             total=$( gunzip -c $p.list.gz | wc -l )
  593.             new_files=$( gunzip -c $p.new.gz | wc -l )
  594.             obsolete=$( gunzip -c $p.obsolete.gz | wc -l )
  595.             errors=$( gunzip -c $p.error.gz | grep -a '<' | wc -l )
  596.             printf "%-12s %s %s |%8s |%8i %8i |%5i %5i |%5i\n" \
  597.                    $p $(date -r $p.list.gz +"%Y-%m-%d %H:%M") "$size" \
  598.                    $skipped $total $new_files $obsolete $errors
  599.         done
  600.     fi
  601.     echo
  602.     df -h $BACKUP_DIR
  603. }
  604.  
  605.  
  606.  
  607.  
  608.  
  609. ##################################################
  610. # backup
  611.  
  612.  
  613. compute_level_and_bids ()
  614. {
  615.     # Determine level and base BID for new backup...
  616.     if [ ! -f $VOLNAME.1.list.gz ]; then
  617.         LEVEL="0";
  618.     else
  619.         if [[ "$1" -gt 0 || "$1" == "0" ]]; then
  620.             LEVEL=$1
  621.         else
  622.             LEVEL=$MAX_LEVEL
  623.         fi
  624.         BASE_BID=`get_last_bid`
  625.         while [[ "$LEVEL" -gt 0 && "${BASE_BID:$LEVEL:1}" -gt "$[MAX_PER_LEVEL-1]" ]]; do
  626.             : $[LEVEL-=1]
  627.         done
  628.         BASE_BID=${BASE_BID:0:$[LEVEL+1]}
  629.         BASE_BID=${BASE_BID%%+(0)}
  630.     fi
  631.  
  632.     # Determine new archive's name
  633.     if [ "$LEVEL" != "0" ]; then
  634.         NEW_BID=$BASE_BID
  635.         while [ ${#NEW_BID} -le $LEVEL ]; do
  636.             NEW_BID=${NEW_BID}"0"
  637.         done
  638.         : $[NEW_BID+=1]
  639.     else
  640.         NEW_BID="1"
  641.         while [ -f $VOLNAME.$NEW_BID.list.gz ]; do
  642.             : $[NEW_BID+=1]
  643.         done
  644.     fi
  645. }
  646.  
  647.  
  648. prepare_backup ()
  649. {
  650.     # Input: Comment ("Preparing"/"Estimating"), selected level (optional)
  651.     # Output: $LEVEL, $BASE_BID, $NEW_BID
  652.     #         $TMP.list $TMP.skipped $TMP.new $TMP.obsolete $TMP.files
  653.  
  654.     # Determine level, base BID and new BID
  655.     compute_level_and_bids $2
  656.     if [ "$LEVEL" != "0" ]; then
  657.         echo "$1 differential level-$LEVEL backup <$VOLNAME.$NEW_BID> based on <$VOLNAME.$BASE_BID>..."
  658.     else
  659.         echo "$1 full backup <$VOLNAME.$NEW_BID>..."
  660.     fi
  661.  
  662.     # Determine main list & which files are new or obsolete...
  663.     set -f  # pathname expansion off as it may destroy $SKIPCOND
  664.     OLDIFS="$IFS"
  665.     IFS=""
  666.     OLDTZ="$TZ"
  667.     if [[ "$TIME_ZONE" != "" ]]; then export TZ="$TIME_ZONE"; else unset TZ; fi;
  668.     find ${SRCLIST[*]} \( \( ${SKIPCOND[*]} \) \
  669.         \( -type d -fprintf $TMP.skipped.dirs "$FORMAT/\n" -o -fprintf $TMP.skipped.files "$FORMAT\n" \) \) \
  670.         -o \( -not -type d -printf "$FORMAT\n" -o -printf "$FORMAT/\n" \) \
  671.         | sed -e 's#   \([0-9]*\..* /.*\)# 00\1#' -e 's#  \([0-9]*\..* /\)# 0\1#' \
  672.               -e 's#\(\. *\)  \([0-9 ]* /\)#\100\2#' -e 's#\. \([0-9 ]* /\)#\.0\1#' \
  673.         | sort -k 6 \
  674.         > $TMP.list
  675.     if [[ "$OLDTZ" != "" ]]; then export TZ="$OLDTZ"; else unset TZ; fi;
  676.     IFS=$OLDIFS
  677.     cat $TMP.skipped.dirs $TMP.skipped.files \
  678.         | sed -e 's#   \([0-9]*\..* /.*\)# 00\1#' -e 's#  \([0-9]*\..* /\)# 0\1#' \
  679.               -e 's#\(\. *\)  \([0-9 ]* /\)#\100\2#' -e 's#\. \([0-9 ]* /\)#\.0\1#' \
  680.         | sort -k 6 \
  681.         > $TMP.skipped
  682.           # WORKAROUND: The reason for the two 2-line sed's above is a bug in find 4.1.7, where the format
  683.           #             directives %04x do not produce leading 0's.
  684.     set +f
  685.     if [ $LEVEL != 0 ]; then
  686.         gunzip -c $VOLNAME.$BASE_BID.list.gz | sort -k 6 | diff - $TMP.list > $TMP.diff
  687.           # WORKAROUND: 'sort -k 6' can be removed if sort uses the same options for every user,
  688.           #             which seems to be not the case!!
  689.         if [[ $(( `grep -a "<" $TMP.diff | tail -n 1 | sed 's# /.*##' | wc -w` )) == 5 ]]; then
  690.             echo "  file '$VOLNAME.$BASE_BID.list.gz' has an old format - using compatibility mode"
  691.             sed 's#[0-9]*\.[0-9 ]* /#- /#' < $TMP.list > $TMP.list.old
  692.             gunzip -c $VOLNAME.$BASE_BID.list.gz | sort -k 5 | diff - $TMP.list.old > $TMP.diff
  693.         fi
  694.         grep -a "<" $TMP.diff | sed 's/^< //' > $TMP.obsolete
  695.         grep -a ">" $TMP.diff | sed 's/^> //' > $TMP.new
  696.     else
  697.         # by convention, the *.new and *.obsolete files always exist, although redundant for level-0 backups
  698.         do_symlink $TMP.list $TMP.new
  699.         touch $TMP.obsolete
  700.     fi
  701.     eval "$FILTER_NAME" < $TMP.new | grep -av "/$" > $TMP.files   # extract real files
  702.  
  703.     # Print statistics...
  704.     echo " " `wc -l < $TMP.files` / `grep -av '/$' $TMP.list | wc -l` "file(s)," \
  705.         `grep -a '/$' $TMP.new | wc -l` / `grep -a '/$' $TMP.list | wc -l` "dir(s)," \
  706.         `grep -av '/$' $TMP.new | readable_bytes_sum`"B /" \
  707.         `grep -av '/$' $TMP.list | readable_bytes_sum`"B (uncompressed)"
  708.     echo "  skipping:" `grep -av '/$' $TMP.skipped | wc -l` "file(s)," \
  709.         `grep -a '/$' $TMP.skipped | wc -l` "dir(s)," \
  710.         `grep -av '/$' $TMP.skipped | readable_bytes_sum`"B (uncompressed)"
  711. }
  712.  
  713.  
  714. show_backup_estimates ()
  715. {
  716.     require_tools $COMMON_TOOLS
  717.  
  718.     mount_dev
  719.     cd $BACKUP_DIR
  720.     rm -fr $TMP.*
  721.     prepare_backup "Estimating" $1
  722.     rm -f $TMP.*
  723. }
  724.  
  725.  
  726. do_backup ()
  727. {
  728.     require_tools $COMMON_TOOLS
  729.     require_drivers $CREATE_DRIVER
  730.  
  731.     # Print time stamp & mount backup drive...
  732.     date
  733.     echo
  734.     mount_dev
  735.  
  736.     # Run pre-backup
  737.     echo "Running pre-backup procedure..."
  738.     PRE_BACKUP
  739.  
  740.     # Operate in destination directory
  741.     cd $BACKUP_DIR
  742.     rm -fr $TMP.*
  743.  
  744.     # Remove old backups...
  745.     echo
  746.     echo "Removing old backups..."
  747.     name_cleanup  # should not do anything in normal cases
  748.     compute_level_and_bids $1
  749.     SAVED_LEVEL=$LEVEL
  750.  
  751.     # Rotate level-0 backups if necessary...
  752.     TOO_MANY=$(( ${NEW_BID:0:1} - $MAX_FULL ))
  753.     if [[ $TOO_MANY -gt 0 ]]; then
  754.         N=0
  755.         while [[ $N -lt $TOO_MANY ]]; do
  756.             : $[N+=1]
  757.             purge $N
  758.         done
  759.         name_cleanup
  760.         compute_level_and_bids $SAVED_LEVEL
  761.     fi
  762.  
  763.     # Remove old differential backups...
  764.     if [[ $BASE_BID -gt 0 ]]; then
  765.         ARCH_LIST=(`ls $VOLNAME.*1.list.gz`)
  766.         LV=0
  767.         while [ $LV -le 10 ]; do
  768.             : $[LV+=1]
  769.             MATCHCNT=0
  770.             N=${#ARCH_LIST[*]}
  771.             while [[ $N -gt 0 ]]; do
  772.                 : $[N-=1]
  773.                 BID=`get_bid_of_name ${ARCH_LIST[$N]}`
  774.                 if [[ ${#BID} == $[LV+1] && ${BID:0:$LV} != ${NEW_BID:0:$LV} ]]; then
  775.                     : $[MATCHCNT+=1]
  776.                     if [[ $MATCHCNT -gt $GENERATIONS ]]; then
  777.                         purge $BID
  778.                     fi
  779.                 fi
  780.             done
  781.         done
  782.     fi
  783.  
  784.     # Prepare backup...
  785.     echo
  786.     prepare_backup "Preparing" $SAVED_LEVEL
  787.  
  788.     # Create and verify archive file...
  789.     echo
  790.     echo "Creating archive using '"$CREATE_DRIVER"'..."
  791.     ARCH_SUFFIX=`$CREATE_DRIVER -suffix`
  792.     $CREATE_DRIVER -create $NEW_BID $TMP.$ARCH_SUFFIX $TMP.files 2>&1 | sed 's/^/  /'
  793.     if [ "$ARCH_SUFFIX" != "" ]; then
  794.         echo "Checking TOC of archive file (< real file, > archive entry)..."
  795.         $CREATE_DRIVER -toc $NEW_BID $TMP.$ARCH_SUFFIX | eval "$FILTER_UNIFY_NAME" > $TMP.toc
  796.         eval "$FILTER_UNIFY_NAME" < $TMP.files | diff - $TMP.toc | tee $TMP.error | sed 's/^/  /'
  797.     fi
  798.  
  799.     # Move files in place...
  800.     gzip -9 $TMP.list $TMP.skipped $TMP.obsolete $TMP.error
  801.     if [ $LEVEL != 0 ]; then
  802.         gzip -9 $TMP.new
  803.     else
  804.         rm -f $TMP.new
  805.             # Here we don't use the do_symlink function because we need to copy or
  806.             # symlink different files, depending on wether we copy or symlink
  807.         ln -s $VOLNAME.$NEW_BID.list.gz $TMP.new.gz &> /dev/null ||
  808.         cp -af $TMP.list.gz $TMP.new.gz
  809.     fi
  810.     for SUFFIX in skipped.gz new.gz obsolete.gz error.gz $ARCH_SUFFIX list.gz ; do
  811.         # *.list.gz has to be the last for transaction safety
  812.         mv $TMP.$SUFFIX $VOLNAME.$NEW_BID.$SUFFIX
  813.     done
  814.  
  815.     # Create check file if requested...
  816.     if [[ "$CREATE_CHECK_FILE" == "1" ]]; then
  817.         create_check $NEW_BID
  818.     fi
  819.  
  820.     # print summary and finish...
  821.     rm -f $TMP.*
  822.  
  823.     # Run post-backup
  824.     echo
  825.     echo "Running post-backup procedure..."
  826.     POST_BACKUP
  827.  
  828.     echo
  829.     date
  830.     echo
  831.     echo
  832.     show_summary
  833. }
  834.  
  835.  
  836.  
  837.  
  838.  
  839. ##################################################
  840. # show_availability
  841.  
  842.  
  843. show_availability ()
  844. {
  845.     # parameters: masks
  846.  
  847.     require_tools $COMMON_TOOLS
  848.  
  849.     mount_dev 1>&2
  850.     cd $BACKUP_DIR
  851.     rm -fr $TMP.*
  852.  
  853.     MASK="$@"
  854.     if [ "$MASK" = "" ]; then
  855.         MASK="/"
  856.     fi
  857.  
  858.     echo "Listing available files..." 1>&2
  859.     for F in $VOLNAME.*.list.gz; do
  860.         BID=`get_bid_of_name $F`
  861.         FBID=$BID
  862.         while [[ ${#FBID} -lt 5 ]]; do
  863.             FBID=$FBID" "
  864.         done
  865.         for X in $MASK; do
  866.             gunzip -c $VOLNAME.$BID.obsolete.gz | grep -a "$X" | sed "s/^/$VOLNAME.$FBID - /"
  867.             gunzip -c $VOLNAME.$BID.new.gz | grep -a "$X" | sed "s/^/$VOLNAME.$FBID + /"
  868.         done
  869.     done
  870.     cd /
  871.     umount_dev 1>&2
  872. }
  873.  
  874.  
  875.  
  876.  
  877.  
  878. ##################################################
  879. # show_location
  880.  
  881.  
  882. get_location ()
  883. {
  884.     # parameter 1: BID of snapshot
  885.     # other parameters: masks
  886.  
  887.     if [[ $1 == "head" ]]; then
  888.         BID=`get_last_bid`
  889.     else
  890.         BID=$1
  891.     fi
  892.     if [[ ! -f $VOLNAME.$BID.list.gz ]]; then
  893.         echo "ERROR: Specified backup archive <$VOLNAME.$BID> does not exist!"
  894.         exit 3
  895.     fi
  896.     shift
  897.  
  898.     MASK_LIST="$@"
  899.     if [ "$MASK_LIST" = "" ]; then
  900.         MASK_LIST="/"
  901.     fi
  902.  
  903.     # determine active files...
  904.     for MASK in $MASK_LIST; do
  905.         gunzip -c $VOLNAME.$BID.list.gz | grep -a "$MASK" | tee $TMP.found | grep -a '/$' >> $TMP.dirs
  906.             # dirs go to $TMP.dirs WITH attributes
  907.         grep -av '/$' $TMP.found | eval "$FILTER_NAME" >> $TMP.left
  908.             # files go to $TMP.left WITHOUT attributes
  909.     done
  910.     echo "Active files in <$VOLNAME.$BID>:" `wc -l < $TMP.left`
  911.  
  912.     sort < $TMP.left > $TMP.nowleft
  913.     mv $TMP.nowleft $TMP.left
  914.  
  915.     # generate location list
  916.     LAST_BID="xxx"
  917.     touch $TMP.located
  918.     touch $TMP.archlist
  919.     touch $TMP.noarch
  920.     while [ ${#LAST_BID} -gt 1 -a `wc -l < $TMP.left` -gt 0 ]; do
  921.         gunzip -c $VOLNAME.$BID.new.gz | eval "$FILTER_NAME" | sort | comm -1 -2 - $TMP.left | tee $TMP.found \
  922.             | comm -1 -3 - $TMP.left > $TMP.nowleft
  923.         local FOUND=`wc -l < $TMP.found`
  924.         printf "  found in %-12s%5i   (%5i left)\n" \
  925.             "$VOLNAME.$BID:" $FOUND `wc -l < $TMP.nowleft`
  926.         if [ "$FOUND" -gt 0 ]; then
  927.             sed "s/^/$VOLNAME.$BID: /" < $TMP.found >> $TMP.located
  928.             mv $TMP.nowleft $TMP.left
  929.             DRIVER=`get_driver $VOLNAME.$BID`
  930.             if [ "$DRIVER" = "" ]; then
  931.                 echo $VOLNAME.$BID >> $TMP.noarch
  932.             else
  933.                 echo $VOLNAME.$BID:$DRIVER >> $TMP.archlist
  934.             fi
  935.         fi
  936.         LAST_BID=$BID
  937.         BID=`get_base_bid $LAST_BID`
  938.     done
  939.     echo
  940.     # leaves $TMP.left, $TMP.found, $TMP.located, $TMP.dirs, $TMP.archlist, $TMP.noarch
  941. }
  942.  
  943.  
  944. show_location ()
  945. {
  946.     # parameter 1: BID of snapshot
  947.     # other parameters: masks
  948.  
  949.     require_tools $COMMON_TOOLS comm
  950.  
  951.     mount_dev 1>&2
  952.     cd $BACKUP_DIR
  953.     rm -fr $TMP.*
  954.  
  955.     get_location "$@" 1>&2
  956.     echo "Listing locations..." 1>&2
  957.     cat $TMP.located
  958.     sed 's#^#NOT FOUND: #' < $TMP.left
  959.  
  960.     if [ `wc -l < $TMP.noarch` -gt 0 ]; then
  961.         echo -e "\nTo restore, the archive files of the following backups are missing and" \
  962.                 "\nhave to be copied or linked into $BACKUP_DIR:" 1>&2
  963.         sed 's#^#  #' < $TMP.noarch 1>&2
  964.     else
  965.         echo -e "\nAll required archive files are present in $BACKUP_DIR." 1>&2
  966.     fi
  967.  
  968.     rm -f $TMP.*
  969.     cd /
  970.     umount_dev 1>&2
  971. }
  972.  
  973.  
  974.  
  975.  
  976.  
  977. ##################################################
  978. # do_restore
  979.  
  980.  
  981. do_restore ()
  982. {
  983.     # parameter 1: <level>.<version> of snapshot
  984.     # other parameters: masks
  985.  
  986.     require_tools $COMMON_TOOLS comm
  987.  
  988.     mount_dev
  989.     pushd $BACKUP_DIR > /dev/null
  990.     rm -fr $TMP.*
  991.  
  992.     # determine which file to get from which archive
  993.     get_location "$@"
  994.  
  995.     popd > /dev/null
  996.  
  997.     if [ `wc -l < $BACKUP_DIR/$TMP.noarch` -gt 0 ]; then
  998.         echo "Cannot access archive file(s) of the following backup(s):"
  999.         sed 's#^#  #' < $BACKUP_DIR/$TMP.noarch
  1000.         echo -e "\nNothing has been restored."
  1001.     else
  1002.  
  1003.         # check availability of all required drivers in advance...
  1004.         require_drivers `sed 's#^.*:##' < $BACKUP_DIR/$TMP.archlist`
  1005.  
  1006.         # create directories...
  1007.         DIRS=`wc -l < $BACKUP_DIR/$TMP.dirs`
  1008.         if [ $DIRS -gt 0 ]; then
  1009.             echo "Restoring" $DIRS "directories..."
  1010.             eval "$FILTER_NAME" < $BACKUP_DIR/$TMP.dirs | sed 's#^/\(.*\)$#"\1"#' | xargs -l1 mkdir -p
  1011.             eval "$FILTER_CHMOD" < $BACKUP_DIR/$TMP.dirs | xargs -l1 chmod
  1012.             eval "$FILTER_CHOWN" < $BACKUP_DIR/$TMP.dirs | xargs -l1 chown
  1013.         fi
  1014.  
  1015.         # process all archives...
  1016.         echo "Restoring files..."
  1017.         for ARCH_AND_DRIVER in `cat $BACKUP_DIR/$TMP.archlist`; do
  1018.             ARCH=${ARCH_AND_DRIVER%%:*}
  1019.             BID=${ARCH#$VOLNAME.}
  1020.             DRIVER=${ARCH_AND_DRIVER##*:}
  1021.             SUFFIX=`$DRIVER -suffix`
  1022.  
  1023.             grep -a "^$ARCH:" $BACKUP_DIR/$TMP.located \
  1024.                 | sed -e "s#^$ARCH: /##" -e 's#\([][*]\)#\\\1#g' \
  1025.                 > $BACKUP_DIR/$TMP.curlist
  1026.                 # The second sed expression most escapes special glob(7) characters ([]*?).
  1027.             FILES=`wc -l < $BACKUP_DIR/$TMP.curlist`
  1028.             echo "  $ARCH.$SUFFIX:" $FILES "file(s) using '"$DRIVER"'"
  1029.             $DRIVER -extract $BID $BACKUP_DIR/$ARCH.$SUFFIX $BACKUP_DIR/$TMP.curlist | sed "s/^/    /"
  1030.         done
  1031.     fi
  1032.  
  1033.     # Cleanup...
  1034.     rm -f $BACKUP_DIR/$TMP.*
  1035. }
  1036.  
  1037.  
  1038.  
  1039. ##################################################
  1040. # External archiving
  1041.  
  1042.  
  1043. do_external ()
  1044. {
  1045.     require_tools $COMMON_TOOLS split tail md5sum
  1046.  
  1047.     CTRL_EXPR="$VOLNAME\.[0-9]*\.((list)|(new)|(obsolete)|(skipped)|(error)|(check))"
  1048.     mount_dev
  1049.  
  1050.     CAPACITY=$(( $1 * 1024 ))
  1051.     shift
  1052.     MAXFREE=$(( $1 * 1024 ))
  1053.     shift
  1054.  
  1055.     # determine BID_LIST...
  1056.     BID_LIST=`expand_bid_list list.gz "$@"`
  1057.  
  1058.     # determine distribution...
  1059.     LAST_DISK=1
  1060.     REMAINING=$CAPACITY
  1061.     rm -f TMP.xdist 2> /dev/null
  1062.     touch TMP.xdist
  1063.     for BID in $BID_LIST; do
  1064.         CTRL_SIZE=`ls -1s $BACKUP_DIR/$VOLNAME.$BID.* | grep -E "$CTRL_EXPR" \
  1065.             | awk '{ sum += $1 } END { print sum }'`
  1066.         if [ $CTRL_SIZE -gt $REMAINING -a $REMAINING -lt $CAPACITY ]; then
  1067.             : $[ LAST_DISK += 1 ]
  1068.             REMAINING=$CAPACITY
  1069.         fi
  1070.         LAST_DISK_DIR=`printf "data-%02d" $LAST_DISK`
  1071.         ls -1s $BACKUP_DIR/$VOLNAME.$BID.* | grep -E "$CTRL_EXPR" \
  1072.             | sed "s#$BACKUP_DIR#$LAST_DISK_DIR#" >> TMP.xdist
  1073.         : $[ REMAINING -= $CTRL_SIZE ]
  1074.         for ARCHIVE_PATH in `ls -1d $BACKUP_DIR/$VOLNAME.$BID.* | grep -vE "$CTRL_EXPR"`; do
  1075.             ARCHIVE=${ARCHIVE_PATH##*/}
  1076.             if [ -f $BACKUP_DIR/$ARCHIVE ]; then
  1077.                 DATA_SIZE=`ls -1s $BACKUP_DIR/$ARCHIVE | sed 's/ [^ ]*$//'`
  1078.                 if [ $DATA_SIZE -gt $REMAINING -a $REMAINING -lt $MAXFREE ]; then
  1079.                     : $[ LAST_DISK += 1 ]
  1080.                     REMAINING=$CAPACITY
  1081.                 fi
  1082.                 if [ $DATA_SIZE -le $REMAINING ]; then
  1083.                     LAST_DISK_DIR=`printf "data-%02i" $LAST_DISK`
  1084.                     ls -1s $BACKUP_DIR/$ARCHIVE | sed "s#$BACKUP_DIR#$LAST_DISK_DIR#" >> TMP.xdist
  1085.                     : $[ REMAINING -= $DATA_SIZE ]
  1086.                 else
  1087.                     SPLIT_NO=1
  1088.                     while [ $DATA_SIZE -gt $REMAINING ]; do
  1089.                         LAST_DISK_DIR=`printf "data-%02i" $LAST_DISK`
  1090.                         printf "%4i $LAST_DISK_DIR/$ARCHIVE.%02i\n" $REMAINING $SPLIT_NO >> TMP.xdist
  1091.                         : $[ DATA_SIZE -= $REMAINING ]
  1092.                         : $[ SPLIT_NO += 1 ]
  1093.                         : $[ LAST_DISK += 1 ]
  1094.                         REMAINING=$CAPACITY
  1095.                     done
  1096.                     LAST_DISK_DIR=`printf "data-%02i" $LAST_DISK`
  1097.                     printf "%4i $LAST_DISK_DIR/$ARCHIVE.%02i\n" $DATA_SIZE $SPLIT_NO >> TMP.xdist
  1098.                     : $[ REMAINING -= $DATA_SIZE ]
  1099.                 fi
  1100.             else
  1101.                 echo "WARNING: Cannot handle directory: $ARCHIVE (skipping)"
  1102.                 echo
  1103.             fi
  1104.         done
  1105.     done
  1106.  
  1107.     SPACE=$(( (`grep -a "\.[0-9]*$" TMP.xdist | awk '{ sum += $1 } END { print sum }'` + 1023) / 1024 ))
  1108.     cat << EOT
  1109. I am about to split and combine the selected backup archives into directories
  1110. of equal size, so that they can be stored on a set of removable media
  1111. (e. g. CDs). If 'cdlabelgen' is installed, I will create CD covers.
  1112. It is up to you to burn the CDs or store the data in whichever way you like.
  1113.  
  1114. All files are generated in the current working directory ($PWD).
  1115. Make sure it is empty!
  1116.  
  1117. In order to save disk space, only symbolic links will be generated wherever
  1118. possible. Make sure that they are followed by your CD-burn/storage tool and
  1119. that the backup device is mounted!
  1120.  
  1121. You have selected a medium capacity of $(( $CAPACITY / 1024 )) MB with a maximum waste of $(( $MAXFREE /1024 )) MB
  1122. per medium. The selected BIDs are:
  1123.  
  1124.   $BID_LIST
  1125.  
  1126. I need about $SPACE MB of disk space in the current directory.
  1127. You will get $LAST_DISK volume(s).
  1128.  
  1129. EOT
  1130.     read -p "Do you want to see details? [y/N] " ANSWER
  1131.     if [[ "$ANSWER" == "y" ]]; then
  1132.         echo -e "\nSize  Volume/File\n======================================="
  1133.         cat TMP.xdist
  1134.     fi
  1135.     echo
  1136.     read -p "Do you want to continue? [y/N] " ANSWER
  1137.     if [[ "$ANSWER" != "y" ]]; then
  1138.         rm -f TMP.xdist
  1139.         return
  1140.     fi
  1141.  
  1142.     echo -e "\nCreating links..."
  1143.     rm -fr data-??
  1144.  
  1145.     # create directories...
  1146.     sed -e 's#^[ 0-9]*##' -e 's#/.*$##' < TMP.xdist | sort -u | xargs -l1 mkdir -p
  1147.  
  1148.     # create links...
  1149.     for FILE in `grep -av '\.[0-9]*$' TMP.xdist | sed 's#^[ 0-9]*##'`; do
  1150.         do_symlink $BACKUP_DIR/${FILE#data-??/} $FILE
  1151.     done
  1152.  
  1153.     echo "Splitting large files..."
  1154.     for FILE in `grep -a '\.01$' TMP.xdist | sed -e 's#^[ 0-9]*##' -e 's#.01$##'`; do
  1155.         ORG_FILE=${FILE#data-??/}
  1156.         echo "  $ORG_FILE"
  1157.         HEAD_SIZE=$(( `grep -a $FILE TMP.xdist | sed 's/ [^ ]*$//'` * 1024 ))
  1158.         head $BACKUP_DIR/$ORG_FILE -c $HEAD_SIZE > $FILE.01
  1159.         tail $BACKUP_DIR/$ORG_FILE -c +$(( $HEAD_SIZE + 1 )) | split -b $(( $CAPACITY * 1024 )) - TMP.split.
  1160.         SPLIT_NO=2
  1161.         for SPLIT in `ls -1 TMP.split.*`; do
  1162.             DST_FILE=`printf "$ORG_FILE.%02i" $SPLIT_NO`
  1163.             DST=`grep -a $DST_FILE TMP.xdist | sed 's#^[ 0-9]*##'`
  1164.             mv $SPLIT $DST
  1165.             : $[ SPLIT_NO += 1 ];
  1166.         done
  1167.     done
  1168.  
  1169.     # create self-check files...
  1170.     cat << EOT
  1171.  
  1172. I can now generate check scripts for each volume that can later be used to
  1173. verify the integrity of all files. This is e.g. useful if, in a couple of
  1174. years, you want to know whether your backup media are still readable.
  1175. Then simply mount your media and type '. check_these_files.sh' inside the
  1176. media's main directory.
  1177.  
  1178. EOT
  1179.     read -p "Create self-check scripts? [Y/n] " ANSWER
  1180.     if [[ "$ANSWER" != "n" ]]; then
  1181.         echo
  1182.         echo "Creating self-check scripts..."
  1183.         for DISK in data-??; do
  1184.             echo "  $DISK"
  1185.             cd $DISK
  1186.  
  1187.             DST_FILE="check_these_files.sh"
  1188.             rm -f $DST_FILE
  1189.             FILES=`find . -follow -type f`
  1190.             echo "#!/bin/sh" > $DST_FILE
  1191.             echo >> $DST_FILE
  1192.             echo "echo \"This script has been auto-generated by $PROG v$VER.\"" >> $DST_FILE
  1193.             echo "echo \"Verifying file(s) using md5sum(1)...\"" >> $DST_FILE
  1194.             echo >> $DST_FILE
  1195.             echo "md5sum -c << EOF" >> $DST_FILE
  1196.             md5sum $FILES >> $DST_FILE
  1197.             echo "EOF" >> $DST_FILE
  1198.  
  1199.             cd ..
  1200.         done
  1201.     fi
  1202.  
  1203.     # create CD labels
  1204.     if which cdlabelgen > /dev/null; then
  1205.       echo
  1206.       echo "Creating CD labels..."
  1207.       read -p "  Enter CD title [Backup]: " ANSWER
  1208.       if [[ "$ANSWER" == "" ]]; then
  1209.           ANSWER="Backup"
  1210.       fi
  1211.       if [ -r /usr/share/cdlabelgen/penguin.eps ]; then
  1212.           TRAYPIC="-e /usr/share/cdlabelgen/penguin.eps -S 0.5"
  1213.       else
  1214.           TRAYPIC=""
  1215.       fi
  1216.       for DISK in data-??; do
  1217.           DISK_NO=${DISK#data-}
  1218.           echo -e "\nContents\n========\n" > contents-$DISK_NO.txt
  1219.           grep -a $DISK TMP.xdist | sed -e 's#^.*/##' -e 's#\.list\.gz# - control files#' \
  1220.               | grep -vE "$CTRL_EXPR" >> contents-$DISK_NO.txt
  1221.           cdlabelgen -c "$ANSWER" -s "${DISK_NO#0} of $LAST_DISK" -d `date -I` \
  1222.               -f contents-$DISK_NO.txt $TRAYPIC -o cd-cover-$DISK_NO.ps
  1223.       done
  1224.     fi
  1225.  
  1226.     # clean up
  1227.     rm -f TMP.xdist
  1228. }
  1229.  
  1230.  
  1231.  
  1232.  
  1233.  
  1234. ##################################################
  1235. # Usage & banner
  1236.  
  1237.  
  1238. banner ()
  1239. {
  1240.     echo "$PROG v$VER by Gundolf Kiefer"
  1241.     echo
  1242. }
  1243.  
  1244.  
  1245. usage ()
  1246. {
  1247.     banner
  1248.     cat << EOF
  1249. Usage: $PROG [-h] [-c <conffile>] [-t <BID>] <command>
  1250. Where
  1251.     -h | --help                    : Help
  1252.     -c | --conf <conffile>         : specifies configuration file [/etc/backup2l.conf]
  1253.     -t | --time <BID>              : specifies backup ID as a point-in-time for --locate and --restore
  1254.  
  1255.   <command>:
  1256.     -b | --backup [<level>]        : Create new backup
  1257.     -e | --estimate [<level>]      : Like -b, but nothing is really done
  1258.     -s | --get-summary             : Show backup summary
  1259.  
  1260.     -a | --get-available <pattern> : Show all files in all backups containing <pattern> in their path names
  1261.     -l | --locate [<pattern>]      : Show most recent backup location of all active files matching <pattern>
  1262.     -r | --restore [<pattern>]     : Restore active files matching <pattern> into current directory
  1263.  
  1264.     -p | --purge <BID-list>        : Remove the specified backup archive(s) and all depending backups
  1265.  
  1266.     -v | --verify [<BID-list>]     : Verify the specified / all backup archive(s)
  1267.     -m | --make-check [<BID-list>] : Create md5 checksum file for the specified archive(s) / wherever missing
  1268.  
  1269.     -x | --extract <volume size> <max free> <BID-list> :
  1270.                                      Split and collect files to be stored on removable media (e. g. CDs)
  1271.  
  1272. EOF
  1273.     echo "Built-in archive drivers:" $BUILTIN_DRIVER_LIST
  1274.     if [ "$USER_DRIVER_LIST" != "" ]; then
  1275.         echo "User-defined drivers:    " $USER_DRIVER_LIST
  1276.     fi
  1277. }
  1278.  
  1279.  
  1280.  
  1281.  
  1282.  
  1283. ##################################################
  1284. # Main
  1285.  
  1286.  
  1287. shopt -s nullglob
  1288. shopt -s extglob
  1289. unset LANG LC_ALL LC_COLLATE # otherwise: unpredictable sort order in sort, ls
  1290.  
  1291.  
  1292. # Set defaults...
  1293. CREATE_DRIVER="DRIVER_TAR_GZ"  # works with last stable version 1.01
  1294.  
  1295. # Read & validate setup...
  1296. CONF_FILE="/etc/backup2l.conf"
  1297.  
  1298. # Go ahead...
  1299. case $1 in
  1300.     -h | --help)
  1301.         usage
  1302.         exit 0
  1303.         ;;
  1304.     -c | --conf)
  1305.         shift
  1306.         CONF_FILE=$1
  1307.         shift
  1308.         ;;
  1309. esac        
  1310.  
  1311. if [ -f "$CONF_FILE" ]; then
  1312.     . $CONF_FILE
  1313. else
  1314.   echo "Could not open configuration file '$CONF_FILE'. Aborting."
  1315.   exit 3
  1316. fi
  1317.  
  1318. if [ "$UNCONFIGURED" = "1" ]; then
  1319.     banner
  1320.     echo -e "The configuration file '$CONF_FILE' has to be edited before using ${0##*/}.\n"
  1321.     echo -e "For help, look into the comments or read the man page.\n"
  1322.     exit 3
  1323. fi
  1324. if [ "$VOLNAME" = "" -o "$SRCLIST" = "" -o "${SKIPCOND[*]}" = "" -o "$BACKUP_DIR" = "" -o \
  1325.      "$MAX_LEVEL" = "" -o "$MAX_PER_LEVEL" = "" -o "$MAX_FULL" = "" ]; then
  1326.     echo "ERROR: The configuration file '$CONF_FILE' is missing or incomplete."
  1327.     exit 3
  1328. fi
  1329.  
  1330. if [ "$FOR_VERSION" = "" ]; then
  1331.     FOR_VERSION="0.9"
  1332. fi
  1333. if [[ "$FOR_VERSION" < "1.1" || "$FOR_VERSION" > "${VER%-*}" ]]; then
  1334.     banner
  1335.     cat << EOF
  1336. The configuration file '$CONF_FILE' seems to be written for
  1337. version $FOR_VERSION and may be incompatible with this version of backup2l.
  1338.  
  1339. The following variables have been added, removed or their syntax may have
  1340. changed. Details can be found in first_time.conf and the man page.
  1341.  
  1342.   1.1 : CREATE_DRIVER, USER_DRIVER_LIST
  1343.   0.93: POST_BACKUP
  1344.   0.91: SRCLIST, SKIPCOND, FOR_VERSION
  1345.  
  1346. If you think your configuration file is correct, please change the value
  1347. of FOR_VERSION.
  1348. EOF
  1349.     exit 3
  1350. fi
  1351. TMP="TMP.$VOLNAME"
  1352.  
  1353. # Read time point if given
  1354. if [ "$1" = "-t" -o "$1" = "--time" ]; then
  1355.     shift
  1356.     BID=${1#$VOLNAME.}
  1357.     shift
  1358. else
  1359.     BID="head"
  1360. fi
  1361.  
  1362. # Go ahead...
  1363. case $1 in
  1364.     -e | --estimate)
  1365.         banner
  1366.         show_backup_estimates "$2"
  1367.         ;;
  1368.     -b | --backup)
  1369.         banner
  1370.         do_backup "$2"
  1371.         ;;
  1372.     -s | --get-summary)
  1373.         banner
  1374.         mount_dev
  1375.         show_summary
  1376.         ;;
  1377.     -a | --get-available)
  1378.         banner 1>&2
  1379.         shift
  1380.         show_availability "$@"
  1381.         ;;
  1382.     -l | --locate)
  1383.         banner 1>&2
  1384.         shift
  1385.         show_location $BID "$@"
  1386.         ;;
  1387.     -r | --restore)
  1388.         banner
  1389.         shift
  1390.         do_restore $BID "$@"
  1391.         ;;
  1392.     -p | --purge)
  1393.         banner
  1394.         shift
  1395.         do_purge "$@"
  1396.         ;;
  1397.     -v | --verify)
  1398.         banner
  1399.         shift
  1400.         do_check "$@"
  1401.         ;;
  1402.     -m | --make-check)
  1403.         banner
  1404.         shift
  1405.         do_create_check "$@"
  1406.         ;;
  1407.     -x | --extract)
  1408.         banner
  1409.         shift
  1410.         if [[ "$3" == "" ]]; then
  1411.             usage
  1412.         else
  1413.             do_external "$@"
  1414.         fi
  1415.         ;;
  1416.     *)
  1417.         if [ "$AUTORUN" = "1" ]; then
  1418.             banner
  1419.             do_backup
  1420.         else
  1421.             usage
  1422.         fi
  1423.         ;;
  1424. esac
  1425.  
  1426. # Unmount backup device if it was mounted
  1427. cd /            # avoid "device is busy" during unmount
  1428. umount_dev
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement