Subject: v22i006: Send multiple inputs to multiple outputs Newsgroups: comp.sources.unix Approved: rsalz@uunet.UU.NET X-Checksum-Snefru: 20a304ad ccfd1bd0 63a78fe0 7093dc95 Submitted-by: Dan Bernstein Posting-number: Volume 22, Issue 6 Archive-name: multitee2.0 [ The following one-line summary is really all you need, any other attempt I made to abstract the manpage ended up with the whole thing. :-) --r$ ] multitee - send multiple inputs to multiple outputs This program is designed for BSD 4.3 variants. Earlier systems don't provide true per-process non-blocking output. Files: CHANGES Description of changes since first distributed version README This document Makefile Installation commands multitee.c The program multitee.man Documentation Edit the options in Makefile and type make. multitee will be the executable program; multitee.1 will be the nroff'ed documentation. I don't pretend to know your machine's setup so there's no make install. Read CHANGES for a list of changes. Type multitee -C and multitee -W for copyright and warranty information, multitee -H for help. ---Dan #! /bin/sh # This is a shell archive. Remove anything before this line, then unpack # it by saving it into a file and typing "sh file". To overwrite existing # files, type "sh file -c". You can also feed this as standard input via # unshar, or by typing "sh 'CHANGES' <<'END_OF_FILE' X3/27/90: multitee version 2.0 X XBuffer limits so multitee won't take over the machine. X XDies correctly. XLots of minor improvements. XAll error cases dealt with. X X2/21/90: multitee version 1.0 END_OF_FILE if test 190 -ne `wc -c <'CHANGES'`; then echo shar: \"'CHANGES'\" unpacked with wrong size! fi # end of 'CHANGES' fi if test -f 'README' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'README'\" else echo shar: Extracting \"'README'\" \(857 characters\) sed "s/^X//" >'README' <<'END_OF_FILE' Xmultitee - send multiple inputs to multiple outputs X XThis program is designed for BSD 4.3 variants. Earlier systems don't Xprovide true per-process non-blocking output. X Xmultitee version 2.0, March 27, 1990. XCopyright (c) 1990, Daniel J. Bernstein. XAll rights reserved. X XThis distribution packaged March 27, 1990. X XFiles: XCHANGES Description of changes since first distributed version XREADME This document XMakefile Installation commands Xmultitee.c The program Xmultitee.man Documentation X XEdit the options in Makefile and type make. multitee will be the Xexecutable program; multitee.1 will be the nroff'ed documentation. X XI don't pretend to know your machine's setup so there's no make install. X XRead CHANGES for a list of changes. Type multitee -C and multitee -W Xfor copyright and warranty information, multitee -H for help. END_OF_FILE if test 857 -ne `wc -c <'README'`; then echo shar: \"'README'\" unpacked with wrong size! fi # end of 'README' fi if test -f 'Makefile' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'Makefile'\" else echo shar: Extracting \"'Makefile'\" \(822 characters\) sed "s/^X//" >'Makefile' <<'END_OF_FILE' XCC=cc XCCOPTS=-O -UNOSIGINTERRUPT XNROFFOPTS=-man X X# -DNOSIGINTERRUPT if your machine does not have siginterrupt(): X# such machines do not support per-process non-blocking I/O X# -DFDTABLESIZE=nn to override default of FD_SETSIZE X# -DBUFINSIZE=nnnn to override default of 4096 X# -DMAXBUFOUTSIZE=nnnn to override the default default of 16384 X Xdefault: all X Xall: multitee multitee.1 X Xshar: multitee.shar X Xmultitee: multitee.o Makefile X $(CC) $(CCOPTS) -o multitee multitee.o X Xmultitee.o: multitee.c Makefile X $(CC) $(CCOPTS) -c multitee.o multitee.c X Xmultitee.1: multitee.man Makefile X nroff $(NROFFOPTS) < multitee.man > multitee.1 X Xmultitee.shar: CHANGES README Makefile multitee.man multitee.c djberr.h X shar CHANGES README Makefile multitee.man multitee.c djberr.h > multitee.shar X chmod 400 multitee.shar END_OF_FILE if test 822 -ne `wc -c <'Makefile'`; then echo shar: \"'Makefile'\" unpacked with wrong size! fi # end of 'Makefile' fi if test -f 'multitee.man' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'multitee.man'\" else echo shar: Extracting \"'multitee.man'\" \(3286 characters\) sed "s/^X//" >'multitee.man' <<'END_OF_FILE' X.TH multitee 1 X.SH NAME Xmultitee \- send multiple inputs to multiple outputs X.SH SYNTAX Xmultitee X[ X\fB\-o\fIsz X] [ X\fB\-bBv\fI X] [ X\fB\-ACHUVW\fI X] [ X\fIfd-fd,fd,fd... X] ... X.SH DESCRIPTION X.I multitee Xsends multiple inputs to multiple outputs. XGiven an argument of the form X.I fdin-fdout,fdout,fdout... Xit will send all input on file descriptor X.I fdin Xto each descriptor X.I fdout. X.I multitee Xwill never block reading or writing a descriptor X(unless it runs out of buffer space---see below). X.PP XIn the X.I fd Xarguments, hyphens can be replaced with colons, Xand any 0 can be omitted. Other than that, each X.I fd Xmust be an integer in the right range. XFuture extensions to X.I multitee Xmay abuse the syntax of these arguments, Xso don't insert any extra characters. X.PP XOptions X.B ACHUVW Xprint the authorship notice, Xcopyright notice, Xhelp notice, Xshort usage summary, Xversion number, Xand warranty information respectively. X.PP X.I multitee Xhas several flags: X.TP 12 X\fB\-o\fIsz XSet maximum output buffer size to X.I sz, Xdefault 16384. XIf X.I multitee Xoverflows an output buffer, Xit will not read any more characters until the buffer Xempties down to the maximum buffer size. XThe actual maximum space taken by buffers is X.I sz Xfor each output buffer, plus 4096 per Xoutput X.I fd, Xthough it would take a lot of effort to Xachieve the latter. X.TP X\fB\-b XNever block, unless out of buffer space. XThis is the default. XFor example, if one output stream is blocked, X.I multitee Xwill not sit around waiting for it to clear up before it Xprocesses characters waiting on other streams. This helps Xprevent deadlock. X.TP X\fB\-B XDon't be so paranoid about blocking. XSome systems do not support true per-process non-blocking I/O; Xon such systems, X.I multitee Xwill refuse to run unless you specify X.B\-B. XUnder X.B\-B, X.I multitee Xmight block when, for instance, Xit writes more bytes onto a pipe than the pipe has free. X.PP X.SH "EXIT VALUE" X0 if all inputs reach end-of-file normally. X1 for usage messages. X2 if a file descriptor is closed. X3 if X.I multitee Xruns out of memory. X4 in various impossible situations. X5 if you don't specify X.B\-B Xon machines that don't support true per-process non-blocking I/O. X6 on an I/O error. X7 on an unrecognized error. X8 if X.I multitee Xhits a secondary storage resource limit X(past quota, no more disk space, past process file limit). X.SH DIAGNOSTICS X.TP X.I fatal: no memory X.I multitee Xhas run out of memory. X.TP X.I fatal: out of space X.I multitee Xhas exhausted a secondary storage resource, Xsuch as disk space, disk quota, or maximum file size. X.TP X.I fatal: nonblocking I/O not supported XSelf-explanatory. X.TP X.I fatal: unopened fd XOne of the file descriptors is not open. XSorry, there's no way to figure out which one. X.TP X.I fatal: I/O error X.I multitee Xhas encountered an error in disk input or output. X.TP X.I weird XThis includes various errors, none of which can possibly happen. X.SH RESTRICTIONS X.I multitee Xsimply can't work on machines without X.I siginterrupt(3) Xas well as it can on machines with that call. X.SH BUGS XNone known. X.SH MACHINES X.I multitee Xhas been tested Xon an Astronautics ZS-2 running ZSUnix Xand a Sun 4 running SunOS. X.SH VERSION Xmultitee version 2.0, dated March 27, 1990. X.SH AUTHOR XCopyright 1990, Daniel J. Bernstein. X.SH "SEE ALSO" Xtee(1) END_OF_FILE if test 3286 -ne `wc -c <'multitee.man'`; then echo shar: \"'multitee.man'\" unpacked with wrong size! fi # end of 'multitee.man' fi if test -f 'multitee.c' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'multitee.c'\" else echo shar: Extracting \"'multitee.c'\" \(13252 characters\) sed "s/^X//" >'multitee.c' <<'END_OF_FILE' X/* Xmultitee.c: send multiple inputs to multiple outputs X*/ X Xstatic char multiteeauthor[] = X"multitee was written by Daniel J. Bernstein.\n\ XInternet address: brnstnd@acf10.nyu.edu.\n"; X Xstatic char multiteeversion[] = X"multitee version 2.0, March 27, 1990.\n\ XCopyright (c) 1990, Daniel J. Bernstein.\n\ XAll rights reserved.\n"; X Xstatic char multiteecopyright[] = X"multitee version 2.0, March 27, 1990.\n\ XCopyright (c) 1990, Daniel J. Bernstein.\n\ XAll rights reserved.\n\ X\n\ XUntil January 1, 2095, you are granted the following rights: A. To make\n\ Xcopies of this work in original form, so long as (1) the copies are exact\n\ Xand complete; (2) the copies include the copyright notice, this paragraph,\n\ Xand the disclaimer of warranty in their entirety. B. To distribute this\n\ Xwork, or copies made under the provisions above, so long as (1) this is\n\ Xthe original work and not a derivative form; (2) you do not charge a fee\n\ Xfor copying or for distribution; (3) you ensure that the distributed form\n\ Xincludes the copyright notice, this paragraph, and the disclaimer of\n\ Xwarranty in their entirety. These rights are temporary and revocable upon\n\ Xwritten, oral, or other notice by Daniel J. Bernstein. These rights are\n\ Xautomatically revoked on January 1, 2095. This copyright notice shall be\n\ Xgoverned by the laws of the state of New York.\n\ X\n\ XIf you have questions about multitee or about this copyright notice,\n\ Xor if you would like additional rights beyond those granted above,\n\ Xplease feel free to contact the author at brnstnd@acf10.nyu.edu\n\ Xon the Internet.\n"; X Xstatic char multiteewarranty[] = X"To the extent permitted by applicable law, Daniel J. Bernstein disclaims\n\ Xall warranties, explicit or implied, including but not limited to the\n\ Ximplied warranties of merchantability and fitness for a particular purpose.\n\ XDaniel J. Bernstein is not and shall not be liable for any damages,\n\ Xincidental or consequential, arising from the use of this program, even\n\ Xif you inform him of the possibility of such damages. This disclaimer\n\ Xshall be governed by the laws of the state of New York.\n\ X\n\ XIn other words, use this program at your own risk.\n\ X\n\ XIf you have questions about multitee or about this disclaimer of warranty,\n\ Xplease feel free to contact the author at brnstnd@acf10.nyu.edu\n\ Xon the Internet.\n"; X Xstatic char multiteeusage[] = X"Usage: multitee [ -osz ] [ -bBvACHUVW ] [ fd-fd,fd,fd... ] ... \n\ XHelp: multitee -H\n"; X Xstatic char multiteehelp[] = X"multitee sends multiple inputs to multiple outputs.\n\ X\n\ Xmultitee -A: print authorship notice\n\ Xmultitee -C: print copyright notice\n\ Xmultitee -H: print this notice\n\ Xmultitee -U: print short usage summary\n\ Xmultitee -V: print version number\n\ Xmultitee -W: print disclaimer of warranty\n\ X\n\ Xmultitee [ -osz ] [ -bBv ][ fd-fd,fd,fd... ] ... : read and write descriptors\n\ X -osz: limit size of each output buffer to sz (default 16384)\n\ X -b: never block, no matter what (default)\n\ X -B: don't worry so much about blocking (forced on some systems)\n\ X -v: print error messages to stderr\n\ X fdin-fdout,fdout,fdout...: read from fdin, write to each fdout\n\ X (0 can be left out, - can be replaced by :)\n\ XExamples: multitee 0:1,2 like normal tee\n\ X multitee -- -1,2 same thing (-- ends options; -1,2 is 0-1,2)\n\ X multitee 0-6 6-1 send 0 in to 6 out while sending 6 in to 1 out\n\ X\n\ XIf you have questions about or suggestions for multitee, please feel free\n\ Xto contact the author, Daniel J. Bernstein, at brnstnd@acf10.nyu.edu\n\ Xon the Internet.\n"; X X#include /* just for EOF, sigh */ X#include X#include X#include Xextern char *malloc(); /* sigh */ X#include Xextern int errno; Xextern int getopt(); Xextern char *optarg; /* these should be in getopt.h! */ Xextern int optind; X#include "djberr.h" X X#define copy(dst,src,num) bcopy(src,dst,num) X X#ifndef FDTABLESIZE X#define FDTABLESIZE FD_SETSIZE /* or NOFILE from param.h? */ X#endif X Xstruct fdlist X { X int fd; X struct fdlist *next; X } X; X#define FDNULL ((struct fdlist *) 0) X X#ifndef BUFINSIZE X#define BUFINSIZE 4096 X#endif X X#ifndef MAXBUFOUTSIZE X#define MAXBUFOUTSIZE 16384 X#endif X X/* The following kludges always work and are better than nothing. */ X#ifndef EINVAL X#define EINVAL 0 X#endif X#ifndef EBADF X#define EBADF 0 X#endif X#ifndef EINTR X#define EINTR 0 X#endif X#ifndef EWOULDBLOCK X#define EWOULDBLOCK 0 X#endif X#ifndef EFAULT X#define EFAULT 0 X#endif X#ifndef EIO X#define EIO 0 X#endif X#ifndef EPIPE X#define EPIPE 0 X#endif X#ifndef EFBIG X#define EFBIG 0 X#endif X#ifndef ENOSPC X#define ENOSPC 0 X#endif X#ifndef EDQUOT X#define EDQUOT 0 X#endif X Xvoid nothing() X{ X} X Xmain(argc,argv,envp) Xint argc; Xchar *argv[]; Xchar *envp[]; X{ X int opt; X char *bufin; X int bufinsize; X char *oldbuf; X char *bufout[FDTABLESIZE]; X int bufoutsize[FDTABLESIZE]; X int bufoutpos[FDTABLESIZE]; X fd_set rfds; X fd_set wfds; X int numrfds; X int numwfds; X int numfds; X struct fdlist *fds[FDTABLESIZE]; X register struct fdlist *t; X register int i; X register int j; X register int r; X struct itimerval it; X int flagneverblock = 1; X int flagverbose = 0; X int maxbufoutsize = MAXBUFOUTSIZE; X int flagpastmax = 0; X int flagdie = 0; X X while ((opt = getopt(argc,argv,"o:bBvACHUVW")) != EOF) X switch(opt) X { X case 'o': maxbufoutsize = atoi(optarg); break; X case 'b': flagneverblock = 1; break; X case 'B': flagneverblock = 0; break; X case 'v': flagverbose = 1; break; X case 'A': (void) err(multiteeauthor); exit(1); X case 'C': (void) err(multiteecopyright); exit(1); X case 'H': (void) err(multiteehelp); exit(1); X case 'U': (void) err(multiteeusage); exit(1); X case 'V': (void) err(multiteeversion); exit(1); X case 'W': (void) err(multiteewarranty); exit(1); X case '?': (void) err(multiteeusage); exit(1); X } X argv += optind, argc -= optind; X X numrfds = numwfds = 0; X while (*argv) X { X i = 0; X while (**argv >= '0' && **argv <= '9') X i = 10 * i + *((*argv)++) - '0'; X if (i < 0 || i >= FDTABLESIZE) X { if (flagverbose) err(multiteeusage); exit(1); } X if (**argv != '-' && **argv != ':') X { if (flagverbose) err(multiteeusage); exit(1); } X if (i + 1 > numrfds) numrfds = i + 1; X do X { X (*argv)++; X j = 0; X while (**argv >= '0' && **argv <= '9') X j = 10 * j + *((*argv)++) - '0'; X if (j < 0 || j >= FDTABLESIZE) X { if (flagverbose) err(multiteeusage); exit(1); } X if (!(t = (struct fdlist *) malloc(sizeof(struct fdlist)))) X /* XXXXXX: should have manually allocated avail list */ X { if (flagverbose) errn("multitee: fatal: no memory"); exit(3); } X t->fd = j; X t->next = fds[i]; X fds[i] = t; X if (j + 1 > numwfds) numwfds = j + 1; X } X while(**argv == ','); X if (**argv) /* not a comma but not a zero */ X { if (flagverbose) err(multiteeusage); exit(1); } X argv++; X } X X bufinsize = BUFINSIZE; X if (!(bufin = malloc((unsigned) bufinsize))) X { if (flagverbose) errn("multitee: fatal: no memory"); exit(3); } X if (numrfds > numwfds) X numfds = numrfds; X else X numfds = numwfds; X if (flagneverblock) X { X#ifdef NOSIGINTERRUPT X if (flagverbose) errn("multitee: fatal: nonblocking I/O not supported"); X exit(5); X#else X (void) siginterrupt(SIGALRM,1); X (void) signal(SIGALRM,nothing); X#endif X } X for (;;) X { X FD_ZERO(&rfds); X if (flagpastmax == 0) /* as long as somebody's past, we'll block */ X for (i = 0;i < numrfds;i++) X if (fds[i] != FDNULL) X FD_SET(i,&rfds); X r = 0; X FD_ZERO(&wfds); X for (i = 0;i < numwfds;i++) X if (bufoutpos[i]) X { X r++; X FD_SET(i,&wfds); X } X if (flagdie && !r) X exit(0); /* aaah, a normal exit */ X r = select(numfds,&rfds,&wfds,(fd_set *) 0,(struct timeval *) 0); X if (r == -1) X switch(errno) X { X case EINTR: X#ifndef NOSIGINTERRUPT X it.it_value.tv_sec = it.it_interval.tv_sec = 0; X it.it_value.tv_usec = it.it_interval.tv_usec = 0; X (void) setitimer(ITIMER_REAL,&it,(struct itimerval *) 0); X#endif X break; /* turn ALRM off---usually unnecessary */ X case EINVAL: X break; /* impossible */ X case EBADF: X if (flagverbose) errn("multitee: fatal: unopened fd"); X exit(2); /* annoying: no way to find the bad fd! */ X default: X break; /* XXXXXX */ X } X else if (r > 0) X { X if (flagneverblock) X { X#ifndef NOSIGINTERRUPT X it.it_value.tv_sec = it.it_interval.tv_sec = 0; X it.it_value.tv_usec = it.it_interval.tv_usec = 10000; X (void) setitimer(ITIMER_REAL,&it,(struct itimerval *) 0); X#endif X } X for (i = 0;i < numrfds;i++) X if (FD_ISSET(i,&rfds)) X { X r = read(i,bufin,bufinsize); X if (r == -1) X switch(errno) X { X case EINTR: case EWOULDBLOCK: X break; /* why readable, then? */ X case EBADF: X if (flagverbose) errn("multitee: weird: unopened read fd"); X exit(4); /* impossible: select would know */ X case EINVAL: X if (flagverbose) errn("multitee: weird: negative read fd"); X exit(4); /* impossible: select would know */ X case EFAULT: X if (flagverbose) errn("multitee: weird: read memory fault"); X exit(4); /* impossible: bufin is allocated */ X case EIO: X if (flagverbose) errn("multitee: fatal: read I/O error"); X exit(6); /* XXXXXX: there's almost certainly a better way */ X default: X if (flagverbose) perrn2("%s","multitee: weird: unknown error"); X exit(7); /* if we dunno what's happening, too bad */ X } X else if (r == 0) /* EOF */ X { X while (fds[i] != FDNULL) X { X t = fds[i]; X fds[i] = t->next; X (void) free((char *) t); X } X /* XXXXXX: should have option for ``die on eof here'' */ X while (fds[numrfds - 1] == FDNULL) X numrfds--; X if (numrfds == 0) X flagdie = 1; X } X else X for (t = fds[i];t != FDNULL;t = t->next) X { X if (bufoutpos[t->fd] > maxbufoutsize) /* anti-aack! */ X flagpastmax--; /* otherwise we'd count this pastmax twice */ X if (bufoutsize[t->fd] - bufoutpos[t->fd] < r) X { X bufoutsize[t->fd] = ((bufoutpos[t->fd] + r + 255) / 256) * 256; X /* can't use realloc: it doesn't return sensible errors */ X oldbuf = bufout[t->fd]; X bufout[t->fd] = malloc((unsigned) bufoutsize[t->fd]); X if (bufout[t->fd]) X { X (void) copy(bufout[t->fd],oldbuf,bufoutpos[t->fd]); X (void) free(oldbuf); X } X else X { /* damn, out of memory */ X bufout[t->fd] = oldbuf; X /* XXXXXX: shouldn't just die... */ X if (flagverbose) errn("multitee: fatal: no memory"); X exit(3); X } X } X (void) copy(bufout[t->fd] + bufoutpos[t->fd],bufin,r); X bufoutpos[t->fd] += r; X if (bufoutpos[t->fd] > maxbufoutsize) /* aack! */ X flagpastmax++; X } X } X for (i = 0;i < numwfds;i++) X if (FD_ISSET(i,&wfds)) X { X r = write(i,bufout[i],bufoutpos[i]); X if (r == -1) X switch(errno) X { X case EINTR: case EWOULDBLOCK: X break; /* why writable, then? */ X case EBADF: X if (flagverbose) errn("multitee: weird: unopened write fd"); X exit(4); /* impossible: select would know */ X case EINVAL: X if (flagverbose) errn("multitee: weird: negative write fd"); X exit(4); /* impossible: select would know */ X case EFAULT: X if (flagverbose) errn("multitee: weird: write memory fault"); X exit(4); /* impossible: bufout[i] is allocated */ X case EPIPE: X for (j = 0;j < numrfds;j++) X { X t = fds[j]; X /* XXXXXX: should have option ``die on broken pipe here'' */ X while (t != FDNULL) X if (t->fd == i) X { X free((char *) t); X t = fds[j]; /* should use extra variable, not retrace */ X } X else X t = t->next; X if (fds[j] == FDNULL) X { X /* basically same as eof on read */ X while (fds[numrfds - 1] == FDNULL) X numrfds--; X if (numrfds == 0) X flagdie = 1; X } X } X break; X case EFBIG: case ENOSPC: case EDQUOT: X if (flagverbose) errn("multitee: fatal: out of space"); X exit(8); /* XXXXXX: what to do? this is difficult */ X case EIO: X if (flagverbose) errn("multitee: fatal: write I/O error"); X exit(6); /* XXXXXX: there's almost certainly a better way */ X default: X if (flagverbose) perrn2("%s","multitee: weird: unknown error"); X exit(7); /* if we dunno what's happening, too bad */ X } X else if (r == 0) X { X ; /* wtf does this mean? */ X } X else X { X if (bufoutpos[i] > maxbufoutsize) /* anti-aack! */ X flagpastmax--; X bufoutpos[i] -= r; X if (bufoutpos[i] > 0) /* worth trading test for a function call */ X copy(bufout[i],bufout[i] + r,bufoutpos[i]); X if (bufoutpos[i] > maxbufoutsize) /* aack! */ X flagpastmax++; X } X } X if (flagneverblock) X { X#ifndef NOSIGINTERRUPT X it.it_value.tv_sec = it.it_interval.tv_sec = 0; X it.it_value.tv_usec = it.it_interval.tv_usec = 0; X (void) setitimer(ITIMER_REAL,&it,(struct itimerval *) 0); X#endif X } X } X } X} END_OF_FILE if test 13252 -ne `wc -c <'multitee.c'`; then echo shar: \"'multitee.c'\" unpacked with wrong size! fi # end of 'multitee.c' fi if test -f 'djberr.h' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'djberr.h'\" else echo shar: Extracting \"'djberr.h'\" \(491 characters\) sed "s/^X//" >'djberr.h' <<'END_OF_FILE' X/* djberr.h, 11/1/89. */ X X#ifndef __DJBERRH_ X#define __DJBERRH_ X Xextern int errno; X X#define errn(s) (((void) fputs(s,stderr)), putc('\n',stderr)) X#define err(s) (fputs(s,stderr)) X#define errn2(s,t) (((void) fprintf(stderr,s,t)), putc('\n',stderr)) X#define errn3(s,t,u) (((void) fprintf(stderr,s,t,u)), putc('\n',stderr)) X#define perrn2(s,t) { int dummyerrno = errno; (void) fprintf(stderr,s,t); \ X (void) fputs(": ",stderr); errno = dummyerrno; \ X (void) perror(""); } X X#endif END_OF_FILE if test 491 -ne `wc -c <'djberr.h'`; then echo shar: \"'djberr.h'\" unpacked with wrong size! fi # end of 'djberr.h' fi echo shar: End of shell archive. exit 0 exit 0 # Just in case...