blob: a03287b49c2494fb94fe7136ce5dab72f707fe08 [file] [log] [blame]
/*
* The main pounder process controller and scheduler program.
* Author: Darrick Wong <djwong@us.ibm.com>
*/
/*
* Copyright (C) 2003-2006 IBM
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/
#include <errno.h>
#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <stdarg.h>
#include <sys/time.h>
#include <time.h>
#include <stdio.h>
#include <dirent.h>
#include <sys/stat.h>
#include "proclist.h"
#include "debug.h"
// List of subprocesses to wait upon
struct proclist_t wait_ons = {NULL};
struct proclist_t daemons = {NULL};
static int is_leader = 0;
static char *pidfile = "";
static inline int is_executable(const char *fname);
static inline int is_directory(const char *fname);
static inline int test_filter(const struct dirent *p);
static inline int test_sort(const struct dirent **a, const struct dirent **b);
static int wait_for_pids(void);
static void wait_for_daemons(void);
static void note_process(pid_t pid, char *name);
static void note_daemon(pid_t pid, char *name);
static void kill_tests(void);
static void kill_daemons(void);
static int process_dir(const char *fname);
static pid_t spawn_test(char *fname);
static void note_child(pid_t pid, char *fname, char type);
static int child_finished(const char *name, int stat);
static char *progname;
#define TEST_PATH_LEN 512
#define TEST_FORK_WAIT 100
/**
* Kill everything upon ^C.
*/
static void jump_out(int signum) {
pounder_fprintf(stdout, "Control-C received; aborting!\n");
//unlink("pounder_pgrp");
kill_tests();
kill_daemons();
if (is_leader) {
unlink(pidfile);
}
exit(0);
}
/**
* Kills tests launched from within.
*/
static void kill_tests(void) {
struct proclist_item_t *curr;
curr = wait_ons.head;
while (curr != NULL) {
kill(-curr->pid, SIGTERM);
curr = curr->next;
}
}
/**
* Kills daemons launched from within.
*/
static void kill_daemons(void) {
struct proclist_item_t *curr;
curr = daemons.head;
while (curr != NULL) {
kill(-curr->pid, SIGTERM);
curr = curr->next;
}
}
/**
* Record the pounder leader's PID in a file.
*/
static void record_pid(void) {
FILE *fp;
pidfile = getenv("POUNDER_PIDFILE");
if (pidfile == NULL) {
pidfile = "pounder.pid";
}
fp = fopen(pidfile, "w");
if (fp == NULL) {
perror(pidfile);
}
fprintf(fp, "%d", getpid());
fclose(fp);
}
/**
* Main program. Returns 1 if all programs run successfully, 0 if
* something failed and -1 if there was an error running programs.
*/
int main(int argc, char *argv[]) {
int retcode;
struct sigaction zig;
pid_t pid;
char *c;
/* Check parameters */
if (argc < 2) {
fprintf(stderr, "Usage: %s test_prog\n", argv[0]);
return 1;
}
if (argc > 2 && strcmp(argv[2], "--leader") == 0) {
pounder_fprintf(stdout, "Logging this test output to %s/POUNDERLOG.\n",
getenv("POUNDER_LOGDIR"));
is_leader = 1;
record_pid();
}
progname = argv[0];
/* Set up signals */
memset(&zig, 0x00, sizeof(zig));
zig.sa_handler = jump_out;
sigaction(SIGHUP, &zig, NULL);
sigaction(SIGINT, &zig, NULL);
sigaction(SIGTERM, &zig, NULL);
if (is_directory(argv[1])) {
retcode = process_dir(argv[1]);
} else {
if (is_executable(argv[1])) {
c = rindex(argv[1], '/');
c++;
// Start the test
pid = spawn_test(argv[1]);
if (pid < 0) {
perror("fork");
retcode = -1;
goto out;
}
// Track the test
note_process(pid, argv[1]);
if (wait_for_pids() == 0) {
retcode = 1;
} else {
retcode = 0;
}
} else {
pounder_fprintf(stderr, "%s: Not a directory or a test.\n",
argv[1]);
retcode = -1;
}
}
out:
kill_daemons();
wait_for_daemons();
if (is_leader) {
if (retcode == 0) {
pounder_fprintf(stdout, "%s: %s.\n", argv[1], pass_msg);
} else if (retcode < 0 || retcode == 255) {
pounder_fprintf(stdout, "%s: %s with code %d.\n",
argv[1], abort_msg, retcode);
} else {
pounder_fprintf(stdout, "%s: %s with code %d.\n",
argv[1], fail_msg, retcode);
}
unlink(pidfile);
}
exit(retcode);
}
/**
* Helper function to determine if a file is executable.
* Returns 1 if yes, 0 if no and -1 if error.
*/
static inline int is_executable(const char *fname) {
struct stat tmp;
if (stat(fname, &tmp) < 0) {
return -1;
}
if (geteuid() == 0) {
return 1;
} else if (geteuid() == tmp.st_uid) {
return tmp.st_mode & S_IXUSR;
} else if (getegid() == tmp.st_gid) {
return tmp.st_mode & S_IXGRP;
} else {
return tmp.st_mode & S_IXOTH;
}
}
/**
* Helper function to determine if a file is a directory.
* Returns 1 if yes, 0 if no and -1 if error.
*/
static inline int is_directory(const char *fname) {
struct stat tmp;
if (stat(fname, &tmp) < 0) {
return 0;
}
return S_ISDIR(tmp.st_mode);
}
/**
* Returns 1 if the directory entry's filename fits the test name pattern.
*/
static inline int test_filter(const struct dirent *p) {
return ((p->d_name[0] == 'T' || p->d_name[0] == 'D')
&& isdigit(p->d_name[1]) && isdigit(p->d_name[2]));
}
/**
* Simple routine to compare two tests names such that lower number/name pairs
* are considered "lesser" values.
*/
//static inline int test_sort(const struct dirent **a, const struct dirent **b) {
static inline int test_sort(const struct dirent **a, const struct dirent **b) {
return strcmp(&(*b)->d_name[1], &(*a)->d_name[1]);
}
/**
* Takes the wait() status integer and prints a log message.
* Returns 1 if there was a failure.
*/
static int child_finished(const char *name, int stat) {
int x;
// did we sig-exit?
if (WIFSIGNALED(stat)) {
pounder_fprintf(stdout, "%s: %s on signal %d.\n",
name, fail_msg, WTERMSIG(stat));
return 1;
} else {
x = WEXITSTATUS(stat);
if (x == 0) {
pounder_fprintf(stdout, "%s: %s.\n", name, pass_msg);
return 0;
} else if (x < 0 || x == 255) {
pounder_fprintf(stdout, "%s: %s with code %d.\n",
name, abort_msg, x);
return 1;
// FIXME: add test to blacklist
} else {
pounder_fprintf(stdout, "%s: %s with code %d.\n",
name, fail_msg, x);
return 1;
}
}
}
/**
* Wait for some number of PIDs. If any of them return nonzero, we
* assume that there was some kind of failure and return 0. Otherwise,
* we return 1 to indicate success.
*/
static int wait_for_pids(void) {
struct proclist_item_t *curr;
int i, stat, res, nprocs;
pid_t pid;
res = 1;
// figure out how many times we have to wait...
curr = wait_ons.head;
nprocs = 0;
while (curr != NULL) {
nprocs++;
curr = curr->next;
}
// now wait for children.
for (i = 0; i < nprocs;) {
pid = wait(&stat);
if (pid < 0) {
perror("wait");
return 0;
}
// go find the child
curr = wait_ons.head;
while (curr != NULL) {
if (curr->pid == pid) {
res = (child_finished(curr->name, stat) ? 0 : res);
// one less pid to wait for
i++;
// stop observing
remove_from_proclist(&wait_ons, curr);
free(curr->name);
free(curr);
break;
}
curr = curr->next;
}
curr = daemons.head;
while (curr != NULL) {
if (curr->pid == pid) {
child_finished(curr->name, stat);
remove_from_proclist(&daemons, curr);
free(curr->name);
free(curr);
break;
}
curr = curr->next;
}
}
return res;
}
/**
* Wait for daemons to finish. This function does NOT wait for wait_ons.
*/
static void wait_for_daemons(void) {
struct proclist_item_t *curr;
int i, stat, res, nprocs;
pid_t pid;
res = 1;
// figure out how many times we have to wait...
curr = daemons.head;
nprocs = 0;
while (curr != NULL) {
nprocs++;
curr = curr->next;
}
// now wait for daemons.
for (i = 0; i < nprocs;) {
pid = wait(&stat);
if (pid < 0) {
perror("wait");
if (errno == ECHILD) {
return;
}
}
curr = daemons.head;
while (curr != NULL) {
if (curr->pid == pid) {
child_finished(curr->name, stat);
i++;
remove_from_proclist(&daemons, curr);
free(curr->name);
free(curr);
break;
}
curr = curr->next;
}
}
}
/**
* Creates a record of processes that we want to watch for.
*/
static void note_process(pid_t pid, char *name) {
struct proclist_item_t *it;
it = calloc(1, sizeof(struct proclist_item_t));
if (it == NULL) {
perror("malloc proclist_item_t");
// XXX: Maybe we should just waitpid?
return;
}
it->pid = pid;
it->name = calloc(strlen(name) + 1, sizeof(char));
if (it->name == NULL) {
perror("malloc procitem name");
// XXX: Maybe we should just waitpid?
return;
}
strcpy(it->name, name);
add_to_proclist(&wait_ons, it);
}
/**
* Creates a record of daemons that should be killed on exit.
*/
static void note_daemon(pid_t pid, char *name) {
struct proclist_item_t *it;
it = calloc(1, sizeof(struct proclist_item_t));
if (it == NULL) {
perror("malloc proclist_item_t");
// XXX: what do we do here?
return;
}
it->pid = pid;
it->name = calloc(strlen(name) + 1, sizeof(char));
if (it->name == NULL) {
perror("malloc procitem name");
// XXX: what do we do here?
return;
}
strcpy(it->name, name);
add_to_proclist(&daemons, it);
}
/**
* Starts a test, with the stdin/out/err fd's redirected to logs.
* The 'fname' parameter should be a relative path from $POUNDER_HOME.
*/
static pid_t spawn_test(char *fname) {
pid_t pid;
int fd, tmp;
char buf[TEST_PATH_LEN], buf2[TEST_PATH_LEN];
char *last_slash;
pid = fork();
if (pid == 0) {
if (setpgrp() < 0) {
perror("setpgid");
}
pounder_fprintf(stdout, "%s: %s test.\n", fname, start_msg);
// reroute stdin
fd = open("/dev/null", O_RDWR);
if (fd < 0) {
perror("/dev/null");
exit(-1);
}
close(0);
tmp = dup2(fd, 0);
if (tmp < 0) {
perror("dup(/dev/null)");
exit(-1);
}
close(fd);
// generate log name-- '/' -> '-'.
snprintf(buf2, TEST_PATH_LEN, "%s|%s",
getenv("POUNDER_LOGDIR"), fname);
fd = strlen(buf2);
for (tmp = (index(buf2, '|') - buf2); tmp < fd;
tmp++) {
if (buf2[tmp] == '/') {
buf2[tmp] = '-';
} else if (buf2[tmp] == '|') {
buf2[tmp] = '/';
}
}
// make it so that we have a way to get back to the
// original console.
tmp = dup2(1, 3);
if (tmp < 0) {
perror("dup(stdout, 3)");
exit(-1);
}
// reroute stdout/stderr
fd = open(buf2, O_RDWR | O_CREAT | O_TRUNC | O_SYNC,
S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH);
if (fd < 0) {
perror(buf2);
exit(-1);
}
close(1);
tmp = dup2(fd, 1);
if (tmp < 0) {
perror("dup(log, 1)");
exit(-1);
}
close(2);
tmp = dup2(fd, 2);
if (tmp < 0) {
perror("dup(log, 2)");
exit(-1);
}
close(fd);
// let us construct the absolute pathname of the test.
// first find the current directory
if (getcwd(buf, TEST_PATH_LEN) == NULL) {
perror("getcwd");
exit(-1);
}
// then splice cwd + fname
snprintf(buf2, TEST_PATH_LEN, "%s/%s", buf, fname);
// find the location of the last slash
last_slash = rindex(buf2, '/');
if (last_slash != NULL) {
// copy the filename part into a new buffer
snprintf(buf, TEST_PATH_LEN, "./%s", last_slash + 1);
// truncate at the last slash
*last_slash = 0;
// and chdir
if (chdir(buf2) != 0) {
perror(buf2);
exit(-1);
}
// reassign variables
fname = buf;
}
// spawn the process
execlp(fname, fname, NULL);
// If we get here, we can't run the test.
perror(fname);
exit(-1);
}
tmp = errno;
/* yield for a short while, so that the test has
* a little bit of time to run.
*/
usleep(TEST_FORK_WAIT);
errno = tmp;
return pid;
}
/**
* Adds a child process to either the running-test or running-daemon
* list.
*/
static void note_child(pid_t pid, char *fname, char type) {
if (type == 'T') {
note_process(pid, fname);
} else if (type == 'D') {
note_daemon(pid, fname);
} else {
pounder_fprintf(stdout, "Don't know what to do with child `%s' of type %c.\n", fname, type);
}
}
/**
* Process a directory--for each entry in a directory, execute files or spawn
* a new copy of ourself on the new directory. Process execution is subject to
* these rules:
*
* - Test files that start with the same number '00foo' and '00bar' are allowed
* to run simultaneously.
* - Test files are run in order of number and then name.
*
* If a the fork fails, bit 1 of the return code is set. If a
* program runs but fails, bit 2 is set.
*/
static int process_dir(const char *fname) {
struct dirent **namelist;
int i, result = 0;
char buf[TEST_PATH_LEN];
int curr_level_num = -1;
int test_level_num;
pid_t pid;
int children_ok = 1;
pounder_fprintf(stdout, "%s: Entering directory.\n", fname);
i = scandir(fname, &namelist, test_filter,
(int (*) (const void *, const void *))test_sort);
if (i < 0) {
perror(fname);
return -1;
}
while (i--) {
/* determine level number */
test_level_num = ((namelist[i]->d_name[1] - '0') * 10)
+ (namelist[i]->d_name[2] - '0');
if (curr_level_num == -1) {
curr_level_num = test_level_num;
}
if (curr_level_num != test_level_num) {
children_ok &= wait_for_pids();
curr_level_num = test_level_num;
}
snprintf(buf, TEST_PATH_LEN, "%s/%s", fname,
namelist[i]->d_name);
if (is_directory(buf)) {
pid = fork();
if (pid == 0) {
if (setpgrp() < 0) {
perror("setpgid");
}
// spawn a new copy of ourself.
execl(progname, progname, buf, NULL);
perror(progname);
exit(-1);
}
} else {
pid = spawn_test(buf);
}
if (pid < 0) {
perror("fork");
result |= 1;
free(namelist[i]);
continue;
}
note_child(pid, buf, namelist[i]->d_name[0]);
free(namelist[i]);
}
free(namelist);
/* wait for remaining runners */
children_ok &= wait_for_pids();
if (children_ok == 0) {
result |= 2;
}
pounder_fprintf(stdout, "%s: Leaving directory.\n", fname);
return result;
}