/****************************************************
 *     This software released to the public domain  *
 *     without restrictions of any kind.            *
 ****************************************************/

/*
 *  NAME
 *	perf - Test I/O system PERFormance.
 *
 *  SYNOPSIS
 *	perf [ -noqsv ] [ -a time ] [ -c command ] [ -e text ]
 *	     [ -t throttle ] [ -x outcount ] [ outcount ... ]
 *
 *  DESCRIPTION
 *	Perf measures the transmission of characters through
 *	a communication media, keeping statistics on throughput
 *	and system utilization.
 *
 *	When the any of the options c, e, q, t or x, or an
 *	outcount is specified, the program runs in output mode;
 *	Otherwise it runs in input mode.  A perf in output mode
 *	may be used to test a system I/O subsystem output speed
 *	directly,  or its output may be fed through communication
 *	hardware back into a perf in input mode for input
 *	performance testing.
 *
 *      -a time		    Run at the given time() compatible time.
 *
 *	-c shell_command    Output the given shell command to be
 *			    executed by the receiving perf program.
 *
 *	-e text		    Output the given text to be displayed
 *			    by the receiving perf program.
 *
 *      -f		    Fast mode.  As much as reasonably
 *			    possible, use no user mode CPU at all.
 *	                    Output preprepared buffers only, do
 *			    minimal input checking.
 *
 *	-h HZ		    Set the value of HZ, the number of
 *			    UNIX clock ticks/second.  Only useful
 *			    when the program is run on a version
 *			    of UNIX with a different clock speed
 *			    than where originally compiled.
 *
 *	-n 		    Print out the current time() compatible
 *			    time and exit w/o further processing.
 *
 *	-o		    Output only mode.  Transmits simpler
 *			    data to speed up processing.
 *
 *	-q		    Output code 99998, which tells the
 *			    receiving perf to terminate.
 *
 *	-s		    Silent option.  Suppress warning messages.
 *
 *	-t maxcps	    Throttle the input to maxcps by sleeping
 *			    for one second every maxcps characters.
 *
 *	-v		    On input, print statistics showing
 *			    the length and number of times a
 *			    stream of characters was missed.
 *
 *	-x nchar	    Output enough 6 digit + checksum
 *			    codes to produce the given number of
 *			    characters.
 *
 *	nchar		    Same as -x outcount above.
 *
 *	All data sent by an output perf can be interpreted by
 *	an input perf, but the data is all printable ASCII in
 *	a simple format so a human can understand it as well.
 *
 *	To test system throughput, the perf program uses 6 digit
 *	sequential codes, with a checksum.  This format allows
 *	the input program--or a human observer--to easily and
 *	quantitatively compare	desired to actual data.
 *
 *	For input testing, it is often valuable to send data
 *	from one system to another.  In this mode, perf can
 *	pass diagnostics text and shell commands through the
 *	communications link.  This allows the output system
 *	to control the overall test procedure, and provide a
 *	test log on the input system.
 *
 *	To improve accuracy, on output a code of 99998 is output
 *	for about 10K characters before measurement begins and
 *	after measurement completes.  This is done so the
 *	measurement is more likely to be done under steady-state
 *	conditions.
 *
 *  WARNING
 *	The order of parameters 2 & 3 on setvbuf() vary from
 *	system, and are even inconsistent between the manuals
 *	and the libraries on stock system V.2.  You are wise
 *	to check the order of the parameters on your system and
 *	set the SETVBUF #define accordingly.
 */

#if !defined(lint)
char whatstring[] = "@(#)perf.c 6.1 1/18/92" ;
#endif

#if sun
#define BSD 1
#endif

#if BSD
#   include <sys/types.h>
#   include <sys/time.h>
#   include <sys/resource.h>
#else
#   include <sys/types.h>
#   include <sys/times.h>
#   include <sys/param.h>
#endif

#include <ctype.h>
#include <stdio.h>

#define uchar unsigned char
#define ushort unsigned short
#define ulong unsigned long

extern char *optarg ;
extern int optind ;

extern char *getenv() ;
extern char *ttyname() ;
extern long atol() ;
extern long time() ;
extern unsigned sleep() ;
extern void *malloc() ;
extern char *memset() ;

#if BSD
extern char *sprintf() ;
#else
extern int sprintf() ;
#endif

extern void exit() ;
extern time_t times() ;


#define NDIG	6		/* Number of digits/code */
#define MAXCODE	999999		/* Largest NDIG number */

#define NHEAD	10000		/* Number of header characters */
#define NTRAIL	10000		/* Number of trailer characters */

#define CPL	9		/* Number of codes per line */
#define BASE	0x3f		/* Base character for checksum */

#define NCHAR(ncode) ((NDIG + 2) * (ncode) + 2 * (((ncode) + CPL - 1) / CPL))
#define NCODE(nchar) (((nchar) * CPL) / (CPL * (NDIG + 2) + 2))

#define BUFLEN 1024		/* Fast mode buffer length */


char iobuf[3][BUFSIZ] ;

/*
 *  Command options.
 */

int verbose ;			/* Verbose error reporting */
long throttle ;			/* Max chars between sleep calls */
int silent ;			/* Silent option */
int out ;			/* Output test */
int brief ;			/* Output brief mode */
int fast ;			/* Fast mode */
ulong starttime ;		/* Start time */

/*
 *  Misc globals.
 */

int linepos ;			/* Code position in line */
char *tname ;			/* Input/output ttyname */

/*
 *  Test performance statistics.
 */

float real ;			/* Computed real time */
float user ;			/* Computed user time */
float sys ;			/* Computed system time */
long cps ;			/* Computed characters/sec */

#if BSD
struct timeval start_tv ;	/* Start time */
struct timeval stop_tv ;	/* Stop time */
struct rusage start_ru ;	/* Start resource usage */
struct rusage stop_ru ;		/* Stop resource usage */
#else
int hertz ;			/* HZ variable value */
ulong start_real ;		/* Start time */
struct tms start_tms ;		/* Start user/sys time */
struct tms stop_tms ;		/* Stop user/sys time */
#endif

/*
 *  Fast mode buffers.
 */

char *hdr_buf ;			/* Buffer containing '1' characters */
char *data_buf ;		/* Buffer containing '2' characters */
char *trail_buf ;		/* Buffer containing '3' characters */

/*
 *  Error statistics.
 */

#define MAXSTAT	1000		/* Gap max for statistics */
int gap[MAXSTAT] ;		/* Statistics on code gaps */



/*************************************************************
 *     Output a numeric code sequence                        *
 *************************************************************/

outcode(v)
register long v ;
{
    static char digits[NDIG] ;
    static long lastv ;

    if (brief)
    {
	register FILE *file = stdout ;

	(void) putc(':', file) ;
	(void) putc('1', file) ;
	(void) putc('2', file) ;
	(void) putc('3', file) ;
	(void) putc('4', file) ;
#if NDIG >= 5
	(void) putc('5', file) ;
#endif
#if NDIG >= 6
	(void) putc('6', file) ;
#endif
#if NDIG >= 7
	(void) putc('7', file) ;
#endif
#if NDIG >= 8
	(void) putc('8', file) ;
#endif
	(void) putc(':', file) ;
    }
    else
    {
	register int ch ;
	register char *p ;

	if (v == lastv)
	{
	    p = &digits[NDIG] ;
	}
	else if (v == lastv + 1)
	{
	    lastv++ ;
	    for (p = digits ; *p == 9 ;) *p++ = 0 ;
	    *p += 1 ;
	    p = &digits[NDIG] ;
	}
	else
	{
	    lastv = v ;
	    for (p = digits ; p != &digits[NDIG] ;)
	    {
		*p++ = v % 10 ;
		v /= 10 ;
	    }
	}

	v = 0 ;
	while (p != &digits[0])
	{
	    ch = *--p ;
	    v += (v << 4) + ch ;
	    ch += '0' ;
	    (void) putc(ch, stdout) ;
	}

	ch = (v & 0x3f) + BASE ;
	(void) putc(ch, stdout) ;

	ch = ((v >> 6) & 0x3f) + BASE ;
	(void) putc(ch, stdout) ;
    }

    if (++linepos == CPL)
    {
	(void) putc('\r', stdout) ;
	(void) putc('\n', stdout) ;
	linepos = 0 ;
    }
}



/***************************************************************
 *    Close out the current line                               *
 ***************************************************************/

closeline()
{
    if (linepos)
    {
	(void) printf("\r\n") ;
	linepos = 0 ;
    }
}



/************************************************************
 *        Send text to a remote                             *
 ************************************************************/

sendtext(prefix, text)
int prefix ;
char *text ;
{
    int ch ;
    unsigned check ;

    closeline() ;

    for (;;)
    {
	check = 0 ;
	(void) fputc(prefix, stdout) ;

	while ((ch = *text++) && ch != '\n')
	{
	    (void) fputc(ch, stdout) ;
	    check = 17 * check + ch ;
	}

	(void) fputc((int)(check & 0x3f) + BASE, stdout) ;
	check >>= 6 ;
	(void) fputc((int)(check & 0x3f) + BASE, stdout) ;

	(void) printf("\r\n") ;

	if (ch == 0) break ;
    }
}



/****************************************************************
 ***  Mark time at the start of the test.
 ****************************************************************/

marktime()
{
#if BSD
    (void) gettimeofday(&start_tv, (struct timezone *)0) ;
    (void) getrusage(RUSAGE_SELF, &start_ru) ;
#else
    start_real = times(&start_tms) ;
#endif
}



/****************************************************************
 ***  Measure real, user, and system time.  Compute the
 ***  effective CPS.
 ****************************************************************/

measuretime(nchar)
long nchar ;
{
#if BSD
    (void) gettimeofday(&stop_tv, (struct timezone *)0) ;
    (void) getrusage(RUSAGE_SELF, &stop_ru) ;

    real = (stop_tv.tv_sec - start_tv.tv_sec)
	+ (stop_tv.tv_usec - start_tv.tv_usec) / 1000000. ;

    user = (stop_ru.ru_utime.tv_sec - start_ru.ru_utime.tv_sec)
	+ (stop_ru.ru_utime.tv_usec - start_ru.ru_utime.tv_usec) / 1000000. ;

    sys = (stop_ru.ru_stime.tv_sec - start_ru.ru_stime.tv_sec)
	+ (stop_ru.ru_stime.tv_usec - start_ru.ru_stime.tv_usec) / 1000000. ;

    cps = nchar / (real ? real : 1) + .5 ;
#else
    real = (times(&stop_tms) - start_real) / (float) hertz ;
    user = (stop_tms.tms_utime - start_tms.tms_utime) / (float) hertz ;
    sys =  (stop_tms.tms_stime - start_tms.tms_stime) / (float) hertz ;
    cps = nchar / (real ? real : 1) ;
#endif
}



/***************************************************************
 *    Transmit fast mode.
 ***************************************************************/

fast_transmit(nchar)
long nchar ;
{
    register long i ;
    long nbuf ;
    char buf[200] ;

    /*
     *  Put the receiver in fast receive mode.
     */

    (void) sprintf(buf, "%ld", nchar) ;
    sendtext('*', buf) ;
    (void) fflush(stdout) ;

    /*
     *  Figure the actual number of codes that will be output.
     */

    nbuf = (nchar + BUFLEN/2) / BUFLEN ;
    nchar = nbuf * BUFLEN ;

    /*
     *	Output header codes to fill the output queue so we
     *	will get a better idea of the true transmission times.
     */

    i = (nchar / 8 + NHEAD + BUFLEN / 2) / BUFLEN ;
    while (--i >= 0) (void) write(1, hdr_buf, BUFLEN) ;

    /*
     *	Get time statistics.
     */
    
    marktime() ;

    /*
     *	Output timed benchmark data.
     */

    for (i = nbuf ; --i >= 0 ;) (void) write(1, data_buf, BUFLEN) ;

    /*
     *	Figure the system resources used in the
     *	duration of the test.
     */

    measuretime(nchar) ;

    /*
     *	Output trailer codes to end the sequence.
     */

    i = (nchar / 8 + NTRAIL + BUFLEN / 2) / BUFLEN ;
    while (--i >= 0) (void) write(1, trail_buf, BUFLEN) ;

    /*
     *  Output codes to switch back into slow receive mode.
     */
    
    for (i = 0 ; i < 3*BUFLEN ; i++)
    {
	switch (i & 0x3f)
	{
	case 62:
	    (void) putc('\r', stdout) ;
	    break ;
	case 63:
	    (void) putc('\n', stdout) ;
	    break ;
	default:
	    (void) putc('4', stdout) ;
	}
    }

    (void) putc('\r', stdout) ;
    (void) putc('\n', stdout) ;

    (void) fflush(stdout) ;
}



/***************************************************************
 *    Transmit slow mode.
 ***************************************************************/

slow_transmit(nchar)
long nchar ;
{
    register long i ;
    long ncode ;

    /*
     *  Figure the actual number of codes that will be output.
     */

    ncode = NCODE(nchar) ;
    nchar = NCHAR(ncode) ;

    /*
     *	Output header codes to fill the output queue so we
     *	will get a better idea of the true transmission times.
     */

    (void) printf("\r\n") ;

    linepos = 0 ;
    i = NCODE(nchar / 8 + NHEAD) ;
    while (--i >= 0) outcode((long) (MAXCODE - 1)) ;
    closeline() ;
    (void) fflush(stdout) ;

    /*
     *	Get time statistics.
     */

    marktime() ;

    /*
     *	Output timed benchmark data.
     */

    for (i = 0 ; i < ncode ; i++) outcode(i) ;
    closeline() ;
    (void) fflush(stdout) ;

    /*
     *	Figure the system resources used in the
     *	duration of the test.
     */

    measuretime(nchar) ;

    /*
     *	Output trailer codes to end the sequence.
     */

    linepos = 0 ;
    i = NCODE(nchar / 8 + NTRAIL) ;
    while (--i >= 0) outcode((long) (MAXCODE - 1)) ;
    closeline() ;
    (void) fflush(stdout) ;
}



/**************************************************************
 ***  Transmit timed data.
 **************************************************************/

transmit(nchar)
long nchar ;
{
    register int i ;
    long delay ;
    char buf[200] ;

    /*
     *  Setup data buffers for repeated writes.
     */

    if (fast && hdr_buf == 0)
    {
	hdr_buf = (char *) malloc(BUFLEN) ;
	data_buf = (char *) malloc(BUFLEN) ;
	trail_buf = (char *) malloc(BUFLEN) ;

	(void) memset(hdr_buf, '1', BUFLEN) ;
	(void) memset(data_buf, '2', BUFLEN) ;
	(void) memset(trail_buf, '3', BUFLEN) ;

	for (i = 0 ; (i += 64) <= BUFLEN ;)
	{
	    hdr_buf[i-2] = '\r' ;
	    hdr_buf[i-1] = '\n' ;

	    data_buf[i-2] = '\r' ;
	    data_buf[i-1] = '\n' ;

	    trail_buf[i-2] = '\r' ;
	    trail_buf[i-1] = '\n' ;
	}
    }

    /*
     *  Wait until the selected start time,
     *  then transmit.
     */

    delay = starttime - time((long *)0) ;
    if (delay > 0) (void) sleep((unsigned) delay) ;

    /*
     *  Transmit data.
     */

    if (fast)
	fast_transmit(nchar) ;
    else
	slow_transmit(nchar) ;

    /*
     *	Output results.
     */

    (void) sprintf(buf,
	"%s : %s cps=%ld, real=%.2f, user=%.1f, sys=%.1f\n",
	tname, fast ? "FASTOUT" : "OUT",
	cps, real,
	100.0 * (double) user / (double) real,
	100.0 * (double) sys / (double) real) ;

    (void) write(2, buf, (unsigned) strlen(buf)) ;
}



/***********************************************************
 ***  Fast Receive routine.
 ***********************************************************/

fast_receive()
{
    register int c ;
    register int n ;
    register int state ;
    register long nchar ;
    char buf[BUFLEN] ;

    /*
     *  Read a stream of '1's, followed by a timed sequence
     *  of '2's, followed by a sequence of '3's.  Exit when
     *  a '4' is found.  Error on anything else.
     */

    state = 1 ;
    nchar = 0 ;

    for (;;)
    {
	/*
	 *  Read the next buffer of data.
	 */

	n = read(0, buf, BUFLEN) ;
	if (n <= 0)
	{
	    if (n < 0) perror("Read error") ;
	    else if (!silent) (void) fprintf(stderr, "Exiting on EOF!\n") ;
	    exit(1) ;
	}

	nchar += n ;

	/*
	 *  Ignore RETURN and NEWLINE only.
	 */

	for (;;)
	{
	    c = buf[n - 1] ;
	    if (c != '\r' && c != '\n') break ;
	    if (--n <= 0) break ;
	}

	if (n == 0) continue ;

	/*
	 *  State transition table.
	 */

	switch(state)
	{
	case 1:
	    if (c == '1') continue ;
	    if (c != '2') goto err ;
	    marktime() ;
	    state++ ;
	    nchar = 0 ;
	    continue ;
	
	case 2:
	    if (c == '2') continue ;
	    if (c != '3') goto err ;
	    measuretime(nchar) ;
	    state++ ;
	    continue ;

	case 3:
	    if (c == '3') continue ;
	    if (c != '4') goto err ;
	    break ;
	}
	break ;
    }

    /*
     *  Discard the trailers.
     */

    while (fgetc(stdin) == '4') ;

    /*
     *  Print results.
     */

    (void) sprintf(buf,
	"%s : FASTIN  cps=%ld, real=%.2f, user=%.1f, sys=%.1f\n",
	tname, cps, real,
	100.0 * user / real, 100.0 * sys / real) ;

    (void) write(2, buf, (unsigned) strlen(buf)) ;
    return ;

err:
    (void) fprintf(stderr, "Fast receive expected 0x%02x, got 0x%02x\n",
	state + '0', c) ;
}



/***********************************************************
 *    Honor a text escape from the remote.                 *
 ***********************************************************/

readtext(prefix)
int prefix ;
{
    int ch ;
    int n ;
    int i ;
    int check ;
    char buf[1000] ;

    n = 0 ;

    for (;;)
    {
	if ((ch = fgetc(stdin)) == EOF) break ;
	ch &= 0x7f ;

	if (ch == '\r' || ch == '\n') break ;

	if (n < sizeof(buf))
	{
	    buf[n] = ch ;
	}
	n++ ;
    }

    if (n < 2 || n > sizeof(buf))
    {
	(void) fprintf(stderr, "Text size error!\n", prefix) ;
	return ;
    }

    n -= 2 ;
    check = 0 ;

    for (i = 0 ; i < n ; i++) check = 17 * check + buf[i] ;

    if	(   ((check & 0x3f) + BASE) != buf[n]
	||  (((check >>= 6) & 0x3f) + BASE) != buf[n+1]
	)
    {
	(void) fprintf(stderr, "Text checksum error!\n", prefix) ;
	return ;
    }

    buf[n] = 0 ;

    if (prefix == '!') (void) system(buf) ;

    if (prefix == '#')
    {
	(void) fputs(buf, stderr) ;
	(void) fputc('\n', stderr) ;
    }

    if (prefix == '*') fast_receive() ;
}



/***********************************************************
 *    General Receive Routine. 
 ***********************************************************/

receive()
{
    register int ch ;
    register long v ;
    int d ;
    int i ;
    int n ;
    int lch ;
    unsigned short s ;
    long code ;
    long error ;
    long trash ;
    long scode ;
    long incount ;
    char buf[200] ;

    real = 0 ;
    user = 0 ;
    sys = 0 ;
    trash = 0 ;
    error = 0 ;
    code = MAXCODE - 1 ;
    scode = 0 ;
    incount = 0 ;

    ch = getc(stdin) ;

    /*
     *	Loop to read codes until and exit code appears.
     */

    for (;;)
    {
	v = 0 ;
	d = 0 ;
	s = 0 ;

	if (++incount >= throttle)
	{
	    (void) sleep(1) ;
	    incount = 0 ;
	}

	/*
	 *  Ignore white space, detect EOF, and scan
	 *  until a digit appears.
	 */

	lch = '\n' ;

	for (;;)
	{
	    if (ch == EOF)
	    {
		if (!silent) (void) fprintf(stderr, "Exiting on EOF!\n") ;
		exit(1) ;
	    }

	    ch &= 0x7f ;

	    if (isdigit(ch)) break ;

	    if (lch == '\n' && (ch == '!' || ch == '#' || ch == '*'))
	    {
		readtext(ch) ;
		lch = '\n' ;
	    }
	    else if
		(   ch != '\r' && ch != '\n'
		&&  (ch < BASE || ch > (BASE + 0x3f))
		)
		trash++ ;

	    lch = '\n' ;
	    ch = getc(stdin) ;
	}

	/*
	 *  Pick up a digit string.
	 */

	for (;;)
	{
	    ch -= '0' ;
	    v = 10 * v + ch ;
	    s += (s << 4) + ch ;
	    d++ ;
	    if ((ch = getc(stdin)) == EOF) break ;
	    ch &= 0x7f ;
	    if (!isdigit(ch)) break ;
	}

	/*
	 *  Process checksum.
	 */

	if (d != NDIG || (ch - BASE) != (s & 0x3f)) continue ;

	if ((ch = getc(stdin)) == EOF) continue ;
	ch &= 0x7f ;

	if ((ch - BASE) != ((s >> 6) & 0x3f)) continue ;

	ch = getc(stdin) ;

	/*
	 *  Pick up end of sequence or quit indicator.
	 */

	if (v >= MAXCODE - 1)
	{
	    if (code != MAXCODE - 1)
	    {
		code -= scode ;

		/*
		 *  Figure resources used, and print results.
		 */

		measuretime(NCHAR(code)) ;

		(void) sprintf(buf,
		    "%s : IN  cps=%ld, real=%.2f, user=%.1f, sys=%.1f, errs=%ld, stray=%ld\n",
		    tname, cps, real,
		    100.0 * user / real, 100.0 * sys / real,
		    error, trash) ;

		(void) write(2, buf, (unsigned) strlen(buf)) ;

		/*
		 *  Print verbose error statistics.
		 */

		if (verbose && error)
		{
		    (void) fprintf(stderr,
			"%s : Sequence gaps were:\n    ", tname) ;

		    n = 0 ;
		    for (i = 0 ; i < MAXSTAT ; i++)
		    {
			if (gap[i])
			{
			    if (n == 6)
			    {
				(void) fprintf(stderr, "\n    ") ;
				n = 0 ;
			    }
			    (void) fprintf(stderr,
				"%7d:%4d", NCHAR(i+1), gap[i]) ;
			    n++ ;
			}
		    }
		    if (n) (void) fprintf(stderr, "\n") ;
		}
	    }

	    /*
	     *	Quit when the maximum input code is
	     *	received.
	     */

	    if (v == MAXCODE)
	    {
		(void) fprintf(stderr,
		    "%s : Received exit code, quitting\n", tname) ;
		exit(0) ;
	    }
	    if (code != MAXCODE - 1)
	    {
		code = MAXCODE - 1 ;
	    }
	}

	/*
	 *  Handle expected sequence number.
	 */

	else if (v == code) code++ ;

	/*
	 *  Handle sequence lower than our current sequence,
	 *  indicating a restart.
	 */

	else if (v < code)
	{
	    if (code != MAXCODE - 1)
	    {
		(void) fprintf(stderr,
		    "%s : Incomplete sequence aborted\n", tname) ;
	    }

	    for (i = 0 ; i < MAXSTAT ; i++) gap[i] = 0 ;

	    trash = 0 ;
	    error = 0 ;
	    code = v + 1 ;

	    /*
	     *	Get start time statistics.
	     */

	    marktime() ;

	    scode = v ;
	}

	/*
	 *  Handle sequence greater than our expected
	 *  sequence.
	 */

	else
	{
	    i = v - code ;
	    error += i ;
	    if (i > MAXSTAT) i = MAXSTAT ;
	    gap[i-1]++ ;
	    code = v + 1 ;
	}
    }
}



/*************************************************************
 *        Main Program                                       *
 *************************************************************/

main(argc, argv)
int argc ;
char **argv ;
{
    int quit = 0 ;
    int i ;
    int ch ;
    long nchar ;

    /*
     *	Use HZ environment variable so Xenix binaries
     *	will run on UNIX machines with a different
     *	basic clock speed.
     */

#ifndef BSD
    char *p ;

    hertz = HZ ;

    if (p = getenv("HZ")) hertz = atoi(p) ;
#endif

    /*
     *	Unix V.2 (original release) had parameters 2 & 3 of
     *	the SETVBUF call reversed.   This has been corrected
     *	SVR3.   Set SETVBUF if you get a lint error.
     */

#if SETVBUF
    (void) setvbuf(stdin,  _IOFBF, iobuf[0], sizeof(iobuf[0])) ;
    (void) setvbuf(stdout, _IOFBF, iobuf[1], sizeof(iobuf[0])) ;
    (void) setvbuf(stderr, _IOLBF, iobuf[2], sizeof(iobuf[0])) ;
#else
    (void) setvbuf(stdin,  iobuf[0], _IOFBF, sizeof(iobuf[0])) ;
    (void) setvbuf(stdout, iobuf[1], _IOFBF, sizeof(iobuf[0])) ;
    (void) setvbuf(stderr, iobuf[2], _IOLBF, sizeof(iobuf[0])) ;
#endif

    /*
     *	Break out options.
     */

    throttle = 9999999 ;

    while ((ch = getopt(argc, argv, "a:c:e:fh:noqst:vx:")) != -1)
    {
	switch (ch)
	{
	case 'a':
	    starttime = atol(optarg) ;
	    break ;

	case 'c':
	    out++ ;
	    sendtext('!', optarg) ;
	    break ;

	case 'e':
	    out++ ;
	    sendtext('#', optarg) ;
	    break ;

	case 'f':
	    fast++ ;
	    break ;

	case 'h':
#if BSD
	    (void) fprintf(stderr, "-h ignored in BSD systems\n") ;
#else
	    hertz = atoi(optarg) ;
#endif
	    break ;

	case 'n':
	    (void) printf("%ld\n", time((long *)0)) ;
	    exit(0) ;
  	    break ;
  
	case 'o':
	    out++ ;
	    brief++ ;
	    break ;

	case 'q':
	    out++ ;
	    quit++ ;
	    break ;

	case 's':
	    silent++ ;
	    break ;

	case 't':
	    throttle = atoi(optarg) ;
	    throttle = NCODE(throttle) ;
	    break ;

	case 'v':
	    verbose++ ;
	    break ;

	case 'x':
	    out++ ;
	    nchar = atoi(optarg) ;
	    transmit(nchar) ;
	    break ;

	default:
	    (void) fprintf(stderr,
		"usage: %s [-oqsv] [-c cmd] [-e text] [-t maxcps] [nchar]\n",
		argv[0]) ;
	    exit(2) ;
	}
    }

    /*
     *	Check for terminal input/output.
     */

    if (optind != argc) out++ ;

    if (out)
    {
	tname = ttyname(1) ;
	if (tname == 0)
	{
	    if (!silent)
		(void) fprintf(stderr, "Standard output is not a terminal!\n") ;
	    tname = "OUTPUT" ;
	}
    }
    else
    {
	tname = ttyname(0) ;
	if (tname == 0)
	{
	    if (!silent)
		(void) fprintf(stderr, "Standard input is not a terminal!\n") ;
	    tname = "INPUT" ;
	}
    }

    /*
     *	Output code counts as requested.
     */

    while (optind < argc)
    {
	nchar = atol(argv[optind]) ;
	if (nchar > 0)
	    transmit(nchar) ;
	optind++ ;
    }

    /*
     *	Output quit codes.
     */

    if (quit)
    {
	for (i = 0 ; i < NTRAIL ; i++)
	{
	    outcode((long) MAXCODE) ;
	}
	closeline() ;
    }

    (void) fflush(stdout) ;

    /*
     *	If no code counts or the quit flag, this is
     *	a receive data test.
     */

    if (!out)
    {
	receive() ;
	exit(0) ;
    }

    return(0) ;
}
