Home | History | Annotate | Download | only in freezer
      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