#!/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



