Wednesday, July 29, 2009

bak2disc-tpb

#!/bin/bash
################################################################################
#
# bak2disc - backup to disc volumes while maintaining original data structure
# Copyright (C) 2005-2006 dorphell <dorphell [AT] gmail [DOT] com>
#
#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; either version 2 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program; if not, write to the Free Software
#   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#
################################################################################

#--------------------------------------------------------------------
# PUBLIC CONSTANTS [Default Parameters]
#--------------------------------------------------------------------

## Dir used for meta-data (a few MBs of ASCII files) ##
TMP_DIR="${TEMP:-/tmp/bak2disc}"
mkdir -p $TMP_DIR

## Disc type (valid types: CD74, CD80, DVD5, DVD9, DLT7, LTO2, LTO3) ##
MEDIA_TYPE="DVD5"

## Space allocated for index metadata (MiB)##
INDEX_SIZE=4

## mkisofs options used by growisofs ##
MKISOFS_OPTS=" -r -J "

## Limit for the volume-filling search in the element array ##
SEARCH_RANGE=150

Label="Volume"


#--------------------------------------------------------------------
# PRIVATE CONSTANTS [Do not Modify]
#--------------------------------------------------------------------

VERSION="0.7-tpb"
PROG_NAME="bak2disc"
GB=1073741824         # 1024^3
MB=1048576            # 1024^2

## ANSI Color modifiers ##
C_BOLD="\e[1m"        # Bold
C_NORM="\e[m\e[0;39m" # Normal
C_BLUE="\e[1;34m"     # Blue
C_RED="\e[1;31m"      # Red

## Only use newlines for field separation ##
IFS=$'\n'

## Error Messages ##
ErrorMsgs=(
"00: No error"
"01: Unknown error"
"02: Program \'\$2\' not in \\\$PATH"
"03: Working directory \'\$2\' already exists"
"04: Permission denied or file/directory does not exist: \$2"
"05: File[s] too big, choose a higher-capacity disc or suppress oversized file[s]"
"06: Temporary directory \'\$2\' exists"
"07: Could not create temporary directory \'\$2\'"
"08: User reported failed burn for disc volume \$2, --resume to try again"
"09: Option \'\$2\' requires an argument"
"10: Invalid option: \$2"
"11: Previous session metadata not found. Incorrect --temp parameter?"
"12: No options given, what do you want to do?"
"13: No \'--include\' files/directories defined"
"14: Disc \$2 index larger than allocated space, increase --index size"
"15: Please provide a single valid operating mode"
"16: Unknown disc type \'\$2\'. Valid types: cD74, CD80, DVD5, DVD9, DLT7, LTO2, LTO3"
"17: SIGINT caught"
"18: Invalid --speed argument, value must be an integer"
"19: mkisofs is really genisoimage which will not work")

#--------------------------------------------------------------------
# FUNCTIONS
#--------------------------------------------------------------------

Help() {
cat << EOF
$PROG_NAME v$VERSION (c) 2006 dorphell
Usage: $PROG_NAME <mode> [options]
Backup to disc volumes while maintaining original data structure

    Informative flags:
    -h, --help      Print help screen
    -v, --version   Print version information

    Operating mode flags: (one required)
    -f, --fresh     Start a new backup session
    -n, --noburn    Generate backup volume information but don't burn to disc
    -r, --resume    Resume the burn phase of a previous backup session (relies
                    on original metadata)
    -w, --wipe      Clean up temporary directory and exit

    Accessibility flags:
    -j, --eject     Open drive tray for disc loading/unloading and skip the
                    verification prompt.
    -l, --exlarge   Automatically exclude files too large for backup media
    -c, --nocolor   Turn off all text color/highlighting

    Configuration options: (every option requires an argument)
    -i, --include   Colon-separated file/directory paths to include (multiple
                    definitions and bash globbing wildcards are also vallid)
    -e, --exclude   File/directory paths to exclude (same syntax as --include)
    -t, --temp      Temporary directory [default: $TMP_DIR]
    -o, --mkisofs   Burning options passed onto mkisofs/growisofs
                    [default: $MKISOFS_OPTS]
    -m, --mtype     Disc type (CD74, CD80, DVD5, DVD9, DLT7, LTO2, LTO3) [default: $MEDIA_TYPE]
    -x, --index     Space allocated for index metadata out of disc capacity
                    [default: $INDEX_SIZE]
    -g, --range     Number of files/directories to poll when filling slack
                    space on volumes [default: $SEARCH_RANGE]
    -s, --speed     Speed factor of the writing process (integer value)
                    [default: Maximum supported by the drive]
    --label         Label the .iso and volume name

    Examples:
    $PROG_NAME --fresh --include "/etc:/mnt/HD*:/opt" -d /dev/hdc --mtype DVD5
    $PROG_NAME -n -i /usr -i /var -e /var/log --temp /tmp --range 10 -m DVD5
    $PROG_NAME --resume --temp /var/tmp

    GNUtar:
    gtar cf <tape> -T $TMP_DIR/Volume_1

EOF
}

Version() {
   echo "$PROG_NAME $VERSION"
}

## ErrorExit "int error_code" "string reference_name" ##
ErrorExit() {
   echo -ne "$C_BLUE" >&2
   echo -ne ">> "
   echo -ne "$C_RED" >&2
   eval echo -n "ERROR ${ErrorMsgs[$1]}"
   echo -ne "$C_NORM" >&2
   echo
   exit $1
}

## PrintBold "string message" ##
PrintBold() {
   echo -ne "$C_BOLD" >&2
   echo -ne "$1"
   echo -ne "$C_NORM" >&2
}

## PrintStatus "string message" ##
PrintStatus() {
   echo -ne "$C_BLUE" >&2
   echo -ne ">>"
   echo -ne "$C_NORM$C_BOLD " >&2
   echo -ne "$1"
   echo -ne "$C_NORM" >&2
   echo
}

## YesNo "string question" "boolean default" [y/n] ##
YesNo() {
   OPTIONS="[y/n]"
   [ "$2" == "y" ] && OPTIONS="[Y/n] "
   [ "$2" == "n" ] && OPTIONS="[y/N] "

   PrintBold "$1 $OPTIONS"
   IFS=$' \t\n' read ANSWER
   [ ! "$ANSWER" ] && [ "$2" ] && ANSWER="$2"
   while [ "$ANSWER" != 'y' ] && [ "$ANSWER" != 'n' ] && [ "$ANSWER" != 'Y' ] && [ "$ANSWER" != 'N' ]; do
      PrintBold "Invalid response, please answer 'y' or 'n': "
      IFS=$' \t\n' read ANSWER
   done
   [[ "$ANSWER" == 'y' || "$ANSWER" == 'Y' ]] && return 0 || return 1
}

## CheckPath "string program_name" ##
CheckPath() {
   while [ "$1" ]; do
      ! type "$1" &>/dev/null && ErrorExit 2 "$1"
      shift
   done
}

## ParseArgs "string args" (e.g. "$@") ##
ParseArgs() {
   while [ "$1" ]; do
   ## Informative flags ##
      case "$1" in
         -h|--help)
            Help;                  exit ;;
         -v|--version)
            Version;               exit ;;
   ## Operating modes flags ##
         -f|--fresh)
            FRESH=1;              shift ;;
         -r|--resume)
            RESUME=1;             shift ;;
         -n|--noburn)
            NOBURN=1;             shift ;;
         -w|--wipe)
            WIPE=1;               shift ;;
   ## Accessibility flags ##
         -j|--eject)
            CheckPath 'eject'
            EJECT=1;              shift ;;
         -l|--exlarge)
            AUTO_EXCLUDE=1;       shift ;;
         -c|--nocolor)
            NOCOLOR=1;            shift ;;
   ## Configuration options ##
         -i|--include)
            [ ! "$2" ] && ErrorExit 9 "$1"
          INCLUDE="$INCLUDE:$2"; shift 2;;
         -e|--exclude)
            [ ! "$2" ] && ErrorExit 9 "$1"
          EXCLUDE="$EXCLUDE:$2"; shift 2;;
         -o|--mkisofs)
            [ ! "$2" ] && ErrorExit 9 "$1"
            MKISOFS_OPTS="$2";   shift 2;;
         -m|--mtype)
            [ ! "$2" ] && ErrorExit 9 "$1"
            MEDIA_TYPE="$2";     shift 2;;
         -x|--index)
            [ ! "$2" ] && ErrorExit 9 "$1"
            INDEX_SIZE="$2";     shift 2;;
         -t|--temp)
            [ ! "$2" ] && ErrorExit 9 "$1"
            TMP_DIR="$2";        shift 2;;
         -g|--range)
            [ ! "$2" ] && ErrorExit 9 "$1"
            SEARCH_RANGE="$2";   shift 2;;
         -s|--speed)
            [ ! "$2" ] && ErrorExit 9 "$1"
      [ "${2##*[^0-9]*}" ] || ErrorExit 18
            SPEED="-speed=$2";   shift 2;;
         --label)
             [ ! "$2" ] && ErrorExit 9 "$1"
             Label="$2"; shift 2;;
         *)
            Help;      ErrorExit 10 "$1";;
      esac
   done
}


## SetMediaSize "string preset" (e.g. SetMediaSize "dvd9") ##
SetMediaSize() {
   MEDIA_TYPE="$(echo $1| tr a-z A-Z)"
   case "$MEDIA_TYPE" in
      CD74) MEDIA_SIZE=650  ;;
      CD80) MEDIA_SIZE=703  ;;
      DVD5) MEDIA_SIZE=4482 ;;
      DVD9) MEDIA_SIZE=8144 ;;
      DLT7) MEDIA_SIZE=30000 ;;
      LTO2) MEDIA_SIZE=190000 ;;
      LTO2c) MEDIA_SIZE=323000 ;;
      LTO3) MEDIA_SIZE=390000;;
      LTO3c) MEDIA_SIZE=663000;;
         *) Help;
            ErrorExit 16 "$1" ;;
   esac
   let "MEDIA_SIZE-=INDEX_SIZE"
}


## ValidatePath "string path" "string output_variable" ##
## If path is readable, return its proper pathname; else error + exit ##
ValidatePath() {
   [ ! -r "$1" ] && CleanUp && ErrorExit 4 "$1"
   [ "$1" == '/' ] && return
   eval "$2=\"$(echo "$1"| sed 's|/\+|/|g;s|/$||')\""
}

## GetList "string du_output_syntax" (e.g. "1234 <TAB> /path/to/dir") ##
## Returns a list of files/dirs that are less than media size ##
GetList() {
   ## Set Size and Name ##
   VARS=(${1//$'\t'/$'\n'})
   Size=${VARS[0]}
   Name=${VARS[1]}
   if [ $Size -gt $((MEDIA_SIZE*MB)) ] && [ -d "$Name" ]; then
      for x in $($DU -ba --max-depth=1 "$Name"| gawk -F '\t' '$2 != "'$Name'" {print}'); do
         GetList "$x"
      done
   else
      printf "%s\t%s\n" "$Name" "$Size"
   fi
}

## Burn disc volumes defined in TMP_DIR ##
BurnVolumes() {
   NumOfDiscs=$(head -n 1 "$TMP_DIR/summary" 2>/dev/null| gawk '{print $8}')
   MEDIA_TYPE=$(head -n 1 "$TMP_DIR/summary" 2>/dev/null| gawk '{print $9}')

   ## Check if metadata exists ##
   [ ! $NumOfDiscs ] && ErrorExit 11

   ## If --resume, reprint distribution summary ##
   if [ $RESUME ]; then
      PrintStatus "Resuming burn session..."
      while read line; do
         vol=$(echo $line| gawk '$1 == "Disc" {printf "%i\n", $2}')
         [ ${line:0:1} == " " ] && echo "$line"        && continue
         [ ${line:0:1} == "I" ] && PrintStatus "$line" && continue
         [ -z $vol ] && PrintBold "$line\n" && continue
         [ -e "$TMP_DIR/Volume_$vol" ] && PrintBold "$line\n" || echo "$line [Done]"
      done < "$TMP_DIR/summary"
      echo
   fi


## Make ISO files
#   for (( DISC_NUM=1; DISC_NUM<=$NumOfDiscs; DISC_NUM++ )); do
   DISC_NUM=1
   while [ $DISC_NUM -le $NumOfDiscs ]; do
      ## Volume already burned ##
      [ ! -e "$TMP_DIR/Volume_$DISC_NUM" ] && continue


      ## Burn the volume ##
      ALL_MKISOFS_OPTS="$MKISOFS_OPTS -quiet -V ${Label}-${DISC_NUM}_of_$NumOfDiscs -graft-points -exclude-list $EXC_LIST -path-list $TMP_DIR/Volume_$DISC_NUM  -o ${Label}_$DISC_NUM.iso"

      IFS=$' \n';
      echo "mkisofs $ALL_MKISOFS_OPTS"
      mkisofs $ALL_MKISOFS_OPTS
      IFS=$'\n'

      ## Volume successfully burned, delete volume metadata ##
      rm "$TMP_DIR/Volume_$DISC_NUM" "$TMP_DIR/volume_${DISC_NUM}_of_${NumOfDiscs}"
      DISC_NUM=$(expr $DISC_NUM + 1)
   done

   ## Backup complete, wipe out metadata ##
   PrintStatus "All $NumOfDiscs backup volumes burned."
   CleanUp
}

## CleanUp (removes "$TMP_DIR" entirely) ##
CleanUp() {
   PrintStatus "Cleaning up temporary data"
   rm -rf "$TMP_DIR"
}

Terminate() {
   echo
   ErrorExit 17
}

#--------------------------------------------------------------------
# SCRIPT START
#--------------------------------------------------------------------

## Capture ^C exit code ##
trap Terminate SIGINT

## Check if utilities are in $PATH ##
# Solaris /bin/du won't work.  Look for GNU du named gdu
[ -f $(which gdu) ] && DU=$(which gdu) || DU=$(which du)
[ $(uname -s) == "SunOS" -a $(basename $DU) != "gdu" ] && ErrorExit 2 "gdu"

CheckPath 'gawk' 'sed' 'printf' 'sort' "$DU" 'stat' 'comm' 'mkisofs'

mkisofs --version | grep -s genisoimage > /dev/null
[ $? -eq 0 ] && echo "mkisofs is really genisoimage which will not work"

## Parse command-line arguments ##
[ -z "$1" ] && Help && ErrorExit 12
ParseArgs "$@"

ValidatePath "$TMP_DIR" "TMP_DIR"

TMP_DIR="$TMP_DIR/$PROG_NAME-$USER"
FILE_LIST="$TMP_DIR/include"
EXC_LIST="$TMP_DIR/exclude"

SetMediaSize "$MEDIA_TYPE"

[ $NOCOLOR ] && unset C_BOLD C_NORM C_BLUE C_RED

## No operating modes defined, error ##
[ $((FRESH+NOBURN+RESUME+WIPE)) -ne 1 ] && Help && ErrorExit 15

## If --resume specified, just burn using preexisting metadata ##
[ $RESUME ] && BurnVolumes && exit

## Delete any garbage/leftovers and start fresh ##
[ $WIPE ] && CleanUp && exit

## Check for leftovers, if not clean, ask what to do ##
if [ -e "$TMP_DIR" ]; then
   ! YesNo "Leftover metadata detected; DELETE and start fresh?" "n" && PrintBold "Nothing dnoe... exiting\n" && exit
   CleanUp
fi

## Check/Ask for required variables ##
[ ! "$INCLUDE" ] && Help && ErrorExit 13

PrintBold "$C_RED-------------------------------------------------------------------$C_NORM\n"
PrintBold " Do not modify included files until they have been written to disc\n"
PrintBold " Excessive growth may cause volume to overflow disc capacity\n"
PrintBold "$C_RED-------------------------------------------------------------------$C_NORM\n"

## Start a fresh backup now ##
mkdir -p "$TMP_DIR" || ErrorExit 7 "$TMP_DIR"

## Convert path lists to arrays ##
INCLUDE=(${INCLUDE//:/$'\n'})
EXCLUDE=(${EXCLUDE//:/$'\n'})

## Validate all dir/file paths ##
x=0
while [ $x -lt ${#INCLUDE[@]} ]; do ValidatePath "${INCLUDE[$x]}" "INCLUDE[$x]"; x=$(expr $x + 1); done
x=0
while [ $x -lt ${#EXCLUDE[@]} ]; do ValidatePath "${EXCLUDE[$x]}" "EXCLUDE[$x]"; x=$(expr $x + 1); done

## Remove all dupes ##
for x in "${INCLUDE[@]}"; do INCLUDE=($(echo "${INCLUDE[*]}"| sed "\|^$x/|d"| sort -u)); done
for x in "${EXCLUDE[@]}"; do EXCLUDE=($(echo "${EXCLUDE[*]}"| sed "\|^$x/|d"| sort -u)); done

## Generate element filelist from INCLUDE paths ##
for x in "${INCLUDE[@]}"; do
   GetList "$($DU -bs "$x")"
done| sort > "$FILE_LIST"

## Remove excluded items from filelist ##
if [ "${EXCLUDE[*]}" ]; then
   for item in $($DU -bs "${EXCLUDE[@]}"); do
      item=(${item//$'\t'/$'\n'})
      Size=${item[0]}
      Name=${item[1]}

      ## *Exact* match ##
      Exists=$(gawk -F '\t' '$1 == "'$Name'" || $1 ~ "'$Name'/" {print $1}' "$FILE_LIST")

      if [ "$Exists" ]; then
         for x in $Exists; do sed "\|^$x\t.*|d" -i "$FILE_LIST"; done
      else
         Parent=""
         while [ ! "$Parent" ] && [ "$Name" ]; do
            Name="${Name%/*}"
            Parent=$(gawk -F '\t' '$1 == "'$Name'" {print}' "$FILE_LIST")
         done
         Parent=(${Parent//$'\t'/$'\n'})
         PName=${Parent[0]}
         PSize=${Parent[1]}
         NewSize=$((PSize-Size))
         sed "s|\(^$PName\)\t\(.*$\)|\1\t$NewSize|" -i "$FILE_LIST"
      fi
   done
fi

## Initialize element array ##
ELEMENTS=($(cat "$FILE_LIST"))

## Fill content elements into disc volumes ##
Current=0; Count=0; CountRange=0; declare -i Size; LastElement=${#ELEMENTS[@]}

while [ "${ELEMENTS[*]}" ]; do
   line=(${ELEMENTS[$Count]//$'\t'/$'\n'})
   Name="${line[0]}" # File name
   Size="${line[1]}" # File size

   if [ $Size -gt $((MEDIA_SIZE*MB)) ]; then                      # file bigger than disc capacity
      AEXCLUDE[${#AEXCLUDE[@]}]="$Name"                           # add file to auto-exclude list
#      unset ELEMENTS[$Count] Name
      unset ELEMENTS[$Count]
      unset Name
   fi

   if [ ! $Name ] && [ $Count -ne $CountRange ]; then             # skip null elements
      :
   elif [ $CountRange -ne 0 ] && [ $Count -eq $CountRange ]; then # end of search range, volume filled
      CountRange=0
      let "Count=KeepCount-1"                                     # go to 1st element that didn't fit
      let "Current++"
#      echo "$Current"
#      echo "$((VolSizes[$Current]+Size))  $((MEDIA_SIZE*MB))"

   elif [ $((VolSizes[$Current]+Size)) -lt $((MEDIA_SIZE*MB)) ]; then
      VolSizes[$Current]=$((${VolSizes[$Current]}+$Size))
      VolNames[$Current]="${VolNames[$Current]}$Name\n"
      unset ELEMENTS[$Count]
   else                                                           # element too big for current vol
      if [ $CountRange -eq 0 ]; then                              # remember element position
         KeepCount=$Count
         let "CountRange=Count+SEARCH_RANGE"
         if [ $CountRange -gt $LastElement ]; then                # don't set range outside the array!
            CountRange=$LastElement
         fi
      fi
   fi
   let "Count++"
done

## Ask if big files should be auto-excluded ##
if [ "$AEXCLUDE" ]; then
   if [ ! $AUTO_EXCLUDE ]; then
      for x in "${AEXCLUDE[@]}"; do echo "| $x"; done
      YesNo "+ The preceding file[s] will not fit on a $MEDIA_TYPE disc, exclude and continue?" "y" || ErrorExit 5
      echo
   fi
   EXCLUDE=("${EXCLUDE[@]}" "${AEXCLUDE[@]}")
fi

## Account for excluded files in distribution summary ##
find "${EXCLUDE[@]}" ! -type d 2>/dev/null| sort > "$EXC_LIST"

## Generate index and filelists ##
x=0;
while [ $x -lt ${#VolNames[@]} ]; do
   BurnName="Volume_$((x+1))"                      # e.g. Volume_1
   IndexName="volume_$((x+1))_of_${#VolNames[@]}"  # e.g. volume_1_of_16

   ## Generate mkisofs graft-points formated filelist ##
   echo -e "${VolNames[$x]}"| gawk -F '\n' '$1 {printf "data%s=%s\n", $1, $1}' > "$TMP_DIR/$BurnName"

   ## Generate volume reference index ##
   VolFiles[$x]=$(comm -23 <(find $(echo -e "${VolNames[$x]}") ! -type d 2>/dev/null| sort) "$EXC_LIST")
   echo "${VolFiles[$x]}" > "$TMP_DIR/$IndexName"

   ## Generate master catalog ##
   echo "### $IndexName ###" >> "$TMP_DIR/catalog.idx"
   echo -e "${VolFiles[$x]}\n"| sed 's|^/|'$((x+1))': /|' >> "$TMP_DIR/catalog.idx"

   ## Append reference index and master catalog to mkisofs input filelist ##
   #echo "$IndexName=$TMP_DIR/$IndexName" >> "$TMP_DIR/$BurnName"
   echo "catalog.idx=$TMP_DIR/catalog.idx" >> "$TMP_DIR/$BurnName"
   x=$(expr $x + 1 )
done

## Generate mkisofs exclude list file ##
echo "${EXCLUDE[*]}" > "$EXC_LIST"

TotalSize=$(echo "${VolSizes[*]}"| gawk '{sum+=$1} END {printf "%.2f", (sum/'$GB') }')
Index_Size=$(stat -c %s "$TMP_DIR/catalog.idx" 2>/dev/null)
TotalFiles=$(echo "${VolFiles[*]}"| grep -c "[^\n]")
NumOfDiscs=${#VolNames[@]}

## Overall backup summary ##
SUM_TOTALS=$(printf "Summary: %s files (%s GiB) divided into %s %s disc volumes" "$TotalFiles" "$TotalSize" "$NumOfDiscs" "$MEDIA_TYPE")
SUM_INC=$(for x in "${INCLUDE[@]}"; do echo "   $x"; done)
SUM_EXC=$(for x in "${EXCLUDE[@]}"; do echo "   $x"; done)

PrintBold "$SUM_TOTALS\n"; echo -e "$SUM_TOTALS" > "$TMP_DIR/summary"
                PrintStatus "Items Included:" && echo "$SUM_INC" && echo -e "Items Included:\n$SUM_INC" >> "$TMP_DIR/summary"
[ $EXCLUDE ] && PrintStatus "Items Excluded:" && echo "$SUM_EXC" && echo -e "Items Excluded:\n$SUM_EXC" >> "$TMP_DIR/summary"

## Disc volume distribution summary ##
x=0
while [ $x -lt $NumOfDiscs ]; do
   DISC_NUM=$((x+1))
   DISC_FILES=$(echo "${VolFiles[$x]}"| grep -c "[^\n]")
   VIDX_SIZE=$(stat -c %s "$TMP_DIR/volume_${DISC_NUM}_of_${NumOfDiscs}")
   DISC_SIZE=$(echo "${VolSizes[$x]}" $Index_Size $VIDX_SIZE $MB| gawk '{printf "%f", ($1+$2+$3)/$4 }')
   DISC_FREE=$(echo $MEDIA_SIZE $INDEX_SIZE $DISC_SIZE| gawk '{printf "%i", $1+$2-$3}')
   PRINT_DIST="$PRINT_DIST$(printf "Disc %3d: %5d files -> %8.2f MiB, %4i MiB free" "$DISC_NUM" "$DISC_FILES" "$DISC_SIZE" "$DISC_FREE")\n"
   [ $DISC_FREE -lt 1 ] && ErrorExit 14 $DISC_NUM
   x=$(expr $x + 1 )
done; PrintBold $PRINT_DIST

## Save distribution summary ##
echo -ne $PRINT_DIST >> "$TMP_DIR/summary"

## If --noburn specified, exit now ##
[ $NOBURN ] && PrintStatus "Backup metadata generated." && exit

## Ask whether to burn now or leave metadata for later ##
YesNo "\nBegin burn sequence now?" "y" && BurnVolumes

#--------------------------------------------------------------------
# SCRIPT END
#--------------------------------------------------------------------

Archiving a tree to DVD with bak2disc

bak2disc by dorphell is a script to create DVDs of a directory tree, preserving the structure. It will create an index file on the DVD so you can find which DVD the files are on.

I modified the script to create .iso files. You can also feed the file lists created to tar. So I modified the script to create lists contining X GB of data. I feed that to tar and can tar files to a DLT, SDLT, LTO, etc tape.

I sent an email and didn't get a reply. I'll try to get a version up somewhere.