#!/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
#--------------------------------------------------------------------
Wednesday, July 29, 2009
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.
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.
Subscribe to:
Posts (Atom)