#!/bin/sh VERSION="6.01 (April 11, 2002)" # # tpio (tape I/O) Author: Tony Sanderson, Bluehaze Solutions. # # Rev history (most recent at top): # # Apr 3,02 - Remove the need for the '099.dummy' seed file (silly thing) # May 9,00 - Modify DEV and BUF to suit Archive Python 4mm DAT with Linux # Sep 10,98 - Add MB (megabytes) to summary report # 24/8/98 - Change help text slightly again (doco the existing -z option) # 18/8/98 - Change help text slightly # 16/7/96 - Put "dd" back for tape writes # 13/7/96 - Add "c" (compression) to the Solaris 8mm DEV name # 08/5/96 - Remove dd in O/P section so we can detect cpio/tape errors # 21/5/96 - Ammend OPARG (cpio) for 10K blocking; remove .gz from msg. # 16/5/96 - delete gzipping of index files # 8/5/96 - add -n & -c for no rewind, and close, and %taped trace files # 27/3/96 - add -4 option for data (still have -q for QIK cart's) # 28/8/95 - add test for working out tape device name # 17/7/95 - add sed filter to find command # # Definitions: # NB: The /dev one's are of course quite O/S and config specific, # and the dir's quite arbitrary - all would need changing for other # systems. # # set -x # Set USER to the username who gets result-emails (if any): USER=${USER:-root} # Default=root but ext env value can override. NM=`basename $0` #echo "NM=$NM, \$0=$0" LOG="&2" TN1="tape_" TN2="delta_" LFLS= clean () { cd $L_DIR rm -f $LFLS $LOG ${TN1}[0-9]* } define_S2 () { S2="written to tape no. $TAPE_NUM on $DATE from $DIRS. Name of first file-set (as verified from tape) is:" } # Definition of "interrupt" (via DEL) response: trap 'clean; exit 1' 1 2 15 # Define tape driver names (etc) to suit O/S and SCSI config (bit hairy): if [ -c "/dev/rmt/0n" ] then OSV=solaris DEV=/dev/rmt/0cn MAILER="/usr/ucb/Mail" elif [ -c "/dev/nrst1" ] then OSV=sunos DEV=/dev/nrst1 MAILER="/usr/bin/Mail" # Err ... this is a guess (no SunOS here now) elif [ -c "/dev/nst0" ] then OSV=linux DEV=/dev/nst0 MAILER="/usr/bin/Mail" if ! mt setblk 0 2>/dev/null then echo "You need the BSD mt program to read var block size tapes" >&2 fi else echo "No known tape devices found - giving up" >&2 exit 1 fi L_DIR=${L_DIR:-/usr/local/adm/dumpdir/labels} MSG=$L_DIR/msg OPARG="-ov"; IPARG="-ivdm"; DSPL=n; MODS=; PRT=" -print"; FOPT=" -type f " DATE_STAMP=`date '+%Y%m%d%H%M'` LOG=$L_DIR/tpio_log.$DATE_STAMP NOERR=" 2>/dev/null "; BUF="dd bs=96b $NOERR"; E="**Error: " TAPED="%taped" PAT=; TYPE=DIR; DSPL=n; NAMES=n; CPOPT=; QIK150=n; REWIND=n; USRCMD="$NM $*"; DOM=n; DELTA=n; SKIP=n; EJECT=n; SED=; USERDIR=`pwd`; EXC_FILE=; NORWD=n; CLOSE=n; OPTIONS="\+iDs:f:d4orexzntcqp:m:" set -- `getopt $OPTIONS $*` Usage="$NM (tape I/O) version ${VERSION}. Options: o: Out to tape, i: In from tape (Use 'iz' if archive was compressed via zcat), f filename: filename contains backup exceptions as (reg-exp) sed delete cmds, n: No rewinds - start from current position and don't rewind when done, r: Rewind tape (always true if -o selected), e: Eject tape (if avail) mN: (only valid with o): Only tape files modified WITHIN last N days. x: (only valid with o): Exclude sub mounts (see -xdev in 'find' cmd) D: Debug (verbose) mode. d: (daily re-usable tapes) Use day-of-month name, sN: Skip (next) N archives, c: Close (use after 1 or more -n's to tidy up), q: QIK150 drive (1/4 in cart), 4: 4mm DAT (Conner (Archive) Python, t: List names (only) of files on tape (ie: files NOT extracted), p 'pattern' (only valid with i): Only pass names which match 'pattern' * Default = 8mm Exabyte. Use option q for QIK150 carts, -4 for 4mm DAT. EXAMPLES: $NM -o dir(s) Copy tree(s) dir(s) to tape (preceding each with index) $NM -ox dir(s) As above but don't cross any disc mount points $NM -oxn dir(s) As above but don't rewind tape (ie: add an archive) $NM -oxnc dir(s) As above but close job when finished (show summary) $NM -od dir (s) As above but only do a delta (index/log ID = 001-031) $NM -odm7 dir(s) As above but only those files up to one week old. $NM -i Copy everything from tape to disc (into current dir) $NM -iz As above, but for compressed (gzip/zcat) archives $NM -p 'pattern' -i As above, but only include files matching pattern (Use *'s to match slashes for dir names if whole directories are required) Eg: $NM -ox / /usr /home Archives the / /usr and /home trees (and indexes) to tape but without decending into any mount points in those trees. $NM -ox -f /usr/local/sys/xcep / /usr /home As above, and excludes all files listed in /usr/local/sys/xcep $NM assmes the existence of '/usr/local/adm/dumpdir/labels' where it can create numbered index files. If L_DIR is supplied in the environment, this is used instead. The storage format on tape is as pairs of cpio archives: index, then tree. File names are stored in relative form. $NM will include all sub-mounted directories in a tree unless x is used. It is up to the USER to position the tape before starting a tape read or an archive add." # echo "Args now: $*" >&2 if [ $# -eq 1 ]; then echo "$Usage" >&2; exit; fi for arg do # echo "arg is $arg " >&2 case $arg in -m) DELTA=y; DAYS=$2; FOPT="$FOPT -mtime -$2 "; shift; shift;; -r) REWIND=y; shift;; -f) EXC_FILE=$2; shift; shift;; -e) EJECT=y; shift;; -d) DOM=y; shift;; -n) NORWD=y; shift;; -c) CLOSE=y; NORWD=y; shift;; -x) FOPT="$FOPT -xdev "; shift;; -D) DSPL=y; shift;; -o) CPOPT="$OPARG"; shift;; -t) NAMES=y; shift;; -s) SKIP=y; SN=$2; shift; shift;; -i) CPOPT="$IPARG"; shift;; -z) ZCAT=" | zcat "; shift;; -p) PAT="\"$2\""; shift; shift;; -4) if [ $OSV = sunos ] then echo "No such device on this machine" >&2 exit 1 else DEV=/dev/rmt/4ln HOPT=" -H crc " fi shift;; -q) if [ $OSV = sunos ] then DEV=/dev/nrst0 else DEV=/dev/rmt/1n #?? fi shift;; -+) echo "$Usage" >&2; exit;; --) shift;; -*) echo "$E dunno about $arg (Type \"$NM\" for options)" >&2 exit 1;; *) ;; # esac done DIRS="$*" # Check tape - if okay, continue, else exit: RES="`mt -f $DEV status 2>&1`" if [ $? -ne 0 ] then echo "$RES" >&2 exit 1 fi if [ "$CPOPT" != "$OPARG" ] then LOG="&2" fi if [ $EJECT = y ] then eval mt -f $DEV offline >$LOG if [ $? != 0 ] then echo "Problems with tape unit - please check" >&2 exit 1 else echo "Tape ejected" >&2 exit 0 fi fi if [ $NORWD = "n" ] then eval mt -f $DEV status >$LOG if [ $? != 0 ] then echo "Problems with tape unit - please check and reload tape" >&2 exit 1 fi else LOG=$L_DIR/tpio_log fi if [ $REWIND = y ] then echo "Rewinding tape (wait):" >&2 mt -f $DEV rewind if [ $? = 0 ] then echo "Tape rewound." >&2 exit 0 else echo "Problems with tape unit - please check and reload tape" >&2 exit 1 fi fi if [ $NAMES = y ] then echo "Following file-names read from current position on tape:" >&2 CMD="$BUF < $DEV | cpio -ivt $PAT $NOERR" if [ $DSPL = y ] then echo "Full command = $CMD" >&2 fi eval $CMD exit 0 fi if [ $SKIP = y ] then echo "Skipping next $SN archive(s) - wait:" >&2 mt -f $DEV fsf $SN echo "Done." >&2 exit 0 fi # Trap any pattern-attempt in output-mode: if [ -n "$PAT" -a "$CPOPT" = $OPARG ] then echo "$E p option not allowed on output" >&2; exit 2 fi TPHDR="This is the log file for the cmnd: $USRCMD Archives are recorded as pairs - index file, then the actual tree, with name(s) of the form 'tape_XXX_DIR_list.(date-stamp)' where XXX = tape no., and DIR = name of the selected tree node (eg: home). Titles/files may be read back using this script (or via the appropriate mt and cpio commands). " ECPIO="---------------- end cpio record ------------------" if [ "$CPOPT" = $OPARG ] #If we're outputting to tape .... then if [ ! -d $L_DIR ] then echo "Dir $L_DIR not found - stopping." >&2 exit 1 fi cd $L_DIR if [ $? != 0 ] then echo "Could not change to $L_DIR)" >&2 exit 1 fi if [ $DOM = n ] then # Number this (full) backup type starting from 0100: if [ ! -f [1-9]* ]; then LAST_NUM=099; fi TAPE_NUM=`expr $LAST_NUM + 1` else TAPE_NUM="`date +0%d`" # 001-031 inclusive if a "daily". fi date >>$LOG echo "$TPHDR" >>$LOG if [ $NORWD = "n" ] then echo "Re-winding tape at $DEV" >>$LOG mt -f $DEV rewind 2>>$LOG fi export DIR LFLS= for DIR in $DIRS do if [ "$DIR" = "." ] then DIR=$USERDIR fi if [ $DSPL = y ] then echo "DIR=$DIR" >&2 fi BASE="`basename $DIR`" if [ $BASE = "/" ]; then BASE=root; fi DATE_STAMP=`date '+%Y%m%d%H%M%S'` L_NAME="${TN1}${TAPE_NUM}_${BASE}_list.$DATE_STAMP" if [ $DELTA = y ] then L_NAME="${TN2}${DAYS}day_$L_NAME" fi LISTFILE="$L_DIR/$L_NAME" LFLS="$LFLS $LISTFILE" # Verify existence of selected dir and go there: if [ -d "$DIR" ] then cd $DIR >>$LOG 2>>$LOG rm -rf $TAPED date > $LISTFILE echo "Tree = `pwd`\n" >> $LISTFILE if [ -n "$EXC_FILE" -a -r "$EXC_FILE" ] then # Following sed piping definition is required because # the output of find is in dot-relative form but the format # of an EXC_FILE must contain full pathnames, so we need to # convert the find output into full form so sed can get a match # to remove the requested lines. We then convert that result # back to the original dot-rel form for input to cpio. Sounds # messy, I know, but until I can see a more elegant way ... SED="| sed \"s@^\\.@$DIR@\" | sed -f $EXC_FILE | sed s\"@$DIR@.@\"" fi CMD="find . $FOPT -depth -print $SED >> $LISTFILE" if [ $DSPL = y ] then echo "find command is: $CMD" >&2 fi eval $CMD cd $L_DIR >>$LOG 2>>$LOG echo "Copying the INDEX file $L_NAME to tape:" >>$LOG # Kludge here - add -C 10240 for cpio blocking size: echo $L_NAME | cpio $OPARG -C 10240 > $DEV 2>>$LOG echo "$ECPIO" >>$LOG cd $DIR >>$LOG 2>>$LOG CMD="awk 'NR > 3 {print}' $LISTFILE | cpio $OPARG 2>>$LOG | dd bs=96b of=$DEV 2>>$LOG" echo "Copying $DIR TREE to tape at $DEV:" >>$LOG # ; CMD = $CMD" if [ $DSPL = y ] then echo "cpio command is: $CMD" >&2 fi TAPERS=n eval $CMD if [ $? != 0 ] then TAPERS=y fi echo "$ECPIO" >>$LOG if [ $? = 0 ] then touch $TAPED fi else echo "$DIR not found" >>$LOG exit 1 fi done # Clean up files and mail results: if [ $NORWD = "n" -o $CLOSE = "y" ] then mt -f $DEV rewind cd $L_DIR NF=`cat $LOG | wc -l | sed s'/ *//'` DATE="`date`" MB=`grep '^[0-9][0-9]* blocks' ${LOG} |awk '{x=x+$1}END{print x/2000}'` if [ $CLOSE = y ] then # With the -n/-c args, can't be sure how many just here, so .... S1="Data" else S1="Approx $NF files ($MB Mb)" fi define_S2 echo "$S1 $S2" > $MSG eval $BUF < $DEV | cpio -it >>$MSG 2>/dev/null mt -f $DEV rewind 2>/dev/null mv $LOG $TAPE_NUM S2="The log file ${TAPE_NUM} in $L_DIR has full list." echo "$S2" >> $MSG IDIR=`basename $L_DIR` # This conditional a bit of a kludge! if [ "$IDIR" = offsite ] then SS="Tape backup (for Clayton box) OK" else SS="Tape backup OK" fi $MAILER -s "$SS" $USER < $MSG clean fi if [ $TAPERS = y ] then exit 1 else exit 0 fi fi if [ "$CPOPT" = $IPARG ] #If we're reading in from tape to disc ... then cd $USERDIR echo "Following files read from current position on tape:" >&2 CMD="$BUF < $DEV $ZCAT | cpio $IPARG $HOPT" if [ -n "$PAT" ] then CMD="$CMD $PAT" fi if [ $DSPL = y ] then echo "Full command is: $CMD" >&2 fi eval $CMD fi