#! /bin/sh # This is a shell archive, meaning: # 1. Remove everything above the #! /bin/sh line. # 2. Save the resulting text in a file. # 3. Execute the file with /bin/sh (not csh) to create the files: # README # Makefile # copytape.c # copytape.1 # copytape.5 # copytape.c.dist # cptape # This archive created: Mon Feb 24 19:21:59 1997 # By: Daniel E. Singer (des@cs.duke.edu) # Duke University CS Dept. export PATH; PATH=/bin:$PATH echo shar: extracting "'README'" '(988 characters)' if test -f 'README' then echo shar: will not over-write existing file "'README'" else sed 's/^ X//' << \SHAR_EOF > 'README' X X'copytape' is a program for copying tapes on UNIX systems. X XI originally downloaded it from "ftp.cs.uni-sb.de". Since then I Xhave rewritten large portions and added some features. X X'copytape' can copy between drives on one machine, between drives on Xmachines connected by a network (using 'rsh'), can change the blocking Xused in the copy, can store tape files to disk with imbedded blocking Xdata, give realtime feedback as the copy progresses, and provide online Xhelp ("copytape -help"). X XIt has been used on SunOS 4.1.3 and Solaris 2.5 with Exabyte 8mm tape Xdrives. On Solaris, it can copy files and tapes larger than 2GB. X XFiles included with this distribution: X X.. README - this file X.. Makefile - the compile makefile X.. copytape.c - the source X.. cptape - shell script wrapper for copytape X X.. copytape.1 - original man page; I haven't updated it X.. copytape.5 - original man page; I haven't updated it X.. copytape.c.dist - original source X X-Daniel Singer, des@cs.duke.edu, 2/97 X SHAR_EOF if test 988 -ne "`wc -c < 'README'`" then echo shar: error transmitting "'README'" '(should have been 988 characters)' fi fi # end of overwriting check echo shar: extracting "'Makefile'" '(1686 characters)' if test -f 'Makefile' then echo shar: will not over-write existing file "'Makefile'" else sed 's/^ X//' << \SHAR_EOF > 'Makefile' X# X# makefile for 'copytape' X# hacked by des X# X# NOTE: this will probably need more hacking for OS's other than SunOS 4.1.3 X# and SunOS 5.5+ (and maybe even them) X# X XPROG = copytape X XSHARE_FILE_NAME = ${PROG}.share XSHARE_FILE_NAME_ZIPPED = ${SHARE_FILE_NAME}.gz XSHARE_FILES = README Makefile ${PROG}.c ${PROG}.1 ${PROG}.5 ${PROG}.c.dist cptape X XMAN1 = /usr/man/man1 XMAN5 = /usr/man/man5 X#BIN = /usr/local/bin X X# construct install dir as ~des/bin/sun4-4 or ~des/bin/sun4-5 XARCH :sh = arch XOS :sh = uname -r | sed 's/[^0-9].*//' XBIN = /u/des/bin/${ARCH}-${OS} X X# for Solaris, need to pass "-DSVR4" to CC XSYSV_FLAG :sh = test "`uname -r | sed 's/[^0-9].*//'`" = "5" && echo "-DSVR4" || echo "" X X# use SunPro compiler if available X#_CC = cc X_CC :sh = test "`uname -r | sed 's/[^0-9].*//'`" = "5" && echo "/usr/pkg/SUNWspro/bin/cc" || echo "/usr/lang/SC3.0.1/bin/acc" X X# need different flag for SunOS and Solaris install XINST_UFLAG :sh = test "`uname -r | sed 's/[^0-9].*//'`" = "5" && echo "-u" || echo "-o" X X#CFLAGS = -O -DSVR4 # for optimizing X##CFLAGS = -g -DSVR4 # for debugging XCFLAGS = -O ${SYSV_FLAG} # for optimizing X#CFLAGS = -g ${SYSV_FLAG} # for debugging X#CC = cc ${CFLAGS} XCC = ${_CC} ${CFLAGS} X X${PROG}: ${PROG}.c X ${CC} -o ${PROG} ${PROG}.c X Xinstall: ${PROG} X install -m 0750 ${INST_UFLAG} operator -g operator ${PROG} ${BIN} X Xman: man1 man5 X Xman1: ${MAN1}/${PROG}.1 X cp ${PROG}.1 ${MAN1} X Xman5: ${MAN5}/${PROG}.5 X cp ${PROG}.5 ${MAN5} X Xshare: ${SHARE_FILE_NAME_ZIPPED} X X${SHARE_FILE_NAME_ZIPPED}: ${SHARE_FILES} X /usr/bin/rm -f ${SHARE_FILE_NAME} ${SHARE_FILE_NAME_ZIPPED} X /usr/local/bin/shar -a ${SHARE_FILES} > ${SHARE_FILE_NAME} X /usr/local/bin/gzip ${SHARE_FILE_NAME} SHAR_EOF if test 1686 -ne "`wc -c < 'Makefile'`" then echo shar: error transmitting "'Makefile'" '(should have been 1686 characters)' fi fi # end of overwriting check echo shar: extracting "'copytape.c'" '(51184 characters)' if test -f 'copytape.c' then echo shar: will not over-write existing file "'copytape.c'" else sed 's/^ X//' << \SHAR_EOF > 'copytape.c' X/* X * COPYTAPE.C X * X * This program duplicates magnetic tapes, preserving the X * blocking structure and placement of tape marks. X * X * For command usage, see 'do_help()', below. X * X * X * NOTE: this version has been extensively hacked, and is only X * tested for the following environment: X * Exabyte 8500/8505 8mm tape drive w/ SCSI interface X * SunOS 4.1.x (Solaris 1.1) X * SunOS 5.5.x (Solaris 2.5.x) X * X * NOTE: this still needs more reliable checking for end-of-media X * as well as better diagnostics for read and write errors; X * X * NOTE: this program can handle 64-bit file sizes, though this must X * have OS and C compiler support; see 'fsize_t' below; X * further adjustments will probably need to be made for a true X * 64-bit machine (eg, UltraSparc, Solaris 2.6); X * X * NOTE: this program assumes 32-bit (or larger) integers; on X * a 16-bit machine, some declarations might need to be changed X * to long; X * X * NOTE: command line options must be separate, ie, not run together; X * X * X * TAPE DATA TRANSFER METHOD & FORMAT: X * X * The tape copying is done be reading from the tape block- X * by-block into a large buffer, using the blocks read as the X * determining factor for file block size. If output is X * directly to another tape, these blocks are then just X * written out, unless biased blocking (-b option) is used, X * in which case the specified output block size will be used. X * X * If output is to a file or pipe, then special formatting X * headers and markers are added to the output stream, so that X * the desired tape/file/block format can be recreated when X * the data is eventually transferred to another tape. X * X * When this program reads data from a file or pipe, this X * formatting information is expected as part of the input. X * Incorrect format will cause an error message and program X * termination. X * X * The format used for tape data in its file or pipe intermediate X * state is as follows, where each block is preceded by a block X * size, each file is followed by "MRK", and the tape is followed X * by "EOT": X * X * CPTP:BLK 999999\n X * [raw data block0]\n X * CPTP:BLK 999999\n X * [raw data block1]\n X * ... X * MRK\n X * CPTP:BLK 999999\n X * ... X * MRK\n X * ... X * MRK\n X * EOT\n X * X * X * MODIFICATION HISTORY: X * X * Jan 1997, D.Singer, Duke Univ. Computer Science: X * added support for 64-bit (long long) file and tape sizes; X * X * Jul 1995, D.Singer, Duke Univ. Computer Science: X * changed report file numbering to 0..n-1; X * added factor option to -vb; X * added retry on read; X * X * May 1995, D.Singer, Duke Univ. Computer Science: X * added -m flag; X * improved program name handling, and tape in & out reporting; X * X * May 1995, D.Singer, Duke Univ. Computer Science: X * more of the same (see 4/95); X * added file_io and data_buf types; X * Note: the -o (old format) option has been removed. X * X * Apr 1995, D.Singer, Duke Univ. Computer Science: X * added some debugging and feedback; X * improved recognition of end-of-tape for Exabyte 8mm; X * lots of reformatting and some reorganizing; X * X * Sep 1988 entered changes of D.Hayes' last posting in v10i099 X * flag -o for old copytape (64k) format added [bs] X * X * Aug 1988 biased blocking inserted [bs] X * io_write now controls write-operations X * X * Nov 1987 now stdin in first I/O position allowed [bs] X * io_read function added to maintain pipe-reading X * X * Jul 1986 David S. Hayes X * Made data file format human-readable. X * X * Apr 1985 David S. Hayes X * Original Version. X */ X X/* SCCS info */ Xstatic char sccsid[] = "%W%\t%G%\t%P%\tdes@cs.duke.edu"; X X#include /* for mtio.h and disk addr size */ X#include /* for ioctl() */ X#include /* for magtape ops */ X#include /* for open(), close() */ X#include /* for open(), close() */ X#include /* for fstat() */ X#include /* for everything */ X X X/*#define SMTIO /* status from exebyte_toc package, might not work... */ X X#ifdef SMTIO X# include "smtio.h" X#endif SMTIO X X/* X * NOTE: disk address types are signed quantities, but for our purposes, X * unsigned will work better; X */ X#if __STDC__ - 0 == 0 && !defined(_NO_LONGLONG) X# define fsize_t unsigned long long X# define ffsize_t long double X# define LL /* use long long */ X#else X# define fsize_t unsigned long X# define ffsize_t double X# undef LL /* don't use long long */ X#endif X#define uint unsigned int X X#define MAX_REC_SIZE (256*1024) /* max tape block size */ X#define MEGABYTE (1024*1024) /* megabyte size */ X#define B_SHIFT (9) /* number to shift to divide by 512 */ X#define K_SHIFT (10) /* number to shift to divide by 1024 */ X#define MAIN_BUF_SIZE (2*MAX_REC_SIZE) /* maximal limit for biased blocking */ X#define MAX_FILES (1000) /* size for file arrays */ X#define PNAME_MAX_LEN (50) /* progname max len */ X#define HEAD_BUF_SIZE (101) /* size of rec header buffer */ X /* this doesn't seem to do any good, so don't bother */ X#define NUM_READ_RETRY (0) /* number of times to retry tape read; X * this doesn't really work, so it's X * to zero */ X X/* flags for cprint() */ X#define CP_REG (000) /* regular, no special processing */ X#define CP_NONL (001) /* treat as not printing a new line */ X#define CP_PERROR (002) /* send thru perror() */ X X/* flags for read and write statuses, FILE_IO.io_stat */ X#define FIO_OK (000) /* regular data, no problems */ X#define FIO_EOF (001) /* end of file */ X#define FIO_EOT (002) /* end of tape */ X#define FIO_ERROR (004) /* error encountered */ X#define FIO_FMT_ERR (010) /* format error encountered */ X X/* flags for reading and writing data, input_rec(), output_rec() */ X#define RW_REG (000) /* regular, nothing special */ X#define RW_BLOCK (001) /* try to read a "whole" record */ X#define RW_NO_AD (002) /* no advance, ie, don't move pointer */ X X/* tokens and formats used in reading/writing record/file headers/markers */ X#define CT_BLK_DIGITS (6) /* number of digits for CPTP:BLK num */ X#define CT_PREFIX "CPTP:" /* introduces each header/marker */ X#define CT_BLOCK "BLK " /* indicates block */ X#define CT_END_FILE "MRK\n" /* indicates tape mark */ X#define CT_END_TAPE "EOT\n" /* indicates end of tape */ X#define CT_END_BLK_HDR "\n" /* string after data block header */ X#define CT_END_BLK "\n" /* string after data block */ X#define CT_BLK_FMT "%s%s%0*d%s" /* format of block header */ X#define CT_MARKER_LEN (4) /* len of each of the marker strings */ X /* make block string for writing, supply the string and block length */ X#define CT_MAKE_BLK_HDR(S,L) \ X sprintf(S, CT_BLK_FMT, CT_PREFIX, CT_BLOCK, CT_BLK_DIGITS, L, CT_END_BLK_HDR) X#define SZ(S) (sizeof(S)-1) /* for string sizes */ X#define CT_SZ_BLK_HDR (SZ(CT_PREFIX)+SZ(CT_BLOCK)+CT_BLK_DIGITS+SZ(CT_END_BLK_HDR)) X X X/* X * command line options X */ Xint X debug = 0, /* some additional info */ X help = 0, /* print help message and exit */ X magtape = 0, /* output is 1/2 in. magtape */ X report = 0, /* print summary report at end */ X verbose = 0, /* tell what we're up to */ X v_blocks = 0, /* report on blocks as read or written */ X vb_mult = 1, /* report on blocks/vb_mult blocks */ X fromtape = 0, /* treat source as a tape drive */ X totape = 0, /* treat destination as a tape drive */ X from_arg = 0, /* input source given */ X to_arg = 0; /* output source given */ X X/* X * command line option related, eg, args X */ Xuint X skip = 0, /* number of files to skip before copying */ X outblock = 0, /* output blocking factor */ X limit = 0xffffffff; /* allows many, many files */ Xint X i_and_o = 0; /* input and output are to tape */ X X X/* X * buffers and structure to manage data to/from tapes/files/pipes X */ X Xchar X io_buffer[MAIN_BUF_SIZE], /* main i/o buffer */ X head_buffer[HEAD_BUF_SIZE]; /* buffer for record headers, etc */ X X/* used with io_read(), io_write() */ Xstruct data_buf { X char X *db_min, /* the start of the buffer */ X *db_max, /* the end of the buffer (+1) */ X *db_bod, /* the beginning of the data */ X *db_eod; /* the end of the data (+1) */ X }; Xtypedef struct data_buf DATA_BUF; X XDATA_BUF X /* tape rec data buffer */ X io_buf = { X io_buffer, X io_buffer+sizeof(io_buffer), X io_buffer, X io_buffer X }, X /* header info */ X head_buf = { X head_buffer, X head_buffer+sizeof(head_buffer), X head_buffer, X head_buffer X }; X X X/* X * info for each I/O stream X */ Xstruct file_io { X char X *io_filename, /* file name */ X *io_ftype; /* file type */ X int X io_fd; /* file descriptor */ X uint X io_stat, /* io status */ X io_errno, /* io error */ X io_maybetape, /* maybe is a tape drive */ X io_reallytape, /* really seems to be a tape drive */ X io_file_num, /* current file number */ X io_block_size, /* block size of current file, assumes fixed */ X io_block_sizes[MAX_FILES]; /* block size of each file, or at least X * the size of the first block */ X fsize_t X io_block_num, /* current file block number */ X io_file_size, /* current file bytes read */ X io_total_size, /* total bytes read */ X io_total_blks, /* total blocks read */ X io_file_sizes[MAX_FILES], /* size of each file */ X io_block_nums[MAX_FILES]; /* number of blocks in each file */ X }; Xtypedef struct file_io FILE_IO; X XFILE_IO X /* input stream */ X io_in = { X "stdin", X "", X 0, X FIO_OK, X 0, X 0, X 0, X 0, X 0, X 0, X 0, X 0, X 0, X { 0 }, X { 0 }, X { 0 } X }, X /* output stream */ X io_out = { X "stdout", X "", X 1, X FIO_OK, X 0, X 0, X 0, X 0, X 0, X 0, X 0, X 0, X 0, X { 0 }, X { 0 }, X { 0 } X }; X X Xchar X msgbuf[1001], /* status message buffer */ X *fmt_msg, /* string to identify format error area */ X /*fmt_buf[201], /* string to hold printf format */ X prog[PNAME_MAX_LEN+1], /* program name for status messages; X * 1 byte at the end is for the null; X * 4 bytes are so an indicator can be appended, X * for input or output */ X prog_in[PNAME_MAX_LEN+5], /* indicates input from tape */ X prog_out[PNAME_MAX_LEN+5], /* indicates output to tape */ X *p_io = prog, X X *get_ftype(), /* assign a file type string */ X X usage[] = "\ XUsage: %s [-d] [-f] [-m] [-r] [-t] [-v] [-vb[#]] [-help] [-bnn] [-lnn] \\\n\ X [-snn] |- [|-]\n"; X Xextern struct X#ifdef SMTIO X smt_stat *mt_status(); X#else X mtget *mt_status(); X#endif SMTIO X Xstruct mtget X mt_statbuf; /* for use in detecting tape device */ X Xstruct mtop X mt_opbuf; /* structure for tape operations */ X Xchar X *basename(); X Xextern int X errno; X X X/** X ** primary steps in main are: X ** X ** - process command line options; X ** X ** - open input and output; X ** X ** - do the skip (-s) option, if specified; X ** X ** - copy tape files; X ** X ** - do the report (-r) option, if specified; X **/ X Xmain(argc, argv) Xint X argc; Xchar X *argv[]; X{ X int X ac; /* arg count */ X X /* get the program name, but in a way that we can append to it */ X strncpy(prog,basename(argv[0]),PNAME_MAX_LEN); X prog[PNAME_MAX_LEN] = '\0'; X strcat(strcpy(prog_in,prog),"-in"); X strcat(strcpy(prog_out,prog),"-out"); X/* X#ifdef LL Xfprintf(stderr,"LL defined\n"); X{ X fsize_t xx = 3; X fprintf(stderr,"xx size = %d, xx = %llu\n",sizeof xx,xx); X fprintf(stderr,"xx size = %d, xx = %5.3Lf\n",sizeof xx,(ffsize_t)xx); X} X#else Xfprintf(stderr,"LL NOT defined\n"); X#endif Xexit(0); X*/ X X /* process command line options */ X for (ac=1; ac < argc && argv[ac][0] == '-' && argv[ac][1]; ++ac) { X X switch (argv[ac][1]) { X X /* skip option */ X case 's': X if ( !check_num_arg(&argv[ac][2],&skip) || skip < 0 ) { X sprintf(msgbuf, "%s: bad skip value\n", prog); X cprint(CP_REG, msgbuf); X exit(-1); X } X break; X X /* limit option */ X case 'l': X if ( !check_num_arg(&argv[ac][2],&limit) || limit < 0 ) { X sprintf(msgbuf, "%s: bad limit value\n", prog); X cprint(CP_REG, msgbuf); X exit(-1); X } X break; X X /* output block size */ X case 'b': X if ( !check_num_arg(&argv[ac][2],&outblock) || outblock <= 0 ) { X sprintf(msgbuf, "%s: bad blocksize value\n", prog); X cprint(CP_REG, msgbuf); X exit(-1); X } X outblock <<= B_SHIFT; /* calculating tape blocks */ X if (outblock > MAX_REC_SIZE) { X sprintf(msgbuf, X "%s: blocksize too big, max = %d\n", X prog, MAX_REC_SIZE>>B_SHIFT); X cprint(CP_REG, msgbuf); X exit(-1); X } X break; X X /* debug option */ X case 'd': X debug = 1; X verbose = 1; X break; X X /* from tape option */ X case 'f': X fromtape = 1; X break; X X /* print help option */ X case 'h': X help = 1; X do_help(); X exit(0); X X /* magtape option */ X case 'm': X magtape = 1; X break; X X /* report option */ X case 'r': X report = 1; X break; X X /* to tape option */ X case 't': X totape = 1; X break; X X /* verbose option */ X case 'v': X verbose = 1; X if (argv[ac][2] == 'b') { X v_blocks = 1; X if ( argv[ac][3] != '\0' && X (!check_num_arg(&argv[ac][3],&vb_mult) || vb_mult < 1) ) { X sprintf(msgbuf, "%s: bad block factor value\n", prog); X cprint(CP_REG, msgbuf); X exit(-1); X } X } X break; X X /* Oops! */ X default: X sprintf(msgbuf, "%s: unknown option \"%s\"\n", X prog, argv[ac]); X cprint(CP_REG, msgbuf); X sprintf(msgbuf, usage, prog); X cprint(CP_REG, msgbuf); X exit(-1); X } X } X X /* see if is input arg; none or "-" is stdin */ X if (ac < argc) { X if (strcmp(argv[ac], "-") != 0) { X io_in.io_filename = argv[ac]; X from_arg = 1; X } X ++ac; X } X X /* see if is output arg; none or "-" is stdin */ X if (ac < argc) { X if (strcmp(argv[ac], "-") != 0) { X io_out.io_filename = argv[ac]; X to_arg = 1; X } X ++ac; X } X X if (ac < argc) { X sprintf(msgbuf, "%s: too many arguments\n", prog); X cprint(CP_REG, msgbuf); X sprintf(msgbuf, usage, prog); X cprint(CP_REG, msgbuf); X exit(-1); X } X X /* print a blank line */ X sprintf(msgbuf, "\n", prog); X cprint(CP_REG, msgbuf); X X /* open input */ X if (from_arg) { X io_in.io_fd = open(io_in.io_filename, O_RDONLY); X if (io_in.io_fd == -1) { X sprintf(msgbuf, X "%s: input open of \"%s\" failed", X prog, io_in.io_filename); X cprint(CP_PERROR, msgbuf); X exit(-1); X } X if (verbose) { X sprintf(msgbuf, "%s: OPENED INPUT %s\n", X prog, io_in.io_filename); X cprint(CP_REG, msgbuf); X } X } X io_in.io_ftype = get_ftype(io_in.io_fd); X X /* open output */ X if (to_arg) { X io_out.io_fd = open(io_out.io_filename, X O_WRONLY | O_CREAT | O_TRUNC, 0600); X if (io_out.io_fd == -1) { X sprintf(msgbuf, "%s: output open to \"%s\" failed", X prog, io_out.io_filename); X cprint(CP_PERROR, msgbuf); X exit(-1); X } X if (verbose) { X sprintf(msgbuf, "%s: OPENED OUTPUT %s\n", X prog, io_out.io_filename); X cprint(CP_REG, msgbuf); X } X } X io_out.io_ftype = get_ftype(io_out.io_fd); X X /* X * Determine if source and/or destination is a tape device. Try to X * issue a magtape ioctl to it. If it doesn't error, then it is a X * magtape, no? X */ X errno = 0; X ioctl(io_in.io_fd, MTIOCGET, &mt_statbuf); X io_in.io_reallytape = errno == 0; X io_in.io_maybetape = fromtape || io_in.io_reallytape; X X errno = 0; X ioctl(io_out.io_fd, MTIOCGET, &mt_statbuf); X io_out.io_reallytape = errno == 0; X io_out.io_maybetape = totape || io_out.io_reallytape; X X errno = 0; X X /* X * append "-io", "-in", or "-out" to prog for messages; X * this is to help distinguish messages; X */ X if (io_in.io_maybetape && io_out.io_maybetape) { X i_and_o = 1; X } X else if (io_in.io_maybetape) { X p_io = prog_in; X } X else if (io_out.io_maybetape) { X p_io = prog_out; X } X X if (verbose) { X sprintf(msgbuf, "%s: from %s (%s) to %s (%s)\n", X p_io, io_in.io_filename, X /*io_in.io_maybetape ? "tape" : "data",*/ X io_in.io_maybetape ? "tape" : io_in.io_ftype, X io_out.io_filename, X /*io_out.io_maybetape ? "tape" : "data");*/ X io_out.io_maybetape ? "tape" : io_out.io_ftype); X cprint(CP_REG, msgbuf); X } X X if (debug) { X sprintf(msgbuf, "%s: SKIP = %d, LIMIT = %d\n", X prog, skip, limit); X cprint(CP_REG, msgbuf); X } X X if (debug) { X if (io_in.io_reallytape) X pr_mt_status(mt_status(io_in.io_fd)); X if (io_out.io_reallytape) X pr_mt_status(mt_status(io_out.io_fd)); X } X X X if (skip) X do_skip(skip,&io_in,&io_buf); X X X if (io_in.io_stat == FIO_OK) X do_copy(); X X X if (report) X do_report(); X X X if (verbose) { X sprintf(msgbuf, "%s: EXITING\n", p_io); X cprint(CP_REG, msgbuf); X } X X exit(0); X} X X X/* X * print help message X */ Xdo_help() X{ X char X buf[10001], X *m; X X m = "\ X ---------------------------\n\ X'%s' is a program to copy a tape either to/from a file,\n\ Xor alternatively, to copy a tape between hosts. (To copy a tape\n\ Xbetween drives on the same host, 'tcopy' is also available.)\n\ X\n\ XIf sending data to a pipeline or using a disk file for intermediate\n\ Xstorage, %s imbeds information indicating block sizes and boundaries\n\ Xand file boundaries; these are later used to recreate the tape.\n\ X\n\ XAdditionally, %s can change the block size via the -b option, and\n\ Xskip and limit files copied via -s and -l.\n\ X\n\ XTo copy a tape between hosts, some form of pipeline and remote\n\ Xshell needs to be used. For example:\n\ X\n\ X%s -vb -r /dev/rst0 | rsh mambo \"%s -v -r - /dev/rst8\"\n\ X\n"; X X sprintf(buf, m, prog, prog, prog, prog, prog); X cprint(CP_REG, buf); X X sprintf(buf, usage, prog); X cprint(CP_REG, buf); X X m = "\ X\n\ XOptions:\n\ X -d debug, supplies some additional status info;\n\ X implies -v;\n\ X -f from, act like reading from a tape;\n\ X -m magtape, add extra tape mark for 1/2 inch magtape;\n\ X -r report, prints a summary at the end;\n\ X -t to, act like writing to a tape;\n\ X -v verbose, lots of status info;\n\ X -vb[#] verbose-block, prints info for each data block;\n\ X if # (a number) is included, reduce reporting by blocks/#;\n\ X implies -v;\n\ X -help print this help message and exit;\n\ X -lnn limit, stop copying after file nn;\n\ X -snn skip, skip the first nn input files;\n\ X -bnn block, output blocks are nn 512 byte blocks;\n\ X input source, a file, tapedrive, etc; '-' for stdin;\n\ X output dest, a file, tapedrive, etc; '-' for stdout;\n\ X\n\ XNumeric arguments can be given in decimal, octal, or hexidecimal.\n\ X ---------------------------\n\ X"; X X sprintf(buf, m); X cprint(CP_REG, buf); X} X X X/* X * Skip number of files in input, specified by -snnn on the command line. X */ Xdo_skip(num_to_skip,iop,dbp) Xint X num_to_skip; XFILE_IO X *iop; XDATA_BUF X *dbp; X{ X int X len; X X if (num_to_skip <= 0) X return; X X if (verbose) { X sprintf(msgbuf, "%s: SKIPPING %d INPUT FILE%s\n", p_io, num_to_skip, num_to_skip == 1 ? "" : "S"); X cprint(CP_REG, msgbuf); X } X X mt_opbuf.mt_op = MTFSF; /* operation */ X mt_opbuf.mt_count = (daddr_t)1; /* number of operations */ X X for ( ; iop->io_file_num < num_to_skip; ++iop->io_file_num) { X X /* if this is a tape, save some time by using ioctl() */ X X if (iop->io_reallytape) { X errno = 0; X if (ioctl(iop->io_fd, MTIOCTOP, &mt_opbuf) == -1) { X sprintf(msgbuf, X "%s: ioctl call failed skipping file %d", X p_io, iop->io_file_num); X cprint(CP_REG, msgbuf); X sprintf(msgbuf, "%s: errno=%d", p_io, errno); X cprint(CP_PERROR, msgbuf); X X iop->io_stat = FIO_ERROR; X iop->io_errno = errno; X return; X } X } X X /* otherwise, probably reading from a file or pipe */ X X else { X /* read to end of file */ X while (input_rec(iop,dbp) > 0 && X iop->io_stat == FIO_OK) { X /* X * reset the buffer after each record; X * this is normally done when writing, X * but that's not done here. X */ X dbp->db_bod = dbp->db_eod = dbp->db_min; X } X X if (iop->io_stat & (FIO_ERROR | FIO_FMT_ERR)) { X input_error(iop,dbp); X return; X } X if (iop->io_stat & FIO_EOT) { X sprintf(msgbuf, "%s: only %d files in input\n", X p_io, iop->io_file_num); X cprint(CP_REG, msgbuf); X return; X } X X /* otherwise, assuming FIO_EOF */ X iop->io_stat = FIO_OK; X } X X if (verbose) { X sprintf(msgbuf, "%s: SKIPPED FILE %d\n", X p_io, iop->io_file_num); X cprint(CP_REG, msgbuf); X } X } X} X X X/* X * do the copy: X * X * read and write records until the appropriate number of files X * are copied. X * X * each read produces one of: X * - data block X * - EOF X * - EOT X * - error X */ Xdo_copy() X{ X int X len = 0, /* number of bytes in record */ X bytes_in_buf = 0; /* holds num bytes in data buffer */ X X /* loop-de-loop */ X for ( ; ; ) { X X /* X * if max files copied, simulate EOT; X * else read the next block; X */ X if (io_in.io_file_num >= limit) { X /* simulate an end-of-tape */ X io_in.io_stat = FIO_EOT; X if (verbose) { X sprintf(msgbuf, "%s: LIMIT REACHED\n", p_io); X cprint(CP_REG, msgbuf); X } X } X else { X /* X * read in a block; X * at EOF, len will be zero; X */ X len = input_rec(&io_in,&io_buf); X } X X /* record the block size */ X if (len > 0 && io_in.io_block_sizes[io_in.io_file_num] == 0) { X io_in.io_block_sizes[io_in.io_file_num] = len; X io_out.io_block_sizes[io_out.io_file_num] = X outblock ? outblock : len; X } X X /* error? */ X if (io_in.io_stat & (FIO_ERROR | FIO_FMT_ERR)) { X input_error(&io_in,&io_buf); X return; X } X X /* a regular data block to copy? */ X if (io_in.io_stat == FIO_OK) { X X if (v_blocks && io_in.io_maybetape) X eor_msg(&io_in,1,len); X X /* X * 1:1 or biased blocking? X */ X X /* input block size == output size block */ X if (outblock == 0) { X if (output_rec(&io_out,&io_buf,FIO_OK,len) X != len) { X output_error(&io_out,&io_buf); X return; X } X if (v_blocks && io_out.io_maybetape) { X if (!i_and_o) X eor_msg(&io_out,0,len); X } X X /* be safe and make sure all X * the buffer pointers are reset */ X io_buf.db_bod = io_buf.db_eod = io_buf.db_min; X X continue; X } X X /* X * "biased" blocking: X * input block size != output block size X */ X X /* this should never happen... */ X if (io_buf.db_eod > io_buf.db_max) { X sprintf(msgbuf, X "%s: MAIN_BUF_SIZE exceeded. Blocking?!\n", p_io); X cprint(CP_REG, msgbuf); X return; X } X X bytes_in_buf = io_buf.db_eod - io_buf.db_bod; X X /* only do if at least one whole output block X * in buffer */ X while (bytes_in_buf >= outblock) { X if (output_rec(&io_out,&io_buf,FIO_OK,outblock) X != outblock) { X output_error(&io_out,&io_buf); X return; X } X if (v_blocks && io_out.io_maybetape) { X if (!i_and_o) X eor_msg(&io_out,0,outblock); X } X bytes_in_buf -= outblock; X } X X /* shift any data remaining to the beginning X * of the buffer */ X if (bytes_in_buf > 0) { X if (io_buf.db_bod != io_buf.db_min) { X bcopy(io_buf.db_bod, io_buf.db_min, bytes_in_buf); X io_buf.db_bod = io_buf.db_min; X io_buf.db_eod = io_buf.db_min + bytes_in_buf; X } X } X else X /* otherwise, be safe and make sure all X * the buffer pointers are reset */ X io_buf.db_bod = io_buf.db_eod = io_buf.db_min; X X continue; X } X X /* X * End Of File: X * - update input info; X * - output any remaining bytes (biased blocking); X * - write EOF; X * - update output info; X * - check for too many files for arrays; X */ X if (io_in.io_stat & FIO_EOF) { X X /* update file info for input rec */ X io_in.io_file_sizes[io_in.io_file_num] = X io_in.io_file_size; X io_in.io_block_nums[io_in.io_file_num] = X io_in.io_block_num; X X if (verbose) { X if (io_in.io_maybetape) X eof_msg(&io_in,1); X } X if (debug) { X if (io_in.io_reallytape) X pr_mt_status(mt_status(io_in.io_fd)); X } X X X /* update file info for input rec */ X io_in.io_file_size = 0; X io_in.io_block_num = 0; X ++io_in.io_file_num; X X /* check for remaining bytes for biased blocking */ X bytes_in_buf = io_buf.db_eod - io_buf.db_bod; X if (bytes_in_buf > 0) { X if (output_rec(&io_out,&io_buf,FIO_OK,bytes_in_buf) X != bytes_in_buf) { X output_error(&io_out,&io_buf); X return; X } X if (v_blocks && io_out.io_maybetape) { X if (!i_and_o) X eor_msg(&io_out,0,bytes_in_buf); X } X if (verbose) { X sprintf(msgbuf, X "%s: wrote %d byte trailer\n", X p_io, bytes_in_buf); X cprint(CP_REG, msgbuf); X } X } X X /* write the eof tape mark */ X output_rec(&io_out,&io_buf,FIO_EOF,0); X if (io_out.io_stat == FIO_ERROR) { X output_error(&io_out,&io_buf); X return; X } X X /* update file info for output rec */ X io_out.io_file_sizes[io_out.io_file_num] = X io_out.io_file_size; X io_out.io_block_nums[io_out.io_file_num] = X io_out.io_block_num; X X if (verbose) { X if (io_out.io_maybetape) X eof_msg(&io_out,0); X } X if (debug) { X if (io_out.io_reallytape) X pr_mt_status(mt_status(io_out.io_fd)); X } X X /* update file info for output rec */ X io_out.io_file_size = 0; X io_out.io_block_num = 0; X ++io_out.io_file_num; X X /* X * check for too many files for the arrays X */ X if (io_in.io_file_num >= MAX_FILES) { X sprintf(msgbuf, X "%s: NUM FILES EXCEEDS LIMIT\n", p_io); X cprint(CP_REG, msgbuf); X sprintf(msgbuf, X "%s: time to increase MAX_FILES and recompile!\n", p_io); X cprint(CP_REG, msgbuf); X X /* SHOULD FIX HERE TO SIMULATE END OF TAPE */ X X return; X } X X continue; X } X X /* X * End Of Tape: X * output EOT mark; X */ X if (io_in.io_stat & FIO_EOT) { X X /* see 1/2 in. tape Note in output_rec() */ X output_rec(&io_out,&io_buf,FIO_EOT,0); X X if (io_out.io_stat == FIO_ERROR) { X output_error(&io_out,&io_buf); X return; X } X if (verbose) { X /*sprintf(msgbuf, " copied EOT\n");*/ X sprintf(msgbuf, "%s: END OF TAPE\n", p_io); X cprint(CP_REG, msgbuf); X } X if (debug) { X if (io_in.io_reallytape) X pr_mt_status(mt_status(io_in.io_fd)); X if (io_out.io_reallytape) X pr_mt_status(mt_status(io_out.io_fd)); X } X return; X } X } X} X X X/* X * stuff to print after a file is copied X */ Xeof_msg(iop,in) Xregister FILE_IO X *iop; Xregister int X in; X{ X register char X *how = in ? "read" : "written"; X register int X /*filenum = iop->io_file_num - 1;*/ X filenum = iop->io_file_num; X X /* X * if using a block count factor, make sure the last X * block stats are printed X */ X /*if (vb_mult > 1 && iop->io_block_num % vb_mult)*/ X if (vb_mult > 1) X if (in || !i_and_o) X eor_msg(iop,in,0); X X sprintf(msgbuf, X#ifdef LL X "%s: END OF FILE %d: %llu records, %llu bytes (%3.1Lf MB) %s\n", X#else X "%s: END OF FILE %d: %u records, %u bytes (%3.1f MB) %s\n", X#endif LL X p_io, X filenum, X iop->io_block_nums[filenum], X iop->io_file_sizes[filenum], X (ffsize_t)(iop->io_file_sizes[filenum])/MEGABYTE, X how); X cprint(CP_REG, msgbuf); X} X X X/* X * stuff to print after a record is copied X */ Xeor_msg(iop,in,size) Xregister FILE_IO X *iop; Xregister int X in, X size; X{ X register char X *how = in ? "read" : "written"; X register int X do_msg = 1; /* skip? */ X static int X last_size = 0; /* for eof processing w/ block factor */ X X /* X * if using a block count factor, make sure that only X * the appropriate blocks are reported, including the last block; X */ X if (vb_mult > 1) { X /* end of file processing? */ X if (iop->io_stat & FIO_EOF) { X /* already printed this one? */ X if (iop->io_block_num % vb_mult == 0) X do_msg = 0; X size = last_size; X } X /* only print when the modulus is zero */ X else if (iop->io_block_num % vb_mult != 0) X do_msg = 0; X } X X if (do_msg) { X sprintf(msgbuf, X#ifdef LL X "%3u %6llu [%6u] %10llu (%9.1Lf) %6llu %10llu (%9.1Lf) %s\r", X#else X "%3u %6u [%6u] %10u (%9.1f) %6u %10u (%9.1f) %s\r", X#endif LL X iop->io_file_num, iop->io_block_num, size, X iop->io_file_size, (ffsize_t)iop->io_file_size/MEGABYTE, X iop->io_total_blks, X iop->io_total_size, (ffsize_t)iop->io_total_size/MEGABYTE, X how); X cprint(CP_NONL, msgbuf); X } X X last_size = iop->io_stat & FIO_EOF ? 0 : size; X} X X X/* X * report input error X */ Xinput_error(iop,dbp) Xregister FILE_IO X *iop; Xregister DATA_BUF X *dbp; X{ X sprintf(msgbuf, "\n%s: ERROR READING FROM %s\n", X p_io, iop->io_filename); X cprint(CP_REG, msgbuf); X X if (iop->io_stat & FIO_FMT_ERR) { X sprintf(msgbuf, "%s: input format error, %s\n", p_io, fmt_msg); X cprint(CP_REG, msgbuf); X } X if (iop->io_stat & FIO_ERROR) { X sprintf(msgbuf, "%s: input error, errno=%d", X p_io, iop->io_errno); X cprint(CP_PERROR, msgbuf); X } X X sprintf(msgbuf, X#ifdef LL X "%s: file_num=%d, recs_read=%llu, bytes_read=%llu\n", X#else X "%s: file_num=%d, recs_read=%u, bytes_read=%u\n", X#endif LL X p_io, iop->io_file_num, iop->io_block_num, iop->io_file_size); X cprint(CP_REG, msgbuf); X X sprintf(msgbuf, X#ifdef LL X "%s: total_recs_read=%llu, total_bytes_read=%llu\n", X#else X "%s: total_recs_read=%u, total_bytes_read=%u\n", X#endif LL X p_io, iop->io_total_blks, iop->io_total_size); X cprint(CP_REG, msgbuf); X X sprintf(msgbuf, "%s: fd=%d, buffer=0x%x, bufptr=0x%x, max_rec_len=%d\n", X p_io, iop->io_fd, dbp->db_min, dbp->db_bod, dbp->db_max-dbp->db_min); X cprint(CP_REG, msgbuf); X X if (iop->io_reallytape) X pr_mt_status(mt_status(iop->io_fd)); X} X X X/* X * report output error X */ Xoutput_error(iop,dbp) Xregister FILE_IO X *iop; Xregister DATA_BUF X *dbp; X{ X sprintf(msgbuf, "\n%s: ERROR WRITING TO %s\n", p_io, iop->io_filename); X cprint(CP_REG, msgbuf); X X sprintf(msgbuf, "%s: output error, errno=%d", X p_io, iop->io_errno); X cprint(CP_PERROR, msgbuf); X X sprintf(msgbuf, X#ifdef LL X "%s: file_num=%d, recs_read=%llu, bytes_read=%llu\n", X#else X "%s: file_num=%d, recs_read=%u, bytes_read=%u\n", X#endif LL X p_io, iop->io_file_num, iop->io_block_num, iop->io_file_size); X cprint(CP_REG, msgbuf); X X sprintf(msgbuf, X#ifdef LL X "%s: total_recs_read=%llu, total_bytes_read=%llu\n", X#else X "%s: total_recs_read=%u, total_bytes_read=%u\n", X#endif LL X p_io, iop->io_total_blks, iop->io_total_size); X cprint(CP_REG, msgbuf); X X sprintf(msgbuf, "%s: fd=%d, buffer=0x%x, bufptr=0x%x, max_rec_len=%d\n", X p_io, iop->io_fd, dbp->db_min, dbp->db_bod, dbp->db_max-dbp->db_min); X cprint(CP_REG, msgbuf); X X if (iop->io_reallytape) X pr_mt_status(mt_status(iop->io_fd)); X} X X X/* X * Input a record up to MAX_REC_SIZE bytes from a file or tape. X * X * If input is from a file or pipe, observe record and file X * header info inserted by a previous instance of this program. X * X * If input is from a tape, then do markcount stuff; X * input record length will be supplied by the device. X */ Xinput_rec(iop,dbp) XFILE_IO X *iop; XDATA_BUF X *dbp; X{ X iop->io_stat = FIO_OK; X iop->io_errno = 0; X X /* X * input is from a tape? X * (or at least we're acting like it is (-f option)) X */ X if (iop->io_maybetape) X X return( input_rec_tape(iop,dbp) ); X X /* X * input is from a file or pipe X */ X else X X return( input_rec_data(iop,dbp) ); X} X X X/* X * Input a record from tape. X * X * Keep track of tape marks and record length. X * X * Input record length will be supplied by the device. X */ Xinput_rec_tape(iop,dbp) XFILE_IO X *iop; XDATA_BUF X *dbp; X{ X register int X len, /* num bytes read or to read */ X retry_cnt; /* times retried read */ X static int X markcount = 0, /* number of consecutive tape marks */ X last_len = 0; /* previous num bytes read */ X X#if 0 X errno = 0; X len = io_read(iop,dbp,RW_REG,MAX_REC_SIZE); X if (iop->io_stat & FIO_ERROR) { X if (last_len != 0) { X return(0); X } X X /* else must be end of file */ X iop->io_stat = FIO_EOF; X iop->io_errno = 0; X } X#else X /* X * allow for multiple read retries, X * but this doesn't seem to do any good; X */ X for (retry_cnt=0; ; ) { X errno = 0; X len = io_read(iop,dbp,RW_REG,MAX_REC_SIZE); X /* X * the Exabyte 8500 seems to return an error on a read X * after the read that gives EOF. X */ X if (iop->io_stat & FIO_ERROR) { X if (last_len != 0) { X if (retry_cnt++ >= NUM_READ_RETRY) X return(0); X X input_error(iop,dbp); X sprintf(msgbuf, X "%s: RETRYING [%d]\n", X p_io, retry_cnt); X cprint(CP_REG, msgbuf); X iop->io_stat = FIO_OK; X iop->io_errno = 0; X continue; X } X X /* else must be end of file (??) */ X iop->io_stat = FIO_EOF; X iop->io_errno = 0; X } X break; X } X#endif X X if (iop->io_stat & FIO_EOF) { X last_len = len; X if (++markcount > 1) { X iop->io_stat = FIO_EOT; X return(0); X } X return(0); X } X X /* not error, not eof, so must be a non-empty block */ X iop->io_file_size += len; X iop->io_total_size += len; X ++iop->io_block_num; X ++iop->io_total_blks; X X markcount = 0; X last_len = len; X X return(len); X} X X X/* X * Input a record from a file or pipe. X * X * Observe record and file header info inserted by a X * previous instance of this program. X */ Xinput_rec_data(iop,dbp) XFILE_IO X *iop; XDATA_BUF X *dbp; X{ X register char X *p; /* used for string operations */ X register int X len, /* num bytes read or to read */ X len2; /* num bytes read */ X X /* shouldn't need to do this, but to be safe... */ X head_buf.db_bod = head_buf.db_eod = head_buf.db_min; X X /* get the header/marker prefix */ X len2 = io_read(iop,&head_buf,RW_NO_AD|RW_BLOCK,SZ(CT_PREFIX)); X if (len2 != SZ(CT_PREFIX) || X strncmp(head_buf.db_bod, CT_PREFIX, SZ(CT_PREFIX)) != 0) { X iop->io_stat |= FIO_FMT_ERR; X fmt_msg = "prefix"; X return(0); X } X X /* get the header/marker name */ X len2 = io_read(iop,&head_buf,RW_NO_AD|RW_BLOCK,CT_MARKER_LEN); X if (len2 != CT_MARKER_LEN) { X iop->io_stat |= FIO_FMT_ERR; X fmt_msg = "marker"; X return(0); X } X/*fprintf(stderr,"\nIN=<%s>\n",head_buf.db_min);*/ X X /* process block header? */ X if (strncmp(head_buf.db_bod, CT_BLOCK, CT_MARKER_LEN) == 0) { X X /* read the block size */ X len2 = io_read(iop, &head_buf, RW_NO_AD|RW_BLOCK, X CT_BLK_DIGITS); X if (len2 != CT_BLK_DIGITS) { X iop->io_stat |= FIO_FMT_ERR; X fmt_msg = "block size"; X return(0); X } X/*fprintf(stderr,"\nIN=<%s>\n",head_buf.db_min);*/ X X /* convert the block size to int, and check it */ X head_buf.db_bod[CT_BLK_DIGITS] = '\0'; X for (p=head_buf.db_bod; *p != '\0'; ++p) X if (*p < '0' || *p > '9') { X iop->io_stat |= FIO_FMT_ERR; X fmt_msg = "block digits"; X return(0); X } X len = atoi(head_buf.db_bod); X if (len > MAX_REC_SIZE) { X iop->io_stat |= FIO_FMT_ERR; X fmt_msg = "block size too big"; X return(0); X } X/*fprintf(stderr,"\nIN=<%s>\n",head_buf.db_min);*/ X X /* read the block header trailer */ X len2 = io_read(iop, &head_buf, RW_NO_AD|RW_BLOCK, X SZ(CT_END_BLK_HDR)); X if (len2 != SZ(CT_END_BLK_HDR) || X strncmp(head_buf.db_bod,CT_END_BLK_HDR,SZ(CT_END_BLK_HDR)) != 0) { X iop->io_stat |= FIO_FMT_ERR; X fmt_msg = "block size trailer"; X/*fprintf(stderr,"\nIN=<%s>\n",head_buf.db_min);*/ X return(0); X } X/*fprintf(stderr,"\nIN=<%s>\n",head_buf.db_min);*/ X X /* read the data block */ X len2 = io_read(iop,dbp,RW_BLOCK,len); X X if (len2 > 0) { X iop->io_file_size += len2; X iop->io_total_size += len2; X ++iop->io_block_num; X ++iop->io_total_blks; X } X X#if 0 Xsprintf(msgbuf, "len = %d, len2 = %d\n", len, len2); Xcprint(CP_REG, msgbuf); X#endif X if (len2 != len) { X iop->io_stat |= FIO_FMT_ERR; X fmt_msg = "block read"; X return(0); X } X X /* skip the record trailer */ X if (io_read(iop,&head_buf,RW_NO_AD|RW_BLOCK,SZ(CT_END_BLK)) X != SZ(CT_END_BLK) X || strncmp(head_buf.db_bod,CT_END_BLK,SZ(CT_END_BLK) X != 0)) { X iop->io_stat |= FIO_FMT_ERR; X fmt_msg = "block trailer"; X return(0); X } X X return(len2); X } X X /* tape mark (end of file)? */ X else if (strncmp(head_buf.db_bod, CT_END_FILE, CT_MARKER_LEN) == 0) { X#if 0 X iop->io_file_sizes[iop->io_file_num] = iop->io_file_size; X iop->io_block_nums[iop->io_file_num] = iop->io_block_num; X iop->io_file_size = 0; X iop->io_block_num = 0; X ++iop->io_file_num; X#endif X iop->io_stat = FIO_EOF; X return(0); X } X X /* end of tape? */ X else if (strncmp(head_buf.db_bod, CT_END_TAPE, CT_MARKER_LEN) == 0) { X iop->io_stat = FIO_EOT; X return(0); X } X X /* none of the above... */ X else { X iop->io_stat |= FIO_FMT_ERR; X fmt_msg = "unknown marker"; X return(0); X } X} X X X/* X * Copy a buffer out to a file or tape. X * X * If output is a tape, write the record. X * If not a tape, write len to the output file, then the buffer. X */ Xoutput_rec(iop, dbp, type, len) XFILE_IO X *iop; XDATA_BUF X *dbp; Xuint X type; Xint X len; X{ X iop->io_stat = FIO_OK; X iop->io_errno = 0; X X /* X * output is to a tape? X * (or at least we're acting like it is (-t option)) X */ X if (iop->io_maybetape) X X return( output_rec_tape(iop, dbp, type, len) ); X X /* X * else write to file or pipe X */ X else X X return( output_rec_data(iop, dbp, type, len) ); X} X X X/* X * Copy a buffer out to tape. X */ Xoutput_rec_tape(iop, dbp, type, len) XFILE_IO X *iop; XDATA_BUF X *dbp; Xuint X type; Xint X len; X{ X int X len2; X struct mtop X tape_op; X X if (type & (FIO_EOF | FIO_EOT)) { X X /* X * another EOF mark is only needed for 1/2 in. tape X * drives. If you need to use such a drive, remove X * the next stanza, or add a command line option. X */ X#if 0 X if (type & FIO_EOT) { X#else X if (type & FIO_EOT && !magtape) { X#endif X return(0); X } X X /* write EOF mark */ X if (iop->io_reallytape) { X tape_op.mt_op = MTWEOF; X tape_op.mt_count = (daddr_t)1; X if (ioctl(iop->io_fd, MTIOCTOP, &tape_op) != 0) { X sprintf(msgbuf, X "%s: ERROR WRITING TAPE MARK, errno=%d", X p_io, errno); X cprint(CP_PERROR, msgbuf); X iop->io_stat = FIO_ERROR; X iop->io_errno = errno; X return(0); X } X } X X if (type & FIO_EOF) { X } X X return(0); X } X X /* regular data */ X if ((len2 = io_write(iop, dbp, RW_REG, len)) > 0) { X iop->io_file_size += len2; X iop->io_total_size += len2; X ++iop->io_block_num; X ++iop->io_total_blks; X } X X return(len2); X} X X X/* X * Copy a buffer out to a file or pipe. X * X * write len to the output file, then the buffer. X */ Xoutput_rec_data(iop, dbp, type, len) XFILE_IO X *iop; XDATA_BUF X *dbp; Xuint X type; Xint X len; X{ X int X len2; X X /* shouldn't need to do this, but to be safe... */ X head_buf.db_bod = head_buf.db_eod = head_buf.db_min; X X /* write tape mark (end of file)? */ X if (type & FIO_EOF) { X sprintf(head_buf.db_bod,"%s%s",CT_PREFIX,CT_END_FILE); X if (io_write(iop, &head_buf, RW_NO_AD, X SZ(CT_PREFIX)+CT_MARKER_LEN) != SZ(CT_PREFIX)+CT_MARKER_LEN) { X return(0); X } X return(0); X } X X /* write end of tape indicator? */ X if (type & FIO_EOT) { X sprintf(head_buf.db_bod,"%s%s",CT_PREFIX,CT_END_TAPE); X io_write(iop, &head_buf, RW_NO_AD, SZ(CT_PREFIX)+CT_MARKER_LEN); X return(0); X } X X /* write data block header */ X /*sprintf(head_buf.db_bod, "CPTP:BLK %0*d\n", CT_BLK_DIGITS, len);*/ X CT_MAKE_BLK_HDR(head_buf.db_bod, len); X /*io_write(iop, &head_buf, RW_NO_AD, strlen(head_buf.db_bod));*/ X io_write(iop, &head_buf, RW_NO_AD, CT_SZ_BLK_HDR); X if (iop->io_stat & FIO_ERROR) X return(0); X X /* write data block */ X len2 = io_write(iop, dbp, RW_REG, len); X iop->io_file_size += len2; X iop->io_total_size += len2; X if (iop->io_stat & FIO_ERROR) X return(len2); X X /* write data block trailer */ X strcpy(head_buf.db_bod, CT_END_BLK); X io_write(iop, &head_buf, RW_NO_AD, SZ(CT_END_BLK)); X if (iop->io_stat & FIO_ERROR) X return(len2); X X /* block number increases only if there were no errors */ X ++iop->io_block_num; X ++iop->io_total_blks; X X return(len2); X} X X X/* X * function to control reading also in a pipe. X * Otherwise read() in pipe gets munged. 87/11/23 [bs] X * X * read in the indicated number of bytes, w/ w/o blocking, etc. X */ Xio_read(iop, dbp, flags, num_to_read) XFILE_IO X *iop; XDATA_BUF X *dbp; Xint X flags, X num_to_read; X{ X register char X *p = dbp->db_eod; X register int X this_read = 0, X bytes_read = 0, X fd = iop->io_fd, X block = flags & RW_BLOCK, X no_ad = flags & RW_NO_AD; X X errno = 0; X X#if 0 Xsprintf(msgbuf, "p = 0x%x\n", (int)p); Xcprint(CP_REG, msgbuf); X#endif X while ((this_read = read(fd, p, num_to_read-bytes_read)) > 0) { X X bytes_read += this_read; X p += this_read; X X if (bytes_read >= num_to_read || errno != 0 || !block) X break; X } X X if (this_read < 0 || errno != 0) { X iop->io_stat = FIO_ERROR; X iop->io_errno = errno; X } X else if (this_read == 0) X iop->io_stat = FIO_EOF; X X if (!no_ad) X /*dbp->db_eod += bytes_read;*/ X dbp->db_eod = p; X X return(bytes_read); X} X X X/* X * check the writing [bs] X * X * write out the indicated number of bytes; X */ Xio_write(iop, dbp, flags, num_to_writ) XFILE_IO X *iop; XDATA_BUF X *dbp; Xint X flags, X num_to_writ; X{ X register char X *p = dbp->db_eod; X register int X bytes_writ = 0, X fd = iop->io_fd, X no_ad = flags & RW_NO_AD; X X errno = 0; X X bytes_writ = write(fd, dbp->db_bod, num_to_writ); X X if (bytes_writ < 0 || bytes_writ != num_to_writ || errno != 0) { X iop->io_stat = FIO_ERROR; X iop->io_errno = errno; X if (bytes_writ < 0) X return(0); X } X if (!no_ad) X dbp->db_bod += bytes_writ; X X return(bytes_writ); X} X X X/* X * print out a report, X * for the tape sides of the copy X */ Xdo_report() X{ X#if 0 X if (io_in.io_maybetape) X pr_report(&io_in,1); X X if (io_out.io_maybetape) X pr_report(&io_out,0); X#else X int X type; X X /* X * If were reading from tape, report the "in" info, X * and any differences with what was written. X * X * If were writing to tape, report the "out" info, X * and any differences with what was read. X * X * If were reading from and writing to tape, X * report what was read, and any differences with what was written. X */ X X type = i_and_o ? 2 : io_out.io_maybetape ? 1 : 0; X X pr_report(&io_in,&io_out,type); X#endif X} X X X/* X * print out a report of the copy for input or output X */ Xpr_report(iop,iop1,type) Xregister FILE_IO X *iop, /* info about input */ X *iop1; /* info about output */ Xregister int X type; /* treat as tape input (0), tape output (1), X * or both (2) */ X{ X register uint X i, X diff, /* difference between input and output */ X tot_diff, X skp = skip, /* skip only relevant for input */ X files = iop->io_file_num - skp, X files1 = iop1->io_file_num, X blk_size, X blk_size1; X fsize_t X size, X size1, X blocks, X blocks1, X tot_size = 0, X tot_size1 = 0, X tot_blocks = 0, X tot_blocks1 = 0; X char X *type_str = " in", X *type_str1 = " out"; X X /* print "copytape: # file(s) copied ..." */ X sprintf(msgbuf, "\n%s: %d file%s copied", p_io, files, (files==1?"":"s")); X cprint(CP_REG, msgbuf); X X if (skp) { X sprintf(msgbuf, " (%d file%s skipped)", skp, X (skp==1?"":"s")); X cprint(CP_REG, msgbuf); X } X sprintf(msgbuf, " from %s to %s", iop->io_filename, iop1->io_filename); X cprint(CP_REG, msgbuf); X sprintf(msgbuf, "\n"); X cprint(CP_REG, msgbuf); X X /* print the size (and other info) of each file copied */ X for (i=0; i < files; ++i) { X X size = iop->io_file_sizes[i+skp]; X size1 = iop1->io_file_sizes[i]; X blk_size = iop->io_block_sizes[i+skp]; X blk_size1 = iop1->io_block_sizes[i]; X blocks = iop->io_block_nums[i+skp]; X blocks1 = iop1->io_block_nums[i]; X X tot_diff += diff = size != size1 || blk_size != blk_size1 X || blocks != blocks1; X X tot_size += size; X tot_size1 += size1; X tot_blocks += blocks; X tot_blocks1 += blocks1; X X if (type == 0 || type == 2 || diff) { X sprintf(msgbuf, X#ifdef LL X "%4u. %7.1Lf MB, %8llu KB, %11llu B, %7llu [%6u] rec%s\n", X#else X "%4u. %7.1f MB, %8u KB, %11u B, %7u [%6u] rec%s\n", X#endif LL X i, (ffsize_t)size/MEGABYTE, size>>K_SHIFT, X size, blocks, blk_size, X diff ? type_str : ""); X cprint(CP_REG, msgbuf); X } X X if (type == 1 || diff) { X sprintf(msgbuf, X#ifdef LL X "%4u. %7.1Lf MB, %8llu KB, %11llu B, %7llu [%6u] rec%s\n", X#else X "%4u. %7.1f MB, %8u KB, %11u B, %7u [%6u] rec%s\n", X#endif LL X i, (ffsize_t)size1/MEGABYTE, size1>>K_SHIFT, X size1, blocks1, blk_size1, X diff ? type_str1 : ""); X cprint(CP_REG, msgbuf); X } X } X X /* print the totals */ X sprintf(msgbuf, "%4s %7s %8s %11s %7s\n", X "----", "-------", "--------", "-----------", "-------"); X cprint(CP_REG, msgbuf); X X if (type == 0 || type == 2 || tot_diff) { X sprintf(msgbuf, X#ifdef LL X "%4u %7.1Lf MB, %8llu KB, %11llu B, %7llu [......] rec%s\n", X#else X "%4u %7.1f MB, %8u KB, %11u B, %7u [......] rec%s\n", X#endif LL X files, (ffsize_t)tot_size/MEGABYTE, X tot_size>>K_SHIFT, tot_size, tot_blocks, X tot_diff ? type_str : ""); X cprint(CP_REG, msgbuf); X } X X if (type == 1 || tot_diff) { X sprintf(msgbuf, X#ifdef LL X "%4u %7.1Lf MB, %8llu KB, %11llu B, %7llu [......] rec%s\n", X#else X "%4u %7.1f MB, %8u KB, %11u B, %7u [......] rec%s\n", X#endif LL X files1, (ffsize_t)tot_size1/MEGABYTE, X tot_size1>>K_SHIFT, tot_size1, tot_blocks1, X tot_diff ? type_str1 : ""); X cprint(CP_REG, msgbuf); X } X X /* print any abnormal status and such */ X /* well, maybe one day... */ X X if (iop->io_block_num > 0) { X i = iop->io_file_num; X#if 0 X size = iop->io_file_sizes[i]; X blk_size = iop->io_block_sizes[i]; X blocks = iop->io_block_nums[i]; X#else X size = iop->io_file_size; X blk_size = iop->io_block_size; X blocks = iop->io_block_num; X#endif X X sprintf(msgbuf, X "\n%s: last input file not finished reading:\n", p_io); X cprint(CP_REG, msgbuf); X sprintf(msgbuf, X#ifdef LL X "%4u. %7.1Lf MB, %8llu KB, %11llu B, %7llu [%6u] rec%s\n", X#else X "%4u. %7.1f MB, %8u KB, %11u B, %7u [%6u] rec%s\n", X#endif LL X i - skp, (ffsize_t)size/MEGABYTE, size>>K_SHIFT, X size, blocks, blk_size, type_str); X cprint(CP_REG, msgbuf); X } X X if (iop1->io_block_num > 0) { X i = iop1->io_file_num; X#if 0 X size1 = iop1->io_file_sizes[i]; X blk_size1 = iop1->io_block_sizes[i]; X blocks1 = iop1->io_block_nums[i]; X#else X size1 = iop1->io_file_size; X blk_size1 = iop1->io_block_size; X blocks1 = iop1->io_block_num; X#endif X X sprintf(msgbuf, X "\n%s: last output file not finished writing:\n", p_io); X cprint(CP_REG, msgbuf); X sprintf(msgbuf, X#ifdef LL X "%4u. %7.1Lf MB, %8llu KB, %11llu B, %7llu [%6u] rec%s\n", X#else X "%4u. %7.1f MB, %8u KB, %11u B, %7u [%6u] rec%s\n", X#endif LL X i, (ffsize_t)size1/MEGABYTE, size1>>K_SHIFT, X size1, blocks1, blk_size1, type_str1); X cprint(CP_REG, msgbuf); X } X} X X X/* X * the main purpose of this print function is to make sure that X * a message printed after a block count will start on a new line X * (since the block counts don't include a newline); X */ Xcprint(flags, msg) Xregister int X flags; Xregister char X *msg; X{ X static int X nonl = 0; /* last line printed w/o newline */ X X if (nonl && !(flags & CP_NONL)) X fputs("\n", stderr); X X if (flags & CP_PERROR) X perror(msg); X else X fputs(msg, stderr); X X nonl = flags & CP_NONL; X} X X X/* X * Make sure a numeric arg is a valid number; X * this should work with decimal, octal, hexidecimal, when X * specified in the usual 'C' styles. X * If 'num_ptr' in a non-null pointer, the number is placed there. X * X * Note: advanced features of printf and scanf are used which X * might not be available on all (esp. older) systems. X */ Xcheck_num_arg(num_arg,num_ptr) Xchar X *num_arg; Xint X *num_ptr; X{ X int X number; X register char X *p; X char X num_buf[16]; X X if (num_arg == (char*)0) X return(0); X X /* skip any white space */ X for ( ; *num_arg == ' ' || *num_arg == '\t'; ++num_arg) X ; X /* skip extra leading zeros */ X for ( ; *num_arg == '0' && *(num_arg+1) == '0'; ++num_arg) X ; X /* early exit? */ X if (*num_arg < '0' || *num_arg > '9') X return(0); X /* any upper case to lower case */ X for (p=num_arg; *p != '\0'; ++p) X if (*p >= 'A' && *p <= 'Z') X *p |= ' '; X X /* %i should be able to handle decimal, octal, and hex */ X sscanf(num_arg,"%i",&number); X X /* convert back to proper numeric string */ X if (*num_arg == '0' && *(num_arg+1) == 'x') { X /*num_arg += 2;*/ X sprintf(num_buf,"%#x",number); X } X else if (*num_arg == '0' && *(num_arg+1) != '\0') { X /*num_arg += 1;*/ X sprintf(num_buf,"%#o",number); X } X else X sprintf(num_buf,"%d",number); X X /*return(strcmp(num_buf,num_arg) == 0);*/ X if (strcmp(num_buf,num_arg) == 0) { X *num_ptr = number; X return(1); X } X return(0); X} X X X/* X * return the component after the last slash in the path string; X */ Xchar* Xbasename(path) Xregister char X *path; X{ X register char X *p = path; X X for ( ; *p != '\0'; ++p) X if (*p == '/') X path = p + 1; X X return(path); X} X X X/* X * return a string for the type of object the open file descriptor X * is associated with; X */ Xchar* Xget_ftype(fd) Xregister int X fd; X{ X struct stat X stat_buf; X X if (fstat(fd,&stat_buf) != 0) X return("?"); X X if (S_ISREG(stat_buf.st_mode)) X return("file"); X if (S_ISFIFO(stat_buf.st_mode)) X return("pipe"); X if (S_ISCHR(stat_buf.st_mode)) X return("chr_dev"); X if (S_ISBLK(stat_buf.st_mode)) X return("blk_dev"); X if (S_ISDIR(stat_buf.st_mode)) X return("directory"); X if (S_ISLNK(stat_buf.st_mode)) X return("link"); X if (S_ISSOCK(stat_buf.st_mode)) X return("socket"); X#ifdef S_ISDOOR X if (S_ISDOOR(stat_buf.st_mode)) X return("door"); X#endif S_ISDOOR X X return("???"); X} X X X#ifdef SMTIO X/* X * added by des, taken from 'exebyte_toc' source; X * X * mt_status(): Return the status of the tape drive. X * X * This code is cribbed from the mts command. The smt_stat structure X * looks like this: X * X * struct smt_stat X * { X * char smt_type[8]; -- cartridge type X * u_long smt_remain; -- KBytes left on tape X * u_long smt_size; -- Total size of tape (KBytes) X * u_long smt_ecc; -- ECC numbers X * long smt_wp:1; -- write protected? X * long smt_bot:1; -- at beginning of tape? X * } X */ Xstruct smt_stat *mt_status(tapefd) Xint tapefd; X{ X static struct smt_stat X status; X int X e; X X if ((e = ioctl(tapefd, SMTIOGETSTAT, &status)) < 0) { X sprintf(msgbuf,"%s: %s", p_io, "tape status"); X cprint(CP_PERROR, msgbuf); X /*exit(EXIT_IO);*/ X /*exit((struct smt_stat*)1);*/ X return((struct smt_stat*)(-e)); X } X return (&status); X} X X/* X * print out a smt_struct X */ Xpr_mt_status(sp) Xstruct smt_stat *sp; X{ X if ((int)sp > 0 && (int)sp < 200) { X sprintf(msgbuf, X "%s: mt_status returns -%d, errno=%d\n", p_io, (int)sp, errno); X cprint(CP_REG, msgbuf); X return; X } X sprintf(msgbuf, "%s: smt_stat value:\n", p_io); X cprint(CP_REG, msgbuf); X sprintf(msgbuf, " type = \"%s\"\n",sp->smt_type); X cprint(CP_REG, msgbuf); X sprintf(msgbuf, " remain-kb = %lu\n",sp->smt_remain); X cprint(CP_REG, msgbuf); X sprintf(msgbuf, " size-kb = %lu\n",sp->smt_size); X cprint(CP_REG, msgbuf); X sprintf(msgbuf, " ecc-cnt = %lu\n",sp->smt_ecc); X cprint(CP_REG, msgbuf); X sprintf(msgbuf, " protect = %ld\n",sp->smt_wp); X cprint(CP_REG, msgbuf); X sprintf(msgbuf, " bot = %ld\n",sp->smt_bot); X cprint(CP_REG, msgbuf); X} X X#else X X/* X * added by des X * X * mt_status(): Return the status of the tape drive. X */ Xstruct mtget* Xmt_status(tapefd) Xint X tapefd; X{ X static struct mtget X status; X int X e; X X if ((e = ioctl(tapefd, MTIOCGET, &status)) < 0) { X /*sprintf(msgbuf,"%s: %s", p_io, "tape status");*/ X /*cprint(CP_PERROR, msgbuf);*/ X /*exit((struct mtget*)1);*/ X return((struct mtget*)(-e)); X } X return (&status); X} X X/* X * print out a mt_struct X */ Xpr_mt_status(sp) Xstruct mtget *sp; X{ X if ((int)sp > 0 && (int)sp < 200) { X sprintf(msgbuf, X "%s: mt_status returns -%d, errno=%d\n", p_io, (int)sp, errno); X cprint(CP_REG, msgbuf); X errno = 0; X return; X } X sprintf(msgbuf, "%s: mtget value:\n", p_io); X cprint(CP_REG, msgbuf); X sprintf(msgbuf, " type = 0x%x\n",sp->mt_type); X cprint(CP_REG, msgbuf); X sprintf(msgbuf, " stat-reg = 0x%x\n",sp->mt_dsreg); X cprint(CP_REG, msgbuf); X sprintf(msgbuf, " err-reg = 0x%x\n",sp->mt_erreg); X cprint(CP_REG, msgbuf); X sprintf(msgbuf, " residual = %u\n",sp->mt_resid); X cprint(CP_REG, msgbuf); X sprintf(msgbuf, " fileno = %u\n",sp->mt_fileno); X cprint(CP_REG, msgbuf); X sprintf(msgbuf, " blkno = %d\n",sp->mt_blkno); X cprint(CP_REG, msgbuf); X sprintf(msgbuf, " flags = 0x%x\n",sp->mt_flags); X cprint(CP_REG, msgbuf); X sprintf(msgbuf, " blk-fact = %u\n",sp->mt_bf); X cprint(CP_REG, msgbuf); X} X#endif SMTIO SHAR_EOF if test 51184 -ne "`wc -c < 'copytape.c'`" then echo shar: error transmitting "'copytape.c'" '(should have been 51184 characters)' fi fi # end of overwriting check echo shar: extracting "'copytape.1'" '(3961 characters)' if test -f 'copytape.1' then echo shar: will not over-write existing file "'copytape.1'" else sed 's/^ X//' << \SHAR_EOF > 'copytape.1' X.TH COPYTAPE 1 "25 June 1986" X.\"@(#)copytape.1 1.0 86/07/08 AICenter; by David S. Hayes X.SH NAME Xcopytape \- duplicate magtapes X.SH SYNOPSIS X.B copytape X\[\-f\] X\[\-t\] X\[\-s\fInnn\fP\] X\[\-l\fInnn\fP\] X\[\-v\] X.I X\[input \[output\]\] X.SH DESCRIPTION X.LP X.I copytape Xduplicates magtapes. It is intended for duplication of Xbootable or other non-file-structured (non-tar-structured) Xmagtapes on systems with only one tape drive. X.I copytape Xis blissfully ignorant of tape formats. It merely makes Xa bit-for-bit copy of its input. X.PP XIn normal use, X.I copytape Xwould be run twice. First, a boot tape is copied to an Xintermediate disk file. The file is in a special format that Xpreserves the record boundaries and tape marks. On the second Xrun, X.I copytape Xreads this file and generates a new tape. The second step Xmay be repeated if multiple copies are required. The typical Xprocess would look like this: X.sp X.RS +.5i Xtutorial% copytape /dev/rmt8 tape.tmp X.br Xtutorial% copytape tape.tmp /dev/rmt8 X.br Xtutorial% rm tape.tmp X.RE X.PP X.I copytape Xcopies from the standard input to the standard output, unless Xinput and output arguments are provided. It will automatically Xdetermine whether its input and output are physical tapes, or Xdata files. Data files are encoded in a special (human-readable) Xformat. X.PP XSince X.I copytape Xwill automatically determine what sort of thing its input Xand output are, a twin-drive system can duplicate a tape in Xone pass. The command would be X.RS +.5i Xtutorial% copytape /dev/rmt8 /dev/rmt9 X.RE X.SH OPTIONS X.TP 3 X.RI \-s nnn XSkip tape marks. The specified number of tape marks are skipped Xon the input tape, before the copy begins. By default, nothing is Xskipped, resulting in a copy of the complete input tape. Multiple Xtar(1) and dump(1) archives on a single tape are normally Xseparated by a single tape mark. On ANSI or IBM labelled tapes, Xeach file has three associated tape marks. Count carefully. X.TP 3 X.RI \-l nnn XLimit. Only nnn files (data followed by a tape mark), at most, Xare copied. This can be used to terminate a copy early. If the Xskip option is also specified, the files skipped do not count Xagainst the limit. X.TP 3 X\-f XFrom tape. The input is treated as though it were a physical Xtape, even if it is a data file. This option can be used Xto copy block-structured device files other than magtapes. X.TP 3 X\-t XTo tape. The output is treated as though it were a physical Xtape, even if it is a data file. Normally, data files mark Xphysical tape blocks with a (human\-readable) header describing Xthe block. If the \-t option is used when the output is Xactually a disk file, these headers will not be written. XThis will extract all the information from the tape, but X.I copytape Xwill not be able to duplicate the original tape based on Xthe resulting data file. X.TP 3 X\-v XVerbose. X.I copytape Xdoes not normally produce any output on the control terminal. XThe verbose option will identify the input and output files, Xtell whether they are physical tapes or data files, and Xannounce the size of each block copied. This can produce Xa lot of output on even relatively short tapes. It is Xintended mostly for diagnostic work. X.SH FILES X/dev/rmt* X.SH "SEE ALSO" Xansitape(1), dd(1), tar(1), mtio(4), copytape(5) X.SH AUTHOR XDavid S. Hayes, Site Manager, US Army Artificial Intelligence Center. XOriginally developed September 1984 at Rensselaer Polytechnic Institute, XTroy, New York. XRevised July 1986. This software is in the public domain. X.SH BUGS X.LP X.I copytape Xtreats two successive file marks as logical end-of-tape. X.LP XThe intermediate data file can consume huge amounts of Xdisk space. A 2400-foot reel at 6250-bpi can burn 140 megabytes. XThis is not strictly speaking a bug, but users should Xbe aware of the possibility. Check disk space with X.I df(1) Xbefore starting X.IR copytape . XCaveat Emptor! X.LP XA 256K buffer is used internally. This limits the maximum block Xsize of the input tape. SHAR_EOF if test 3961 -ne "`wc -c < 'copytape.1'`" then echo shar: error transmitting "'copytape.1'" '(should have been 3961 characters)' fi fi # end of overwriting check echo shar: extracting "'copytape.5'" '(1472 characters)' if test -f 'copytape.5' then echo shar: will not over-write existing file "'copytape.5'" else sed 's/^ X//' << \SHAR_EOF > 'copytape.5' X.TH COPYTAPE 5 "8 August 1986" X.SH NAME Xcopytape \- copytape intermediate data file format X.SH DESCRIPTION X.I copytape Xduplicates magtapes on single\-tape systems by making Xan intermediate copy of the tape in a disk file. XThis disk file has a special format that preserves Xthe block boundaries and tape marks of the original Xphysical tape. X.PP XEach block is preceded by a header identifying what Xsort of block it is. In the case of data blocks, Xthe length of the data is also given. Each header is Xon a separate text line, followed by a newline character. X.sp X.TP 3 XCPTP:BLK \fInnnnnn\fP X.ti -3 X\fIdata\fP\\n X.sp XA data block is identified by the keyword X.IR BLK . XThe length of the block is given in a six\-character Xnumeric field. The field is zero\-padded on the left if Xless than six characters are needed. The header is Xfollowed by a newline character. XThe original data follows. The data may have any characters Xin it, since X.I copytape Xuses a read(2) to extract it. XThe data is followed by a newline, to make the file easy Xto view with an editor. X.TP 3 XCPTP:MRK XA tape mark was encountered in the original tape. X.TP 3 XCPTP:EOT XWhen two consecutive tape marks are encountered, X.I copytape Xtreats the second as a logical end\-of\-tape. On Xoutput, both MRK and EOT generate Xa physical tape mark. X.I copytape Xstops processing after copying an EOT. X.SH "SEE ALSO" Xmtio(4) X.SH BUGS XSome weird tapes may not use two consecutive tape marks Xas logical end\-of\-tape. SHAR_EOF if test 1472 -ne "`wc -c < 'copytape.5'`" then echo shar: error transmitting "'copytape.5'" '(should have been 1472 characters)' fi fi # end of overwriting check echo shar: extracting "'copytape.c.dist'" '(8928 characters)' if test -f 'copytape.c.dist' then echo shar: will not over-write existing file "'copytape.c.dist'" else sed 's/^ X//' << \SHAR_EOF > 'copytape.c.dist' X/* X * COPYTAPE.C X * X * This program duplicates magnetic tapes, preserving the X * blocking structure and placement of tape marks. X * X * Sep 1988 entered changes of D.Hayes' last posting in v10i099 X * flag -o for old copytape (64k) format added [bs] X * X * Aug 1988 biased blocking inserted [bs] X * mywrite now controls write-operations X * X * Nov 1987 now stdin in first i/o position allowed [bs] X * myread function added to maintain pipe-reading X * X * July 1986 David S. Hayes X * Made data file format human-readable. X * X * April 1985 David S. Hayes X * Original Version. X */ X Xstatic char *sccsid = "@(#)copytape 1.6 (UniSB from D.Hayes) 88/09/21"; X X#include X#include X#include X#include X#include X Xextern int errno; X X#define BUFLEN 262144 /* max tape block size */ X#define MAXLIMIT 2*BUFLEN/* maximal limit for biased blocking */ X#define TAPE_MARK -100 /* return record length if we read a X * tape mark */ X#define END_OF_TAPE -101 /* 2 consecutive tape marks */ X#define FORMAT_ERROR -102 /* data file munged */ X Xint totape = 0, /* treat destination as a tape drive */ X fromtape = 0; /* treat source as a tape drive */ X Xint verbose = 0; /* tell what we're up to */ Xint nrbytes = 7; /* number of bytes for CPBLK number */ X Xchar *source = "stdin", X *dest = "stdout"; X Xchar globbuffer[MAXLIMIT]; Xchar *tapebuf = globbuffer; X Xmain(argc, argv) X int argc; X char *argv[]; X{ X int from = 0, X to = 1; X int len; /* number of bytes in record */ X int skip = 0; /* number of files to skip before X * copying */ X unsigned int limit = 0xffffffff; X int i; X struct mtget status; X int outblock=0; /* output blocking factor */ X int times, rest;/* control of biased blocking */ X X for (i = 1; i < argc && argv[i][0] == '-' && argv[i][1]; i++) { X switch (argv[i][1]) { X case 's': /* skip option */ X skip = atoi(&argv[i][2]); X break; X X case 'l': /* limit option */ X limit = atoi(&argv[i][2]); X break; X X case 'b': /* output block size */ X if ((outblock = atoi(&argv[i][2])) <= 0) { X fprintf(stderr, "copytape: bad blocksize value\n"); X exit(-1); X } X outblock <<= 9; /* calculating tape blocks */ X break; X X case 'o': /* understanding old copytape-format */ X nrbytes = 6; X break; X X case 'f': /* from tape option */ X fromtape = 1; X break; X X case 't': /* to tape option */ X totape = 1; X break; X X case 'v': /* be wordy */ X verbose = 1; X break; X X default: X fprintf(stderr, "usage: copytape [-f] [-t] [-lnn] [-snn] [-bnn] [-v] |- []\n"); X exit(-1); X } X } X X if (i < argc) { X if (argv[i][0] != '-' || argv[i][1]) { X from = open(argv[i], O_RDONLY); X source = argv[i]; X if (from == -1) { X perror("copytape: input open failed"); X exit(-1); X } X } /* false part: allow stdin (is preset), e.g. in a pipe [bs] */ X X i++; X } X if (i < argc) { X to = open(argv[i], O_WRONLY | O_CREAT | O_TRUNC, 0666); X dest = argv[i]; X if (to == -1) { X perror("copytape: output open failed"); X exit(-1); X } X i++; X } X if (i < argc) X perror("copytape: extra arguments ignored"); X X /* X * Determine if source and/or destination is a tape device. Try to X * issue a magtape ioctl to it. If it doesn't error, then it was a X * magtape. X */ X X errno = 0; X ioctl(from, MTIOCGET, &status); X fromtape |= errno == 0; X errno = 0; X ioctl(to, MTIOCGET, &status); X totape |= errno == 0; X errno = 0; X X if (verbose) { X fprintf(stderr, "copytape: from %s (%s)\n", X source, fromtape ? "tape" : "data"); X fprintf(stderr, " to %s (%s)\n", X dest, totape ? "tape" : "data"); X } X X /* X * Skip number of files, specified by -snnn, given on the command X * line. This is used to copy second and subsequent files on the X * tape. X */ X X if (verbose && skip) { X fprintf(stderr, "copytape: skipping %d input files\n", skip); X } X for (i = 0; i < skip; i++) { X do { X len = input(from); X } while (len > 0); X if (len == FORMAT_ERROR) { X fprintf(stderr, "copytape: format error on skip"); X exit(-1); X }; X if (len == END_OF_TAPE) { X fprintf(stderr, "copytape: only %d files in input\n", i); X exit(-1); X }; X }; X X /* X * Do the copy. X */ X X len = 0; rest = 0; X while (limit && !(len == END_OF_TAPE || len == FORMAT_ERROR)) { X do { X do { X len = input(from); X if (len == FORMAT_ERROR) X perror("copytape: data format error - block ignored"); X } while (len == FORMAT_ERROR); X X switch (len) { X X case TAPE_MARK: X if (rest > 0) { X tapebuf = globbuffer; X output(to, rest); X if (verbose) X fprintf(stderr, " copied %d bytes (trailer)\n", rest); X rest = 0; X } X output(to, TAPE_MARK); X if (verbose) X fprintf(stderr, " copied MRK\n"); X break; X X case END_OF_TAPE: X output(to, END_OF_TAPE); X if (verbose) X fprintf(stderr, " copied EOT\n"); X break; X X default: X if (outblock == 0) { /* 1:1 or biased blocking? */ X output(to, len); X if (verbose) X fprintf(stderr, " copied %d bytes\n", len); X break; /* 1:1 blocking, quit the switch */ X } X len += rest; X if (len > MAXLIMIT) { X fprintf(stderr, "copytape: MAXLIMIT exceeded. Blocking?!\n"); X exit(-1); X } X rest = len % outblock; X times= (len - rest) / outblock; X tapebuf = globbuffer; X while (times-- > 0) { X output(to, outblock); X if (verbose) X fprintf(stderr, " copied %d bytes\n", outblock); X tapebuf += outblock; X } X if (rest > 0) X bcopy(tapebuf, globbuffer, rest); X tapebuf = globbuffer + rest; X X } /* switch */ X X X } while (len > 0); X limit--; X } /* while */ X exit(0); X} X X X/* X * Input up to 256K from a file or tape. If input file is a tape, then X * do markcount stuff. Input record length will be supplied by the X * operating system. X */ X Xinput(fd) X int fd; X{ X static markcount = 0; /* number of consecutive tape X * marks */ X int len, X l2; X char header[40]; X X if (fromtape) { X len = read(fd, tapebuf, BUFLEN); X switch (len) { X case -1: X perror("copytape: can't read input"); X return END_OF_TAPE; X X case 0: X if (++markcount == 2) X return END_OF_TAPE; X else X return TAPE_MARK; X X default: X markcount = 0; /* reset tape mark count */ X return len; X }; /* switch */ X } X /* Input is really a data file. */ X l2 = myread(fd, header, 5); X if (l2 != 5 || strncmp(header, "CPTP:", 5) != 0) X return FORMAT_ERROR; X l2 = myread(fd, header, 4); X if (l2 != 4) X return FORMAT_ERROR; X if (strncmp(header, "BLK ", 4) == 0) { X l2 = myread(fd, header, nrbytes); X if (l2 != nrbytes) X return FORMAT_ERROR; X header[6] = '\0'; X len = atoi(header); X l2 = myread(fd, tapebuf, len); X if (l2 != len) X return FORMAT_ERROR; X (void) read(fd, header, 1); /* skip trailing newline */ X } else if (strncmp(header, "MRK\n", 4) == 0) X return TAPE_MARK; X else if (strncmp(header, "EOT\n", 4) == 0) X return END_OF_TAPE; X else X return FORMAT_ERROR; X X return len; X} X X X/* X * Copy a buffer out to a file or tape. X * X * If output is a tape, write the record. A length of zero indicates that X * a tapemark should be written. X * X * If not a tape, write len to the output file, then the buffer. X */ X Xoutput(fd, len) X int fd, X len; X{ X struct mtop op; X char header[20]; X X if (totape && (len == TAPE_MARK || len == END_OF_TAPE)) { X op.mt_op = MTWEOF; X op.mt_count = 1; X if (ioctl(fd, MTIOCTOP, &op) < 0) { X perror("copytape: can't write tape mark"); X exit(-1); X } X return; X } X if (!totape) { X switch (len) { X case TAPE_MARK: X mywrite(fd, "CPTP:MRK\n", 9); X break; X X case END_OF_TAPE: X mywrite(fd, "CPTP:EOT\n", 9); X break; X X case FORMAT_ERROR: X break; X X default: X sprintf(header, "CPTP:BLK %0*d\n", nrbytes-1, len); X mywrite(fd, header, strlen(header)); X mywrite(fd, tapebuf, len); X mywrite(fd, "\n", 1); X } X } else X mywrite(fd, tapebuf, len); X} X X/* X * Added function to control reading also in a pipe. X * Otherwise read() in pipe gets munged. 87/11/23 [bs] X */ X Xmyread(fd, buffer, len) Xint fd, len; Xchar *buffer; X{ X register got; X register rdc = len; X while ((got = read(fd, buffer+len-rdc, rdc)) > 0) { X rdc -= got; X if (rdc == 0) break; X } X return(len-rdc); X} X X/* X * check the writing [bs] X */ X Xmywrite(fd, buffer, len) Xint fd, len; Xchar *buffer; X{ X if (write(fd, buffer, len) != len) { X perror("copytape: write error"); X exit(-1); X } X} SHAR_EOF if test 8928 -ne "`wc -c < 'copytape.c.dist'`" then echo shar: error transmitting "'copytape.c.dist'" '(should have been 8928 characters)' fi fi # end of overwriting check echo shar: extracting "'cptape'" '(8650 characters)' if test -f 'cptape' then echo shar: will not over-write existing file "'cptape'" else sed 's/^ X//' << \SHAR_EOF > 'cptape' X#!/bin/sh X# cptape: copy a tape X# X# Description: X# 'cptape' copies a tape; X# it is a front end to 'copytape'; X# handles machine to machine copies; X# currently tested on SunOS 4.1.3 and SunOS 5.5.x; X# X# Usage: X# cptape [opts] from-host:tape to-host:tape X# X# D.Singer, 4/10/95 X# X# Modifications: X# X# 5/22/95, D.Singer X# added better check for same tape drive; X# added check for same host to run only one copytape process; X X#PATH='/usr/bin:/bin:/usr/sbin:/sbin:/usr/ucb:/home/lab/bin' XPATH='/usr/bin:/bin:/usr/sbin:/sbin:/usr/ucb:/usr/project/support/lab/bin' Xexport PATH X XPROG=`basename $0` XUSAGE=" XUsage: $PROG [-d] [-m \"user...\"] [-s] [-q] [-z] \\ X from-host:from-tape to-host:to-tape X X -d debug, don't really copy X -m send mail to user(s) after copy X -q quiet, don't echo commands X -s separate, use separate processes, even if same host X -z use compression, if appropriate X X '-', ':', or '.' for either host:tape means local host, default tape X" X XHOST="${HOST:-`hostname`}" XDFLT_TAPE="/dev/rmt12" X#COPY_PROG="copytape" XCOPY_PROG="/usr/project/support/lab/bin/copytape" X X# these are not currently necessary, see /home/lab/bin/copytape X#COPY_PROG_SOL1="/u/des/bin/sun4-4/$COPY_PROG" # <== CHANGE THIS ! X#COPY_PROG_SOL2="/u/des/bin/sun4-5/$COPY_PROG" # <== CHANGE THIS ! X XFROM_COPY_PROG= XTO_COPY_PROG= X X# if you add flags, they should start with a space, eg, " -d" X#FROM_COPY_FLAGS=" -r" X#TO_COPY_FLAGS=" -r -d" XFROM_COPY_FLAGS=" -r -vb10" XTO_COPY_FLAGS=" -r -v" XBOTH_COPY_FLAGS=" -r -vb10" X XFROM_RSH="" XTO_RSH="" XFROM_ZIP="" XTO_ZIP="" X XCMD1='echo "$PROG: $cmd" >&2' XCMD2='eval "$cmd"' XCMD="$CMD1; $CMD2" XSYNTAX="$PROG: option syntax error." X XDEBUG=0 XMAIL=0 XMAIL_CMD="/usr/ucb/mail" XMAIL_TO="" XQUIET=0 XSEPARATE=0 X X# can change to your fav compress/uncompress commands XZIP=0 X#ZIP_CMD="/usr/gnu/bin/gzip -1" X#UNZIP_CMD="/usr/gnu/bin/gunzip" XZIP_CMD="/usr/local/bin/gzip -1" XUNZIP_CMD="/usr/local/bin/gunzip" X X# X# system differences X# XLL_4x="ls -Llg" XLL_5x="ls -Ll" XSYS_TYPE=`uname -sr` Xcase "$SYS_TYPE" in X "SunOS 4."*) X LL="$LL_4x" X PING=/usr/etc/ping X #N='-n' X #C= X ;; X "SunOS 5."*|*) X LL="$LL_5x" X PING=/usr/sbin/ping X #N= X #C='\c' X esac X X X# X# process command line options X# XSKIP=0 Xfor OPT do X case "$SKIP" in X 1) X SKIP=0 X shift X continue X esac X case "$OPT" in X -d) X DEBUG=1 X echo "$PROG: debug mode." >&2 X ;; X -m) X MAIL=1 X MAIL_TO="$2" X SKIP=1 X ;; X -q) X QUIET=1 X ;; X -s) X SEPARATE=1 X ;; X -z) X ZIP=1 X ;; X -?*) X echo "$SYNTAX" >&2 X echo "$USAGE" >&2 X exit 1 X ;; X *) X break X esac X shift X done X Xcase "$DEBUG$QUIET" in X 11) X CMD= X ;; X 10) X CMD="$CMD1" X ;; X 01) X CMD="$CMD2" X esac X Xcase "$#" in X 2) X FROM_HOST="$1" X TO_HOST="$2" X ;; X *) X echo "$SYNTAX" >&2 X echo "$USAGE" >&2 X exit 1 X esac X X# X# parse a host:tape command line option, X# substituting defaults where appropriate; X# returns 0 and a string for success, else returns 1; X# Xdo_opt () { X TYPE=$1 # "H" or "T" X OPT=$2 X X STR=`nawk ' X BEGIN { X TYPE = "'"$TYPE"'"; X OPT = "'"$OPT"'"; X HOST = "'"$HOST"'"; X TAPE = "'"$DFLT_TAPE"'"; X H_I = 1; X T_I = 2; X H_PAT = "^[a-z][a-z0-9\-]*$"; X T_PAT = "^\/dev\/(n)?r[sm]t([0-9]|[12][0-9]|3[01])$"; X X if (TYPE !~ /^[HT]$/) X exit 1; X X if (OPT ~ /^([-.])?$/) X OPT = ":"; X X N = split(OPT,A,":"); X if (N < 1 || N > 2) X exit 1; X X if (TYPE == "H") { X if (A[H_I] == "" || (N == 1 && A[H_I] ~ T_PAT)) { X print HOST; X exit 0; X } X if (A[H_I] ~ H_PAT) { X print A[H_I]; X exit 0; X } X exit 1; X } X X if (TYPE == "T") { X if (N == 1) X T_I = 1; X if (A[T_I] == "" || (N == 1 && A[T_I] ~ H_PAT)) { X print TAPE; X exit 0; X } X if (A[T_I] ~ T_PAT) { X print A[T_I]; X exit 0; X } X exit 1; X } X X # not reached X }' < /dev/null` X STAT=$? X if [ "$STAT" = 0 ]; then X echo "$STR" X fi X return $STAT X} X X# X# get the system type for a host X# Xget_sys_type () { X _HNAME="$1" # host name X X _RSH= X _SYS_TYPE="$SYS_TYPE" X if [ "$_HNAME" != "$HOST" ]; then X _RSH="/usr/ucb/rsh $_HNAME " X _SYS_TYPE="`${_RSH}uname -sr`" X fi X echo "$_SYS_TYPE" X} X X# X# get copy program, for sys type X# Xget_copy_prog () { X _HTYPE="$1" # host type X case "$_HTYPE" in X "SunOS 4."*) X #echo "$COPY_PROG_SOL1" X echo "$COPY_PROG" X ;; X "SunOS 5."*|*) X #echo "$COPY_PROG_SOL2" X echo "$COPY_PROG" X esac X X} X X# X# get major and minor device numbers for a tape drive; X# returns "major minor", where minor is minor%4; X# Xget_tape_nums () { X HNAME="$1" # host name X TNAME="$2" # tape name X HTYPE="$3" # host type X X RSH= X #_SYS_TYPE="$SYS_TYPE" X LLL="$LL" X #if [ "$HNAME" != "$HOST" ]; then X # RSH="/usr/ucb/rsh $HNAME " X # _SYS_TYPE="`${RSH}uname -sr`" X # fi X if [ "$HNAME" != "$HOST" ]; then X RSH="/usr/ucb/rsh $HNAME " X fi X #case "$_SYS_TYPE" in X case "$HTYPE" in X "SunOS 4."*) X LLL="$LL_4x" X NUMS=`$RSH$LLL $TNAME | nawk '{ X MAJOR = $5; X MINOR = $6; X X MAJOR = substr(MAJOR,1,index(MAJOR,",")-1); X MINOR = MINOR % 4; X print MAJOR " " MINOR; X }'` X ;; X "SunOS 5."*|*) X LLL="$LL_5x" X NUMS=`$RSH$LLL $TNAME | cut -c34-40 | nawk -F',' '{ X MAJOR = sprintf("%d",$1); X MINOR = sprintf("%d",$2); X X #MAJOR = substr(MAJOR,1,index(MAJOR,",")-1); X MINOR = MINOR % 4; X print MAJOR " " MINOR; X }'` X esac X# NUMS=`$RSH$LLL $TNAME | nawk '{ X# MAJOR = $5; X# MINOR = $6; X# X# MAJOR = substr(MAJOR,1,index(MAJOR,",")-1); X# MINOR = MINOR % 4; X# print MAJOR " " MINOR; X# }'` X echo "$NUMS" X} X X# X# parse the host:tape args, checking for errors X# XERROR=0 XFH=`do_opt H "$FROM_HOST"` Xif [ "$?" != "0" -o "$FH" = "" ]; then X echo "$PROG: bad from-host argument." >&2 X ERROR=1 X fi XFT=`do_opt T "$FROM_HOST"` Xif [ "$?" != "0" -o "$FT" = "" ]; then X echo "$PROG: bad from-tape argument." >&2 X ERROR=1 X fi XTH=`do_opt H "$TO_HOST"` Xif [ "$?" != "0" -o "$TH" = "" ]; then X echo "$PROG: bad to-host argument." >&2 X ERROR=1 X fi XTT=`do_opt T "$TO_HOST"` Xif [ "$?" != "0" -o "$TT" = "" ]; then X echo "$PROG: bad to-tape argument." >&2 X ERROR=1 X fi Xif [ "$ERROR" != "0" ]; then X echo "$USAGE" >&2 X exit 1 X fi X X#echo "FH=$FH" X#echo "FT=$FT" X#echo "TH=$TH" X#echo "TT=$TT" X#exit X X# X# make sure the hosts are reachable (via ping) X# Xfor H in "$FH" "$TH"; do X if [ "$H" = "$HOST" ]; then X continue X fi X if $PING "$H" 4 >/dev/null 2>&1; then X : X else X echo "$PROG: cannot reach host \"$H\"." >&2 X exit 1 X fi X done X X# X# get the sys type of each host X# XFROM_SYS_TYPE=`get_sys_type "$FH"` X TO_SYS_TYPE=`get_sys_type "$TH"` X X#FROM_COPY_PROG=`get_copy_prog "$FROM_SYS_TYPE"` X# TO_COPY_PROG=`get_copy_prog "$TO_SYS_TYPE"` X XFROM_COPY_PROG="$COPY_PROG" X TO_COPY_PROG="$COPY_PROG" X X# X# make sure 2 different drives have been specified X# (this is not fool-proof!) X# X#case "$FH:$FT" in X# "$TH:$TT") X# echo "$PROG: cannot copy to same tape, use $COPY_PROG." >&2 X# echo "$USAGE" >&2 X# exit 1 X# esac X X# X# make sure 2 different drives have been specified; X# the check is for "host major minor%4" == "host major minor%4" X# XERROR=0 Xcase "$FH:$FT" in X "$TH:$TT") X ERROR=1 X ;; X "$TH:"*) X # compare the major and minor device numbers X F_NUMS=`get_tape_nums "$FH" "$FT" "$FROM_SYS_TYPE"` X T_NUMS=`get_tape_nums "$TH" "$TT" "$TO_SYS_TYPE"` X if [ "$F_NUMS" = "$T_NUMS" ]; then X ERROR=1 X fi X esac X#echo $F_NUMS X#echo $T_NUMS X#exit Xif [ "$ERROR" != "0" ]; then X echo "$PROG: cannot copy to same tape drive, use $COPY_PROG." >&2 X echo "$USAGE" >&2 X exit 1 X fi X X# X# construct zip commands, if needed X# Xif [ "$ZIP" = 1 ]; then X if [ "$FH" = "$TH" ]; then X echo "$PROG: not zipping for tapes on same host." >&2 X else X FROM_ZIP=" | $ZIP_CMD" X TO_ZIP="$UNZIP_CMD | " X fi X fi X X# X# construct rsh commands, if needed X# Xif [ "$FH" != "$HOST" ]; then X #FROM_RSH="/usr/ucb/rsh $FH -n " X FROM_RSH="/usr/ucb/rsh $FH " X fi Xif [ "$TH" != "$HOST" ]; then X #TO_RSH="/usr/ucb/rsh $TH -n " X TO_RSH="/usr/ucb/rsh $TH " X fi X X# X# basic copy commands X# XFROM_CMD="$FROM_COPY_PROG$FROM_COPY_FLAGS $FT" XTO_CMD="$TO_COPY_PROG$TO_COPY_FLAGS - $TT" XBOTH_CMD="$FROM_COPY_PROG$BOTH_COPY_FLAGS $FT $TT" X X# X# put the commands together; ie, add any ZIP and RSH components X# XFROM_CMD="$FROM_CMD$FROM_ZIP" Xif [ "$FROM_RSH" != "" ]; then X FROM_CMD="$FROM_RSH\"$FROM_CMD\"" X BOTH_CMD="$FROM_RSH\"$BOTH_CMD\"" X fi XTO_CMD="$TO_ZIP$TO_CMD" Xif [ "$TO_RSH" != "" ]; then X TO_CMD="$TO_RSH\"$TO_CMD\"" X fi X X X# X# do the copy X# X Xecho "$PROG: copy starting -- `date`" >&2 X Xif [ \( "$FH" = "$TH" \) -a "$SEPARATE" = "0" ]; then X cmd="$BOTH_CMD" X else X cmd="$FROM_CMD | $TO_CMD" X fi Xeval $CMD X Xecho "$PROG: copy ending -- `date`" >&2 X X X# X# send mail, if specified X# Xif [ "$MAIL" = "1" -a "$MAIL_TO" != "" ]; then X cmd="$MAIL_CMD -s \"TAPE COPY DONE\" $MAIL_TO < /dev/null" X eval $CMD X fi X Xexit X X## end of script SHAR_EOF echo shar: 9 control characters may be missing from "'cptape'" if test 8650 -ne "`wc -c < 'cptape'`" then echo shar: error transmitting "'cptape'" '(should have been 8650 characters)' fi chmod +x 'cptape' fi # end of overwriting check # End of shell archive exit 0