Cover V04, I03
Article
Listing 1
Table 1
Table 2

may95.tar


Listing 1: tape2tape script

#!/bin/ksh
#Program: tape2tape   This script will copy from fixed-block tape to tape
#and can optionally perform a checksum  of source and destination tapes.
#This script will automatically determine tape format, and the number of
#files on a tape, if unknown.
#10/31/94 PVELLECA, HSSC, Rockledge FL

#CAPTURE COMMAND LINE ARGUMENTS FOR LATER PARSING.
THISFILE=$0
ARG1=$1
ARG2=$2
NUMARG=${#*}

#MAKE COPY OF STANDARD I/O FOR USE LATER.
exec 3<&0       #COPY OF STD IN
exec 4>&1       #COPY OF STD OUT
TMPFILE=/tmp/dd$$

#CREATE A TABLE OF SUPPORTED QIC FORMATS AND THEIR RESPECTIVE
#I/O DEVICE INTERFACES (TAPE DRIVE SPECIAL FILES). THIS TABLE MUST
#HAVE THE FOLLOWING FORMAT:
#  : <QIC_TYPE> <NO_REWIND_SOURCE_DEVICE>
<NO_REWIND_DESTINATION_DEVICE>
#
#  QIC_TYPE: THE QIC FORMAT, E.G. QIC150.
#  NO_REWIND_SOURCE_DEVICE: THE NO-REWIND SPECIAL FILE FOR THAT QIC_TYPE,
#  FOR THE SOURCE TAPE DRIVE E.G. /dev/nrmt0m or /dev/nrst0.
#  NO_REWIND_DESTINATION_DEVICE: THE NO-REWIND SPECIAL FILE FOR THAT QIC_TYPE,
#  FOR THE DESTINATION TAPE DRIVE E.G. /dev/nrmt1m or /dev/nrst16.
#
#SOURCE/DESTINATION ENTRIES CAN BE REPEATED FOR DIFFERENT FORMATS THAT USE
#THE SAME SPECIAL FILE. SEE MAN PAGE FOR THE DEVICE INTERFACE FOR YOUR SYSTEM
#(E.G tz(4) FOR ULTRIX, st(4s) FOR SUN).
#
#THESE !!!!!!! MUST !!!!!! BE NO-REWIND DEVICES, OR THE COPY WILL NOT WORK.

#FORMAT         SOURCE DEVICE   DESTINATION DEVICE   -REMEMBER, NO-REWIND!
#######         ############    #################
: QIC24         /dev/nrmt0a     /dev/nrmt1a
: QIC120        /dev/nrmt0l     /dev/nrmt1l
: QIC150        /dev/nrmt0m     /dev/nrmt1m
: QIC320        /dev/nrmt0h     /dev/nrmt1h

#DEFAULT CHECKSUM WHEN FINISHED WITH COPY. OPERATOR CAN TOGGLE THIS
FROM THE
#COMMAND LINE WITH THE '-c' OPTION.
DEFAULT_SUM=FALSE

#################################################
### SHOULDNT NEED TO EDIT BELOW THIS LINE #######
#################################################

#CATCH ^C (INTERRUPT) AND KILL ALL RUNNING CHILDREN BEFORE EXITING.
trap "KillChildProcesses;rm -f $TMPFILE;exit 1" 2

#DEFINE FUNCTIONS
function WaitForTape
{
#WAIT FOR TAPE DRIVES TO GO ONLINE, ETC
#INPUT: (Input|Output) ASSUMES DEFAULT_FORMAT[3] IS SET BY MAIN PROGRAM.
#OUTPUT: none.
#THESE mt STATUS CODES ARE IN HEX, WHICH IS FAIRLY STANDARD, BUT YOU MAY
#WANT TO CHECK YOURS IF THE SCRIPT FAILS. REMEMBER BECAUSE THEY ARE HEX,
#THESE ARE STRINGS TO THE SHELL, NOT INTEGERS.
ONLINE=200
OFFLINE=204
ONLINE_WRITELOCK=208
OFFLINE_WRITELOCK=20C

#DETERMINE IF SOURCE|DEST DEVICE ARGUMENT
IO_TYPE=$1
#GET FIRST CHAR
IO_TYPE=`expr "$IO_TYPE" : '\(.\)' \| 'x'|tr 'A-Z' 'a-z'`
if [ $IO_TYPE = "i" ]; then
#DEVICE IS INPUT
TAPE_TYPE=SOURCE
TAPE_DEVICE=${DEFAULT_FORMAT[1]}
else
#DEVICE IS OUTPUT
TAPE_TYPE=DESTINATION
TAPE_DEVICE=${DEFAULT_FORMAT[2]}
fi
#LOOP AND WAIT UNTIL DRIVE BECOMES AVAILABLE.
STOP=1
SLEEPCOUNTER=0
while [ $STOP -ne 0 ]; do
#CHECK STATUS ON DEVICE
mt -f $TAPE_DEVICE stat >/dev/null 2>&1
STOP=$?
sleep 1  #DONT EAT UP CPU
((SLEEPCOUNTER=SLEEPCOUNTER+1))     #COUNT TIME SPENT SLEEPING.
((MSGTIMER=SLEEPCOUNTER%40))        #INTERVAL BETWEEN WAITING MESSAGES.
if [ $SLEEPCOUNTER -eq 20 -o $MSGTIMER -eq 0 ]; then
print "        Waiting for $TAPE_TYPE drive..."
fi
done

#THIS ASSUMES THE mt COMMAND RETURNS THE HEX STATUS CODE TO STDOUT
#THAT LOOKS LIKE: 'status  <hex_code>'.  CHECK YOUR IMPLEMENTATION.
#ENSURE DRIVE IS ON-LINE
set -A MTSTAT `mt -f $TAPE_DEVICE stat|egrep '^stat'`
MTSTAT=${MTSTAT[1]}
if [ "$MTSTAT" = $OFFLINE -o "$MTSTAT" = $OFFLINE_WRITELOCK ]; then
Beep 2
print "Please place $TAPE_DEVICE ($TAPE_TYPE) ONLINE."
print -n "Hit [return] when ready ..."
read READY <&3
WaitForTape $IO_TYPE
fi

#ENSURE DESTINATION TAPE IS NOT WRITE PROTECTED.
if [ $TAPE_TYPE = "DESTINATION" ]; then
if [ "$MTSTAT" = $ONLINE_WRITELOCK ]; then
Beep 2
print "Please remove WRITE PROTECT on $tape_device ($tape_type)."
print -n "Hit [return] when ready ..."
read READY <&3
WaitForTape $IO_TYPE
fi
fi
}

function Beep
{
#INPUT: integer
#OUTPUT: integer number of beeps
COUNT=$1
COUNT=${count:=1}
while [ $COUNT -gt 0 ]; do
#CONVERT NEWLINE TO BEEP
print|tr '\012' '\007'
((COUNT=COUNT-1))
done
}

function DisplaySyntax
{
#DISPLAY SYNTAX AND EXIT
#INPUT: none.
#OUTPUT: (syntax_message)
print "SYNTAX:  t2t [-c] [numfiles]"
print "         QIC Tape to tape copy."
print "         c: toggles default checksum setting"
print "         numfiles: 0 or blank for unkown; otherwise some integer."
exit 1
}

function KillChildProcesses
{
#KILL PROCESS(ES) (RUNNING AND STOPPED) STARTED BY THIS SCRIPT.
#INPUT: none
#OUTPUT: none
#GET LIST OF JOBS IN FORMAT SUPPORTED BY KILL: %1 %2 ...
JOBS=`jobs|sed  's,\[\([0-9]*\)\].*,%\1,'`
kill -9 $JOBS 1>/dev/null 2>&1
return $?
}

function RewindTapes
{
#WAIT FOR SOURCE AND DESTINATION DRIVES TO GO ONLINE, THEN REWIND.
#INPUT: none
#OUTPUT: none
WaitForTape Input 1>&4 &
WaitForTape Output 1>&4 &
wait
print -n "Rewinding tapes ..."
mt -f ${DEFAULT_FORMAT[1]} re 1>&4 &
mt -f ${DEFAULT_FORMAT[2]} re 1>&4 &
wait
print "done."
}

function ChangeDefault
{
#USE OPERATOR INPUT TO CHANGE A VARIABLE SETTING.
#INPUT: (message, default_value)
#OUTPUT: (new_value)
TEXTMSG=$1

DEFAULT=$2
print -n "$TEXTMSG [${DEFAULT}]: " >&4
read INPUT <&3
print ${INPUT:=$DEFAULT}
}

function SetDefaults
{
#ALLOW OPERATOR TO CHANGE DEFAULT SETIINGS.
print " "
print "======================COPY INFORMATION ==================="
if [ $NUM_FILES_KNOWN -eq 0 ]; then
print "Number of files:..................$NUM_FILES"
else
print "Number of files:..................0 (UNKNOWN)"
fi
print "Perform checksums after copy:.....$SUM"
print "Source format:....................$INPUT_FORMAT"
print "Destination format:...............$OUTPUT_FORMAT"
print "Block transfer size:..............$BLOCK_SIZE"
print " "
print -n "Do you want to change defaults [no]? "
read READY <&3
READY=`expr "$READY" : '\(.\)' \| 'n'|tr 'A-Z' 'a-z'`
if [ $READY = "y" ]; then
if [ $NUM_FILES_KNOWN -eq 0 ]; then
NUM_FILES=$NUM_FILES
else
NUM_FILES=0
fi
NUM_FILES=`ChangeDefault "Number of files" $NUM_FILES`
NUM_FILES=`CheckNumfiles $NUM_FILES`
NUM_FILES_KNOWN=$?
SUM=`ChangeDefault "Perform checksums after copy:" $SUM`
SUM=`CheckCheckSum $SUM`
INPUT_FORMAT=`ChangeDefault "Source format" $INPUT_FORMAT`
set -A INPUT_FORMAT `CheckFormat $INPUT_FORMAT Input`
INPUT_DEVICE=${INPUT_FORMAT[1]}
OUTPUT_FORMAT=`ChangeDefault "Destination format" $OUTPUT_FORMAT`
set -A OUTPUT_FORMAT `CheckFormat $OUTPUT_FORMAT Output`
OUTPUT_DEVICE=${OUTPUT_FORMAT[2]}
BLOCK_SIZE=`ChangeDefault "Block transfer size" $BLOCK_SIZE`
BLOCK_SIZE=`CheckBlockSize $BLOCK_SIZE`
SetDefaults
fi
}

function CheckNumfiles
{
#ENSURE NUMBER OF FILES TO COPY IS LEGAL.
#INPUT: (Number_of_files)
#OUTPUT: (Number_of_file; STATUS=NUM_FILES_KNOWN)
NUM_FILES=$1
#ENSURE NUMBER OF FILES IS AN INTEGER AND KNOWN.
NUM_FILES=`expr "$NUM_FILES" : '\([1-9][0-9]*\)' \| 0`
#DECIDE IF NUMBER OF FILES IS KNOWN.
if [ $NUM_FILES -eq 0 ]; then
#NUMBER OF FILES IS NOT KNOWN
NUM_FILES_KNOWN=1
NUM_FILES=1
else
#NUMBER OF FILES IS KNOWN
NUM_FILES_KNOWN=0
fi
print $NUM_FILES
#RETURN STATUS IS USED BY CALLING PROGRAM:  0-> TRUE 1-> FALSE
return $NUM_FILES_KNOWN
}

function CheckCheckSum
{
#ENSURE SELECTION OF CHECKSUM IS LEGAL.
#INPUT:  (Check_sum)
#OUTPUT: (Check_sum)
REQUEST=$1
#DECIDE IF CHECKSUMS ARE TO BE DONE.
DS=`expr "$DEFAULT_SUM" : '\(.\)'|tr 'A-Z' 'a-z'`
#DISCOVER THE OPPOSITE OF THE DEFAULT SETTING
if [ $DS = "y" -o $DS = "t" ]; then
NOT_DEFAULT_SUM=FALSE
else
NOT_DEFAULT_SUM=TRUE
fi
REQUEST=`expr "$REQUEST" : '-\{0,1\}\([A-Za-z]\)' \| "$DS"|tr 'A-Z' 'a-z'`
#DETERMINE NEW CHECKSUM SETTING.
if [ "$REQUEST" = "c" ]; then
REQUEST=$NOT_DEFAULT_SUM
elif [ "$REQUEST" = "t" -o "$REQUEST" = "y" ]; then
REQUEST=TRUE
else
REQUEST=FALSE
fi
print $REQUEST
}

function CheckFormat
{
#ENSURE QIC FORMAT SELECTED IS SUPPORTED AS DENOTED AT THE TOP OF THIS FILE.
#INPUT:  (QIC_Format, Input|Output)
#OUTPUT: (QIC_Format, Input_Device, Output_Device)
REQUEST=$1
#REMOVE HYPHENS, ETC AND PRETTY UP OPERATOR INPUT.
REQUEST=`print $REQUEST|sed 's,\([^qicQIC0-9]\),,g'|tr 'a-z' 'A-Z'`
IO_TYPE=$2
#GET FIRST CHAR
IO_TYPE=`expr "$IO_TYPE" : '\(.\)' \| 'x'|tr 'A-Z' 'a-z'`
#BASED ON INPUT, GET DEVICES FROM THE TABLE AT THE TOP OF THIS FILE.
set -A CHECKREQUEST `GetTapeInfo $REQUEST`
#IF OPERATOR INPUT DOES NOT RESOLVE, THEN USE THE DEFAULT.
if [ ${#CHECKREQUEST} -eq 0 ]; then
REQUEST=$DEFAULT_FORMAT
else
REQUEST=$CHECKREQUEST
fi
#GET DEVICES.
GetTapeInfo $REQUEST
}

function CheckBlockSize
{

#ENSURE BLOCK SIZE SELECTED IS LEGAL.
#INPUT:  (Block_size)
#OUTPUT: (Block_size)
BLOCK_SIZE=$1
#COPY BLOCK SIZE AS USED BY dd. BIGGER VALUES ALLOW BETTER THROUGHPUT
#BECAUSE THE DRIVES CAN STREAM. 10K WORKS WELL FOR MOST QIC TAPES.
DEFAULT_BLOCK_SIZE=10k
BLOCK_SIZE=${BLOCK_SIZE:=$DEFAULT_BLOCK_SIZE}
#ENSURE BLOCKSIZE IS LEGAL, ALLOWING FOR SUFFIXES PER dd(1).
BLOCK_SIZE=`expr "$BLOCK_SIZE" : '\([0-9][0-9]*[kKbBwW]*\)' \
\| $DEFAULT_BLOCK_SIZE |tr 'KBW' 'kbw'`
print $BLOCK_SIZE
}

function DetermineCopyFormat
{
#READ THE DESTINATION TAPE TO AUTOMATICALLY DETERMINE DEFAULT QIC FORMAT.
#INPUT: none
#OUTPUT: (QIC_Format, Input_Device, Output_Device)
#GET SUPPORTED QIC FORMATS, SORTED BY INCREASING QIC FORMAT.
#IGNORE THE PREFIX "QIC" TO ALLOW NUMERIC SORTING.
set -A SUPPORTED_FORMATS `grep "^: "  $THISFILE|awk '{print $2}'|sort +.3nr`
NSF=${#SUPPORTED_FORMATS[*]}
#CHOOSE A DEFAULT FORMAT FROM LIST (USED ONLY TO MANIPULATE TAPE, NOT COPY).
set -A DEFAULT_FORMAT `GetTapeInfo $SUPPORTED_FORMATS`
RewindTapes 1>&4
print -n "Checking DESTINATION format..." 1>&4
COUNT=0
while [ $COUNT -lt $NSF ]; do
set -A TEST_FORMAT `GetTapeInfo ${SUPPORTED_FORMATS[$COUNT]}`
if [ `expr "$CHECKED_FORMAT" : ".*$TEST_FORMAT"` -eq 0 ]; then
#HAVE NOT YET CHECKED THIS FORMAT.
CHECKED_FORMAT="$CHECKED_FORMAT $TEST_FORMAT"
#WRITE OUT 1 BLOCK OF GARBAGE TO DESTINATION TAPE.
dd if=/bin/sh of=${TEST_FORMAT[2]} bs=512 count=1 >$TMPFILE 2>&1
set -A DDSTAT `cat $TMPFILE 2>/dev/null`
if [ "${DDSTAT[0]}" -eq "${DDSTAT[3]}" ]; then
#THE NUMBER OF RECORDS_IN = RECORDS_OUT FOR dd.  THIS IS NOT TRUE IF
#FORMAT DOES NOT MATCH TAPE TYPE.
#WE HAVE FOUND THE HIGHEST ALLOWABLE CAPACITY.
DISCOVERED_FORMAT=$TEST_FORMAT
((COUNT=NSF))
fi
fi
((COUNT=COUNT+1))
done
print "done." 1>&4
GetTapeInfo ${DISCOVERED_FORMAT:=$HIGHEST_FORMAT}
}

function GetTapeInfo
{
#USE INPUT TO GET SPECIAL FILE INFO OF A SUPPORTED QIC FORMAT.
#INPUT: (QIC_Format)
#OUTPUT: (QIC_Format, Input_Device, Output_Device)
PIECE=$1
set -A INFO `grep -i "^:.*$PIECE" $THISFILE`
print ${INFO[1]} ${INFO[2]} ${INFO[3]}
}


#################################################
#INITALIZE TAPE COPY SETTINGS
#################################################
set -A FORMAT `DetermineCopyFormat`
INPUT_FORMAT="[AUTO-SELECT]"  #OTHERWISE CAN SET TO: ${FORMAT[0]}
OUTPUT_FORMAT=${FORMAT[0]}
INPUT_DEVICE=${FORMAT[1]}
OUTPUT_DEVICE=${FORMAT[2]}
set -A DEFAULT_FORMAT ${FORMAT[*]}
BLOCK_SIZE=`CheckBlockSize`
if [ $NUMARG -gt 2  -o "$ARG1" = "-x" ]; then
Beep 1
DisplaySyntax
elif [ $NUMARG -eq 2 ]; then
#TWO ARGS, TOGGLE CHECKSUM AND NUM_FILES IS POSSIBLY KNOWN
SUM=$ARG1
NUM_FILES=$ARG2
elif [ $NUMARG -eq 1 ]; then
#NOT SURE IF ARG IS CHECKSUM OR NUM_FILES
SUM=$ARG1
NUM_FILES=$ARG1
else
#NO ARGS
SUM=$DEFAULT_SUM
NUM_FILES=0
fi
SUM=`CheckCheckSum $SUM`
NUM_FILES=`CheckNumfiles $NUM_FILES`
NUM_FILES_KNOWN=$?

SetDefaults
RewindTapes 1>&4

#################################################
#BEGIN MAIN PROGRAM
#################################################
#PERFORM TAPE COPY
print " "
print "======================PERFORMING COPY==================="
COUNT=0
while [ $NUM_FILES -gt 0  ]; do
((COUNT=COUNT+1))
print "Copy file #$COUNT ..."
WaitForTape Input 1>&4 &
WaitForTape Output 1>&4 &
wait
#THIS SCRIPT ASSUMES THE FOLLOWING ABOUT dd:
#IF ASKED TO OPERATE PAST EOV, IT MAY PLACE THE DRIVE OFFLINE
#AND IT WILL PRINT THE FOLLOWING MESSAGE TO STD ERROR (2):
#dd: end of media, load another volume.
#WITH SUCCESSFUL COPY OPERATION, dd PRINTS THE FOLLOWING MESSAGE
#TO STD ERROR:
#X+Y records in
#A+B records out
dd if=$INPUT_DEVICE of=$OUTPUT_DEVICE bs=$BLOCK_SIZE 1>$TMPFILE 2>&1 &
DD_PID=${!}
DDISDONE="FALSE"
while [ $DDISDONE = "FALSE" ];do
set -A DDSTAT `cat $TMPFILE 2>/dev/null`
#GET STATUS AS REPORTED BY dd
if [ ${DDSTAT[1]:="notdone"} = "records" ]; then
#dd IS FINISHED
cat $TMPFILE
rm -f $TMPFILE
if [ "${DDSTAT[0]}" = "0+0" -a "${DDSTAT[3]}" = "0+0" ];
then
#BUT FILE IS ONLY AN EOF MARKER- NO DATA.
#MUST EXPLICITLY WRITE EOF, BECAUSE dd WONT FOR A ZERO-LENGTH FILE.
WaitForTape Output 1>&4
mt -f $OUTPUT_DEVICE weof 1
elif [ "${DDSTAT[0]}" != "${DDSTAT[3]}" ]; then
#CHOSEN OUTPUT FORMAT NOT COMPATIBLE WITH TAPE TYPE.
print "ERROR: OUTPUT FORMAT POSSIBLY INCORRECT. COPY ABORTED."
KillChildProcesses
exit 1
fi
DDISDONE="TRUE"
elif [ ${DDSTAT[1]} = "end" ]; then
#dd HAS REPORTED END OF MEDIA, AND TAPE MAY NOW BE OFFLINE.
kill -9 $DD_PID >/dev/null 2>&1
((COUNT=COUNT-1))
print "Source tape contains $COUNT files."
NUM_FILES_KNOWN=0         #NUMBER OF FILES IS NOW KNOWN.
DDISDONE="TRUE"
elif [ ${DDSTAT[1]} = "notdone" ]; then
#dd IS NOT DONE
sleep 1
else
#dd HAS ENCOUNTERED AN ERROR
cat $TMPFILE
rm -f $TMPFILE
print "UNKNOWN dd ERROR: COPY ABORTED."
KillChildProcesses
exit 1
fi
done
if [ $NUM_FILES_KNOWN -eq 0 ]; then
((NUM_FILES=NUM_FILES-1))
fi
done
rm -f $TMPFILE
RewindTapes 1>&4

#################################################
#VERIFY
#################################################
if [ $SUM != 'FALSE' ]; then
print " "
print "======================VERIFY COPY==================="
NUM_FILES=$COUNT
COUNT=0
while [ $NUM_FILES -gt 0 ]; do
((COUNT=COUNT+1))
( print "SOURCE File#$COUNT checksum: \
`dd if=$INPUT_DEVICE bs=$BLOCK_SIZE 2>/dev/null|sum`" )&
( print "DEST   File#$COUNT checksum: \
`dd if=$OUTPUT_DEVICE bs=$BLOCK_SIZE 2>/dev/null|sum`" )&
print " "
wait
((NUM_FILES=NUM_FILES-1))
done
RewindTapes 1>&4
fi

print " "
print "Finished with copy."
exit 0
#END MAIN PROGRAM