blob: 371220f99b7fcd9952cbe2836759e74fa567582a [file] [log] [blame]
/*
* Copyright (c) International Business Machines Corp., 2008
* Author: Matt Helsley <matthltc@us.ibm.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*
* Usage: $0 <num>
*
* vfork <num> times, stopping after each vfork. TODO: Requires an external process
* to send SIGCONT to goto the next vfork. <num> SIGCONT signals must be
* received before exitting.
*
* We can't do anything but execve or _exit in vfork'd processes
* so we use ptrace vfork'd processes in order to pause then during each
* vfork. This places the parent process in "TASK_UNINTERRUPTIBLE" state
* until vfork returns. This can delay delivery of signals to the parent
* process, even delay or stop system suspend.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include "test.h"
#include "config.h"
#include "../../syscalls/ptrace/ptrace.h"
#define str_expand(s) str(s)
#define str(s) #s
#define debug(s) \
perror("ERROR at " __FILE__ ":" str_expand(__LINE__) ": " s )
char *filename = NULL;
FILE *fp = NULL;
int psync[2];
pid_t child = -1;
int TST_TOTAL = 1;
char *TCID = "vfork";
/* for signal handlers */
void parent_cleanup(void)
{
close(psync[1]);
if (fp) {
fflush(fp);
if (filename) {
fclose(fp);
(void)unlink(filename);
}
}
tst_exit();
}
void kill_child(void)
{
/* Avoid killing all processes at the current user's level, and the
* test app as well =].
*/
if (0 < child && kill(child, 0) == 0) {
/* Shouldn't happen, but I've seen it before... */
if (ptrace(PTRACE_KILL, child, NULL, NULL) < 0) {
tst_resm(TBROK | TERRNO,
"ptrace(PTRACE_KILL, %d, ..) failed", child);
}
(void)waitpid(child, NULL, WNOHANG); /* Zombie children are bad. */
}
}
void child_cleanup()
{
close(psync[0]);
tst_exit();
}
int do_vfork(int count)
{
pid_t child;
while (count) {
child = vfork();
if (child == 0)
_exit(0);
else if (child > 0)
count--;
else {
tst_resm(TFAIL | TERRNO, "vfork failed");
tst_exit();
}
}
return EXIT_SUCCESS;
}
/* Options */
int num_vforks = 1;
int do_pause = 0;
int do_sleep = 0;
struct timespec sleep_duration;
void sleepy_time(void)
{
do {
int rc = nanosleep(&sleep_duration, &sleep_duration);
if ((rc == -1) && (errno != EINTR))
continue;
break;
} while (1);
}
void usage()
{
tst_resm(TBROK, "usage: %s [-f [FILE]] [-s [NUM]] [-p] [NUM]\n\n"
"\t-f FILE\t\tFile to output trace data to.\n"
"\t-s NUM\t\tSleep for NUM seconds. [Default: 1 second]\n"
"\t\t\t\tSuffixes ms, us, s, m, and h correspond to\n"
"\t\t\t\tmilliseconds, microseconds, seconds [Default],\n"
"\t\t\t\tminutes, and hours respectively.\n\n"
"\t-p\t\tPause.\n\n"
"\tNUM\t\tExecute vfork NUM times.\n", TCID);
}
void _parse_opts(int argc, char **argv)
{
int opt;
char *units;
unsigned long long duration = 1U;
sleep_duration.tv_sec = 0U;
sleep_duration.tv_nsec = 0U;
while ((opt = getopt(argc, argv, "f:ps::")) != -1) {
switch (opt) {
case 'f':
if ((fp = fopen(optarg, "w")) != NULL) {
filename = optarg;
}
break;
case 'p':
do_pause = 1;
break;
case 's':
if (optarg == NULL) {
sleep_duration.tv_sec = 1;
do_sleep = 1;
break;
}
opt = sscanf(optarg, "%Ld%as", &duration, &units);
if (opt < 1)
break;
if ((opt != 2) || !strcmp(units, "s"))
sleep_duration.tv_sec = duration;
else if (!strcmp(units, "ms"))
sleep_duration.tv_nsec = duration * 1000000U;
else if (!strcmp(units, "us"))
sleep_duration.tv_nsec = duration * 1000U;
else if (!strcmp(units, "m"))
sleep_duration.tv_sec = duration * 60U;
else if (!strcmp(units, "h"))
sleep_duration.tv_sec = duration * 3600U;
else {
tst_resm(TBROK, "Unrecognized time units: %s",
units);
usage();
}
do_sleep = 1;
break;
default:
usage();
}
}
if (optind >= argc)
return;
if (!strcmp(argv[optind], "--"))
return;
sscanf(argv[optind], "%d", &num_vforks);
}
int trace_grandchild(pid_t gchild)
{
#if HAVE_DECL_PTRACE_GETSIGINFO
siginfo_t info;
if (ptrace(PTRACE_GETSIGINFO, gchild, NULL, &info) == -1) {
debug("ptrace(): ");
return 0;
}
/*dump_siginfo(gchild, &info); */
if ((info.si_code != 0) || (info.si_signo != SIGSTOP))
return 0;
tst_resm(TINFO, "Grandchild spawn's pid=%d", gchild);
fprintf(fp, "\t%d\n", gchild);
fflush(fp);
if (do_pause)
pause();
if (do_sleep)
sleepy_time();
if (ptrace(PTRACE_DETACH, gchild, NULL, NULL) == -1)
debug("ptrace(): ");
return -1; /* don't wait for gchild */
#else
return 0;
#endif
}
int do_trace(pid_t child, int num_children)
{
int my_exit_status = EXIT_SUCCESS;
int status;
pid_t process;
while (num_children > 0) {
int died = 0;
/*printf("waiting for %d processes to exit\n", num_children); */
process = waitpid(-1, &status, WUNTRACED);
if (process < 1)
continue;
/*dump_status(process, status); */
died = (WIFEXITED(status) || WIFSIGNALED(status));
if (died)
num_children--;
if (process == child)
my_exit_status = WEXITSTATUS(status);
if (died || !WIFSTOPPED(status))
continue;
if (process == child) {
/* trace_child(process); */
if (ptrace(PTRACE_CONT, process, NULL, NULL) == -1)
debug("ptrace(): ");
} else
num_children += trace_grandchild(process);
}
return my_exit_status;
}
void send_mutex(int fd)
{
ssize_t nbytes = 0;
do {
nbytes = write(fd, "r", 1);
if (nbytes == 1)
break;
if (nbytes != -1)
continue;
if ((errno == EAGAIN) || (errno == EINTR))
continue;
else
exit(EXIT_FAILURE);
debug("write: ");
} while (1);
}
void await_mutex(int fd)
{
char buffer[1];
ssize_t nbytes = 0;
do {
nbytes = read(fd, buffer, sizeof(buffer));
if (nbytes == 1)
break;
if (nbytes != -1)
continue;
if ((errno == EAGAIN) || (errno == EINTR))
continue;
else
exit(EXIT_FAILURE);
} while (1);
}
int main(int argc, char **argv)
{
#if HAVE_DECL_PTRACE_SETOPTIONS && HAVE_DECL_PTRACE_O_TRACEVFORKDONE
int exit_status;
_parse_opts(argc, argv);
if (fp == NULL) {
fp = stderr;
}
if (socketpair(AF_UNIX, SOCK_STREAM, 0, psync) == -1) {
tst_resm(TBROK | TERRNO, "socketpair() failed");
} else {
child = fork();
if (child == -1) {
tst_resm(TBROK | TERRNO, "fork() failed");
} else if (child == 0) {
int rc = EXIT_FAILURE;
tst_sig(FORK, DEF_HANDLER, child_cleanup);
if (close(psync[1])) {
tst_resm(TBROK, "close(psync[1]) failed)");
} else {
/* sleep until the parent wakes us up */
await_mutex(psync[0]);
rc = do_vfork(num_vforks);
}
_exit(rc);
} else {
tst_sig(FORK, kill_child, parent_cleanup);
close(psync[0]);
/* Set up ptrace */
if (ptrace(PTRACE_ATTACH, child, NULL, NULL) == -1) {
tst_resm(TBROK | TERRNO,
"ptrace(ATTACH) failed");
tst_exit();
}
if (waitpid(child, NULL, 0) != child) {
tst_resm(TBROK | TERRNO, "waitpid(%d) failed",
child);
kill_child();
} else {
if (ptrace(PTRACE_SETOPTIONS, child, NULL,
PTRACE_O_TRACEVFORK) == -1) {
tst_resm(TINFO | TERRNO,
"ptrace(PTRACE_SETOPTIONS) "
"failed.");
}
if (ptrace(PTRACE_CONT, child, NULL, NULL) ==
-1) {
tst_resm(TINFO | TERRNO,
"ptrace(PTRACE_CONT) failed.");
}
send_mutex(psync[1]);
close(psync[1]);
tst_resm(TINFO, "Child spawn's pid=%d", child);
fprintf(fp, "%d\n", child);
fflush(fp);
exit_status = do_trace(child, ++num_vforks);
tst_resm(exit_status == 0 ? TPASS : TFAIL,
"do_trace %s",
(exit_status ==
0 ? "succeeded" : "failed"));
parent_cleanup();
}
}
}
#else
tst_resm(TCONF, "System doesn't support have required ptrace "
"capabilities.");
#endif
tst_resm(TINFO, "Exiting...");
tst_exit();
}