pipe_test_02.c revision 2c28215423293e443469a07ae7011135d058b671
1/*
2 *   Copyright (C) Bull S.A. 1996
3 *   Copyright (c) International Business Machines  Corp., 2001
4 *
5 *   This program is free software;  you can redistribute it and/or modify
6 *   it under the terms of the GNU General Public License as published by
7 *   the Free Software Foundation; either version 2 of the License, or
8 *   (at your option) any later version.
9 *
10 *   This program is distributed in the hope that it will be useful,
11 *   but WITHOUT ANY WARRANTY;  without even the implied warranty of
12 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
13 *   the GNU General Public License for more details.
14 *
15 *   You should have received a copy of the GNU General Public License
16 *   along with this program;  if not, write to the Free Software
17 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19/*---------------------------------------------------------------------+
20|                            pipe_test_02                              |
21| ==================================================================== |
22|                                                                      |
23| Description:  Max data transfer through pipe interprocess channel    |
24|               in non-blocking mode                                   |
25|                                                                      |
26| Algorithm:    o  Create a pipe                                       |
27|               o  Make write & read end of pipe non-blocking          |
28|               o  Spawn a child process                               |
29|               o  parent:                                             |
30|                  -  create & send data packets to the child          |
31|                  -  compute checksum on sent packets                 |
32|               o  child:                                              |
33|                  -  recieve packets from parent & compute checksum   |
34|                  -  send final checksum to parent                    |
35|               o  parent:                                             |
36|                  -  compare checksum of sent packets with the        |
37|                     child's checksum                                 |
38|                                                                      |
39| System calls: The following system calls are tested:                 |
40|                                                                      |
41|               pipe () - Creates an interprocess channel              |
42|               fork () - Creates a new process                        |
43|               fcntl () -                                             |
44|               waitpid () - Waits for a child process to stop or      |
45|                                                                      |
46| Usage:        pipe_test_02                                           |
47|                                                                      |
48| To compile:   cc -o pipe_test_02 pipe_test_02.c                      |
49|                                                                      |
50| Last update:   Ver. 1.3, 3/3/94 12:06:38                           |
51|                                                                      |
52| Change Activity                                                      |
53|                                                                      |
54|   Version  Date    Name  Reason                                      |
55|    0.1     010393  DJK   Initial version for AIX 4.1                 |
56|    1.2     021394  DJK   Move to "prod" directory                    |
57|                                                                      |
58+---------------------------------------------------------------------*/
59
60#include <errno.h>
61#include <fcntl.h>
62#include <signal.h>
63#include <stdio.h>
64#include <stdlib.h>
65#include <string.h>
66#include <sys/types.h>
67#include <sys/wait.h>
68#include <unistd.h>
69
70/* Defines:
71 *
72 * MB: one megabyte (MB)
73 *
74 * VALID_PACKET: value sent with each packet, used to verify that the
75 * packets contents were not garbled.
76 *
77 * DEFAULT_NUM_CHILDREN: default number of child processes spawned
78 *
79 * DEFAULT_PACKETS_TO_SEND: default number of packets sent to each child
80 * process.
81 *
82 * MAXCHILD: maximum number of child processes which may be spawned
83 * (based upon the number of file descriptors that a process may use)
84 *
85 * USAGE: usage statement
86 */
87#define MB			(1024*1024)
88#define DEFAULT_PACKETS_TO_SEND 1024
89#define DEFAULT_NUM_CHILDREN	1
90#define OPEN_MAX		256
91#define MAXCHILD 		(OPEN_MAX/2 - 2)
92#define VALID_PACKET		0xabcdef01
93#define USAGE	"\nUsage: %s [-n] [-p nprocs] [{-m totmegs | -b totbytes}]\n\n" \
94		"\t-n          transfer data with NON-BLOCKING reads & writes\n" \
95		"\t-p nprocs   number of child processes to spawn\n" \
96		"\t-m totmegs  number of MB to send through pipe\n" \
97		"\t-b totmegs  number of bytes to send through pipe\n" \
98		"\t                  (must be less than %d)\n\n"
99
100/*
101 * Function Prototypes:
102 *
103 * setup (): Parse command line arguments and intialize variables
104 * child (): Child process
105 * cleanup (): Close all pipes and kill child processes
106 * sys_error (): System error message function
107 * error (): Error message function
108 * setup_signal_handlers (): Sets up signal catching functions
109 * handler (): Signal catching function
110 */
111void setup (int, char **);
112void child (int [], int []);
113void cleanup ();
114void sys_error (const char *, int);
115void error (const char *, int);
116void setup_signal_handlers ();
117void handler (int, int, struct sigcontext *);
118
119/*
120 * Structures & Global variables
121 *
122 * num_children: number of child processes to be spawned
123 *
124 * num_packets: number of packets to be sent to each child process
125 *
126 * non_blocking_flag: uses NON-BLOCKING
127 *
128 * pid: process id's of the spawned processes
129 *
130 * p2child: half duplex pipes from parent to child (parent writes,
131 *          child reads).
132 *
133 * p2parent: half duplex pipe from child to parent (child writes,
134 *           parent reads).
135 */
136
137enum { READ, WRITE };		/* Pipe read & write end indices */
138
139struct data_packet {
140	pid_t		pid;		/* Child process id */
141	int 		last;		/* Indicates last packet when set */
142	long 		valid;		/* Insure packet was not garbled */
143	long 		seq_number;	/* Packet sequence number */
144	unsigned long	checksum;	/* Cumulative checksum so far */
145	unsigned char	data;		/* Data sent in packet */
146};
147typedef struct data_packet data_packet;
148
149int     num_children = DEFAULT_NUM_CHILDREN;
150long	num_packets  = DEFAULT_PACKETS_TO_SEND;
151int	non_blocking_flag = 0;	/* Uses NON-BLOCKING pipes when set */
152int	bflg = 0;		/* Data quantity flag (MB) */
153int	mflg = 0;		/* Data quantity flag (bytes) */
154
155pid_t	parent_pid;		/* Parent's process id */
156pid_t	pid [MAXCHILD];		/* Process id's of spawned processes */
157int	p2child [MAXCHILD][2]; 	/* Pipes from parent to child processes */
158int	p2parent [2];  		/* Pipe from child processes to parent */
159char	err_msg [256];		/* Generic error message buffer */
160
161/*---------------------------------------------------------------------+
162|                               main ()                                |
163| ==================================================================== |
164|                                                                      |
165| Function:  Main program  (see prolog for more details)               |
166|                                                                      |
167| Returns:   (0)  Successful completion                                |
168|            (-1) Error occurred                                       |
169|                                                                      |
170+---------------------------------------------------------------------*/
171int main (int argc, char **argv)
172{
173	int 	i;
174	int 	n;		/* Number of bytes written */
175	int	status;		/* Child's exit status */
176	long	packets_sent;
177	unsigned char 	data;
178	unsigned long 	cksum_parent = 0;
179	data_packet	packet;
180
181	/*
182	 * Parse command line arguments, initialize global variables and
183	 * print program header
184	 */
185	setup (argc, argv);
186	printf ("%s: IPC Pipe TestSuite program\n", *argv);
187	fflush (stdout);
188
189	/*
190	 * Create two sets of half duplex pipes:
191	 *
192	 * p2child: for sending packets from the parent process to the child
193	 *          processes and
194	 * p2parent: for sending checksums from the child processes to the
195	 *           parent
196	 *
197	 * If the non-blocking command line option was specified, use fcntl ()
198	 * to set the O_NONBLOCK file descriptor status flags.  This will
199	 * prevent reads & and writes from blocking if the data is not yet
200	 * available
201	 */
202	printf ("\n\tCreating pipes...\n");
203	fflush (stdout);
204
205	if (pipe (p2parent) < 0)
206		sys_error ("pipe failed", __LINE__);
207
208	if (non_blocking_flag) {
209		printf ("\n\tSending data NON-BLOCKING!\n");
210		fflush (stdout);
211	}
212
213	for (i=0; i<num_children; i++) {
214		if (pipe (&p2child [i][0]) < 0)
215			sys_error ("pipe failed", __LINE__);
216		if (non_blocking_flag) {
217			if (fcntl (p2child [i][READ], F_SETFL, O_NONBLOCK) < 0)
218				sys_error ("fcntl (O_NONBLOCK) failed", __LINE__);
219			if (fcntl (p2child [i][WRITE], F_SETFL, O_NONBLOCK) < 0)
220				sys_error ("fcntl (O_NONBLOCK) failed", __LINE__);
221		}
222	}
223
224	/*
225	 * Spawn num_children processes
226	 *
227	 * Fork of the child process & record the newly created process's
228	 * id for future reference.
229	 *
230	 * Then close the READ end of the p2child pipe, since the parent
231	 * process will be writing into this pipe rather than reading.
232	 * Also close the WRITE end of the p2parent pipe, for just the
233	 * the reverse reasons...
234	 */
235	printf ("\n\tSpawning %d child processes ... \n", num_children);
236	fflush (stdout);
237
238	for (i=0; i<num_children; i++) {
239
240		if ((pid [i] = fork()) == 0) {
241
242			/* Child process */
243			child (&p2child[i][0], p2parent);
244			exit (0);
245
246		} else if (pid [i] < (pid_t)0)
247			sys_error ("fork failed", __LINE__);
248
249		if (close (p2child [i][READ]) < 0)
250			sys_error ("close failed", __LINE__);
251	}
252	if (close (p2parent [WRITE]) < 0)
253		sys_error ("close failed", __LINE__);
254
255	/*
256	 * Send data packets to the child processes
257	 *
258	 * Build packets (initialize all of the packets fields) and then
259	 * send the packets to all of the child processes.
260	 *
261	 * Might have to make several attempts with the NON-BLOCKING writes
262	 * if the resource is not immediately available.
263	 */
264	printf ("\n\tParent: sending %ld packets (%ld bytes) to child processes ...\n",
265		num_packets, num_packets * sizeof (struct data_packet));
266
267	packet.last = 0;
268	packet.valid = VALID_PACKET;
269
270	for (packets_sent = data = 0; num_packets > 0; num_packets--) {
271
272		packet.seq_number = ++packets_sent;
273		packet.data = data++;
274		packet.pid = pid [i];
275		packet.checksum = cksum_parent += packet.data;
276
277		for (i=0; i<num_children; i++) {
278			try_write_ETXN_again:
279			if ((n = write (p2child [i][WRITE], &packet,
280					sizeof (packet))) < 0) {
281				if (non_blocking_flag && errno == EAGAIN) {
282					goto try_write_ETXN_again;
283				} else {
284					sys_error ("write failed", __LINE__);
285				}
286			}
287		}
288	}
289
290	/*
291	 * Send the last packet to the child processes
292	 *
293	 * [ Upon receiving this packet, the child process will know that all
294	 *   of the packets have been sent and that the parent process is
295	 *   expecting the child to send it's checksum back. ]
296	 *
297	 * After sending the last packet, close the WRITE end of the p2child
298	 * pipe as we are finish sending packets to the child processes.
299	 *
300	 * Then wait for all of the child processes to send the checksum
301	 * packets.  Upon receiving the checksum packets verify that the
302	 * child's checksum matches that of the parent.
303	 *
304	 * Might have to make several attempts with the NON-BLOCKING writes
305	 * if the resource is not immediately available.
306	 *
307	 * Finally, close READ end of p2parent pipe as we have finished
308	 * receiving checksums from the child.
309	 */
310	packet.last = 1;
311	printf ("\n\tParent: done sending packets & waiting for children to complete!\n");
312	for (i=0; i<num_children; i++) {
313		try_read_again:
314		if (write (p2child [i][WRITE], &packet, sizeof (packet)) < 0) {
315			if (non_blocking_flag && errno == EAGAIN) {
316				goto try_read_again;
317			} else {
318				sys_error ("write failed", __LINE__);
319			}
320		}
321		if (close (p2child [i][WRITE]) < 0)
322			sys_error ("close failed", __LINE__);
323
324		if (read (p2parent [READ], &packet, sizeof (packet)) <= 0)
325			sys_error ("read failed", __LINE__);
326
327		if (packet.valid != VALID_PACKET)
328			error ("received packet with corrupted data from child!",
329				__LINE__);
330
331		if (cksum_parent != packet.checksum) {
332			sprintf (err_msg, "checksum of data sent by parent " \
333				"does not match checksum of data received by " \
334				"child [pid %d]\n"	\
335				"\tchild's checksum: %08lx\n" \
336				"\tparent's checksum: %08lx\n",
337				packet.pid, packet.checksum, cksum_parent);
338			error (err_msg, __LINE__);
339		}
340	}
341	if (close (p2parent [READ]) < 0)
342		sys_error ("close failed", __LINE__);
343
344	/*
345	 * Wait for all of the child processes to complete & check their
346	 * exit status.
347	 *
348	 * Upon completion of the child proccesses, exit program with success.
349	 */
350	for (i=0; i<num_children; i++) {
351		waitpid (pid [i], &status, 0);
352
353		if (!WIFEXITED (status))
354			sys_error ("child process terminated abnormally",
355				__LINE__);
356	}
357	printf ("\n\tParent: children received all packets & exited successfully\n");
358
359	/* Program completed successfully -- exit */
360	printf ("\nsuccessful!\n");
361
362	return (0);
363}
364
365/*---------------------------------------------------------------------+
366|                               child ()                               |
367| ==================================================================== |
368|                                                                      |
369| Function:  Receive packets from the parent, insure they are valid    |
370|            and not out of sequence, and calculate a running          |
371|            checksum.  Upon receiving the last packet from the        |
372|            parent, build a checksum packet and send it to the parent.|
373|                                                                      |
374| Args:      p2child   - Pipe from parent to child                     |
375|            p2parent  - Pipe from child to parent                     |
376|                                                                      |
377| Returns:   Exits with (-1) if an error occurs                        |
378|                                                                      |
379+---------------------------------------------------------------------*/
380void child (int p2child [], int p2parent [])
381{
382	int	n;			/* Bytes read */
383	pid_t	pid = getpid ();	/* Process id of child */
384	int	end_of_transmission = 0;
385	long	packets_received = 0;	/* Number of packets received
386					 * from parent
387					 */
388
389	data_packet 	packet;		/* Packet used to transmiting data */
390	unsigned long 	cksum_child = 0;/* Checksum of data fields received */
391
392	/*
393	 * Close the WRITE end of the p2child pipe, since the child
394	 * process will be reading from this pipe rather than writing.
395	 * Also close the READ end of the p2parent pipe, for just the
396	 * the reverse reasons...
397	 */
398	if (close (p2child [WRITE]) < 0)
399		sys_error ("close failed", __LINE__);
400	if (close (p2parent [READ]) < 0)
401		sys_error ("close failed", __LINE__);
402
403	/*
404	 * Receive packets from parent & insure packets are valid
405	 *
406	 * Read packets from the parent through p2child pipe.  Upon
407	 * recieving the packet, verify that it is valid, in sequence
408	 * and that both the parent's and child's checksums match.
409	 *
410	 * Might have to make several attempts with the NON-BLOCKING
411	 * reads if the resource is not immediately available.
412	 *
413	 * Continue reading packets until the "last" packet is received
414	 * from the parent.  Upon receiving the last packet, close
415	 * the p2child READ pipe, as we are finished receiving packets
416	 * from the parent.
417	 */
418	while (!end_of_transmission) {
419		try_write_again:
420		n = read (p2child [READ], &packet, sizeof (packet));
421		if (n < 0) {
422			/* Resource not available */
423			if (non_blocking_flag && errno == EAGAIN)
424				goto try_write_again;
425			else
426				sys_error ("read failed", __LINE__);
427		} else if (n > 0) {
428			/* Insure packet is valid */
429			if (packet.valid != VALID_PACKET) {
430				sprintf (err_msg,
431					"child received invalid packet " \
432					"from parent:\n\tpacket #: %ld\n",
433					packets_received);
434				error (err_msg, __LINE__);
435			}
436			/* Received last packet */
437			if (packet.last) {
438				end_of_transmission = 1;
439			} else {
440
441				/* Insure packet was not received out of sequence */
442				packets_received++;
443				if (packets_received != packet.seq_number) {
444					sprintf (err_msg,
445						"child received packet out of sequence\n" \
446						"\texpecting packet: %ld\n" \
447						"\treceived packet:  %ld\n",
448						packets_received, packet.seq_number);
449					error (err_msg, __LINE__);
450				}
451
452				/* Insure checksums still match */
453				cksum_child += packet.data;
454				if (cksum_child != packet.checksum) {
455					sprintf (err_msg,
456						"child & parent checksums do not match\n" \
457						"\tchild checksum:  %08lx\n" \
458						"\tparent checksum: %08lx\n" \
459						"\tpacket number:   %ld\n",
460						cksum_child, packet.checksum, packets_received);
461					error (err_msg, __LINE__);
462				}
463			}
464		}
465	}
466	if (close (p2child [READ]) < 0)
467		sys_error ("close failed", __LINE__);
468
469	/*
470	 * Send parent packet containing child's checksum
471	 *
472	 * Build a checksum packet (initialize packet fields) and then
473	 * send the packet to the parent.
474	 *
475	 * Then close the WRITE p2parent pipe as we have finished sending packets
476	 * to the parent.
477	 */
478	printf ("\t\tChild:  pid [%d] received %ld packets from parent\n",
479		pid, packets_received);
480
481	packet.pid = pid;
482	packet.valid = VALID_PACKET;
483	packet.checksum = cksum_child;
484
485	if (write (p2parent [WRITE], &packet, sizeof (packet)) < 0)
486		sys_error ("write failed", __LINE__);
487	if (close (p2parent [WRITE]) < 0)
488		sys_error ("close failed", __LINE__);
489}
490
491/*---------------------------------------------------------------------+
492|                               setup ()                               |
493| ==================================================================== |
494|                                                                      |
495| Function:  Parse the command line arguments & initialize global      |
496|            variables.                                                |
497|                                                                      |
498| Updates:   (command line options)                                    |
499|                                                                      |
500|            [-n] non_blocking_flag: prevents read & write calls from  |
501|                 from blocking if the resource is not available.      |
502|                                                                      |
503|            [-p] num_packets: number of packets ...                   |
504|                                                                      |
505|            [-c] num_children: number of child processes to spawn ... |
506|                                                                      |
507+---------------------------------------------------------------------*/
508void setup (int argc, char **argv)
509{
510	int	i;
511	int	errflag = 0;
512	int 	bytes = 0, megabytes = 0;
513	char	*program_name = *argv;
514	extern char 	*optarg;	/* Command line option */
515
516	while ((i = getopt(argc, argv, "nm:b:p:?")) != EOF) {
517		switch (i) {
518			case 'n':		/* NON-BLOCKING flag */
519				non_blocking_flag++;
520				break;
521			case 'm':		/* MB */
522				mflg++;
523				megabytes = atoi (optarg);
524				break;
525			case 'b':		/* bytes */
526				bflg++;
527				bytes = atoi (optarg);
528				break;
529			case 'p':		/* number of child procs */
530				num_children = atoi (optarg);
531				break;
532			case '?':
533				errflag++;
534				break;
535		}
536	}
537	if (mflg) {
538		num_packets = megabytes * MB / sizeof (struct data_packet);
539	} else if (bflg) {
540		num_packets = bytes / sizeof (struct data_packet);
541	}
542
543	if (num_packets == 0 || num_children == 0 || num_children > MAXCHILD)
544		errflag++;
545
546	if (errflag) {
547		fprintf (stderr, USAGE, program_name, MAXCHILD);
548		exit (2);
549	}
550	/*
551	 * Setup signal catching function for SIGPIPE & SIGINT, record
552	 * the process id of the parent and initialize the child process
553	 * id array.
554	 */
555	setup_signal_handlers ();
556
557	parent_pid = getpid ();
558
559	for (i=0; i<num_children; i++) {
560		pid [i] = (pid_t)0;
561	}
562}
563
564/*---------------------------------------------------------------------+
565|                          setup_handler ()                            |
566| ==================================================================== |
567|                                                                      |
568| Function:  Setup the signal handler for SIGPIPE.                     |
569|                                                                      |
570+---------------------------------------------------------------------*/
571void setup_signal_handlers ()
572{
573	struct sigaction invec;
574
575	invec.sa_handler = (void (*)(int)) handler;
576	sigemptyset (&invec.sa_mask);
577	invec.sa_flags = 0;
578
579	if (sigaction (SIGINT, &invec, (struct sigaction *) NULL) < 0)
580		sys_error ("sigaction failed", __LINE__);
581
582	if (sigaction (SIGPIPE, &invec, (struct sigaction *) NULL) < 0)
583		sys_error ("sigaction failed", __LINE__);
584}
585
586/*---------------------------------------------------------------------+
587|                             handler ()                               |
588| ==================================================================== |
589|                                                                      |
590| Function:  Signal catching function for SIGPIPE signal.              |
591|                                                                      |
592|            o  SIGPIPE: Print message and abort program...            |
593|                                                                      |
594|            o  SIGINT:  Parent process calls cleanup, child processes |
595|                        simply exit                                   |
596|                                                                      |
597|            o  Other:   Print message and abort program...            |
598|                                                                      |
599+---------------------------------------------------------------------*/
600void handler (int sig, int code, struct sigcontext *scp)
601{
602	char 	msg [100];	/* Buffer for error message */
603
604	if (sig == SIGPIPE) {
605		error ("wrote to pipe with closed read end", __LINE__);
606	} else if (sig == SIGINT) {
607		if (getpid () == parent_pid) {
608
609			fprintf (stderr, "Received SIGINT -- cleaning up...\n");
610			fflush (stderr);
611
612			cleanup ();
613		}
614		else
615			exit (-1);
616	} else {
617		sprintf (msg, "Received an unexpected signal (%d)", sig);
618		error (msg, __LINE__);
619	}
620}
621
622/*---------------------------------------------------------------------+
623|                             cleanup ()                               |
624| ==================================================================== |
625|                                                                      |
626| Function:  Closes all of the pipes, kills all of the child           |
627|            processes and exits the program...                        |
628|                                                                      |
629+---------------------------------------------------------------------*/
630void cleanup ()
631{
632	int i;
633
634	if (getpid () == parent_pid) {
635		for (i=0; i<num_children; i++) {
636			if (pid [i] > (pid_t)0 && kill (pid [i], SIGKILL) < 0)
637				sys_error ("signal failed", __LINE__);
638
639			close (p2child [i][READ]);
640			close (p2child [i][WRITE]);
641			close (p2parent [READ]);
642			close (p2parent [WRITE]);
643		}
644	}
645
646	exit (-1);
647}
648
649/*---------------------------------------------------------------------+
650|                             sys_error ()                             |
651| ==================================================================== |
652|                                                                      |
653| Function:  Creates system error message and calls error ()           |
654|                                                                      |
655+---------------------------------------------------------------------*/
656void sys_error (const char *msg, int line)
657{
658	char syserr_msg [256];
659
660	sprintf (syserr_msg, "%s: %s\n", msg, strerror (errno));
661	error (syserr_msg, line);
662}
663
664/*---------------------------------------------------------------------+
665|                               error ()                               |
666| ==================================================================== |
667|                                                                      |
668| Function:  Prints out message and calls cleanup...                   |
669|                                                                      |
670+---------------------------------------------------------------------*/
671void error (const char *msg, int line)
672{
673	fprintf (stderr, "ERROR [line: %d] %s\n", line, msg);
674	fflush (stderr);
675	cleanup ();
676}