vfork.c revision 526fdf8d8ea3b43b73de7cc25eb754f12702c8d2
1/*
2 * Copyright (c) International Business Machines  Corp., 2008
3 * Author: Matt Helsley <matthltc@us.ibm.com>
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library 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 the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18 *
19 *
20 * Usage: $0 <num>
21 *
22 * vfork <num> times, stopping after each vfork. TODO: Requires an external process
23 * to send SIGCONT to goto the next vfork. <num> SIGCONT signals must be
24 * received before exitting.
25 *
26 * We can't do anything but execve or _exit in vfork'd processes
27 * so we use ptrace vfork'd processes in order to pause then during each
28 * vfork. This places the parent process in "TASK_UNINTERRUPTIBLE" state
29 * until vfork returns. This can delay delivery of signals to the parent
30 * process, even delay or stop system suspend.
31 */
32#include <stdio.h>
33#include <stdlib.h>
34#include <string.h>
35#include <unistd.h>
36#include <errno.h>
37#include <time.h>
38
39#include <sys/types.h>
40#include <sys/wait.h>
41#include <sys/socket.h>
42#include "test.h"
43#include "config.h"
44#include "../../syscalls/ptrace/ptrace.h"
45
46#define str_expand(s) str(s)
47#define str(s) #s
48
49#define debug(s) \
50perror("ERROR at " __FILE__ ":" str_expand(__LINE__) ": " s )
51
52char *filename = NULL;
53FILE *fp = NULL;
54int psync[2];
55pid_t child = -1;
56
57int TST_TOTAL = 1;
58char *TCID = "vfork";
59
60/* for signal handlers */
61void parent_cleanup(void)
62{
63	close(psync[1]);
64	if (fp) {
65		fflush(fp);
66		if (filename) {
67			fclose(fp);
68			(void)unlink(filename);
69		}
70	}
71	tst_exit();
72}
73
74void kill_child(void)
75{
76
77	/* Avoid killing all processes at the current user's level, and the
78	 * test app as well =].
79	 */
80	if (0 < child && kill(child, 0) == 0) {
81		/* Shouldn't happen, but I've seen it before... */
82		if (ptrace(PTRACE_KILL, child, NULL, NULL) < 0) {
83			tst_resm(TBROK | TERRNO,
84				 "ptrace(PTRACE_KILL, %d, ..) failed", child);
85		}
86		(void)waitpid(child, NULL, WNOHANG);	/* Zombie children are bad. */
87	}
88
89}
90
91void child_cleanup()
92{
93	close(psync[0]);
94	tst_exit();
95}
96
97int do_vfork(int count)
98{
99	pid_t child;
100
101	while (count) {
102		child = vfork();
103		if (child == 0)
104			_exit(0);
105		else if (child > 0)
106			count--;
107		else {
108			tst_brkm(TFAIL | TERRNO, NULL, "vfork failed");
109		}
110	}
111
112	return EXIT_SUCCESS;
113}
114
115/* Options */
116int num_vforks = 1;
117int do_pause = 0;
118int do_sleep = 0;
119struct timespec sleep_duration;
120
121void sleepy_time(void)
122{
123	do {
124		int rc = nanosleep(&sleep_duration, &sleep_duration);
125
126		if ((rc == -1) && (errno != EINTR))
127			continue;
128		break;
129	} while (1);
130}
131
132void usage()
133{
134	tst_resm(TBROK, "usage: %s [-f [FILE]] [-s [NUM]] [-p] [NUM]\n\n"
135		 "\t-f FILE\t\tFile to output trace data to.\n"
136		 "\t-s NUM\t\tSleep for NUM seconds. [Default: 1 second]\n"
137		 "\t\t\t\tSuffixes ms, us, s, m, and h correspond to\n"
138		 "\t\t\t\tmilliseconds, microseconds, seconds [Default],\n"
139		 "\t\t\t\tminutes, and hours respectively.\n\n"
140		 "\t-p\t\tPause.\n\n"
141		 "\tNUM\t\tExecute vfork NUM times.\n", TCID);
142}
143
144void _parse_opts(int argc, char **argv)
145{
146	int opt;
147	char *units;
148	unsigned long long duration = 1U;
149
150	sleep_duration.tv_sec = 0U;
151	sleep_duration.tv_nsec = 0U;
152
153	while ((opt = getopt(argc, argv, "f:ps::")) != -1) {
154		switch (opt) {
155		case 'f':
156			if ((fp = fopen(optarg, "w")) != NULL) {
157				filename = optarg;
158			}
159			break;
160		case 'p':
161			do_pause = 1;
162			break;
163		case 's':
164			if (optarg == NULL) {
165				sleep_duration.tv_sec = 1;
166				do_sleep = 1;
167				break;
168			}
169			opt = sscanf(optarg, "%Ld%as", &duration, &units);
170			if (opt < 1)
171				break;
172
173			if ((opt != 2) || !strcmp(units, "s"))
174				sleep_duration.tv_sec = duration;
175			else if (!strcmp(units, "ms"))
176				sleep_duration.tv_nsec = duration * 1000000U;
177			else if (!strcmp(units, "us"))
178				sleep_duration.tv_nsec = duration * 1000U;
179			else if (!strcmp(units, "m"))
180				sleep_duration.tv_sec = duration * 60U;
181			else if (!strcmp(units, "h"))
182				sleep_duration.tv_sec = duration * 3600U;
183			else {
184				tst_resm(TBROK, "Unrecognized time units: %s",
185					 units);
186				usage();
187			}
188			do_sleep = 1;
189			break;
190		default:
191			usage();
192		}
193	}
194
195	if (optind >= argc)
196		return;
197	if (!strcmp(argv[optind], "--"))
198		return;
199	sscanf(argv[optind], "%d", &num_vforks);
200}
201
202int trace_grandchild(pid_t gchild)
203{
204#if HAVE_DECL_PTRACE_GETSIGINFO
205	siginfo_t info;
206
207	if (ptrace(PTRACE_GETSIGINFO, gchild, NULL, &info) == -1) {
208		debug("ptrace(): ");
209		return 0;
210	}
211	/*dump_siginfo(gchild, &info); */
212	if ((info.si_code != 0) || (info.si_signo != SIGSTOP))
213		return 0;
214
215	tst_resm(TINFO, "Grandchild spawn's pid=%d", gchild);
216	fprintf(fp, "\t%d\n", gchild);
217	fflush(fp);
218	if (do_pause)
219		pause();
220	if (do_sleep)
221		sleepy_time();
222	if (ptrace(PTRACE_DETACH, gchild, NULL, NULL) == -1)
223		debug("ptrace(): ");
224	return -1;		/* don't wait for gchild */
225#else
226	return 0;
227#endif
228}
229
230int do_trace(pid_t child, int num_children)
231{
232	int my_exit_status = EXIT_SUCCESS;
233	int status;
234	pid_t process;
235
236	while (num_children > 0) {
237		int died = 0;
238
239		/*printf("waiting for %d processes to exit\n", num_children); */
240		process = waitpid(-1, &status, WUNTRACED);
241		if (process < 1)
242			continue;
243		/*dump_status(process, status); */
244		died = (WIFEXITED(status) || WIFSIGNALED(status));
245		if (died)
246			num_children--;
247		if (process == child)
248			my_exit_status = WEXITSTATUS(status);
249		if (died || !WIFSTOPPED(status))
250			continue;
251
252		if (process == child) {
253			/* trace_child(process); */
254			if (ptrace(PTRACE_CONT, process, NULL, NULL) == -1)
255				debug("ptrace(): ");
256		} else
257			num_children += trace_grandchild(process);
258
259	}
260
261	return my_exit_status;
262}
263
264void send_mutex(int fd)
265{
266	ssize_t nbytes = 0;
267
268	do {
269		nbytes = write(fd, "r", 1);
270		if (nbytes == 1)
271			break;
272		if (nbytes != -1)
273			continue;
274		if ((errno == EAGAIN) || (errno == EINTR))
275			continue;
276		else
277			exit(EXIT_FAILURE);
278		debug("write: ");
279	} while (1);
280}
281
282void await_mutex(int fd)
283{
284	char buffer[1];
285	ssize_t nbytes = 0;
286
287	do {
288		nbytes = read(fd, buffer, sizeof(buffer));
289		if (nbytes == 1)
290			break;
291		if (nbytes != -1)
292			continue;
293		if ((errno == EAGAIN) || (errno == EINTR))
294			continue;
295		else
296			exit(EXIT_FAILURE);
297	} while (1);
298}
299
300int main(int argc, char **argv)
301{
302
303#if HAVE_DECL_PTRACE_SETOPTIONS && HAVE_DECL_PTRACE_O_TRACEVFORKDONE
304	int exit_status;
305
306	_parse_opts(argc, argv);
307
308	if (fp == NULL) {
309		fp = stderr;
310	}
311
312	if (socketpair(AF_UNIX, SOCK_STREAM, 0, psync) == -1) {
313		tst_resm(TBROK | TERRNO, "socketpair() failed");
314	} else {
315
316		child = fork();
317		if (child == -1) {
318			tst_resm(TBROK | TERRNO, "fork() failed");
319		} else if (child == 0) {
320
321			int rc = EXIT_FAILURE;
322
323			tst_sig(FORK, DEF_HANDLER, child_cleanup);
324
325			if (close(psync[1])) {
326				tst_resm(TBROK, "close(psync[1]) failed)");
327			} else {
328				/* sleep until the parent wakes us up */
329				await_mutex(psync[0]);
330				rc = do_vfork(num_vforks);
331			}
332			_exit(rc);
333
334		} else {
335
336			tst_sig(FORK, kill_child, parent_cleanup);
337
338			close(psync[0]);
339
340			/* Set up ptrace */
341			if (ptrace(PTRACE_ATTACH, child, NULL, NULL) == -1) {
342				tst_brkm(TBROK | TERRNO,
343					 NULL, "ptrace(ATTACH) failed");
344			}
345			if (waitpid(child, NULL, 0) != child) {
346				tst_resm(TBROK | TERRNO, "waitpid(%d) failed",
347					 child);
348				kill_child();
349			} else {
350
351				if (ptrace(PTRACE_SETOPTIONS, child, NULL,
352					   PTRACE_O_TRACEVFORK) == -1) {
353					tst_resm(TINFO | TERRNO,
354						 "ptrace(PTRACE_SETOPTIONS) "
355						 "failed.");
356				}
357				if (ptrace(PTRACE_CONT, child, NULL, NULL) ==
358				    -1) {
359					tst_resm(TINFO | TERRNO,
360						 "ptrace(PTRACE_CONT) failed.");
361				}
362
363				send_mutex(psync[1]);
364
365				close(psync[1]);
366
367				tst_resm(TINFO, "Child spawn's pid=%d", child);
368				fprintf(fp, "%d\n", child);
369				fflush(fp);
370
371				exit_status = do_trace(child, ++num_vforks);
372
373				tst_resm(exit_status == 0 ? TPASS : TFAIL,
374					 "do_trace %s",
375					 (exit_status ==
376					  0 ? "succeeded" : "failed"));
377
378				parent_cleanup();
379
380			}
381
382		}
383
384	}
385
386#else
387	tst_resm(TCONF, "System doesn't support have required ptrace "
388		 "capabilities.");
389#endif
390	tst_resm(TINFO, "Exiting...");
391	tst_exit();
392
393}
394