1/*
2 * Copyright (c) International Business Machines  Corp., 2002
3 *
4 * This program is free software;  you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY;  without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
12 * the GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program;  if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 *
18 * 06/30/2001   Port to Linux   nsharoff@us.ibm.com
19 * 11/11/2002   Port to LTP     dbarrera@us.ibm.com
20 */
21
22/*
23 * Get and manipulate a message queue.
24 * Same as msgctl09 but gets the actual msgmni value under procfs.
25 */
26
27#define _XOPEN_SOURCE 500
28#include <sys/stat.h>
29#include <sys/types.h>
30#include <sys/ipc.h>
31#include <sys/msg.h>
32#include <sys/wait.h>
33#include <signal.h>
34#include <errno.h>
35#include <stdio.h>
36#include <string.h>
37#include <stdlib.h>
38#include <unistd.h>
39#include "test.h"
40#include "ipcmsg.h"
41#include "../lib/libmsgctl.h"
42
43char *TCID = "msgctl11";
44int TST_TOTAL = 1;
45
46#define MAXNREPS	1000
47#ifndef CONFIG_COLDFIRE
48#define MAXNPROCS	 1000000	/* This value is set to an arbitrary high limit. */
49#else
50#define MAXNPROCS	 100000	/* Coldfire can't deal with 1000000 */
51#endif
52#define MAXNKIDS	10
53#define DEFNKIDS	2
54
55static int maxnkids = MAXNKIDS;	/* Used if pid_max is exceeded */
56static key_t keyarray[MAXNPROCS];
57static int pidarray[MAXNPROCS];
58static int rkidarray[MAXNKIDS];
59static int wkidarray[MAXNKIDS];
60static int tid;
61static int nprocs, nreps, nkids, MSGMNI;
62static int maxnprocs;
63static int procstat;
64
65void setup(void);
66void cleanup(void);
67
68static void term(int);
69static int dotest(key_t, int);
70static void dotest_iteration(int off);
71static void cleanup_msgqueue(int i, int tid);
72
73static char *opt_maxnprocs;
74static char *opt_nkids;
75static char *opt_nreps;
76
77static option_t options[] = {
78	{"n:", NULL, &opt_maxnprocs},
79	{"c:", NULL, &opt_nkids},
80	{"l:", NULL, &opt_nreps},
81	{NULL, NULL, NULL},
82};
83
84static void usage(void)
85{
86	printf("  -n      Number of processes\n");
87	printf("  -c      Number of read/write child pairs\n");
88	printf("  -l      Number of iterations\n");
89}
90
91
92int main(int argc, char **argv)
93{
94	int i, j, ok;
95
96	tst_parse_opts(argc, argv, options, usage);
97
98	setup();
99
100	nreps = MAXNREPS;
101	nkids = MAXNKIDS;
102
103	if (opt_nreps) {
104		nreps = atoi(opt_nreps);
105		if (nreps > MAXNREPS) {
106			tst_resm(TINFO,
107				 "Requested number of iterations too large, "
108				 "setting to Max. of %d", MAXNREPS);
109			nreps = MAXNREPS;
110		}
111	}
112
113	if (opt_nkids) {
114		nkids = atoi(opt_nkids);
115		if (nkids > MAXNKIDS) {
116			tst_resm(TINFO,
117				 "Requested number of read/write pairs too "
118				 "large, setting to Max. of %d", MAXNKIDS);
119			nkids = MAXNKIDS;
120		}
121	}
122
123
124	if (opt_maxnprocs) {
125		if (atoi(opt_maxnprocs) > maxnprocs) {
126			tst_resm(TINFO,
127				 "Requested number of processes too large, "
128				 "setting to Max. of %d", MSGMNI);
129		} else {
130			maxnprocs = atoi(opt_maxnprocs);
131		}
132	}
133
134	procstat = 0;
135	srand48((unsigned)getpid() + (unsigned)(getppid() << 16));
136	tid = -1;
137
138	/* Setup signal handling routine */
139	if (sigset(SIGTERM, term) == SIG_ERR)
140		tst_brkm(TFAIL, cleanup, "Sigset SIGTERM failed");
141
142	/* Set up array of unique keys for use in allocating message
143	 * queues
144	 */
145	for (i = 0; i < MSGMNI; i++) {
146		ok = 1;
147		do {
148			/* Get random key */
149			keyarray[i] = (key_t) lrand48();
150			/* Make sure key is unique and not private */
151			if (keyarray[i] == IPC_PRIVATE) {
152				ok = 0;
153				continue;
154			}
155			for (j = 0; j < i; j++) {
156				if (keyarray[j] == keyarray[i]) {
157					ok = 0;
158					break;
159				}
160				ok = 1;
161			}
162		} while (ok == 0);
163	}
164	/* Fork a number of processes, each of which will
165	 * create a message queue with several (nkids) reader/writer
166	 * pairs which will read and write a number (iterations)
167	 * of random length messages with specific values (keys).
168	 *
169	 * We do not fork more than maxnprocs at a time and
170	 * we fork until all the message queues get used.
171	 */
172
173	if (MSGMNI <= maxnprocs) {
174		nprocs = MSGMNI;
175		dotest_iteration(0);
176	} else {
177		for (i = 0; i < (MSGMNI / maxnprocs); i++) {
178			nprocs = maxnprocs;
179			dotest_iteration(i*(MSGMNI / maxnprocs));
180		}
181
182		nprocs = MSGMNI % maxnprocs;
183		dotest_iteration(i*(MSGMNI / maxnprocs));
184	}
185
186	tst_resm(TPASS, "msgctl11 ran successfully!");
187
188	cleanup();
189	tst_exit();
190}
191
192static void dotest_iteration(int off)
193{
194	key_t key;
195	int i, count, status;
196	pid_t pid;
197
198	memset(pidarray, 0, sizeof(pidarray));
199
200	for (i = 0; i < nprocs; i++) {
201		key = keyarray[off + i];
202
203		if ((pid = FORK_OR_VFORK()) < 0)
204			tst_brkm(TFAIL, cleanup,
205				 "Fork failed (may be OK if under stress)");
206
207		/* Child does this */
208		if (pid == 0) {
209			procstat = 1;
210			exit(dotest(key, i));
211		}
212		pidarray[i] = pid;
213	}
214
215	count = 0;
216	while (1) {
217		if ((wait(&status)) > 0) {
218			if (status >> 8 != PASS)
219				tst_brkm(TFAIL, cleanup,
220					"Child exit status = %d", status >> 8);
221			count++;
222		} else {
223			if (errno != EINTR) {
224				break;
225			}
226#ifdef DEBUG
227			tst_resm(TINFO, "Signal detected during wait");
228#endif
229		}
230	}
231	/* Make sure proper number of children exited */
232	if (count != nprocs)
233		tst_brkm(TFAIL, cleanup,
234			 "Wrong number of children exited, Saw %d, Expected %d",
235			 count, nprocs);
236}
237
238static void cleanup_msgqueue(int i, int tid)
239{
240	/*
241	 * Decrease the value of i by 1 because it
242	 * is getting incremented even if the fork
243	 * is failing.
244	 */
245
246	i--;
247	/*
248	 * Kill all children & free message queue.
249	 */
250	for (; i >= 0; i--) {
251		(void)kill(rkidarray[i], SIGKILL);
252		(void)kill(wkidarray[i], SIGKILL);
253	}
254
255	if (msgctl(tid, IPC_RMID, 0) < 0) {
256		printf("Msgctl error in cleanup_msgqueue %d\n", errno);
257		exit(FAIL);
258	}
259}
260
261static int dotest(key_t key, int child_process)
262{
263	int id, pid;
264	int i, count, status, exit_status;
265
266	sighold(SIGTERM);
267	if ((id = msgget(key, IPC_CREAT | S_IRUSR | S_IWUSR)) < 0) {
268		printf("msgget() error in child %d: %s\n",
269			child_process, strerror(errno));
270		return FAIL;
271	}
272	tid = id;
273	sigrelse(SIGTERM);
274
275	exit_status = PASS;
276
277	for (i = 0; i < nkids; i++) {
278		if ((pid = FORK_OR_VFORK()) < 0) {
279			printf("Fork failure in the first child of child group %d\n",
280				child_process);
281			cleanup_msgqueue(i, tid);
282			return FAIL;
283		}
284		/* First child does this */
285		if (pid == 0) {
286			procstat = 2;
287			exit(doreader(key, tid, getpid(),
288					child_process, nreps));
289		}
290		rkidarray[i] = pid;
291		if ((pid = FORK_OR_VFORK()) < 0) {
292			printf("Fork failure in the second child of child group %d\n",
293				child_process);
294			/*
295			 * Kill the reader child process
296			 */
297			(void)kill(rkidarray[i], SIGKILL);
298
299			cleanup_msgqueue(i, tid);
300			return FAIL;
301		}
302		/* Second child does this */
303		if (pid == 0) {
304			procstat = 2;
305			exit(dowriter(key, tid, rkidarray[i],
306					child_process, nreps));
307		}
308		wkidarray[i] = pid;
309	}
310	/* Parent does this */
311	count = 0;
312	while (1) {
313		if ((wait(&status)) > 0) {
314			if (status >> 8 != PASS) {
315				printf("Child exit status = %d from child group %d\n",
316					status >> 8, child_process);
317				for (i = 0; i < nkids; i++) {
318					kill(rkidarray[i], SIGTERM);
319					kill(wkidarray[i], SIGTERM);
320				}
321				if (msgctl(tid, IPC_RMID, 0) < 0) {
322					printf("msgctl() error: %s\n",
323						strerror(errno));
324				}
325				return FAIL;
326			}
327			count++;
328		} else {
329			if (errno != EINTR) {
330				break;
331			}
332		}
333	}
334	/* Make sure proper number of children exited */
335	if (count != (nkids * 2)) {
336		printf("Wrong number of children exited in child group %d, saw %d, expected %d\n",
337			child_process, count, (nkids * 2));
338		if (msgctl(tid, IPC_RMID, 0) < 0) {
339			printf("msgctl() error: %s\n", strerror(errno));
340		}
341		return FAIL;
342	}
343	if (msgctl(id, IPC_RMID, 0) < 0) {
344		printf("msgctl() failure in child group %d: %s\n",
345			child_process, strerror(errno));
346		return FAIL;
347	}
348	return exit_status;
349}
350
351/* ARGSUSED */
352static void term(int sig LTP_ATTRIBUTE_UNUSED)
353{
354	int i;
355
356	if (procstat == 0) {
357#ifdef DEBUG
358		tst_resm(TINFO, "SIGTERM signal received, test killing kids");
359#endif
360		for (i = 0; i < nprocs; i++) {
361			if (pidarray[i] > 0) {
362				if (kill(pidarray[i], SIGTERM) < 0) {
363					tst_resm(TBROK,
364						 "Kill failed to kill child %d",
365						 i);
366					exit(FAIL);
367				}
368			}
369		}
370		return;
371	}
372
373	if (procstat == 2) {
374		exit(PASS);
375	}
376
377	if (tid == -1) {
378		exit(FAIL);
379	}
380	for (i = 0; i < nkids; i++) {
381		if (rkidarray[i] > 0)
382			kill(rkidarray[i], SIGTERM);
383		if (wkidarray[i] > 0)
384			kill(wkidarray[i], SIGTERM);
385	}
386}
387
388void setup(void)
389{
390	int nr_msgqs, free_pids;
391
392	tst_tmpdir();
393	/* You will want to enable some signal handling so you can capture
394	 * unexpected signals like SIGSEGV.
395	 */
396	tst_sig(FORK, DEF_HANDLER, cleanup);
397
398	/* One cavet that hasn't been fixed yet.  TEST_PAUSE contains the code to
399	 * fork the test with the -c option.  You want to make sure you do this
400	 * before you create your temporary directory.
401	 */
402	TEST_PAUSE;
403
404	nr_msgqs = get_max_msgqueues();
405	if (nr_msgqs < 0)
406		tst_brkm(TBROK, cleanup, "get_max_msgqueues() failed");
407
408	MSGMNI = nr_msgqs - get_used_msgqueues();
409	if (MSGMNI <= 0)
410		tst_brkm(TBROK, cleanup,
411			 "Max number of message queues already used, cannot create more.");
412
413	tst_resm(TINFO, "Found %d available message queues", MSGMNI);
414
415	free_pids = tst_get_free_pids(cleanup);
416	if (free_pids < 0) {
417		tst_brkm(TBROK, cleanup, "Can't obtain free_pid count");
418	} else if (!free_pids) {
419		tst_brkm(TBROK, cleanup, "No free pids");
420	}
421
422	/* We don't use more than a half of available pids.
423	 * For each child we fork up to 2*maxnkids grandchildren. */
424	maxnprocs = (free_pids / 2) / (1 + 2 * maxnkids);
425
426	if (!maxnprocs)
427		tst_brkm(TBROK, cleanup, "Not enough free pids");
428
429	tst_resm(TINFO, "Using upto %d pids", free_pids / 2);
430}
431
432void cleanup(void)
433{
434	int status;
435
436	/*
437	 * Remove the message queue from the system
438	 */
439#ifdef DEBUG
440	tst_resm(TINFO, "Removing the message queue");
441#endif
442	(void)msgctl(tid, IPC_RMID, NULL);
443	if ((status = msgctl(tid, IPC_STAT, NULL)) != -1) {
444		(void)msgctl(tid, IPC_RMID, NULL);
445		tst_resm(TFAIL, "msgctl(tid, IPC_RMID) failed");
446
447	}
448
449	tst_rmdir();
450}
451