1
0
Files
irix-657m-src/eoe/cmd/bstream/bstream.c
2022-09-29 17:59:04 +03:00

756 lines
19 KiB
C

#ident "$Revision: 1.18 $"
/**************************************************************************
* *
* Copyright (C) 1986, Silicon Graphics, Inc. *
* *
* These coded instructions, statements, and computer programs contain *
* unpublished proprietary information of Silicon Graphics, Inc., and *
* are protected by Federal copyright law. They may not be disclosed *
* to third parties or copied or duplicated in any form, in whole or *
* in part, without the prior written consent of Silicon Graphics, Inc. *
* *
**************************************************************************/
/*
* bstream - program to copy from standard input to standard output
* at extremely high rates using double buffering. This version is
* fully System V compatabile. 9/25/87 J. M. Barton
*
* -b size buffer size, in Kbytes
* -n cnt number of buffers to use
* -l attempt to lock segments in core
* -t attempt to lock process as well
* -d turn on debugging
* -v turn on buffer use reporting
* -s bufs statistics every "bufs" buffers
* -r summary of passthrough
* -i file file to stream from
* -o file file to stream to
*
* If files not specified, use standard input and output appropriately.
*
* $Log: bstream.c,v $
* Revision 1.18 1998/07/21 19:00:38 rwu
* Change buffer size type to allow more than 2GB.
*
* Revision 1.17 1992/12/11 15:41:17 wtk
* Correction to allow build
*
* Revision 1.16 1992/11/17 10:02:49 ism
* Fix compile errors
*
* Revision 1.15 91/12/03 00:16:07 olson
* Use waitpid instead of wait when waiting for child; my memory is fuzzy now,
* but it fixed an obscure bug, possibly relating to pipelines.
*
* Revision 1.14 91/06/21 14:30:41 jojo
* Handles new tape driver definition of end of tape.
*
* Revision 1.13 91/02/05 23:19:44 olson
* make usage message more useful
*
* Revision 1.12 91/02/05 21:37:44 olson
* changed a number of error messages to use perror; use HZ instead of
* hardcoding 100 or 64. linebuffer after initial error checking so that
* messages from input and output sides do not get intermixed.
*
* Revision 1.11 90/10/26 17:57:19 bowen
* Changed appropriate instances of strtol to strtoul.
*
* Revision 1.10 90/10/24 15:37:36 bowen
* Fixed prototype warnings.
*
* Revision 1.9 89/10/27 10:10:19 bh
* (textually a change to 1.7)
* Backed out -B mods as they caused the following bug:
* if amount to write mod 16k >512 and <16k and not a mult of 512
* then last partial (16k) block is not written (only when output is tape)
* (-B was undocumented)
* an fprintf in copyout was missing a %s arg causing seg faults in the
* child process causing the parent to hang (waiting forever for a message)
*
* Revision 1.7 88/02/23 10:00:05 eva
* added signal handlers for SIGINT, SIGQUIT and SIGTERM that clean up the
* msg queue, and added a copyright statement to the top of the file.
*
* Revision 1.7 88/02/23 09:58:53 eva
* Added signal handlers for SIGINT, SIGQUIT and SIGTERM that cleans up the
* msg queue, and put a copyright statement at the top of the file.
*
* Revision 1.6.1.1 88/02/23 09:53:26 eva
* auto-integrate
*
* Revision 1.6 87/11/27 11:36:55 jmb
* Added non-block-aligned write zero fill feature, cleaned up handling of
* small files, added parent wait for child to finish writing tape, cleaned
* up handling of end-of-file conditions on tape.
*
* Revision 1.5.1.1 87/11/27 11:32:10 jmb
* auto-integrate
*
* Revision 1.5 87/11/05 12:03:29 jmb
* Added comments and the fullbuf hysterisis code.
*
* Revision 1.4 87/09/30 18:12:13 jmb
* Working version of bstream. Checking in to modify again!
*
* Revision 1.3 87/08/29 09:57:49 jmb
* First tested version, handles multiple tapes.
* Streaming performance is about 77Kb/sec.
*
* Revision 1.2 87/08/26 15:20:47 jmb
* Comments? Comments? We don't need no stinkin' comments!
*
*/
# include <sys/types.h>
# include <sys/param.h>
# include <sys/ipc.h>
# include <sys/shm.h>
# include <sys/msg.h>
# include <sys/lock.h>
# include <sys/times.h>
# include <sys/mtio.h>
# include <signal.h>
# include <stdio.h>
# include <errno.h>
# include <fcntl.h>
# define MAXNBUF 6 /* depends on SHMSEG kernel constant */
char *pname; /* program name */
char *iname = 0; /* input file name */
char *oname = 0; /* output file name */
long bsize = 16 * 1024; /* buffer size */
int nbuf = 4; /* number of buffers */
int shm[MAXNBUF]; /* buffer descriptors */
char *buf[MAXNBUF]; /* buffer locations */
long *len[MAXNBUF]; /* length of a buffer */
char legendbuf[20]; /* for printing nicely */
int msg; /* message queue descriptor */
char lock = 0; /* lock buffers in core */
char lprog = 0; /* lock program in core */
char report = 0; /* statistics */
char istape = 0; /* if a tape device */
int blocksize = 512; /* tape block size */
int blockmask = 0x1ff; /* block size mask */
long stats = 0; /* buffer statistics */
FILE *log = 0; /* debugging log */
int proc; /* child process ID */
off_t nbytes; /* buffer size */
char summary = 0; /* summary report */
struct tms tb; /* start time */
struct tms ta; /* end time */
long ustart; /* start tick value */
long uend; /* end tick value */
int inout; /* my I/O direction */
extern int errno; /* system call error */
void copyin(void);
void copyout(void);
void goodbye(); /* handler to clean up msg q */
main(argc, argv)
int argc;
char *argv[];
{
extern int optind;
extern char *optarg;
int c;
int err = 0;
int i;
pname = argv[0];
while ((c = getopt(argc, argv, "i:o:rs:vn:b:ltd")) != EOF) {
switch (c) {
case 'r':
summary++;
break;
case 'b':
bsize = strtoul(optarg, (char **) 0, 0) * 1024;
break;
case 's':
stats = strtoul(optarg, (char **) 0, 0);
if (stats <= 1) {
fprintf(stderr,
"%s: stat size must > 1\n",
pname);
exit(1);
}
break;
case 'i':
iname = optarg;
break;
case 'o':
oname = optarg;
break;
case 'l':
lock++;
break;
case 't':
lprog++;
break;
case 'd':
log = stderr;
break;
case 'v':
report++;
break;
case 'n':
if ((nbuf = strtoul(optarg, (char **) 0, 0)) < 2 ||
nbuf > MAXNBUF) {
fprintf(stderr,
"%s: nbuf must be > 2 and < %d\n", pname, nbuf);
exit(1);
}
break;
case '?':
err++;
break;
}
}
if (err) {
fprintf(stderr, "usage: %s [-n bufs] [-b size] [-ltrvd] [-s statcnt] [-i file] [-o file]\n", pname);
exit(1);
}
for (i = optind; i < argc; i++) {
fprintf(stderr, "%s: extra argument %s ignored\n", pname,
argv[i]);
}
setbuf(stderr, NULL);
nbytes = 0;
setupbuf();
if (log)
fprintf(log, "using %d byte buffers\n", bsize);
if (lprog)
if (nice(-10) == -1 && log)
fprintf(log, "can't increase process priority\n");
if ((proc = fork()) == 0) {
if (log)
fprintf(log, "child waiting for parent\n");
if (lprog)
plock(PROCLOCK);
inout = 1;
if (summary) {
ustart = times(&tb);
strcpy(legendbuf, "output");
}
copyout();
if (summary)
printrate(0, legendbuf);
msgctl(msg, IPC_RMID, 0);
exit(0);
}
else if (proc == -1) {
fprintf(stderr, "%s: can't fork\n", pname);
msgctl(msg, IPC_RMID, 0);
exit(1);
}
/* CATCH signal so can remove msg_q */
signal(SIGINT, goodbye); /* ignore */
signal(SIGQUIT, goodbye); /* ignore */
signal(SIGTERM, goodbye); /* cleanup and quit */
if (lprog)
if (plock(PROCLOCK) == -1) {
fprintf(stderr, "%s: can't lock process in core\n",
pname);
}
inout = 0;
if (summary) {
ustart = times(&tb);
strcpy(legendbuf, "input");
}
copyin();
if (summary)
printrate(0, legendbuf);
if(waitpid(proc, NULL, 0) == -1)
perror("Child bstream process could not be waited for");
exit(0);
}
/*
* Copyin and copyout are really quite simple. copyin() is run by the parent,
* and it's task is to copy the input to the buffers. As each buffer is
* filled, copyin() sends a message to the child informing it of the full
* buffer. copyout() is run by the child. It's job is to copy out
* of the buffers to the output. As each buffer is emptied, it sends a
* message to the parent informing it that a buffer is available for input.
*/
void
copyin(void)
{
register long cnt;
register long nb;
register long bleft;
register int i;
register int done = 0;
if (log)
fprintf(log, "parent starting\n");
if (iname) {
close(0);
if (open(iname, O_RDONLY) == -1) {
fprintf(stderr, "%s: can't open input ", pname);
perror(iname);
goodbye();
}
}
istape = isatape();
while (1) {
i = getfreebuf();
cnt = 0;
while ((cnt < bsize) && !done) {
bleft = bsize - cnt;
nb = read(0, buf[i]+cnt, bleft);
/* check for eom */
if ((nb == -1) || (istape && (nb == 0))) {
if ((nb == -1) && !((errno == ENXIO) && istape)) {
fprintf(stderr, "%s: read ", pname);
perror("error");
goodbye();
}
nb = chkinout(buf[i]+cnt, bleft);
/* End of the tape? */
if (nb == -1) {
done = 1;
break;
}
if (nb == 0) {
fprintf(stderr,
"%s: read recovery failed\n",
pname);
goodbye();
}
else {
cnt += nb;
continue;
}
}
if (log)
fprintf(log, "parent reads %d bytes to %#x\n",
nb, buf[i]+cnt);
if (nb == 0)
break;
cnt += nb;
}
nbytes += cnt;
*(len[i]) = cnt;
fullbuf(i);
if (report)
fprintf(stderr, "--> %d #%d\n", cnt, i);
if (cnt == 0)
return;
}
}
void
copyout(void)
{
register long out;
register long cnt;
register long nb;
register int i;
if (log)
fprintf(log, "child starting\n");
if (oname) {
close(1);
if (open(oname, O_WRONLY) == -1) {
fprintf(stderr, "%s: can't open output ", pname);
perror(oname);
goodbye();
}
}
istape = isatape();
while (1) {
i = getfullbuf();
if ((out = *(len[i])) == 0) {
if (log)
fprintf(log, "no more data to be passed\n");
return;
}
cnt = 0;
while (out > 0) {
if (istape && (out & blockmask) != 0) {
register int k;
register int l;
k = cnt + out;
l = (k + blocksize) & ~blockmask;
for (; k < l; k++)
*(buf[i]+k) = 0;
out = (out + (blocksize - 1)) & ~blockmask;
fprintf(stderr,
"%s: warning: last tape write rounded up to tape block size\n",
pname);
}
if ((nb = write(1, buf[i]+cnt, out)) == -1) {
/*
* This code is dependent on the brain-damaged
* end-of-medium handling in the tape
* driver. Check this area if problems when
* porting.
*/
if (!(istape && ((errno == ENXIO) || (errno == ENOSPC)))) {
fprintf(stderr, "%s ", pname);
perror("write error on output");
goodbye();
}
nb = chkinout(buf[i]+cnt, out);
if (nb == 0) {
fprintf(stderr, "%s: write recovery failed\n",
pname);
goodbye();
}
}
if (log)
fprintf(log, "child wrote %d bytes from %#x\n",
nb, buf[i]+cnt);
cnt += nb;
out -= nb;
}
nbytes += cnt;
if (report)
fprintf(stderr, "<-- %d #%d\n", cnt, i);
freebuf(i);
}
}
/*
* This routine goes through the rather gross setup needed to create the
* buffers and message queue. Since we can get the OS to automatically
* dispose of the buffers for us on exit, we do so. Too bad messages
* don't have the same property.
*/
setupbuf()
{
int i;
bsize += sizeof(long);
for (i = 0; i < nbuf; i++) {
if ((shm[i] = shmget(IPC_PRIVATE, bsize, IPC_CREAT|0600))
== -1) {
register int j;
fprintf(stderr,
"%s: can't allocate %d byte segment %d\n",
pname, bsize, i);
perror(pname);
for (j = 0; j < i; j++)
shmctl(shm[j], IPC_RMID, 0);
exit(1);
}
}
bsize -= sizeof(long);
for (i = 0; i < nbuf; i++) {
if ((buf[i] = (char *) shmat(shm[i], 0, 0))
== (char *) -1) {
fprintf(stderr,
"%s: can't attach to shared segment %d\n",
pname, i);
perror(pname);
for (i = 0; i < nbuf; i++)
shmctl(shm[i], IPC_RMID, 0);
exit(1);
}
len[i] = (long *) (buf[i] + bsize);
if (log)
fprintf(log, "segment %d attached at %#x\n", i, buf[i]);
}
#ifdef mips
if (lock) {
for (i = 0; i < nbuf; i++)
if (shmctl(shm[i], SHM_LOCK, 0) == -1)
fprintf(stderr,
"%s: warning: can't lock shared segments\n",
pname);
}
#endif
for (i = 0; i < nbuf; i++)
shmctl(shm[i], IPC_RMID, 0);
if ((msg = msgget(IPC_PRIVATE, IPC_CREAT|0660)) == -1) {
fprintf(stderr, "%s: can't allocate message queue\n", pname);
perror(pname);
exit(1);
}
for (i = 0; i < nbuf; i++)
freebuf(i);
}
void
goodbye()
{
msgctl(msg, IPC_RMID, 0);
if(summary && nbytes>0)
/* print summary even on errors, if anything was done. This
* is useful when copying to devices like disks that hit
* the end of the disk and return ENOSPC, etc. */
printrate(0, legendbuf);
if (proc)
kill(proc, SIGTERM);
else
kill(getppid(), SIGTERM);
exit(1);
}
/*
* The following routines deal with the message queue, and implement the
* buffer management protocol. At initialization, messages for the number
* of available buffers are placed on the queue as empty buffers. The
* reader simply blocks waiting for one of these messages. When one arrives,
* he fills the buffer and then sends a buffer full message. The writer
* blocks on the queue waiting for buffer full messages. As he gets each
* one, he writes the buffer and then enters it back on the queue as empty.
* Thus, once started, the management is self-sustaining. A criticial note:
* this code relies on the message queue staying ordered.
*/
# define MSGSZ (sizeof(pbuf)-sizeof(long))
# define DOBUF 1
# define FREEBUF 2
typedef struct {
long m_type;
int m_buf;
} bufmsg_t;
fullbuf(sp)
int sp;
{
bufmsg_t pbuf;
static long lstat = 0;
static struct tms tbefore;
static long tstart;
struct tms tafter;
long tend;
long bufs;
if (log)
fprintf(log, "buf %d full\n", sp);
pbuf.m_type = DOBUF;
pbuf.m_buf = sp;
bufs = *(len[sp]);
if (msgsnd(msg, (const struct msgbuf *)&pbuf, MSGSZ, 0) == -1) {
fprintf(stderr, "%s: message send %d ", pname, sp);
perror("failed");
goodbye();
}
if (stats) {
if (lstat == 0)
tstart = times(&tbefore);
lstat++;
if (lstat > stats) {
tend = times(&tafter);
lstat *= bsize;
fprintf(stderr, "IN: %d Kbytes/sec\n",
((lstat / (tend - tstart)) * HZ) / 1024);
lstat = 0;
}
}
}
freebuf(sp)
int sp;
{
bufmsg_t pbuf;
static long lstat = 0;
static struct tms tbefore;
static long tstart;
struct tms tafter;
long tend;
long bufs;
if (log)
fprintf(log, "buf %d freed\n", sp);
pbuf.m_type = FREEBUF;
pbuf.m_buf = sp;
bufs = *(len[sp]);
if (msgsnd(msg, (const struct msgbuf *)&pbuf, MSGSZ, 0) == -1) {
fprintf(stderr, "%s: message send %d ", pname, sp);
perror("failed");
goodbye();
}
if (stats) {
if (lstat == 0)
tstart = times(&tbefore);
lstat++;
if (lstat > stats) {
tend = times(&tafter);
lstat *= bsize;
fprintf(stderr, "OUT: %d Kbytes/sec\n",
((lstat / (tend - tstart)) * HZ) / 1024);
lstat = 0;
}
}
}
getfullbuf()
{
bufmsg_t pbuf;
static int bufcache[MAXNBUF];
static int ncache = 0;
int half = nbuf / 2;
/*
* This routine attempts to add some hysterisis to the output,
* to help insure maximum overlap between input and output.
* This is done by not starting to write buffers until at least
* half are full, which helps if the reader is slower than the
* writer, making the output flow stop less often. Otherwise,
* the writer could just chase the reader around the buffer
* list, having to wait on each new buffer.
*/
if (ncache > 0)
return(bufcache[--ncache]);
ncache = half;
while (ncache > 0) {
if (msgrcv(msg,(struct msgbuf *)&pbuf,MSGSZ,DOBUF,0) == -1) {
fprintf(stderr, "%s: message receive ", pname);
perror("failed");
goodbye();
}
bufcache[--ncache] = pbuf.m_buf;
if (*(len[pbuf.m_buf]) == 0)
break;
}
ncache = half;
return(bufcache[--ncache]);
}
getfreebuf()
{
bufmsg_t pbuf;
if (log)
fprintf(log, "waiting for free buffer ...\n");
if (msgrcv(msg, (struct msgbuf *)&pbuf, MSGSZ, FREEBUF, 0) == -1) {
fprintf(stderr, "%s: message receive ", pname);
perror("failed");
goodbye();
}
if (log)
fprintf(log, "got empty buffer %d\n", pbuf.m_buf);
return(pbuf.m_buf);
}
/*
* This code implements the multi-volume tape handling strategy.
* If the driver gets smarter and tells us more, this routine can
* shrink and become more intelligent. For instance, it would be REALLY
* nice if re-setting the driver really worked, so we didn't have to
* explicitly know the tape special file name.
*/
chkinout(where, amount)
char *where;
int amount;
{
FILE *id;
char buf[BUFSIZ];
char *s;
int nb;
static int ntapes = 1;
int nmt;
char *td;
if (log)
fprintf(stderr, "checking for tape input/output\n");
if (!isatty(2))
return(0);
if ((td = (inout?oname:iname)) == 0)
return(0);
if ((id = fopen("/dev/tty", "r")) == NULL)
return(0);
if (summary) {
sprintf(legendbuf, "tape %d", ntapes++);
printrate(1, legendbuf);
sprintf(legendbuf, "tape %d", ntapes);
}
close(inout);
gettape:
fprintf(stderr, "<%s> Change tape and type RETURN or \"quit\":", td);
fflush(stderr);
if (fgets(buf, BUFSIZ, id) == NULL) {
fprintf(stderr, "%s: end of input on tty\n", pname);
goodbye();
}
for (s = buf; *s != '\0' && (*s == ' ' || *s == '\t'); s++);
if (strncmp(s, "quit", 4) == 0 || strncmp(s, "q", 1) == 0) {
fclose(id);
return(-1);
}
else if (*s != '\n')
fprintf(stderr, "%s: extra arguments ignored; re-trying\n",
pname);
fclose(id);
if (open(td, (inout?O_WRONLY:O_RDONLY)) == -1) {
fprintf(stderr, "%s: can't open drive %s\n", pname, td);
goto gettape;
}
retry:
if (inout) {
if ((nb = write(1, where, amount)) < 0) {
fprintf(stderr, "%s: write ", pname);
perror("error");
goodbye();
}
}
else {
if ((nb = read(0, where, amount)) < 0) {
fprintf(stderr, "%s: read ", pname);
perror("error");
goodbye();
}
}
return(nb);
}
printrate(upd, legend)
int upd;
char *legend;
{
register double utime;
register double stime;
register double ticks;
uend = times(&ta);
ticks = uend - ustart;
utime = (ta.tms_utime - tb.tms_utime);
stime = (ta.tms_stime - tb.tms_stime);
/* called late, so no worry about error messages without
* newlines; if we don't do this, sometimes the in and out
* messages get intermixed. */
setlinebuf(stderr);
fprintf(stderr, "%s: %s: %lld Kb %lld b/s, %%%2.2f u, %%%2.2f s\n",
pname, legend, (nbytes/1024), (off_t)((nbytes / ticks) * HZ),
((utime / ticks) * HZ),
((stime / ticks) * HZ));
nbytes = 0;
if (upd)
ustart = times(&tb);
}
isatape()
{
struct mtop mtb;
mtb.mt_op = MTNOP;
mtb.mt_count = 1;
if (ioctl(inout, MTIOCTOP, &mtb) == -1)
return(0);
return(1);
}