#!/bin/ksh

# ****************************
# *                          *
# * SIGMET Networked Version *
# *  of UNIX "tar" Command   *
# *                          *
# ****************************

# This script implements a platform independent set of "tar like"
# commands that can backup and restore collections of files on a
# (possibly remote) archive device.

# Must specify exactly one of the following operations:
#    -create : Create a new archive
#   -srcsize : Report archive size if '-create' were to be used
#   -extract : Extract file(s) from an archive
#      -list : List contents of an archive (quickly)
#   -skipfwd : Reposition ourselves to the next/previous
#   -skiprev :    archive (valid for tape restores only).
#    -rewind : Rewind the archive device
#
# Archive device is specified as follows:
#   -device <name> : Tape name or name of archive file on disk
#     -node <name> : Archive device resides on this node (optional)
#
# Additional directives
#   -exclude <name> : Exclude File/Directory
#      -user <name> : Do remote operations as this user
#         -compress : Make a compressed archive
#         -norecent : Ignore files modified in the last minute
#           -delsrc : Delete source files after successful archiving
#    -dvdtmp <file> : Specify DVD temp file for use with -create

# ==============================
# Setup the execute search path to include the other IRIS_BIN
# utilities that will be used throughout this script.  The idea is to
# add whatever directory sigbrush itself is in to the existing PATH
# variable.
#
CMD="${0}"
if [ "${CMD}" = "sigbrush" ] ; then
  ADDPATH="`pwd`"
else
  ADDPATH="`cd ${CMD%sigbrush} ; pwd`"
fi
PATH="${ADDPATH}:${PATH}" ; unset ADDPATH CMD

# ==============================
# Setup temporary directory, trap to cleanup upon exit, and basic
# machine information.
#
TMPDIR=/tmp/$$_sigbru ; rm -rf ${TMPDIR} ; mkdir ${TMPDIR}
DVDTMP="" ; ERRSTT="0"

trap "rm -rf ${TMPDIR} ${DVDTMP}" INT EXIT
trap "ERRSTT='1'" ERR

FILELIST="sigbru_file_list"  # File holding list of files

# WARNING: Manually maintain this code in:
# make_iris_tape, install_iris, ps_iris & sig_uname_filter.
# Anything ever run by root cannot use sig_uname_filter.
#
MACHINE=`uname -s`
if [ "$MACHINE" = 'IRIX64' ] ; then
  MACHINE="IRIX"
fi

OURNODE=`uname -n` ; OURNODE="${OURNODE%%.*}"

set +o noclobber             # Required for some redirections

# ==============================
# Process argument list
#
HELP="false"
PATHS=""   ; OPER=""     ; DEVICE=""  ; TARGNODE="" ;
REMUSER="" ; EXCLUDES="" ; ZIPFLAG="" ; DELSRC="false" ;
PRUNES="-false" ; STUBS="" ; FINDMMIN="" ;

while [ ! "$1" = "" ] ; do
  OPT="$1" ; shift
  case ${OPT} in
    -create | -srcsize | -extract | -list | -skipfwd | -skiprev | -rewind )
      if [ "${OPER}" != "" ] ; then
        echo "Can not specify ${OPT} with -${OPER}." 1>&2 ; exit 1
      fi
      OPER="${OPT#-}"
      ;;

    -device )
      if [ "${1}" = "" ] ; then
        echo "Missing device name for -device" 1>&2 ; exit 1
      else
        DEVICE="${1}" ; shift
      fi
      ;;

    -node )
      if [ "${1}" = "" ] ; then
        echo "Missing node name for -node" 1>&2 ; exit 1
      else
        TARGNODE="${1}" ; shift
      fi
      ;;

    -user )
      if [ "${1}" = "" ] ; then
        echo "Missing remote user name for -user" 1>&2 ; exit 1
      else
        REMUSER="-l ${1}" ; shift
      fi
      ;;

    -exclude )
      if [ "${1}" = "" ] ; then
        echo "Missing directory/file name for -exclude" 1>&2 ; exit 1
      else
        # Keep track of items to prune when creating (PRUNES), items
        # to exclude when extracting (EXCLUDES), and stubs of
        # directories that were excluded from the archive.  The stubs
        # help to recreate the original directory structure, even
        # though the tree below the excluded stubs was not archived.
        #
        PRUNES="${PRUNES} -o \( -path ${1%/} -prune \)"
        EXCLUDES="${EXCLUDES} --exclude ${1%/}"
        if [ -d "${1}" ] ; then
          STUBS="${STUBS}${1%/}\n"
        fi
        shift
      fi
      ;;

    -dvdtmp )
      if [ "${1}" = "" ] ; then
        echo "Missing temporary file pathname for -dvdtmp" 1>&2 ; exit 1
      else
        DVDTMP="${1}" ; shift
      fi
      ;;

    -compress )
      ZIPFLAG="-z" ;;

    -norecent )
      FINDMMIN="-o -mmin -1" ;;

    -delsrc )
      DELSRC="true" ;;

    -help|-? )
      HELP="true" ;;

    * )
      if [ "${OPT#-*}" != "${OPT}" ] ; then
        echo "Unknown option: '${OPT}'" 1>&2 ; HELP="true"
      else
        if [ "${OPT}" = "/" ] ; then PATHS="${PATHS} /"
                                else PATHS="${PATHS} ${OPT%/}" ; fi
      fi
      ;;
  esac
done

# Print HELP messages if these were requested, either directly or
# indirectly.
#
if [ "${HELP}" = "true" ] ; then
  echo "Command Line Options:"
  echo "  Must specify one of the following operations:"
  echo "     -create : Create a new archive"
  echo "    -srcsize : Report archive size if '-create' were to be used"
  echo "    -extract : Extract file(s) from an archive"
  echo "       -list : List contents of an archive (quickly)"
  echo "    -skipfwd : Reposition ourselves to the next/previous"
  echo "    -skiprev :    archive (valid for tape restores only)."
  echo "     -rewind : Rewind the archive device"
  echo " "
  echo "  Archive device is specified as follows:"
  echo "     -device <name> : Tape name or name of archive file on disk"
  echo "       -node <name> : Archive device resides on this node"
  echo "                        (default is current node)."
  echo " "
  echo "  Additional directives:"
  echo "    -exclude <name> : Exclude File/Directory"
  echo "       -user <name> : Do remote operations as this user"
  echo "          -compress : Make a compressed archive"
  echo "          -norecent : Ignore files modified in the last minute"
  echo "            -delsrc : Delete source files after successful archiving"
  echo "     -dvdtmp <file> : Specify DVD temp file for use with -create"
  exit 0
fi

# Verify that there is something to do
#
if [ "${OPER}" = "" ] ; then
  echo "You have not chosen any operation to perform" 1>&2 ; exit 1
fi

# If the node name matches the local computer, then make it look like
# a local operation.  Also setup the remote shell command.
#
if [ "${TARGNODE}" = "" ] ; then
  TARGETED="false" ; TARGNODE="${OURNODE}"
else
  if [ "${TARGNODE}" = "${OURNODE}" ] ; then TARGETED="false"
                                        else TARGETED="true"
  fi
fi

if [ "$MACHINE" = 'HP-UX' ] ; then
  alias REMSH="remsh ${TARGNODE} ${REMUSER}"
else
  alias REMSH="rsh   ${TARGNODE} ${REMUSER}"
fi

# Setup some top level aliases.  Thse can not be defined within an
# "if" clause, so do it here.  MTREPOS is used to reposition a tape to
# the beginning of the current archive.  MTSKIP is used to skip to
# another archive on a tape.  The alias forms are used locally, and
# the text forms are expanded to carry out the same operations for
# remote shells.
#
alias FINDCMD="gnufind ${PATHS} \( ${PRUNES} \) ${FINDMMIN} -o -print"

MTREPOS="if [ -c ${DEVICE} ] ; then \
           if mt -f ${DEVICE} bsf 2>&- ; then mt -f ${DEVICE} fsr 2>&- ; fi ; \
         fi"
alias MTREPOS="${MTREPOS}"

MTREWIND="if [ -c ${DEVICE} ] ; then mt -f ${DEVICE} rewind 2>&- ; fi"
alias MTREWIND="${MTREWIND}"

MTSKIP="if [ -c ${DEVICE} ] ; then \
          if [ ${OPER} = skipfwd ] ; then \
            mt -f ${DEVICE} fsf 2>&- ; \
          else \
            if mt -f ${DEVICE} bsf 2 2>&- ; then mt -f ${DEVICE} fsr 2>&- ; fi ; \
          fi ; \
        fi"
alias MTSKIP="${MTSKIP}"

# Perform some sanity checks on the chosen archive device, but only if
# we are really going to be using it.
#
if [ "${OPER}" != "srcsize" ] ; then
  if [ "${DEVICE}" = "" ] ; then
    echo "You must choose an archive file/device with -device" 1>&2 ; exit 1
  else
    # If we are targeted, then verify that the remote node is reachable.
    # For both targeted and local, verify that the archive device is not
    # a directory, as this is guaranteed to be an error.
    #
    if [ "${TARGETED}" = "true" ] ; then
      TEST=`REMSH if test -d ${DEVICE} ';' then echo oops ';' fi`
      if [ "${ERRSTT}" != "0" ] ; then
        echo "'${DEVICE}' on node '${TARGNODE}' not reachable" 1>&2 ; exit 1
      fi
    else
      TEST=`if test -d ${DEVICE} ; then echo oops ; fi`
    fi
    if [ "${TEST}" = "oops" ] ; then
      echo "'${DEVICE}' on node '${TARGNODE}' is a directory!" 1>&2 ; exit 1
    fi
  
    # Perform some sanity checks on the archive device itself.
    #
    if [ "${OPER}" = "create" ] ; then
      # For -create, verify that the archive device either does not
      # exist (new file case), or exists with write permission.
      #
      if [ "${TARGETED}" = "true" ] ; then
        TEST=`REMSH if test -w ${DEVICE} -o ! -r ${DEVICE} ';' then echo okay ';' fi`
      else
        TEST=`if test -w ${DEVICE} -o ! -r ${DEVICE} ; then echo okay ; fi`
      fi
      if [ "${TEST}" != "okay" ] ; then
        echo "Can not write '${DEVICE}' on node '${TARGNODE}'" 1>&2 ; exit 1
      fi
  
    elif [ "${OPER}" = "extract" -o "${OPER}" = "list" ] ; then
      # Test that the archive device is defined and readable for the
      # cases of -extract and -list.  We don't do this for -create
      # because we may be writing to a new file that does not yet
      # exist.
      #
      if [ "${TARGETED}" = "true" ] ; then
        TEST=`REMSH if test -r ${DEVICE} ';' then echo okay ';' fi`
      else
        TEST=`if test -r ${DEVICE} ; then echo okay ; fi`
      fi
      if [ "${TEST}" != "okay" ] ; then
        echo "Can not access '${DEVICE}' on node '${TARGNODE}'" 1>&2 ; exit 1
      fi
  
      # Determine whether the archive is compressed
      #
      if [ "${TARGETED}" = "true" ] ; then
        TEST="`REMSH dd if=${DEVICE} bs=20b count=1 '2>&1' '|' gunzip -l '2>&1' |
                 egrep -i 'not in .* format'`"
        REMSH ${MTREPOS}
      else
        TEST="`dd if=${DEVICE} bs=20b count=1 2>&1 | gunzip -l 2>&1 |
                 egrep -i 'not in .* format'`"
        MTREPOS
      fi
      if [ "${TEST}" = "" ] ; then ZIPFLAG="-z" ;
                              else ZIPFLAG=""   ; fi
    fi

    # Check for improper usage of DVD options
    #
    if [ "${DVDTMP}" != "" ] ; then
      if [ "${OPER}" != "create" ] ; then
        echo "The -dvdtmp option can only be used with -create" 1>&2 ; exit 1
      fi
    fi

  fi
fi
##echo "   PATHS = '${PATHS}'"    ##!!
##echo "    OPER = '${OPER}'"     ##!!
##echo "  PRUNES = '${PRUNES}'"   ##!!
##echo "EXCLUDES = '${EXCLUDES}'" ##!!
##echo "   STUBS = '${STUBS}'"    ##!!
##echo "TARGNODE = '${TARGNODE}'" ##!!
##echo "TARGETED = '${TARGETED}'" ##!!
##echo " OURNODE = '${OURNODE}'"  ##!!
##echo " ZIPFLAG = '${ZIPFLAG}'"  ##!!
##exit                            ##!!

# Handle one of the actions...
#
if [ "${OPER}" = "skipfwd" -o "${OPER}" = "skiprev" ] ; then
  # ============================================================
  # Skip to the next/previous archive.  The operations to do this have
  # already been setup, so all we have to do here is invoke them.
  #
  if [ "${TARGETED}" = "true" ] ; then
    REMSH ${MTSKIP}
  else
    MTSKIP
  fi

elif [ "${OPER}" = "rewind" ] ; then
  # ============================================================
  # Rewind the archive device.
  #
  if [ "${TARGETED}" = "true" ] ; then
    REMSH ${MTREWIND}
  else
    MTREWIND
  fi

elif [ "${OPER}" = "create" ] ; then
  # ============================================================
  # Create a new archive.  First make a complete list of the files
  # that will be included.  The archive will then be built with this
  # list file first.
  #
  if [ "${PATHS}" = "" ] ; then
    echo "You have not chosen any paths to archive" 1>&2 ; exit 1
  fi

  # The archive list will consist primarily of a list of file path
  # names (normal, symlink, special, etc).  Individual directory names
  # are also included because TAR will be invoked with the
  # (undocumented) "--no-recursion" that prevents descending into
  # them.  Excluded directory stubs are also included so that the
  # archive will contain a remnant of the snipped off tree.  Note that
  # '/' is always removed from the list.
  #
  TMPX="${TMPDIR}/tmpx_file" ; echo "${STUBS}" > ${TMPX}
  echo ${TMPDIR}/${FILELIST}                       >  ${TMPDIR}/${FILELIST}
  FINDCMD | sort -u ${TMPX} - | egrep -v '^/$|^$'  >> ${TMPDIR}/${FILELIST}

  if [ "${ERRSTT}" != "0" ] ; then exit 1 ; fi

  CREATEOPTS="-v -c -T ${TMPDIR}/${FILELIST} -b 20 ${ZIPFLAG}"
  CREATEOPTS="${CREATEOPTS} --no-recursion --atime-preserve"

  if [ "${TARGETED}" = "true" ] ; then
    # Making the live progress listing of files as they are written is
    # tricky in the targeted case, since the standard output is used
    # up handling the pipe.  Redirect STDERR to STDOUT to handle this.
    #
    # Note: Use tar and dd, rather than tar with a direct network
    # device name.  The later is much slower for some reason.
    #
    gnutar ${CREATEOPTS} -f - | REMSH dd of=${DEVICE} ibs=1 obs=20b
  else
    # Most local devices can be written using TAR directly.  However,
    # DVD writing requires a helper utility to move the TAR disk file
    # to the physical DVD device.
    #
    if [ "${DVDTMP}" = "" ] ; then
      gnutar ${CREATEOPTS} -f ${DEVICE}
    else
      gnutar ${CREATEOPTS} -f ${DVDTMP}
      sigdvdrecord -M ${DEVICE} -R -l ${DVDTMP}
      rm -f ${DVDTMP}
    fi
  fi

  if [ "${ERRSTT}" != "0" ] ; then exit 1 ; fi

  # If the archive was created successfully, then optionally delete
  # the input source files.  Leave all of the directories in place.
  #
  if [ "${DELSRC}" = "true" ] ; then
    cat ${TMPDIR}/${FILELIST} | grep -v "${TMPDIR}/${FILELIST}" |
      sed 'N;/^\(.*\)\n\1\//D;P;D' | xargs -i rm -f {} 2> /dev/null ; :
  fi

elif  [ "${OPER}" = "srcsize" ] ; then
  # ============================================================
  # Measure how much data would be included in a new archive if the
  # -create command were given on the same input args.
  #
  SRCSIZE="0"
  if [ "${PATHS}" != "" ] ; then
    # Run the FINDCMD just the way we would if creating an archive,
    # but remove all directories and just leave the files.  This gives
    # the correct numerical result for total input file size, but is
    # somewhat inefficient when thousands of files are involved.
    #
    for SIZE in `FINDCMD | sort -u | sed 'N;/^\(.*\)\n\1\//D;P;D' | egrep -v '^/$' |
      xargs -i du -s {} | sed 's/[	 ].*$//g'` ; do
      let SRCSIZE="((SRCSIZE + SIZE))"
    done

##    for SIZE in `du -s ${PATHS} | sed 's/[	 ].*$//g'` ; do
##      let SRCSIZE="((SRCSIZE + SIZE))"
##    done

  fi
  if [ "${ERRSTT}" != "0" ] ; then exit 1 ; fi
  echo ${SRCSIZE}

elif  [ "${OPER}" = "list" ] ; then
  # ============================================================
  # List contents of an archive.  Look for the complete listing file
  # in the beginning of the archive, and use it to supply the list of
  # available files.  This avoids having to read the entire archive
  # just to get the list of files.
  #
  if [ "${TARGETED}" = "true" ] ; then
    LINE="`REMSH dd if=${DEVICE} ibs=20b obs=1 count=1 '2>&-' |
             gnutar -tvf - -b 20 ${ZIPFLAG} 2>&- | egrep ${FILELIST}`"
    REMSH ${MTREPOS}
  else
    LINE="`dd if=${DEVICE} bs=20b count=1 2>&- |
             gnutar -tvf - -b 20 ${ZIPFLAG} 2>&- | egrep ${FILELIST}`"
    MTREPOS
  fi

  if [ "${LINE}" = "" ] ; then
    # If we can not find the index file, then do a laborious scan across the
    # entire device.
    #
    if [ "${TARGETED}" = "true" ] ; then
      REMSH dd if=${DEVICE} ibs=20b obs=1 '2>&-' | gnutar -tf - -b 20 ${ZIPFLAG} 
      REMSH ${MTREPOS}
    else
      dd if=${DEVICE} bs=20b 2>&- | gnutar -tf - -b 20 ${ZIPFLAG} 
      MTREPOS
    fi

  else
    # If the index file is present, then read just enough data from
    # the device to extract the complete file.  The required number of
    # blocks is estimated from the file size given in the listing line
    # just obtained.  We need to cut the read short ourselves, else
    # the full archive would be scanned before the pipes would finally
    # exit on their own.
    #
    INDEXFILE="${LINE##* }"
    SIZE="`echo ${LINE} | sed 's/^[^0-9]*//' | sed 's/ .*//'`"
    let RECORDS="((1 + (SIZE / 10240)))"

    echo "Archive was created on: `\
      echo ${LINE} | sed 's/^[^0-9]*[0-9]* *//' | sed 's/ tmp.*$//'`"

    if [ "${TARGETED}" = "true" ] ; then
      REMSH dd if=${DEVICE} ibs=20b obs=1 count=${RECORDS} '2>&-' |
        gnutar -xf - -b 20 ${ZIPFLAG} -C ${TMPDIR} ${INDEXFILE} 2>&-
      REMSH ${MTREPOS}
    else
      dd if=${DEVICE} bs=20b count=${RECORDS} 2>&- |
        gnutar -xf - -b 20 ${ZIPFLAG} -C ${TMPDIR} ${INDEXFILE} 2>&-
      MTREPOS
    fi

    # Remove leading '/' from all file names, since the original
    # archive must have been written that way.  Also delete the index
    # file from the listing.
    #
    sed 's+^/++' < ${TMPDIR}/${INDEXFILE} | egrep -v "${INDEXFILE}"
  fi

elif  [ "${OPER}" = "extract" ] ; then
  # ============================================================
  # Extract files/directories from the archive.
  #
  TMPX="${TMPDIR}/tmpx_file" ; cat < /dev/null > ${TMPX}

  if [ "${TARGETED}" = "true" ] ; then
    REMSH dd if=${DEVICE} ibs=20b obs=1 '2>&-' |
      gnutar -xvpf - -b 20 ${ZIPFLAG} --numeric-owner --exclude ${FILELIST} \
      ${EXCLUDES} ${PATHS} 2>${TMPX}
    REMSH ${MTREPOS}
  else
    gnutar -xvpf ${DEVICE} -b 20 ${ZIPFLAG} --numeric-owner --exclude ${FILELIST} \
    ${EXCLUDES} ${PATHS} 2>${TMPX}
    MTREPOS
  fi

  # If the archive was compressed, then certain 'trailing junk' errors
  # are normal.  Filter these out if that is all that seems to have
  # happened.
  #
  if [ "x${ZIPFLAG}" != "x" -a "${?}" = "2" ] ; then
    exit 0
  else
    cat ${TMPX} 1>&2
    if [ "${ERRSTT}" != "0" ] ; then exit 1
                                else exit 0 ; fi
  fi
fi
