/*
 * Copyright (c) 2000 Silicon Graphics, Inc.  All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it would be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * Further, this software is distributed without any warranty that it is
 * free of the rightful claim of any third person regarding infringement
 * or the like.  Any license provided herein, whether implied or
 * otherwise, applies only to this software file.  Patent licenses, if
 * any, provided herein do not apply to combinations of this program with
 * other software, or any other product whatsoever.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy,
 * Mountain View, CA  94043, or:
 *
 * http://www.sgi.com
 *
 * For further information regarding this notice, see:
 *
 * http://oss.sgi.com/projects/GenInfo/NoticeExplan/
 *
 */
/* $Header: /cvsroot/ltp/ltp/testcases/kernel/ipc/pipeio/pipeio.c,v 1.18 2009/03/19 07:10:02 subrata_modak Exp $ */
/*
 *  This tool can be used to beat on system or named pipes.
 *  See the help() function below for user information.
 */
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/wait.h>
#include <time.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/sem.h>

#include "tlibio.h"

#include "test.h"
#include "safe_macros.h"
#include "lapi/semun.h"

char *TCID = "pipeio";
int TST_TOTAL = 1;

#define SAFE_FREE(p) { if (p) { free(p); (p)=NULL; } }

#if defined(__linux__)
#define NBPW sizeof(int)
#endif

#define OCTAL	'o'
#define HEX	'x'
#define DECIMAL	'd'
#define ASCII	'a'
#define NO_OUT	'n'

#define PIPE_NAMED	"named pipe,"
#define PIPE_UNNAMED	"sys pipe,"

#define BLOCKING_IO	"blking,"
#define NON_BLOCKING_IO	"non-blking,"
#define UNNAMED_IO	""

#define MAX_ERRS 16
#define MAX_EMPTY 256

static int parse_options(int argc, char *argv[]);
static void setup(int argc, char *argv[]);
static void cleanup(void);

static void do_child(void);
static void do_parent(void);

static void help(void), usage(void), prt_examples(void);
static void prt_buf(char **addr, char *buf, int length, int format);
static void sig_child(int sig);
static int check_rw_buf(void);

static volatile sig_atomic_t nchildcompleted;

/* variables may be modified in setup() */
static int num_writers = 1;	/* number of writers */
static int num_writes = 1;	/* number of writes per child */
static int loop;		/* loop indefinitely */
static int exit_error = 1;	/* exit on error #, zero means no exit */
static int size = 327;		/* default size */
static int unpipe;		/* un-named pipe if non-zero */
static int verbose;		/* verbose mode if set */
static int quiet;		/* quiet mode if set */
static int num_rpt;		/* ping number, how often to print message */
static int chld_wait;	/* max time to wait between writes, 1 == no wait */
static int parent_wait;	/* max time to wait between reads, 1 == no wait */
static int ndelay = O_NDELAY;	/* additional flag to open */
static char *writebuf;
static char *readbuf;
static char pname[PATH_MAX];	/* contains the name of the named pipe */
static char *blk_type = NON_BLOCKING_IO; /* blocking i/o or not */
static char *pipe_type;		/* type of pipe under test */
static int format = HEX;
static int format_size = -1;
static int iotype;		/* sync io */

/* variables will be modified in running */
static int error;
static int count;
static int read_fd;
static int write_fd;
static int empty_read;
static int sem_id;

static union semun u;

int main(int ac, char *av[])
{
	int i;
	unsigned int j;
	unsigned int uwait_iter = 1000, uwait_total = 5000000;
	pid_t child;

	setup(ac, av);

	for (i = num_writers; i > 0; --i) {

		child = tst_fork();
		switch (child) {
		case -1:
			tst_brkm(TBROK | TERRNO, cleanup, "fork() failed");
		case 0:
			do_child();
			exit(0);
		default:
			break;
		}
	}

	do_parent();

	if (empty_read)
		tst_resm(TWARN, "%d empty reads", empty_read);

	if (error) {
		tst_resm(TFAIL, "%d data errors on pipe, read size = %d, %s %s",
			 error, size, pipe_type, blk_type);
	} else if (!quiet) {
		tst_resm(TPASS, "%d pipe reads complete, read size = %d, %s %s",
			 count + 1, size, pipe_type, blk_type);
	}

	/*
	 * wait for all children to finish, timeout after uwait_total
	 * semtimedop might not be available everywhere
	 */
	for (j = 0; j < uwait_total; j += uwait_iter) {
		if (semctl(sem_id, 1, GETVAL) == 0)
			break;
		usleep(uwait_iter);
	}

	if (j >= uwait_total) {
		tst_resm(TWARN,
			 "Timed out waiting for child processes to exit");
	}

	cleanup();
	tst_exit();
}

static int parse_options(int argc, char *argv[])
{
	char *cp;
	int c;
	int ret = 0;
	static double d;

	while ((c = getopt(argc, argv, "T:bc:D:he:Ef:i:I:ln:p:qs:uvW:w:"))
	       != -1) {
		switch (c) {
		case 'T':
			TCID = optarg;
			break;
		case 'h':
			help();
			ret = 1;
			break;
		case 'D':	/* pipe name */
			strcpy(pname, optarg);
			break;
		case 'b':	/* blocked */
			ndelay = 0;
			blk_type = BLOCKING_IO;
			break;
		case 'c':	/* number childern */
			if (sscanf(optarg, "%d", &num_writers) != 1) {
				fprintf(stderr,
					"%s: --c option invalid arg '%s'.\n",
					TCID, optarg);
				ret = 1;
			} else if (num_writers <= 0) {
				fprintf(stderr, "%s: --c option must be "
					"greater than zero.\n", TCID);
				ret = 1;
			}
			break;
		case 'e':	/* exit on error # */
			if (sscanf(optarg, "%d", &exit_error) != 1) {
				fprintf(stderr,
					"%s: --e option invalid arg '%s'.\n",
					TCID, optarg);
				ret = 1;
			} else if (exit_error < 0) {
				fprintf(stderr, "%s: --e option must be "
					"greater than zero.\n", TCID);
				ret = 1;
			}
			break;
		case 'E':
			prt_examples();
			ret = 1;
			break;
		case 'f':	/* format of buffer on error */
			switch (optarg[0]) {
			case 'x':
			case 'X':
				format = HEX;
				break;
			case 'o':
			case 'O':
				format = OCTAL;
				break;
			case 'd':
			case 'D':
				format = DECIMAL;
				break;
			case 'a':
			case 'A':
				format = ASCII;
				break;
			case 'n':	/* not output */
			case 'N':
				format = NO_OUT;
				break;

			default:
				fprintf(stderr,
					"%s: --f option invalid arg '%s'.\n",
					TCID, optarg);
				fprintf(stderr, "\tIt must be x(hex), o(octal),"
					"d(decimal), a(ascii) or n(none) with "
					"opt sz\n");
				ret = 1;
				break;
			}
			cp = optarg;
			cp++;
			if (*cp) {
				if (sscanf(cp, "%i", &format_size) != 1) {
					fprintf(stderr, "%s: --f option invalid"
						"arg '%s'.\n", TCID, optarg);
					fprintf(stderr, "\tIt must be x(hex),"
						"o(octal), d(decimal), a(ascii)"
						" or n(none) with opt sz\n");
					ret = 1;
					break;
				}
			}
			break;

		case 'I':
			iotype = lio_parse_io_arg1(optarg);
			if (iotype == -1) {
				fprintf(stderr, "%s: --I arg is invalid, "
					"must be s, p, f, a, l, L or r.\n",
					TCID);
				ret = 1;
			}
			break;

		case 'l':	/* loop forever */
			++loop;
			break;

		case 'i':
		case 'n':	/* number writes per child */
			if (sscanf(optarg, "%d", &num_writes) != 1) {
				fprintf(stderr, "%s: --i/n option invalid "
					"arg '%s'.\n", TCID, optarg);
				ret = 1;
			} else if (num_writes < 0) {
				fprintf(stderr, "%s: --i/n option must be "
					"greater than equal to zero.\n",
					TCID);
				ret = 1;
			}

			if (num_writes == 0)	/* loop forever */
				++loop;
			break;
		case 'p':	/* ping */
			if (sscanf(optarg, "%d", &num_rpt) != 1) {
				fprintf(stderr,
					"%s: --p option invalid arg '%s'.\n",
					TCID, optarg);
				ret = 1;
			} else if (num_rpt < 0) {
				fprintf(stderr, "%s: --p option must be greater"
					" than equal to zero.\n", TCID);
				ret = 1;
			}
			break;
		case 'q':	/* Quiet - NOPASS */
			quiet = 1;
			break;
		case 's':	/* size */
			if (sscanf(optarg, "%d", &size) != 1) {
				fprintf(stderr,
					"%s: --s option invalid arg '%s'.\n",
					TCID, optarg);
				ret = 1;
			} else if (size <= 0) {
				fprintf(stderr, "%s: --s option must be greater"
					" than zero.\n", TCID);
				ret = 1;
			}
			break;
		case 'u':
			unpipe = 1;	/* un-named pipe */
			break;
		case 'v':	/* verbose */
			verbose = 1;
			break;
		case 'W':	/* max wait time between reads */
			d = strtod(optarg, &cp);
			if (*cp != '\0') {
				fprintf(stderr,
					"%s: --w option invalid arg '%s'.\n",
					TCID, optarg);
				ret = 1;
			} else if (d < 0) {
				fprintf(stderr, "%s: --w option must be greater"
					" than zero.\n", TCID);
				ret = 1;
			}
			parent_wait = (int)(d * 1000000.0);
			break;
		case 'w':	/* max wait time between writes */
			d = strtod(optarg, &cp);
			if (*cp != '\0') {
				fprintf(stderr,
					"%s: --w option invalid arg '%s'.\n",
					TCID, optarg);
				ret = 1;
			} else if (d < 0) {
				fprintf(stderr, "%s: --w option must be greater"
					" than zero.\n", TCID);
				ret = 1;
			}
			chld_wait = (int)(d * 1000000.0);
			break;
		case '?':
			ret = 1;
			break;
		}

		if (ret == 1) {
			usage();
			return ret;
		}
	}

	return ret;
}

static void setup(int argc, char *argv[])
{
	int ret;
	char *toutput;
	int fds[2];

	tst_sig(FORK, DEF_HANDLER, cleanup);

	TEST_PAUSE;

	tst_tmpdir();

	if (signal(SIGCHLD, sig_child) == SIG_ERR) {
		tst_brkm(TBROK | TERRNO, cleanup,
			 "set signal handler for SIGCHLD failed");
	}

	toutput = getenv("TOUTPUT");
	if (toutput != NULL && strcmp(toutput, "NOPASS") == 0)
		quiet = 1;

	sprintf(pname, "%s", "tpipe");

	ret = parse_options(argc, argv);
	if (ret == 1)
		tst_brkm(TBROK, cleanup, "options parse error");

	if (format_size == -1)
		format_size = size;

	/*
	 * If there is more than one writer, all writes and reads
	 * must be the same size.  Only writes of a size <= PIPE_BUF
	 * are atomic.  T
	 * Therefore, if size is greater than PIPE_BUF, we will break
	 * the writes into PIPE_BUF chunks.  We will also increase the
	 * number of writes to ensure the same (or more) amount of
	 * data is written.  This is the same as erroring and telling
	 * the user the new cmd line to do the same thing.
	 * Example:
	 *      pipeio -s 5000 -n 10 -c 5
	 *      (each child will write at least 50000 bytes, since all
	 *      writes have to be in 4096 chuncks or 13*4096 (53248)
	 *      bytes will be written.)  This is the same as:
	 *      pipeio -s 4096 -n 13 -c 5
	 */
	if (size > PIPE_BUF && num_writers > 1) {
		if (!loop) {
			/*
			 * we must set num_writes*num_writers
			 * doesn't overflow later
			 */
			num_writes = MIN(((long long)num_writes * size +
					 PIPE_BUF - 1) / PIPE_BUF,
					 INT_MAX / num_writers);
			tst_resm(TINFO, "adjusting i/o size to %d, and # of "
				 "writes to %d", PIPE_BUF, num_writes);
		} else {
			tst_resm(TINFO, "adjusting i/o size to %d", PIPE_BUF);
		}
		size = PIPE_BUF;
	}

	writebuf = SAFE_MALLOC(cleanup, size);
	readbuf = SAFE_MALLOC(cleanup, size);

	memset(writebuf, 'Z', size);
	writebuf[size - 1] = 'A';

	sem_id = semget(IPC_PRIVATE, 2, IPC_CREAT | S_IRWXU);
	if (sem_id == -1) {
		tst_brkm(TBROK | TERRNO, cleanup,
			 "Couldn't allocate semaphore");
	}

	if (semctl(sem_id, 0, SETVAL, u) == -1) {
		tst_brkm(TBROK | TERRNO, cleanup,
			 "Couldn't initialize semaphore 0 value");
	}

	if (semctl(sem_id, 1, SETVAL, u) == -1) {
		tst_brkm(TBROK | TERRNO, cleanup,
			 "Couldn't initialize semaphore 1 value");
	}

	if (unpipe) {
		SAFE_PIPE(cleanup, fds);
		read_fd = fds[0];
		write_fd = fds[1];
		pipe_type = PIPE_UNNAMED;
		blk_type = UNNAMED_IO;
	} else {
		if (mkfifo(pname, 0777) == -1) {
			tst_brkm(TBROK | TERRNO, cleanup,
				"mkfifo(%s, 0777) failed", pname);
		}
		pipe_type = PIPE_NAMED;
	}
}

static void cleanup(void)
{
	SAFE_FREE(writebuf);
	SAFE_FREE(readbuf);

	semctl(sem_id, 0, IPC_RMID);

	if (!unpipe)
		unlink(pname);

	tst_rmdir();
}

static void do_child(void)
{
	int *count_word;        /* holds address where to write writers count */
	int *pid_word;          /* holds address where to write writers pid */
	int nb, j;
	long clock;
	char *cp;
	long int n;
	struct sembuf sem_op;
	pid_t self_pid =  getpid();

	if (!unpipe) {
		write_fd = open(pname, O_WRONLY);
		if (write_fd == -1) {
			fprintf(stderr, "child pipe open(%s, %#o) failed",
				pname, O_WRONLY | ndelay);
			exit(1);
		}
		if (ndelay && fcntl(write_fd, F_SETFL, O_NONBLOCK) == -1) {
			fprintf(stderr, "Failed setting the pipe to "
				"nonblocking mode");
			exit(1);
		}
	} else {
		close(read_fd);
	}

	sem_op = (struct sembuf) {
		 .sem_num = 0, .sem_op = 1, .sem_flg = 0};

	if (semop(sem_id, &sem_op, 1) == -1) {
		fprintf(stderr, "child: %d couldn't raise the semaphore 0",
			self_pid);
		exit(1);
	}

	pid_word = (int *)&writebuf[0];
	count_word = (int *)&writebuf[NBPW];

	for (j = 0; j < num_writes || loop; ++j) {
		/*
		 * writes are only in one unit when the size of the write
		 * is <= PIPE_BUF.
		 * Therefore, if size is greater than PIPE_BUF, we will break
		 * the writes into PIPE_BUF chunks.
		 * All writes and read need to be same size.
		 */

		/*
		 * write pid and count in first two
		 * words of buffer
		 */
		*count_word = j;
		*pid_word = self_pid;

		nb = lio_write_buffer(write_fd, iotype, writebuf, size,
				      SIGUSR1, &cp, 0);
		if (nb < 0) {
			/*
			 * If lio_write_buffer returns a negative number,
			 * the return will be -errno.
			 */
			fprintf(stderr, "pass %d: lio_write_buffer(%s) failed;"
				" it returned %d: %s",
				j, cp, nb, strerror(-nb));
				exit(1);
		} else if (nb != size) {
			fprintf(stderr, "pass %d: lio_write_buffer(%s) failed,"
				" write count %d, but expected to write %d",
				j, cp, nb, size);
		}
		if (verbose) {
			fprintf(stderr, "pass %d: pid %d: wrote %d bytes,"
				"expected %d bytes",
				j, self_pid, nb, size);
		}

		if (chld_wait) {
			clock = time(0);
			srand48(clock);
			n = lrand48() % chld_wait;
			usleep(n);
		}
		fflush(stderr);
	}

	/* child waits until parent completes open() */
	sem_op = (struct sembuf) {
		  .sem_num = 1, .sem_op = -1, .sem_flg = 0};
	if (semop(sem_id, &sem_op, 1) == -1)
		fprintf(stderr, "Couldn't lower the semaphore 1");

	exit(0);
}

static int check_rw_buf(void)
{
	int i;

	for (i = 2 * NBPW; i < size; ++i) {
		if (writebuf[i] != readbuf[i]) {
			++error;
			tst_resm(TFAIL,
				 "FAIL data error on byte %d; rd# %d, sz= %d, "
				 "%s %s empty_reads= %d, err= %d",
				 i, count, size, pipe_type, blk_type,
				 empty_read, error);
			prt_buf(&readbuf, readbuf, format_size, format);
			fflush(stdout);
			return 1;
		}
	}

	return 0;
}

static void do_parent(void)
{
	int i, nb;
	long clock;
	time_t start_time, current_time, diff_time;
	char *cp;
	long int n;
	struct sembuf sem_op;

	start_time = time(0);
	if (!unpipe) {
		read_fd = SAFE_OPEN(cleanup, pname, O_RDONLY);
		if (ndelay && fcntl(read_fd, F_SETFL, O_NONBLOCK) == -1) {
			tst_brkm(TBROK | TERRNO, cleanup,
				 "Failed setting the pipe to nonblocking mode");
		}
	} else {
		SAFE_CLOSE(cleanup, write_fd);
	}

	/* raise semaphore so children can exit */
	sem_op = (struct sembuf) {
		  .sem_num = 1, .sem_op = num_writers, .sem_flg = 0};
	if (semop(sem_id, &sem_op, 1) == -1) {
		tst_brkm(TBROK | TERRNO, cleanup,
			 "Couldn't raise the semaphore 1");
	}

	sem_op = (struct sembuf) {
		  .sem_num = 0, .sem_op = -num_writers, .sem_flg = 0};

	while (nchildcompleted < num_writers
	       && semop(sem_id, &sem_op, 1) == -1) {
		if (errno == EINTR)
			continue;
		tst_brkm(TBROK | TERRNO, cleanup,
			 "Couldn't wait on semaphore 0");
	}

	/* parent start to read pipe */
	for (i = num_writers * num_writes; i > 0 || loop; --i) {
		if (error >= MAX_ERRS || empty_read >= MAX_EMPTY)
			break;
		if (parent_wait) {
			clock = time(0);
			srand48(clock);
			n = lrand48() % parent_wait;
			usleep(n);
		}
		++count;
		nb = lio_read_buffer(read_fd, iotype, readbuf, size,
				     SIGUSR1, &cp, 0);
		if (nb < 0) {
			/*
			 * If lio_read_buffer returns a negative number,
			 * the return will be -errno.
			 */
			tst_resm(TFAIL, "pass %d: lio_read_buffer(%s) failed; "
				 "returned %d: %s", i, cp, nb, strerror(-nb));
			++i;
			count--;
			error++;
			continue;
		} else {
			if (nb == 0) {
				if (nchildcompleted >= num_writers && !loop) {
					tst_resm(TWARN, "The children have "
						 "died prematurely");
					break;	/* All children have died */
				}
				empty_read++;
				++i;
				count--;
				continue;
			} else if (nb < size && size <= PIPE_BUF) {
				tst_resm(TFAIL, "pass %d: partial read from the"
					" pipe: read %d bytes, expected %d, "
					"read count %d", i, nb, size, count);
				++error;
			} else if (nb == size) {
				check_rw_buf();
				if (exit_error && exit_error == error)
					return;
			}

			if (verbose || (num_rpt && !(count % num_rpt))) {
				current_time = time(0);
				diff_time = current_time - start_time;
				tst_resm(TFAIL,
					 "(%d) rd# %d, sz= %d, %s %s "
					 "empty_reads= %d, err= %d\n",
					 (int)diff_time, count, size,
					 pipe_type, blk_type,
					 empty_read, error);
				fflush(stdout);
			}
		}
	}

	SAFE_CLOSE(cleanup, read_fd);
}

static void usage(void)
{
	fprintf(stderr, "Usage: %s [-bEv][-c #writers][-D pname][-h]"
		"[-e exit_num][-f fmt][-l][-i #writes][-n #writes][-p num_rpt]"
		"\n\t[-s size][-W max_wait][-w max_wait][-u]\n", TCID);
	fflush(stderr);
}

static void help(void)
{
	usage();

	printf(" -b    - blocking reads and writes. default non-block\n\
  -c #writers  - number of writers (childern)\n\
  -D pname     - name of fifo (def tpipe<pid>)\n\
  -h           - print this help message\n\
  -e exit_num  - exit on error exit_num, 0 is ignore errors, 1 is default.\n\
  -E           - print cmd line examples and exit\n\
  -f format    - define format of bad buffer: h(hex), o(octal)\n\
                 d(decimal), a(ascii), n (none). hex is default\n\
	         option size can be added to control output\n\
  -i #writes   - number write per child, zero means forever.\n\
  -I io_type   - Specifies io type: s - sync, p - polled async, a - async (def s)\n\
                 l - listio sync, L - listio async, r - random\n\
  -l           - loop forever (implied by -n 0).\n\
  -n #writes   - same as -i (for compatability).\n\
  -p num_rpt   - number of reads before a report\n\
  -q           - quiet mode, no PASS results are printed\n\
  -s size      - size of read and write (def 327)\n\
                 if size >= 4096, i/o will be in 4096 chuncks\n\
  -w max_wait  - max time (seconds) for sleep between writes.\n\
                 max_wait is interpreted as a double with ms accuracy.\n\
  -W max_wait  - max time (seconds) for sleep between reads\n\
                 max_wait is interpreted as a double with ms accuracy.\n\
  -u           - un-named pipe instead of named pipe\n\
  -v           - verbose mode, all writes/reads resutlts printed\n");

	fflush(stdout);
}

static void prt_buf(char **addr, char *buf, int length, int format)
{
	int i;
	int num_words = length / NBPW;	/* given length in bytes, get length in words */
	int width;		/* number of columns */
	int extra_words = 0;	/* odd or even number of words */
	char *a = buf;
	char b[NBPW];
	char c[NBPW * 2];
	char *p;
	long *word;

	if (format == NO_OUT)	/* if no output wanted, return */
		return;

	if (length % NBPW)
		++num_words;	/* is length in full words? */
	if (format == ASCII) {
		width = 3;
	} else {
		width = 2;
		/* do we have an odd number of words? */
		extra_words = num_words % width;
	}
	for (i = 0; i < num_words; ++i, a += NBPW, addr++) {
		word = (long *)a;
		if (!(i % width)) {
			if (i > 0 && format != ASCII) {
				/*
				 * print the ascii equivalent of the data
				 * before beginning the next line of output.
				 */
				memset(c, 0x00, width * NBPW);
				/*
				 * get the last 2 words printed
				 */
				memcpy(c, a - (width * NBPW), width * NBPW);
				for (p = c; (p - c) < (int)(width*NBPW); ++p) {
					if (*p < '!' || *p > '~')
						*p = '.';
				}
				printf("\t%16.16s", c);
			}
			printf("\n%p: ", addr);
			/***printf("\n%7o (%d): ",addr,i);***/
		}

		switch (format) {
		case HEX:
			printf("%16.16lx ", *word);
			break;
		case DECIMAL:
			printf("%10.10ld ", *word);
			break;
		case ASCII:
			memcpy(b, a, NBPW);
			for (p = b; (p - b) < (int)NBPW; ++p) {
				if (*p < '!' || *p > '~')
					*p = '.';
			}
			printf("%8.8s ", b);
			break;
		default:
			printf("%22.22lo ", *word);
			break;
		}
	}
	if (format != ASCII) {
		/*
		 * print the ascii equivalent of the last words in the buffer
		 * before returning.
		 */
		memset(c, 0x00, width * NBPW);
		if (extra_words)
			width = extra_words;	/* odd number of words */
		memcpy(c, a - (width * NBPW), width * NBPW);
		for (p = c; (p - c) < (int)(width * NBPW); ++p) {
			if (*p < '!' || *p > '~')
				*p = '.';
		}
		if (width == 2)
			printf("\t%16.16s", c);
		else
			printf("\t\t%16.8s", c);
	}
	printf("\n");
	fflush(stdout);
}

static void prt_examples(void)
{
	printf("%s -c 5 -i 0 -s 4090 -b\n", TCID);
	printf("%s -c 5 -i 0 -s 4090 -b -u \n", TCID);
	printf("%s -c 5 -i 0 -s 4090 -b -W 3 -w 3 \n", TCID);
}

static void sig_child(int sig)
{
	int status;

	nchildcompleted++;
#if DEBUG
	#define STR	"parent: received SIGCHLD\n"
	write(STDOUT_FILENO, str, strlen(STR));
#endif
	waitpid(-1, &status, WNOHANG);
}
