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 exiting.
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) \
50 perror("ERROR at " __FILE__ ":" str_expand(__LINE__) ": " s )
51
52 char *filename = NULL;
53 FILE *fp = NULL;
54 int psync[2];
55 pid_t child = -1;
56
57 int TST_TOTAL = 1;
58 char *TCID = "vfork";
59
60 /* for signal handlers */
parent_cleanup(void)61 void 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
kill_child(void)74 void 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
child_cleanup(void)91 void child_cleanup(void)
92 {
93 close(psync[0]);
94 tst_exit();
95 }
96
do_vfork(int count)97 int 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 */
116 int num_vforks = 1;
117 int do_pause = 0;
118 int do_sleep = 0;
119 struct timespec sleep_duration;
120
sleepy_time(void)121 void 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
usage(void)132 void usage(void)
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
_parse_opts(int argc,char ** argv)144 void _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
trace_grandchild(pid_t gchild)202 int 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
do_trace(pid_t child,int num_children)230 int 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
send_mutex(int fd)264 void 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
await_mutex(int fd)282 void 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
main(int argc,char ** argv)300 int 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