1 /* 2 * Copyright (c) International Business Machines Corp., 2008 3 * Author: Matt Helsley <matthltc (at) 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) \ 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 */ 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 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 91 void child_cleanup(void) 92 { 93 close(psync[0]); 94 tst_exit(); 95 } 96 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 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 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 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 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 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 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 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 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