blob: 479d5c845f003ff0ab7557f8de8a2a140b4c1936 [file] [log] [blame]
/*
* 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 "usctest.h"
#include "safe_macros.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 {
int val;
struct semid_ds *buf;
unsigned short int *array;
} 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)
{
TEST_CLEANUP;
SAFE_FREE(writebuf);
SAFE_FREE(readbuf);
semctl(sem_id, 0, IPC_RMID);
if (!unpipe)
SAFE_UNLINK(NULL, 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);
}
}
}
}
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);
}