[comp.sources.misc] Copytape with remote tapes

david@elroy.jpl.nasa.gov (David Robinson) (12/02/87)

Here is the copytape version that I mentioned earlier that works
with the rmtlib routines to allow you to copy tapes across
a network.

The syntax is the same as normal copytape but allows the input
and output files to be given in the remote file format (ala rcp).

Example:
	% copytape /dev/rmt0 foobar:/dev/rmt8

	-David Robinson
	david@elroy.jpl.nasa.gov

# -----------CUT HERE---------------------
# This is a shell archive.  Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file".
#
# Wrapped by elroy!david on Sun Nov 29 06:40:35 PST 1987
# Contents:  Makefile README.network copytape.1 copytape.5 copytape.c rmt.h
#	rmtlib.c
 
echo x - Makefile
sed 's/^@//' > "Makefile" <<'@//E*O*F Makefile//'
CFLAGS= -O
MAN1 =	/usr/man/man1
MAN5 =	/usr/man/man5
BIN =	/usr/local

copytape:	copytape.o rmtlib.o
	cc -O -o copytape copytape.o rmtlib.o

install:	copytape
	install -s -m 0511 copytape ${BIN}

clean: 
	rm -f copytape copytape.o rmtlib.o

man:	man1 man5

man1:	${MAN1}/copytape.1
	cp copytape.1 ${MAN1}

man5:	${MAN5}/copytape.5
	cp copytape.5 ${MAN5}
@//E*O*F Makefile//
chmod u=rw,g=r,o=r Makefile
 
echo x - README.network
sed 's/^@//' > "README.network" <<'@//E*O*F README.network//'


This is a version of the public domain copytape program that
works with a slightly modified version of the public domain
rmtlib remote tape library.

The version of copytape is not the most uptodate version but
it works.  It should not be difficult to upgrade it if you
have the most recent version.

The rmtlib was slightly modified to get some (not all) of the
ioctl calls to work.  In particular the MTIOCGET ioctl does
not work correctly with a remote tape so it is fake by always
returning zero (0).  This assumes that the user will never try to
copy a tape to a remote file instead of device.

Bugs probably do exist if so forward them on to me.

	11/29/87
	David Robinson
	MS 168-522
	Jet Propulsion Lab
	4800 Oak Grove
	Pasadena CA 91109
	david@elroy.jpl.nasa.gov
@//E*O*F README.network//
chmod u=rw,g=r,o=r README.network
 
echo x - copytape.1
sed 's/^@//' > "copytape.1" <<'@//E*O*F copytape.1//'
@.TH COPYTAPE 1 "25 June 1986"
@.\"@(#)copytape.1 1.0 86/07/08 AICenter; by David S. Hayes
@.SH NAME
copytape \- duplicate magtapes
@.SH SYNOPSIS
@.B copytape
\[\-f\]
\[\-t\]
\[\-s\fInnn\fP\]
\[\-l\fInnn\fP\]
\[\-v\]
@.I
\[input \[output\]\]
@.SH DESCRIPTION
@.LP
@.I copytape
duplicates magtapes.  It is intended for duplication of
bootable or other non-file-structured (non-tar-structured)
magtapes on systems with only one tape drive.
@.I copytape
is blissfully ignorant of tape formats.  It merely makes
a bit-for-bit copy of its input.
@.PP
In normal use,
@.I copytape
would be run twice.  First, a boot tape is copied to an
intermediate disk file.  The file is in a special format that
preserves the record boundaries and tape marks.  On the second
run, 
@.I copytape
reads this file and generates a new tape.  The second step
may be repeated if multiple copies are required.  The typical
process would look like this:
@.sp
@.RS +.5i
tutorial% copytape /dev/rmt8 tape.tmp
@.br
tutorial% copytape tape.tmp /dev/rmt8
@.br
tutorial% rm tape.tmp
@.RE
@.PP
@.I copytape
copies from the standard input to the standard output, unless
input and output arguments are provided.  It will automatically
determine whether its input and output are physical tapes, or
data files.  Data files are encoded in a special (human-readable)
format.
@.PP
Since
@.I copytape
will automatically determine what sort of thing its input
and output are, a twin-drive system can duplicate a tape in
one pass.  The command would be
@.RS +.5i
tutorial% copytape /dev/rmt8 /dev/rmt9
@.RE
@.PP
@.I copytape
will also take as input and/or output the name of a
remote tape drive in the format hostname[.username]:drive.
If for example you are inputing from local rmt8 and wish
to copy to rmt9 on machine fred, the command would be
@.RS +.5i
tutorial% copytape /dev/rmt8 fred:/dev/rmt9
@.RE
@.SH OPTIONS
@.TP 3
@.RI \-s nnn
Skip tape marks.  The specified number of tape marks are skipped
on the input tape, before the copy begins.  By default, nothing is
skipped, resulting in a copy of the complete input tape.  Multiple
tar(1) and dump(1) archives on a single tape are normally
separated by a single tape mark.  On ANSI or IBM labelled tapes,
each file has three associated tape marks.  Count carefully.
@.TP 3
@.RI \-l nnn
Limit.  Only nnn files (data followed by a tape mark), at most,
are copied.  This can be used to terminate a copy early.  If the
skip option is also specified, the files skipped do not count
against the limit.
@.TP 3
\-f
@From tape.  The input is treated as though it were a physical
tape, even if it is a data file.  This option can be used
to copy block-structured device files other than magtapes.
@.TP 3
\-t
To tape.  The output is treated as though it were a physical
tape, even if it is a data file.  Normally, data files mark
physical tape blocks with a (human\-readable) header describing
the block.  If the \-t option is used when the output is
actually a disk file, these headers will not be written.
This will extract all the information from the tape, but
@.I copytape
will not be able to duplicate the original tape based on
the resulting data file.
@.TP 3
\-v
Verbose.
@.I copytape
does not normally produce any output on the control terminal.
The verbose option will identify the input and output files,
tell whether they are physical tapes or data files, and
announce the size of each block copied.  This can produce
a lot of output on even relatively short tapes.  It is
intended mostly for diagnostic work.
@.SH FILES
/dev/rmt*
@.SH "SEE ALSO"
ansitape(1), dd(1), tar(1), mtio(4), copytape(5)
@.SH AUTHOR
David S. Hayes, Site Manager, US Army Artificial Intelligence Center.
Originally developed September 1984 at Rensselaer Polytechnic Institute,
Troy, New York.
Revised July 1986.  This software is in the public domain.
@.SH BUGS
@.LP
@.I copytape
treats two successive file marks as logical end-of-tape.
@.LP
The intermediate data file can consume huge amounts of
disk space.  A 2400-foot reel at 6250-bpi can burn 140 megabytes.
This is not strictly speaking a bug, but users should
be aware of the possibility.  Check disk space with
@.I df(1)
before starting
@.IR copytape .
Caveat Emptor!
@.LP
A 256K buffer is used internally.  This limits the maximum block
size of the input tape.
@//E*O*F copytape.1//
chmod u=rw,g=r,o=r copytape.1
 
echo x - copytape.5
sed 's/^@//' > "copytape.5" <<'@//E*O*F copytape.5//'
@.TH COPYTAPE 5  "8 August 1986"
@.SH NAME
copytape \- copytape intermediate data file format
@.SH DESCRIPTION
@.I copytape
duplicates magtapes on single\-tape systems by making
an intermediate copy of the tape in a disk file.
This disk file has a special format that preserves
the block boundaries and tape marks of the original
physical tape.
@.PP
Each block is preceded by a header identifying what
sort of block it is.  In the case of data blocks,
the length of the data is also given.  Each header is
on a separate text line, followed by a newline character.
@.sp
@.TP 3
CPTP:BLK \fInnnnnn\fP
@.ti -3
\fIdata\fP\\n
@.sp
A data block is identified by the keyword
@.IR BLK .
The length of the block is given in a six\-character
numeric field.  The field is zero\-padded on the left if
less than six characters are needed.  The header is
followed by a newline character.
The original data follows.  The data may have any characters
in it, since
@.I copytape
uses a read(2) to extract it.
The data is followed by a newline, to make the file easy
to view with an editor.
@.TP 3
CPTP:MRK
A tape mark was encountered in the original tape.
@.TP 3
CPTP:EOT
When two consecutive tape marks are encountered,
@.I copytape
treats the second as a logical end\-of\-tape.  On
output, both MRK and EOT generate
a physical tape mark.
@.I copytape
stops processing after copying an EOT.
@.SH "SEE ALSO"
mtio(4)
@.SH BUGS
Some weird tapes may not use two consecutive tape marks
as logical end\-of\-tape.
@//E*O*F copytape.5//
chmod u=rw,g=r,o=r copytape.5
 
echo x - copytape.c
sed 's/^@//' > "copytape.c" <<'@//E*O*F copytape.c//'

/*
 * COPYTAPE.C
 *
 * This program duplicates magnetic tapes, preserving the
 * blocking structure and placement of tape marks.
 *
 * This program was updated at
 *
 *	U.S. Army Artificial Intelligence Center
 *	HQDA (Attn: DACS-DMA)
 *	Pentagon
 *	Washington, DC  20310-0200
 *
 *	Phone: (202) 694-6900
 *
 **************************************************
 *
 *	THIS PROGRAM IS IN THE PUBLIC DOMAIN
 *
 **************************************************
 *
 * July 1986		David S. Hayes
 *		Made data file format human-readable.
 *
 * April 1985		David S. Hayes
 *		Original Version.
 */


#include <stdio.h>
#include <sys/types.h>
#include "rmt.h"
#include <sys/ioctl.h>
#include <sys/mtio.h>
#include <sys/file.h>

extern int      errno;

#define BUFLEN		262144	/* max tape block size */
#define TAPE_MARK	-100	/* return record length if we read a
				 * tape mark */
#define END_OF_TAPE	-101	/* 2 consecutive tape marks */
#define FORMAT_ERROR	-102	/* data file munged */

int             totape = 0,	/* treat destination as a tape drive */
                fromtape = 0;	/* treat source as a tape drive */

int             verbose = 0;	/* tell what we're up to */

char           *source = "stdin",
               *dest = "stdout";

char            tapebuf[BUFLEN];

main(argc, argv)
    int             argc;
    char           *argv[];
{
    int             from = 0,
                    to = 1;
    int             len;	/* number of bytes in record */
    int             skip = 0;	/* number of files to skip before
				 * copying */
    unsigned int    limit = 0xffffffff;
    int             i;
    struct mtget    status;

    for (i = 1; i < argc && argv[i][0] == '-'; i++) {
	switch (argv[i][1]) {
	  case 's':		/* skip option */
	    skip = atoi(&argv[i][2]);
	    break;

	  case 'l':
	    limit = atoi(&argv[i][2]);
	    break;

	  case 'f':		/* from tape option */
	    fromtape = 1;
	    break;

	  case 't':		/* to tape option */
	    totape = 1;
	    break;

	  case 'v':		/* be wordy */
	    verbose = 1;
	    break;

	  default:
	    fprintf(stderr, "usage: copytape [-f] [-t] [-lnn] [-snn] [-v] from to\n");
	    exit(-1);
	}
    }

    if (i < argc) {
	from = open(argv[i], O_RDONLY);
	source = argv[i];
	if (from == -1) {
	    perror("copytape: input open failed");
	    exit(-1);
	}
	i++;;
    }
    if (i < argc) {
	to = open(argv[i], O_WRONLY | O_CREAT | O_TRUNC, 0666);
	dest = argv[i];
	if (to == -1) {
	    perror("copytape: output open failed");
	    exit(-1);
	}
	i++;
    }
    if (i < argc)
	perror("copytape: extra arguments ignored");

    /*
     * Determine if source and/or destination is a tape device. Try to
     * issue a magtape ioctl to it.  If it doesn't error, then it was a
     * magtape. 
     */

    errno = 0;
    ioctl(from, MTIOCGET, &status);
    fromtape |= errno == 0;
    errno = 0;
    ioctl(to, MTIOCGET, &status);
    totape |= errno == 0;
    errno = 0;

    if (verbose) {
	fprintf(stderr, "copytape: from %s (%s)\n",
		source, fromtape ? "tape" : "data");
	fprintf(stderr, "          to %s (%s)\n",
		dest, totape ? "tape" : "data");
    }

    /*
     * Skip number of files, specified by -snnn, given on the command
     * line. This is used to copy second and subsequent files on the
     * tape. 
     */

    if (verbose && skip) {
	fprintf(stderr, "copytape: skipping %d input files\n", skip);
    }
    for (i = 0; i < skip; i++) {
	do {
	    len = input(from);
	} while (len > 0);
	if (len == FORMAT_ERROR) {
	    perror(stderr, "copytape: format error on skip");
	    exit(-1);
	};
	if (len == END_OF_TAPE) {
	    fprintf(stderr, "copytape: only %d files in input\n", i);
	    exit(-1);
	};
    };

    /*
     * Do the copy. 
     */

    len = 0;
    while (limit && !(len == END_OF_TAPE || len == FORMAT_ERROR)) {
	do {
	    do {
		len = input(from);
		if (len == FORMAT_ERROR)
		    perror("copytape: data format error - block ignored");
	    } while (len == FORMAT_ERROR);

	    output(to, len);

	    if (verbose) {
		switch (len) {
		  case TAPE_MARK:
		    fprintf(stderr, "  copied MRK\n");
		    break;

		  case END_OF_TAPE:
		    fprintf(stderr, "  copied EOT\n");
		    break;

		  default:
		    fprintf(stderr, "  copied %d bytes\n", len);
		};
	    };
	} while (len > 0);
	limit--;
    }
    exit(0);
}


/*
 * Input up to 256K from a file or tape. If input file is a tape, then
 * do markcount stuff.  Input record length will be supplied by the
 * operating system. 
 */

input(fd)
    int             fd;
{
    static          markcount = 0;	/* number of consecutive tape
					 * marks */
    int             len,
                    l2,
                    c;
    char            header[40];

    if (fromtape) {
	len = read(fd, tapebuf, BUFLEN);
	switch (len) {
	  case -1:
	    perror("copytape: can't read input");
	    return END_OF_TAPE;

	  case 0:
	    if (++markcount == 2)
		return END_OF_TAPE;
	    else
		return TAPE_MARK;

	  default:
	    markcount = 0;		/* reset tape mark count */
	    return len;
	};
    }
    /* Input is really a data file. */
    l2 = read(fd, header, 5);
    if (l2 != 5 || strncmp(header, "CPTP:", 5) != 0)
	return FORMAT_ERROR;

    l2 = read(fd, header, 4);
    if (strncmp(header, "BLK ", 4) == 0) {
	l2 = read(fd, header, 7);
	if (l2 != 7)
	    return FORMAT_ERROR;
	header[6] = '\0';
	len = atoi(header);
	l2 = read(fd, tapebuf, len);
	if (l2 != len)
	    return FORMAT_ERROR;
	read(fd, header, 1);	/* skip trailing newline */
    } else if (strncmp(header, "MRK\n", 4) == 0)
	return TAPE_MARK;
    else if (strncmp(header, "EOT\n", 4) == 0)
	return END_OF_TAPE;
    else
	return FORMAT_ERROR;

    return len;
}


/*
 * Copy a buffer out to a file or tape. 
 *
 * If output is a tape, write the record.  A length of zero indicates that
 * a tapemark should be written. 
 *
 * If not a tape, write len to the output file, then the buffer.  
 */

output(fd, len)
    int             fd,
                    len;
{
    struct mtop     op;
    char            header[20];

    if (totape && (len == TAPE_MARK || len == END_OF_TAPE)) {
	op.mt_op = MTWEOF;
	op.mt_count = 1;
	ioctl(fd, MTIOCTOP, &op);
	return;
    }
    if (!totape) {
	switch (len) {
	  case TAPE_MARK:
	    write(fd, "CPTP:MRK\n", 9);
	    break;

	  case END_OF_TAPE:
	    write(fd, "CPTP:EOT\n", 9);
	    break;

	  case FORMAT_ERROR:
	    break;

	  default:
	    sprintf(header, "CPTP:BLK %06d\n", len);
	    write(fd, header, strlen(header));
	    write(fd, tapebuf, len);
	    write(fd, "\n", 1);
	}
    } else
	write(fd, tapebuf, len);
}
@//E*O*F copytape.c//
chmod u=rw,g=r,o=r copytape.c
 
echo x - rmt.h
sed 's/^@//' > "rmt.h" <<'@//E*O*F rmt.h//'
/*
 *	rmt.h
 *
 *	Added routines to replace open(), close(), lseek(), ioctl(), etc.
 *	The preprocessor can be used to remap these the rmtopen(), etc
 *	thus minimizing source changes.
 *
 *	This file must be included before <sys/stat.h>, since it redefines
 *	stat to be rmtstat, so that struct stat xyzzy; declarations work
 *	properly.
 *
 *	-- Fred Fish (w/some changes by Arnold Robbins)
 */


#ifndef access		/* avoid multiple redefinition */
#ifndef lint		/* in this case what lint doesn't know won't hurt it */
#define access rmtaccess
#define close rmtclose
#define creat rmtcreat
#define dup rmtdup
#define fcntl rmtfcntl
#define fstat rmtfstat
#define ioctl rmtioctl
#define isatty rmtisatty
#define lseek rmtlseek
#define lstat rmtlstat
#define open rmtopen
#define read rmtread
#define stat rmtstat
#define write rmtwrite

extern long rmtlseek ();	/* all the rest are int's */
#endif
#endif
@//E*O*F rmt.h//
chmod u=rw,g=r,o=r rmt.h
 
echo x - rmtlib.c
sed 's/^@//' > "rmtlib.c" <<'@//E*O*F rmtlib.c//'
/*
 *	rmt --- remote tape emulator subroutines
 *
 *	Originally written by Jeff Lee, modified some by Arnold Robbins
 *
 *	WARNING:  The man page rmt(8) for /etc/rmt documents the remote mag
 *	tape protocol which rdump and rrestore use.  Unfortunately, the man
 *	page is *WRONG*.  The author of the routines I'm including originally
 *	wrote his code just based on the man page, and it didn't work, so he
 *	went to the rdump source to figure out why.  The only thing he had to
 *	change was to check for the 'F' return code in addition to the 'E',
 *	and to separate the various arguments with \n instead of a space.  I
 *	personally don't think that this is much of a problem, but I wanted to
 *	point it out.
 *	-- Arnold Robbins
 *
 *	Redone as a library that can replace open, read, write, etc, by
 *	Fred Fish, with some additional work by Arnold Robbins.
 */
 
/*
 *	MAXUNIT --- Maximum number of remote tape file units
 *
 *	READ --- Return the number of the read side file descriptor
 *	WRITE --- Return the number of the write side file descriptor
 */

#define RMTIOCTL	1

#include <stdio.h>
#include <signal.h>
#include <sys/types.h>

#ifdef RMTIOCTL
#include <sys/ioctl.h>
#include <sys/mtio.h>
#endif

#include <errno.h>
#include <setjmp.h>
#include <sys/stat.h>

#define BUFMAGIC	64	/* a magic number for buffer sizes */
#define MAXUNIT	4

#define READ(fd)	(Ctp[fd][0])
#define WRITE(fd)	(Ptc[fd][1])

static int Ctp[MAXUNIT][2] = { -1, -1, -1, -1, -1, -1, -1, -1 };
static int Ptc[MAXUNIT][2] = { -1, -1, -1, -1, -1, -1, -1, -1 };

static jmp_buf Jmpbuf;
extern int errno;

/*
 *	abort --- close off a remote tape connection
 */

static void abort(fildes)
int fildes;
{
	close(READ(fildes));
	close(WRITE(fildes));
	READ(fildes) = -1;
	WRITE(fildes) = -1;
}



/*
 *	command --- attempt to perform a remote tape command
 */

static int command(fildes, buf)
int fildes;
char *buf;
{
	register int blen;
	int (*pstat)();

/*
 *	save current pipe status and try to make the request
 */

	blen = strlen(buf);
	pstat = signal(SIGPIPE, SIG_IGN);
	if (write(WRITE(fildes), buf, blen) == blen)
	{
		signal(SIGPIPE, pstat);
		return(0);
	}

/*
 *	something went wrong. close down and go home
 */

	signal(SIGPIPE, pstat);
	abort(fildes);

	errno = EIO;
	return(-1);
}



/*
 *	status --- retrieve the status from the pipe
 */

static int status(fildes)
int fildes;
{
	int i;
	char c, *cp;
	char buffer[BUFMAGIC];
        int st;

/*
 *	read the reply command line
 */

	for (i = 0, cp = buffer; i < BUFMAGIC; i++, cp++)
	{
		if ((st = read(READ(fildes), cp, 1)) != 1)
		{
			abort(fildes);
			errno = EIO;
			return(-1);
		}
		if (*cp == '\n')
		{
			*cp = 0;
			break;
		}
	}

	if (i == BUFMAGIC)
	{
		abort(fildes);
		errno = EIO;
		return(-1);
	}

/*
 *	check the return status
 */

	for (cp = buffer; *cp; cp++)
		if (*cp != ' ')
			break;

	if (*cp == 'E' || *cp == 'F')
	{
		errno = atoi(cp + 1);
		while (read(READ(fildes), &c, 1) == 1)
			if (c == '\n')
				break;

		if (*cp == 'F')
			abort(fildes);

		return(-1);
	}

/*
 *	check for mis-synced pipes
 */

	if (*cp != 'A')
	{
		abort(fildes);
		errno = EIO;
		return(-1);
	}

	return(atoi(cp + 1));
}



/*
 *	_rmt_open --- open a magtape device on system specified, as given user
 *
 *	file name has the form system[.user]:/dev/????
 */

#define MAXHOSTLEN	257	/* BSD allows very long host names... */

static int _rmt_open (path, oflag, mode)
char *path;
int oflag;
int mode;
{
	int i, rc;
	char buffer[BUFMAGIC];
	char system[MAXHOSTLEN];
	char device[BUFMAGIC];
	char login[BUFMAGIC];
	char *sys, *dev, *user;

	sys = system;
	dev = device;
	user = login;

/*
 *	first, find an open pair of file descriptors
 */

	for (i = 0; i < MAXUNIT; i++)
		if (READ(i) == -1 && WRITE(i) == -1)
			break;

	if (i == MAXUNIT)
	{
		errno = EMFILE;
		return(-1);
	}

/*
 *	pull apart system and device, and optional user
 *	don't munge original string
 */
	while (*path != '.' && *path != ':') {
		*sys++ = *path++;
	}
	*sys = '\0';
	path++;

	if (*(path - 1) == '.')
	{
		while (*path != ':') {
			*user++ = *path++;
		}
		*user = '\0';
		path++;
	}
	else
		*user = '\0';

	while (*path) {
		*dev++ = *path++;
	}
	*dev = '\0';

/*
 *	setup the pipes for the 'rsh' command and fork
 */

	if (pipe(Ptc[i]) == -1 || pipe(Ctp[i]) == -1)
		return(-1);

	if ((rc = fork()) == -1)
		return(-1);

	if (rc == 0)
	{
		close(0);
		dup(Ptc[i][0]);
		close(Ptc[i][0]); close(Ptc[i][1]);
		close(1);
		dup(Ctp[i][1]);
		close(Ctp[i][0]); close(Ctp[i][1]);
		(void) setuid (getuid ());
		(void) setgid (getgid ());
		if (*user)
		{
			execl("/usr/ucb/rsh", "rsh", system, "-l", login,
				"/etc/rmt", (char *) 0);
			execl("/usr/bin/remsh", "remsh", system, "-l", login,
				"/etc/rmt", (char *) 0);
		}
		else
		{
			execl("/usr/ucb/rsh", "rsh", system,
				"/etc/rmt", (char *) 0);
			execl("/usr/bin/remsh", "remsh", system,
				"/etc/rmt", (char *) 0);
		}

/*
 *	bad problems if we get here
 */

		perror("exec");
		exit(1);
	}

	close(Ptc[i][0]); close(Ctp[i][1]);

/*
 *	now attempt to open the tape device
 */

	sprintf(buffer, "O%s\n%d\n", device, oflag);
	if (command(i, buffer) == -1 || status(i) == -1)
		return(-1);

	return(i);
}



/*
 *	_rmt_close --- close a remote magtape unit and shut down
 */

static int _rmt_close(fildes)
int fildes;
{
	int rc;

	if (command(fildes, "C\n") != -1)
	{
		rc = status(fildes);

		abort(fildes);
		return(rc);
	}

	return(-1);
}



/*
 *	_rmt_read --- read a buffer from a remote tape
 */

static int _rmt_read(fildes, buf, nbyte)
int fildes;
char *buf;
unsigned int nbyte;
{
	int rc, i;
	char buffer[BUFMAGIC];

	sprintf(buffer, "R%d\n", nbyte);
	if (command(fildes, buffer) == -1 || (rc = status(fildes)) == -1)
		return(-1);

	for (i = 0; i < rc; i += nbyte, buf += nbyte)
	{
		nbyte = read(READ(fildes), buf, rc);
		if (nbyte <= 0)
		{
			abort(fildes);
			errno = EIO;
			return(-1);
		}
	}

	return(rc);
}



/*
 *	_rmt_write --- write a buffer to the remote tape
 */

static int _rmt_write(fildes, buf, nbyte)
int fildes;
char *buf;
unsigned int nbyte;
{
	int rc;
	char buffer[BUFMAGIC];
	int (*pstat)();

	sprintf(buffer, "W%d\n", nbyte);
	if (command(fildes, buffer) == -1)
		return(-1);

	pstat = signal(SIGPIPE, SIG_IGN);
	if (write(WRITE(fildes), buf, nbyte) == nbyte)
	{
		signal (SIGPIPE, pstat);
		return(status(fildes));
	}

	signal (SIGPIPE, pstat);
	abort(fildes);
	errno = EIO;
	return(-1);
}



/*
 *	_rmt_lseek --- perform an imitation lseek operation remotely
 */

static long _rmt_lseek(fildes, offset, whence)
int fildes;
long offset;
int whence;
{
	char buffer[BUFMAGIC];

	sprintf(buffer, "L%d\n%d\n", offset, whence);
	if (command(fildes, buffer) == -1)
		return(-1);

	return(status(fildes));
}


/*
 *	_rmt_ioctl --- perform raw tape operations remotely
 */

#ifdef RMTIOCTL
static _rmt_ioctl(fildes, op, arg)
int fildes, op;
char *arg;
{
	char c;
	int rc, cnt;
	char buffer[BUFMAGIC];

/*
 *	MTIOCOP is the easy one. nothing is transfered in binary
 */

	if (op == MTIOCTOP)
	{
		sprintf(buffer, "I%d\n%d\n", ((struct mtop *) arg)->mt_op,
			((struct mtop *) arg)->mt_count);
		if (command(fildes, buffer) == -1)
			return(-1);
		return(status(fildes));
	}

/*
 *	we can only handle 2 ops, if not the other one, punt
 */

	if (op != MTIOCGET)
	{
		errno = EINVAL;
		return(-1);
	}

/*
 *	grab the status and read it directly into the structure
 *	this assumes that the status buffer is (hopefully) not
 *	padded and that 2 shorts fit in a long without any word
 *	alignment problems, ie - the whole struct is contiguous
 *	NOTE - this is probably NOT a good assumption.
 */
/*
 * This doesn't work right so punt!
 */
	errno = 0;
	return(0);

#ifdef notdef
	if (command(fildes, "S\n") == -1 || (rc = status(fildes)) == -1)
		return(-1);

	for (; rc > 0; rc -= cnt, arg += cnt)
	{
		cnt = read(READ(fildes), arg, rc);
		if (cnt <= 0)
		{
			abort(fildes);
			errno = EIO;
			return(-1);
		}
	}

/*
 *	now we check for byte position. mt_type is a small integer field
 *	(normally) so we will check its magnitude. if it is larger than
 *	256, we will assume that the bytes are swapped and go through
 *	and reverse all the bytes
 */

	if (((struct mtget *) arg)->mt_type < 256)
		return(0);

	for (cnt = 0; cnt < rc; cnt += 2)
	{
		c = arg[cnt];
		arg[cnt] = arg[cnt+1];
		arg[cnt+1] = c;
	}

	return(0);
#endif
  }
#endif /* RMTIOCTL */

/*
 *	Added routines to replace open(), close(), lseek(), ioctl(), etc.
 *	The preprocessor can be used to remap these the rmtopen(), etc
 *	thus minimizing source changes:
 *
 *		#ifdef <something>
 *		#  define access rmtaccess
 *		#  define close rmtclose
 *		#  define creat rmtcreat
 *		#  define dup rmtdup
 *		#  define fcntl rmtfcntl
 *		#  define fstat rmtfstat
 *		#  define ioctl rmtioctl
 *		#  define isatty rmtisatty
 *		#  define lseek rmtlseek
 *		#  define lstat rmtlstat
 *		#  define open rmtopen
 *		#  define read rmtread
 *		#  define stat rmtstat
 *		#  define write rmtwrite
 *		#  define access rmtaccess
 *		#  define close rmtclose
 *		#  define creat rmtcreat
 *		#  define dup rmtdup
 *		#  define fcntl rmtfcntl
 *		#  define fstat rmtfstat
 *		#  define ioctl rmtioctl
 *		#  define lseek rmtlseek
 *		#  define open rmtopen
 *		#  define read rmtread
 *		#  define stat rmtstat
 *		#  define write rmtwrite
 *		#endif
 *
 *	-- Fred Fish
 *
 *	ADR --- I set up a <rmt.h> include file for this
 *
 */

/*
 *	Note that local vs remote file descriptors are distinquished
 *	by adding a bias to the remote descriptors.  This is a quick
 *	and dirty trick that may not be portable to some systems.
 */

#define REM_BIAS 128


/*
 *	Test pathname to see if it is local or remote.  A remote device
 *	is any string that contains ":/dev/".  Returns 1 if remote,
 *	0 otherwise.
 */
 
static int remdev (path)
register char *path;
{
#define strchr	index
	extern char *strchr ();

	if ((path = strchr (path, ':')) != NULL)
	{
		if (strncmp (path + 1, "/dev/", 5) == 0)
		{
			return (1);
		}
	}
	return (0);
}


/*
 *	Open a local or remote file.  Looks just like open(2) to
 *	caller.
 */
 
int rmtopen (path, oflag, mode)
char *path;
int oflag;
int mode;
{
	if (remdev (path))
	{
		return (_rmt_open (path, oflag, mode) + REM_BIAS);
	}
	else
	{
		return (open (path, oflag, mode));
	}
}

/*
 *	Test pathname for specified access.  Looks just like access(2)
 *	to caller.
 */
 
int rmtaccess (path, amode)
char *path;
int amode;
{
	if (remdev (path))
	{
		return (0);		/* Let /etc/rmt find out */
	}
	else
	{
		return (access (path, amode));
	}
}


/*
 *	Read from stream.  Looks just like read(2) to caller.
 */
  
int rmtread (fildes, buf, nbyte)
int fildes;
char *buf;
unsigned int nbyte;
{
	if (isrmt (fildes))
	{
		return (_rmt_read (fildes - REM_BIAS, buf, nbyte));
	}
	else
	{
		return (read (fildes, buf, nbyte));
	}
}


/*
 *	Write to stream.  Looks just like write(2) to caller.
 */
 
int rmtwrite (fildes, buf, nbyte)
int fildes;
char *buf;
unsigned int nbyte;
{
	if (isrmt (fildes))
	{
		return (_rmt_write (fildes - REM_BIAS, buf, nbyte));
	}
	else
	{
		return (write (fildes, buf, nbyte));
	}
}

/*
 *	Perform lseek on file.  Looks just like lseek(2) to caller.
 */

long rmtlseek (fildes, offset, whence)
int fildes;
long offset;
int whence;
{
	if (isrmt (fildes))
	{
		return (_rmt_lseek (fildes - REM_BIAS, offset, whence));
	}
	else
	{
		return (lseek (fildes, offset, whence));
	}
}


/*
 *	Close a file.  Looks just like close(2) to caller.
 */
 
int rmtclose (fildes)
int fildes;
{
	if (isrmt (fildes))
	{
		return (_rmt_close (fildes - REM_BIAS));
	}
	else
	{
		return (close (fildes));
	}
}

/*
 *	Do ioctl on file.  Looks just like ioctl(2) to caller.
 */
 
int rmtioctl (fildes, request, arg)
int fildes, request, arg;
{
	if (isrmt (fildes))
	{
#ifdef RMTIOCTL
		return (_rmt_ioctl (fildes - REM_BIAS, request, arg)); 
#else
		errno = EOPNOTSUPP;
		return (-1);		/* For now  (fnf) */
#endif
	}
	else
	{
		return (ioctl (fildes, request, arg));
	}
}


/*
 *	Duplicate an open file descriptor.  Looks just like dup(2)
 *	to caller.
 */
 
int rmtdup (fildes)
int fildes;
{
	if (isrmt (fildes))
	{
		errno = EOPNOTSUPP;
		return (-1);		/* For now (fnf) */
	}
	else
	{
		return (dup (fildes));
	}
}

/*
 *	Get file status.  Looks just like fstat(2) to caller.
 */
 
int rmtfstat (fildes, buf)
int fildes;
struct stat *buf;
{
	if (isrmt (fildes))
	{
		errno = EOPNOTSUPP;
		return (-1);		/* For now (fnf) */
	}
	else
	{
		return (fstat (fildes, buf));
	}
}


/*
 *	Get file status.  Looks just like stat(2) to caller.
 */
 
int rmtstat (path, buf)
char *path;
struct stat *buf;
{
	if (remdev (path))
	{
		errno = EOPNOTSUPP;
		return (-1);		/* For now (fnf) */
	}
	else
	{
		return (stat (path, buf));
	}
}



/*
 *	Create a file from scratch.  Looks just like creat(2) to the caller.
 */

#include <sys/file.h>		/* BSD DEPENDANT!!! */
/* #include <fcntl.h>		/* use this one for S5 with remote stuff */

int rmtcreat (path, mode)
char *path;
int mode;
{
	if (remdev (path))
	{
		return (rmtopen (path, 1 | O_CREAT, mode));
	}
	else
	{
		return (creat (path, mode));
	}
}

/*
 *	Isrmt. Let a programmer know he has a remote device.
 */

int isrmt (fd)
int fd;
{
	return (fd >= REM_BIAS);
}

/*
 *	Rmtfcntl. Do a remote fcntl operation.
 */

int rmtfcntl (fd, cmd, arg)
int fd, cmd, arg;
{
	if (isrmt (fd))
	{
		errno = EOPNOTSUPP;
		return (-1);
	}
	else
	{
		return (fcntl (fd, cmd, arg));
	}
}

/*
 *	Rmtisatty.  Do the isatty function.
 */

int rmtisatty (fd)
int fd;
{
	if (isrmt (fd))
		return (0);
	else
		return (isatty (fd));
}


/*
 *	Get file status, even if symlink.  Looks just like lstat(2) to caller.
 */
 
int rmtlstat (path, buf)
char *path;
struct stat *buf;
{
	if (remdev (path))
	{
		errno = EOPNOTSUPP;
		return (-1);		/* For now (fnf) */
	}
	else
	{
		return (lstat (path, buf));
	}
}
@//E*O*F rmtlib.c//
chmod u=rw,g=r,o=r rmtlib.c
 
echo Inspecting for damage in transit...
temp=/tmp/shar$$; dtemp=/tmp/.shar$$
trap "rm -f $temp $dtemp; exit" 0 1 2 3 15
cat > $temp <<\!!!
      21      47     354 Makefile
      25     136     774 README.network
     132     711    4256 copytape.1
      50     259    1472 copytape.5
     302     951    6549 copytape.c
      35     144     904 rmt.h
     845    2292   13807 rmtlib.c
    1410    4540   28116 total
!!!
wc  Makefile README.network copytape.1 copytape.5 copytape.c rmt.h rmtlib.c | sed 's=[^ ]*/==' | diff -b $temp - >$dtemp
if [ -s $dtemp ]
then echo "Ouch [diff of wc output]:" ; cat $dtemp
else echo "No problems found."
fi
exit 0
-- 
	David Robinson		elroy!david@csvax.caltech.edu     ARPA
				david@elroy.jpl.nasa.gov	  ARPA
				{cit-vax,ames}!elroy!david	  UUCP
Disclaimer: No one listens to me anyway!