blob: 1b1456a9b8bf77b97106ffa3dbf7f5aa713ab255 [file] [log] [blame]
/*
* Copyright (c) 2000 Silicon Graphics, Inc. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it would be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* Further, this software is distributed without any warranty that it is
* free of the rightful claim of any third person regarding infringement
* or the like. Any license provided herein, whether implied or
* otherwise, applies only to this software file. Patent licenses, if
* any, provided herein do not apply to combinations of this program with
* other software, or any other product whatsoever.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write the Free Software Foundation, Inc., 59
* Temple Place - Suite 330, Boston MA 02111-1307, USA.
*
* Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy,
* Mountain View, CA 94043, or:
*
* http://www.sgi.com
*
* For further information regarding this notice, see:
*
* http://oss.sgi.com/projects/GenInfo/NoticeExplan/
*
* Changelog:
*
* Added timer options: William Jay Huie, IBM
* 01/27/03 - Added: Manoj Iyer, manjo@mail.utexas.edu
* - option '-p' (pretty printing)i to enabled formatted printing
* of results.
*
* 01/27/03 - Added: Manoj Iyer, manjo@mail.utexas.edu
* - added code to print system information
*
* 01/28/03 - Added: Manoj Iyer, manjo@mail.utexas.edu
* - added code to print test exit value.
*
* 01/29/03 - Added: Manoj Iyer, manjo@mail.utexas.edu
* - added code supresses test start and test end tags.
*
* 07/22/07 - Added: Ricardo Salveti de Araujo, rsalveti@linux.vnet.ibm.com
* - added option to create a command file with all failed tests.
*
*/
/* $Id: ltp-pan.c,v 1.4 2009/10/15 18:45:55 yaberauneya Exp $ */
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/times.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/utsname.h>
#include <errno.h>
#include <err.h>
#include <limits.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "splitstr.h"
#include "zoolib.h"
/* One entry in the command line collection. */
struct coll_entry
{
char *name; /* tag name */
char *cmdline; /* command line */
char *pcnt_f; /* location of %f in the command line args, flag */
struct coll_entry *next;
};
struct collection
{
int cnt;
struct coll_entry **ary;
};
struct tag_pgrp
{
int pgrp;
int stopping;
time_t mystime;
struct coll_entry *cmd;
char output[PATH_MAX];
};
struct orphan_pgrp
{
int pgrp;
struct orphan_pgrp *next;
};
static pid_t run_child(struct coll_entry *colle, struct tag_pgrp *active,
int quiet_mode);
static char *slurp(char *file);
static struct collection *get_collection(char *file, int optind, int argc,
char **argv);
static void pids_running(struct tag_pgrp *running, int keep_active);
static int check_pids(struct tag_pgrp *running, int *num_active,
int keep_active, FILE * logfile, FILE * failcmdfile,
struct orphan_pgrp *orphans, int fmt_print,
int *failcnt, int quiet_mode);
static void propagate_signal(struct tag_pgrp *running, int keep_active,
struct orphan_pgrp *orphans);
static void dump_coll(struct collection *coll);
static char *subst_pcnt_f(struct coll_entry *colle);
static void mark_orphan(struct orphan_pgrp *orphans, pid_t cpid);
static void orphans_running(struct orphan_pgrp *orphans);
static void check_orphans(struct orphan_pgrp *orphans, int sig);
static void copy_buffered_output(struct tag_pgrp *running);
static void write_test_start(struct tag_pgrp *running);
static void write_test_end(struct tag_pgrp *running, const char *init_status,
time_t exit_time, char *term_type, int stat_loc,
int term_id, struct tms *tms1, struct tms *tms2);
//wjh
static char PAN_STOP_FILE[] = "PAN_STOP_FILE";
static char *panname = NULL;
static char *test_out_dir = NULL; /* dir to buffer output to */
zoo_t zoofile;
static char *reporttype = NULL;
/* zoolib */
int rec_signal; /* received signal */
int send_signal; /* signal to send */
/* Debug Bits */
int Debug = 0;
#define Dbuffile 0x000400 /* buffer file use */
#define Dsetup 0x000200 /* one-time set-up */
#define Dshutdown 0x000100 /* killed by signal */
#define Dexit 0x000020 /* exit status */
#define Drunning 0x000010 /* current pids running */
#define Dstartup 0x000004 /* started command */
#define Dstart 0x000002 /* started command */
#define Dwait 0x000001 /* wait interrupted */
int
main(int argc, char **argv)
{
extern char *optarg;
extern int optind;
char *zooname = NULL; /* name of the zoo file to use */
char *filename = "/dev/null"; /* filename to read test tags from */
char *logfilename = NULL;
char *failcmdfilename = NULL;
char *outputfilename = NULL;
struct collection *coll = NULL;
struct tag_pgrp *running;
struct orphan_pgrp *orphans, *orph;
struct utsname unamebuf;
FILE *logfile = NULL;
FILE *failcmdfile = NULL;
int keep_active = 1;
int num_active = 0;
int failcnt = 0; /* count of total testcases that failed. */
int err, i;
int starts = -1;
int timed = 0;
int run_time = -1; char modifier = 'm'; int ret = 0;
int stop;
int go_idle;
int has_brakes = 0; /* stop everything if a test case fails */
int sequential = 0; /* run tests sequentially */
int fork_in_road = 0;
int exit_stat;
int track_exit_stats = 0; /* exit non-zero if any test exits non-zero */
int fmt_print = 0; /* enables formatted printing of logfiles. */
int quiet_mode = 0; /* supresses test start and test end tags. */
int c;
pid_t cpid;
struct sigaction sa;
while ((c = getopt(argc, argv, "AO:Sa:C:d:ef:hl:n:o:pqr:s:t:x:y")) != -1) {
switch (c) {
case 'A': /* all-stop flag */
has_brakes = 1;
track_exit_stats = 1;
break;
case 'O': /* output buffering directory */
test_out_dir = strdup(optarg);
break;
case 'S': /* run tests sequentially */
sequential = 1;
break;
case 'a': /* name of the zoo file to use */
zooname = strdup(optarg);
break;
case 'C': /* name of the file where all failed commands will be */
failcmdfilename = strdup(optarg);
break;
case 'd': /* debug options */
sscanf(optarg, "%i", &Debug);
break;
case 'e': /* exit non-zero if any test exists non-zero */
track_exit_stats = 1;
break;
case 'f': /* filename to read test tags from */
filename = strdup(optarg);
break;
case 'h': /* help */
fprintf(stdout, "Usage: pan -n name [ -SyAehpq ] [ -s starts ]"
" [-t time[s|m|h|d] [ -x nactive ] [ -l logfile ]\n\t"
"[ -a active-file ] [ -f command-file ] "
"[ -C fail-command-file ] "
"[ -d debug-level ]\n\t[-o output-file] "
"[-O output-buffer-directory] [cmd]\n");
exit(0);
case 'l': /* log file */
logfilename = strdup(optarg);
break;
case 'n': /* tag given to pan */
panname = strdup(optarg);
break;
case 'o': /* send test output here */
outputfilename = strdup(optarg);
break;
case 'p': /* formatted printing. */
fmt_print = 1;
break;
case 'q': /* supress test start and test end messages */
quiet_mode = 1;
break;
case 'r': /* reporting type: none, rts */
reporttype = strdup(optarg);
break;
case 's': /* number of tags to run */
starts = atoi(optarg);
break;
case 't': /* run_time to run */
ret = sscanf(optarg, "%d%c", &run_time, &modifier);
if (ret == 0) { fprintf(stderr, "Need proper time input: ####x where"
"x is one of s,m,h,d\n"); break; }
else if (ret == 1) { fprintf(stderr, "Only got a time value of %d "
"modifiers need to come immediately after #"
" assuming %c\n", run_time, modifier); }
else
{
switch (modifier)
{
case 's': run_time = run_time; break;
case 'm': run_time = run_time * 60; break;
case 'h': run_time = run_time * 60 * 60; break;
case 'd': run_time = run_time * 60 * 60 * 24; break;
default:
fprintf(stderr, "Invalid time modifier, try: s|h|m|d\n"); exit(-1);
}
if (!quiet_mode)
printf("PAN will run for %d seconds\n", run_time);
}
timed = 1; //-t implies run as many starts as possible, by default
break;
case 'x': /* number of tags to keep running */
keep_active = atoi(optarg);
break;
case 'y': /* restart on failure or signal */
fork_in_road = 1;
break;
}
}
if (panname == NULL) {
fprintf(stderr, "pan: Must supply -n\n");
exit(1);
}
if (zooname == NULL) {
zooname = zoo_getname();
if (zooname == NULL) {
fprintf(stderr,
"pan(%s): Must supply -a or set ZOO env variable\n",
panname);
exit(1);
}
}
if (reporttype) {
/* make sure we understand the report type */
if (strcasecmp(reporttype, "rts")
&& strcasecmp(reporttype, "none")
/* && strcasecmp(reporttype, "xml")*/)
reporttype = "rts";
} else {
/* set the default */
reporttype = "rts";
}
if (logfilename != NULL) {
time_t startup;
char *s;
if (!strcmp(logfilename, "-")) {
logfile = stdout;
} else {
if ((logfile = fopen(logfilename, "a+")) == NULL) {
fprintf(stderr,
"pan(%s): Error %s (%d) opening log file '%s'\n",
panname, strerror(errno), errno, logfilename);
exit(1);
}
}
time(&startup);
s = ctime(&startup);
*(s + strlen(s) - 1) = '\0';
if (!fmt_print)
fprintf(logfile, "startup='%s'\n", s);
else
{
fprintf(logfile, "Test Start Time: %s\n", s);
fprintf(logfile, "-----------------------------------------\n");
fprintf(logfile, "%-30.20s %-10.10s %-10.10s\n",
"Testcase", "Result", "Exit Value");
fprintf(logfile, "%-30.20s %-10.10s %-10.10s\n",
"--------", "------", "------------");
}
}
coll = get_collection(filename, optind, argc, argv);
if (!coll)
exit(1);
if (coll->cnt == 0) {
fprintf(stderr,
"pan(%s): Must supply a file collection or a command\n",
panname);
exit(1);
}
if (Debug & Dsetup)
dump_coll(coll);
/* a place to store the pgrps we're watching */
running = (struct tag_pgrp *)malloc((keep_active + 1) * sizeof(struct tag_pgrp));
if (running == NULL) {
fprintf(stderr, "pan(%s): Failed to allocate memory: %s\n", panname,
strerror(errno));
exit(2);
}
memset(running, 0, keep_active * sizeof(struct tag_pgrp));
running[keep_active].pgrp = -1; /* end sentinel */
/* a head to the orphaned pgrp list */
orphans = (struct orphan_pgrp *) malloc(sizeof(struct orphan_pgrp));
memset(orphans, 0, sizeof(struct orphan_pgrp));
srand48(time(NULL) ^ (getpid() + (getpid() << 15)));
/* Supply a default for starts. If we are in sequential mode, use
* the number of commands available; otherwise 1.
*/
if (timed == 1 && starts == -1) { /* timed, infinite by default */
starts = -1;
} else if (starts == -1) {
if (sequential) {
starts = coll->cnt;
} else {
starts = 1;
}
} else if (starts == 0) { /* if the user specified infinite, set it */
starts = -1;
} else { /* else, make sure we are starting at least keep_active processes */
if (starts < keep_active)
starts = keep_active;
}
/* if we're buffering output, but we're only running on process at a time,
* then essentially "turn off buffering"
*/
if (test_out_dir && (keep_active == 1)) {
free(test_out_dir);
test_out_dir = NULL;
}
if (test_out_dir) {
struct stat sbuf;
if (stat(test_out_dir, &sbuf) < 0) {
fprintf(stderr,
"pan(%s): stat of -O arg '%s' failed. errno: %d %s\n",
panname, test_out_dir, errno, strerror(errno));
exit(1);
}
if (!S_ISDIR(sbuf.st_mode)) {
fprintf(stderr, "pan(%s): -O arg '%s' must be a directory.\n",
panname, test_out_dir);
exit(1);
}
if (access(test_out_dir, W_OK | R_OK | X_OK) < 0) {
fprintf(stderr,
"pan(%s): permission denied on -O arg '%s'. errno: %d %s\n",
panname, test_out_dir, errno, strerror(errno));
exit(1);
}
}
if (outputfilename) {
if (!freopen(outputfilename, "a+", stdout)) {
fprintf(stderr,
"pan(%s): Error %s (%d) opening output file '%s'\n",
panname, strerror(errno), errno, outputfilename);
exit(1);
}
}
if (failcmdfilename) {
if (!(failcmdfile = fopen(failcmdfilename, "a+"))) {
fprintf(stderr,
"pan(%s): Error %s (%d) opening fail cmd file '%s'\n",
panname, strerror(errno), errno, failcmdfilename);
exit(1);
}
}
if ((zoofile = zoo_open(zooname)) == NULL) {
fprintf(stderr, "pan(%s): %s\n", panname, zoo_error);
exit(1);
}
if (zoo_mark_args(zoofile, getpid(), panname, argc, argv)) {
fprintf(stderr, "pan(%s): %s\n", panname, zoo_error);
exit(1);
}
/* Allocate N spaces for max-arg commands.
* this is an "active file cleanliness" thing
*/
{
char *av[2], bigarg[82];
memset(bigarg, '.', 81);
bigarg[81] = '\0';
av[0] = bigarg;
av[1] = NULL;
for (c = 0; c < keep_active; c++) {
if (zoo_mark_cmdline(zoofile, c, panname, "")) {
fprintf(stderr, "pan(%s): %s\n", panname, zoo_error);
exit(1);
}
}
for (c = 0; c < keep_active; c++) {
if (zoo_clear(zoofile, c)) {
fprintf(stderr, "pan(%s): %s\n", panname, zoo_error);
exit(1);
}
}
}
rec_signal = send_signal = 0;
if (run_time != -1) { alarm(run_time); }
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sa.sa_handler = wait_handler;
sigaction(SIGALRM, &sa, NULL);
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
sigaction(SIGHUP, &sa, NULL);
sigaction(SIGUSR1, &sa, NULL); /* ignore fork_in_road */
sigaction(SIGUSR2, &sa, NULL); /* stop the scheduler */
c = 0; /* in this loop, c is the command index */
stop = 0;
exit_stat = 0;
go_idle = 0;
while (1) {
while ((num_active < keep_active) && (starts != 0)) {
if (stop || rec_signal || go_idle)
break;
if (!sequential)
c = lrand48() % coll->cnt;
/* find a slot for the child */
for (i = 0; i < keep_active; ++i) {
if (running[i].pgrp == 0)
break;
}
if (i == keep_active) {
fprintf(stderr, "pan(%s): Aborting: i == keep_active = %d\n",
panname, i);
wait_handler(SIGINT);
exit_stat++;
break;
}
cpid = run_child(coll->ary[c], running + i, quiet_mode);
if (cpid != -1)
++num_active;
if ((cpid != -1 || sequential) && starts > 0)
--starts;
if (sequential)
if (++c >= coll->cnt)
c = 0;
} /* while ((num_active < keep_active) && (starts != 0)) */
if (starts == 0)
{
if (!quiet_mode)
printf("incrementing stop\n");
++stop;
}
else if (starts == -1) //wjh
{
FILE *f = (FILE*)-1;
if ((f = fopen(PAN_STOP_FILE, "r")) != 0)
{ printf("Got %s Stopping!\n", PAN_STOP_FILE);
fclose(f); unlink(PAN_STOP_FILE); stop++;
}
}
if (rec_signal) {
/* propagate everything except sigusr2 */
if (rec_signal == SIGUSR2) {
if (fork_in_road)
++go_idle;
else
++stop;
rec_signal = send_signal = 0;
} else {
if (rec_signal == SIGUSR1)
fork_in_road = 0;
propagate_signal(running, keep_active, orphans);
if (fork_in_road)
++go_idle;
else
++stop;
}
}
err = check_pids(running, &num_active, keep_active, logfile,
failcmdfile, orphans, fmt_print, &failcnt, quiet_mode);
if (Debug & Drunning) {
pids_running(running, keep_active);
orphans_running(orphans);
}
if (err) {
if (fork_in_road)
++go_idle;
if (track_exit_stats)
exit_stat++;
if (has_brakes) {
fprintf(stderr, "pan(%s): All stop!%s\n", panname,
go_idle ? " (idling)" : "");
wait_handler(SIGINT);
}
}
if (stop && (num_active == 0))
break;
if (go_idle && (num_active == 0)) {
go_idle = 0; /* It is idle, now resume scheduling. */
wait_handler(0); /* Reset the signal ratchet. */
}
}
/* Wait for orphaned pgrps */
while (1) {
for (orph = orphans; orph != NULL; orph = orph->next) {
if (orph->pgrp == 0)
continue;
/* Yes, we have orphaned pgrps */
sleep(5);
if (!rec_signal) {
/* force an artificial signal, move us
* through the signal ratchet.
*/
wait_handler(SIGINT);
}
propagate_signal(running, keep_active, orphans);
if (Debug & Drunning)
orphans_running(orphans);
break;
}
if (orph == NULL)
break;
}
if (zoo_clear(zoofile, getpid())) {
fprintf(stderr, "pan(%s): %s\n", panname, zoo_error);
++exit_stat;
}
fclose(zoofile);
if (logfile && fmt_print)
{
if (uname(&unamebuf) == -1)
fprintf(stderr, "ERROR: uname(): %s\n", strerror(errno));
fprintf(logfile, "\n-----------------------------------------------\n");
fprintf(logfile, "Total Tests: %d\n", coll->cnt);
fprintf(logfile, "Total Failures: %d\n", failcnt);
fprintf(logfile, "Kernel Version: %s\n", unamebuf.release);
fprintf(logfile, "Machine Architecture: %s\n", unamebuf.machine);
fprintf(logfile, "Hostname: %s\n\n", unamebuf.nodename);
}
if (logfile && (logfile != stdout))
fclose(logfile);
exit(exit_stat);
}
static void
propagate_signal(struct tag_pgrp *running, int keep_active,
struct orphan_pgrp *orphans)
{
int i;
if (Debug & Dshutdown)
fprintf(stderr, "pan was signaled with sig %d...\n", rec_signal);
if (rec_signal == SIGALRM)
{
printf("PAN stop Alarm was received\n");
rec_signal = SIGTERM;
}
for (i = 0; i < keep_active; ++i) {
if (running[i].pgrp == 0)
continue;
if (Debug & Dshutdown)
fprintf(stderr, " propagating sig %d to %d\n",
send_signal, -running[i].pgrp);
if (kill(-running[i].pgrp, send_signal) != 0) {
fprintf(stderr,
"pan(%s): kill(%d,%d) failed on tag (%s). errno:%d %s\n",
panname, -running[i].pgrp, send_signal,
running[i].cmd->name, errno, strerror(errno));
}
running[i].stopping = 1;
}
check_orphans(orphans, send_signal);
rec_signal = send_signal = 0;
}
static int
check_pids(struct tag_pgrp *running, int *num_active, int keep_active,
FILE * logfile, FILE * failcmdfile, struct orphan_pgrp *orphans,
int fmt_print, int *failcnt, int quiet_mode)
{
int w;
pid_t cpid;
int stat_loc;
int ret = 0;
int i;
time_t t;
char *status;
int signaled = 0;
struct tms tms1, tms2;
clock_t tck;
check_orphans(orphans, 0);
tck = times(&tms1);
if (tck == -1) {
fprintf(stderr, "pan(%s): times(&tms1) failed. errno:%d %s\n",
panname, errno, strerror(errno));
}
cpid = wait(&stat_loc);
tck = times(&tms2);
if (tck == -1) {
fprintf(stderr, "pan(%s): times(&tms2) failed. errno:%d %s\n",
panname, errno, strerror(errno));
}
if (cpid < 0) {
if (errno == EINTR) {
if (Debug)
fprintf(stderr, "pan(%s): wait() interrupted\n", panname);
} else if (errno != ECHILD) {
fprintf(stderr, "pan(%s): wait() failed. errno:%d %s\n",
panname, errno, strerror(errno));
}
} else if (cpid > 0) {
if (WIFSIGNALED(stat_loc)) {
w = WTERMSIG(stat_loc);
status = "signaled";
if (Debug & Dexit)
fprintf(stderr, "child %d terminated with signal %d\n", cpid,
w);
--*num_active;
signaled = 1;
} else if (WIFEXITED(stat_loc)) {
w = WEXITSTATUS(stat_loc);
status = "exited";
if (Debug & Dexit)
fprintf(stderr, "child %d exited with status %d\n", cpid, w);
--*num_active;
if (w != 0)
ret++;
} else if (WIFSTOPPED(stat_loc)) { /* should never happen */
w = WSTOPSIG(stat_loc);
status = "stopped";
ret++;
} else { /* should never happen */
w = 0;
status = "unknown";
ret++;
}
for (i = 0; i < keep_active; ++i) {
if (running[i].pgrp == cpid) {
if ((w == 130) && running[i].stopping &&
(strcmp(status, "exited") == 0)) {
/* The child received sigint, but
* did not trap for it? Compensate
* for it here.
*/
w = 0;
ret--; /* undo */
if (Debug & Drunning)
fprintf(stderr,
"pan(%s): tag=%s exited 130, known to be signaled; will give it an exit 0.\n",
panname, running[i].cmd->name);
}
time(&t);
if (logfile != NULL) {
if (!fmt_print)
fprintf(logfile,
"tag=%s stime=%d dur=%d exit=%s stat=%d core=%s cu=%d cs=%d\n",
running[i].cmd->name, (int) (running[i].mystime),
(int) (t - running[i].mystime), status, w,
(stat_loc & 0200) ? "yes" : "no",
(int) (tms2.tms_cutime - tms1.tms_cutime),
(int) (tms2.tms_cstime - tms1.tms_cstime));
else
{
if (w != 0)
++*failcnt;
fprintf(logfile, "%-30.30s %-10.10s %-5d\n",
running[i].cmd->name, ((w != 0) ? "FAIL" : "PASS"),
w);
}
fflush(logfile);
}
if ((failcmdfile != NULL) && (w !=0)) {
fprintf(failcmdfile, "%s %s\n", running[i].cmd->name, running[i].cmd->cmdline);
}
if (running[i].stopping)
status = "driver_interrupt";
if (test_out_dir) {
if (!quiet_mode)
write_test_start(running+i);
copy_buffered_output(running + i);
unlink(running[i].output);
}
if (!quiet_mode)
write_test_end(running+i, "ok", t, status,
stat_loc, w, &tms1, &tms2);
/* If signaled and we weren't expecting
* this to be stopped then the proc
* had a problem.
*/
if (signaled && !running[i].stopping)
ret++;
running[i].pgrp = 0;
if (zoo_clear(zoofile, cpid)) {
fprintf(stderr, "pan(%s): %s\n", panname, zoo_error);
exit(1);
}
/* Check for orphaned pgrps */
if ((kill(-cpid, 0) == 0) || (errno == EPERM)) {
if (zoo_mark_cmdline(zoofile, cpid, "panorphan",
running[i].cmd->cmdline)) {
fprintf(stderr, "pan(%s): %s\n", panname, zoo_error);
exit(1);
}
mark_orphan(orphans, cpid);
/* status of kill doesn't matter */
kill(-cpid, SIGTERM);
}
break;
}
}
}
return ret;
}
static pid_t
run_child(struct coll_entry *colle, struct tag_pgrp *active, int quiet_mode)
{
ssize_t errlen;
int cpid;
int c_stdout = -1; /* child's stdout, stderr */
int capturing = 0; /* output is going to a file instead of stdout */
char *c_cmdline;
static long cmdno = 0;
int errpipe[2]; /* way to communicate to parent that the tag */
char errbuf[1024]; /* didn't actually start */
/* Try to open the file that will be stdout for the test */
if (test_out_dir) {
capturing = 1;
do {
sprintf(active->output, "%s/%s.%ld",
test_out_dir, colle->name, cmdno++);
c_stdout = open(active->output, O_CREAT | O_RDWR | O_EXCL | O_SYNC, 0666);
} while (c_stdout < 0 && errno == EEXIST);
if (c_stdout < 0) {
fprintf(stderr,
"pan(%s): open of stdout file failed (tag %s). errno: %d %s\n file: %s\n",
panname, colle->name, errno, strerror(errno),
active->output);
return -1;
}
}
/* get the tag's command line arguments ready. subst_pcnt_f() uses a
* static counter, that's why we do it here instead of after we fork.
*/
if (colle->pcnt_f) {
c_cmdline = subst_pcnt_f(colle);
} else {
c_cmdline = colle->cmdline;
}
if (pipe(errpipe) < 0) {
fprintf(stderr, "pan(%s): pipe() failed. errno:%d %s\n",
panname, errno, strerror(errno));
if (capturing) {
close(c_stdout);
unlink(active->output);
}
return -1;
}
time(&active->mystime);
active->cmd = colle;
if (!test_out_dir)
if (!quiet_mode)
write_test_start(active);
if ((cpid = fork()) == -1) {
fprintf(stderr, "pan(%s): fork failed (tag %s). errno:%d %s\n",
panname, colle->name, errno, strerror(errno));
if (capturing) {
unlink(active->output);
close(c_stdout);
}
close(errpipe[0]);
close(errpipe[1]);
return -1;
} else if (cpid == 0) {
/* child */
fclose(zoofile);
close(errpipe[0]);
fcntl(errpipe[1], F_SETFD, 1); /* close the pipe if we succeed */
setpgrp();
umask(0);
#define WRITE_OR_DIE(fd, buf, buflen) do { \
if (write((fd), (buf), (buflen)) != (buflen)) { \
err(1, "failed to write out %zd bytes at line %d", \
buflen, __LINE__); \
} \
} while(0)
/* if we're putting output into a buffer file, we need to do the
* redirection now. If we fail
*/
if (capturing) {
if (dup2(c_stdout, fileno(stdout)) == -1) {
errlen = sprintf(errbuf, "pan(%s): couldn't redirect stdout for tag %s. errno:%d %s",
panname, colle->name, errno, strerror(errno));
WRITE_OR_DIE(errpipe[1], &errlen, sizeof(errlen));
WRITE_OR_DIE(errpipe[1], errbuf, errlen);
exit(2);
}
if (dup2(c_stdout, fileno(stderr)) == -1) {
errlen = sprintf(errbuf, "pan(%s): couldn't redirect stderr for tag %s. errno:%d %s",
panname, colle->name, errno, strerror(errno));
WRITE_OR_DIE(errpipe[1], &errlen, sizeof(errlen));
WRITE_OR_DIE(errpipe[1], errbuf, errlen);
exit(2);
}
} else { /* stderr still needs to be redirected */
if (dup2(fileno(stdout), fileno(stderr)) == -1) {
errlen = sprintf(errbuf, "pan(%s): couldn't redirect stderr for tag %s. errno:%d %s",
panname, colle->name, errno, strerror(errno));
WRITE_OR_DIE(errpipe[1], &errlen, sizeof(errlen));
WRITE_OR_DIE(errpipe[1], errbuf, errlen);
exit(2);
}
}
/* If there are any shell-type characters in the cmdline
* such as '>', '<', '$', '|', etc, then we exec a shell and
* run the cmd under a shell.
*
* Otherwise, break the cmdline at white space and exec the
* cmd directly.
*/
if (strpbrk(c_cmdline, "\"';|<>$\\")) {
execlp("sh", "sh", "-c", c_cmdline, (char*)0);
errlen = sprintf(errbuf,
"pan(%s): execlp of '%s' (tag %s) failed. errno:%d %s",
panname, c_cmdline, colle->name, errno, strerror(errno));
} else {
char **arg_v;
arg_v = (char **)splitstr(c_cmdline, NULL, NULL);
execvp(arg_v[0], arg_v);
errlen = sprintf(errbuf,
"pan(%s): execvp of '%s' (tag %s) failed. errno:%d %s",
panname, arg_v[0], colle->name, errno, strerror(errno));
}
WRITE_OR_DIE(errpipe[1], &errlen, sizeof(errlen));
WRITE_OR_DIE(errpipe[1], errbuf, errlen);
exit(errno);
}
/* parent */
/* subst_pcnt_f() allocates the command line dynamically
* free the malloc to prevent a memory leak
*/
if (colle->pcnt_f)
free(c_cmdline);
close(errpipe[1]);
/* if the child couldn't go through with the exec,
* clean up the mess, note it, and move on
*/
if (read(errpipe[0], &errlen, sizeof(errlen))) {
int status;
time_t end_time;
int termid;
char *termtype;
struct tms notime = {0, 0, 0, 0};
read(errpipe[0], errbuf, errlen);
close(errpipe[0]);
errbuf[errlen] = '\0';
/* fprintf(stderr, "%s", errbuf); */
waitpid(cpid, &status, 0);
if (WIFSIGNALED(status)) {
termid = WTERMSIG(status);
termtype = "signaled";
} else if (WIFEXITED(status)) {
termid = WEXITSTATUS(status);
termtype = "exited";
} else if (WIFSTOPPED(status)) {
termid = WSTOPSIG(status);
termtype = "stopped";
} else {
termid = 0;
termtype = "unknown";
}
time(&end_time);
if (!quiet_mode)
{
//write_test_start(active, errbuf);
write_test_end(active, errbuf, end_time, termtype, status,
termid, &notime, &notime);
}
if (capturing) {
close(c_stdout);
unlink(active->output);
}
return -1;
}
close(errpipe[0]);
if (capturing) close(c_stdout);
active->pgrp = cpid;
active->stopping = 0;
if (zoo_mark_cmdline(zoofile, cpid, colle->name, colle->cmdline)) {
fprintf(stderr, "pan(%s): %s\n", panname, zoo_error);
exit(1);
}
if (Debug & Dstartup)
fprintf(stderr, "started %s cpid=%d at %s",
colle->name, cpid, ctime(&active->mystime));
if (Debug & Dstart) {
fprintf(stderr, "Executing test = %s as %s", colle->name, colle->cmdline);
if (capturing)
fprintf(stderr, "with output file = %s\n", active->output);
else
fprintf(stderr, "\n");
}
return cpid;
}
static char *
subst_pcnt_f(struct coll_entry *colle)
{
static int counter = 1;
char pid_and_counter[20];
char new_cmdline[1024];
/* if we get called falsely, do the right thing anyway */
if (!colle->pcnt_f)
return colle->cmdline;
snprintf(pid_and_counter, 20, "%d_%d", getpid(), counter++);
snprintf(new_cmdline, 1024, colle->cmdline, pid_and_counter);
return strdup(new_cmdline);
}
static struct collection *
get_collection(char *file, int optind, int argc, char **argv)
{
char *buf, *a, *b;
struct coll_entry *head, *p, *n;
struct collection *coll;
int i;
buf = slurp(file);
if (!buf)
return NULL;
coll = (struct collection *) malloc(sizeof(struct collection));
coll->cnt = 0;
head = p = n = NULL;
a = b = buf;
while (a) {
/* set b to the start of the next line and add a NULL character
* to separate the two lines */
if ((b = strchr(a, '\n')) != NULL)
*b++ = '\0';
/* If this is line isn't a comment */
if ((*a != '#') && (*a != '\0') && (*a != ' ')) {
n = (struct coll_entry *) malloc(sizeof(struct coll_entry));
if ((n->pcnt_f = strstr(a, "%f"))) {
n->pcnt_f[1] = 's';
}
n->name = strdup(strsep(&a, " \t"));
n->cmdline = strdup(a);
n->next = NULL;
if (p) {
p->next = n;
}
if (head == NULL) {
head = n;
}
p = n;
coll->cnt++;
}
a = b;
}
free(buf);
/* is there something on the commandline to be counted? */
if (optind < argc) {
char workstr[1024] = "";
int workstr_left = 1023;
/* fill arg list */
for (i = 0; optind < argc; ++optind, ++i) {
strncat(workstr, argv[optind], workstr_left);
workstr_left = workstr_left - strlen(argv[optind]);
strncat(workstr, " ", workstr_left);
workstr_left--;
}
n = (struct coll_entry *) malloc(sizeof(struct coll_entry));
if ((n->pcnt_f = strstr(workstr, "%f"))) {
n->pcnt_f[1] = 's';
}
n->cmdline = strdup(workstr);
n->name = "cmdln";
n->next = NULL;
if (p) {
p->next = n;
}
if (head == NULL) {
head = n;
}
coll->cnt++;
}
/* get an array */
coll->ary = (struct coll_entry **) malloc(coll->cnt *
sizeof(struct coll_entry *));
/* fill the array */
i = 0;
n = head;
while (n != NULL) {
coll->ary[i] = n;
n = n->next;
++i;
}
if (i != coll->cnt)
fprintf(stderr, "pan(%s): i doesn't match cnt\n", panname);
return coll;
}
static char *
slurp(char *file)
{
char *buf;
int fd;
struct stat sbuf;
if ((fd = open(file, O_RDONLY)) < 0) {
fprintf(stderr, "pan(%s): open(%s,O_RDONLY) failed. errno:%d %s\n",
panname, file, errno, strerror(errno));
return NULL;
}
if (fstat(fd, &sbuf) < 0) {
fprintf(stderr, "pan(%s): fstat(%s) failed. errno:%d %s\n",
panname, file, errno, strerror(errno));
return NULL;
}
buf = (char *) malloc(sbuf.st_size + 1);
if (read(fd, buf, sbuf.st_size) != sbuf.st_size) {
fprintf(stderr, "pan(%s): slurp failed. errno:%d %s\n",
panname, errno, strerror(errno));
return NULL;
}
buf[sbuf.st_size] = '\0';
close(fd);
return buf;
}
static void
check_orphans(struct orphan_pgrp *orphans, int sig)
{
struct orphan_pgrp *orph;
for (orph = orphans; orph != NULL; orph = orph->next) {
if (orph->pgrp == 0)
continue;
if (Debug & Dshutdown)
fprintf(stderr, " propagating sig %d to orphaned pgrp %d\n",
sig, -(orph->pgrp));
if (kill(-(orph->pgrp), sig) != 0) {
if (errno == ESRCH) {
/* This pgrp is now empty */
if (zoo_clear(zoofile, orph->pgrp)) {
fprintf(stderr, "pan(%s): %s\n", panname, zoo_error);
}
orph->pgrp = 0;
} else {
fprintf(stderr,
"pan(%s): kill(%d,%d) on orphaned pgrp failed. errno:%d %s\n",
panname, -(orph->pgrp), sig, errno, strerror(errno));
}
}
}
}
static void
mark_orphan(struct orphan_pgrp *orphans, pid_t cpid)
{
struct orphan_pgrp *orph;
for (orph = orphans; orph != NULL; orph = orph->next) {
if (orph->pgrp == 0)
break;
}
if (orph == NULL) {
/* make a new struct */
orph = (struct orphan_pgrp *) malloc(sizeof(struct orphan_pgrp));
/* plug in the new struct just after the head */
orph->next = orphans->next;
orphans->next = orph;
}
orph->pgrp = cpid;
}
static void
copy_buffered_output(struct tag_pgrp *running)
{
char *tag_output;
tag_output = slurp(running->output);
if (tag_output) {
printf("%s", tag_output);
/* make sure the output ends with a newline */
if (tag_output[strlen(tag_output) - 1] != '\n')
printf("\n");
fflush(stdout);
free(tag_output);
}
}
static void
write_test_start(struct tag_pgrp *running)
{
if (!strcmp(reporttype, "rts")) {
printf("%s\ntag=%s stime=%ld\ncmdline=\"%s\"\ncontacts=\"%s\"\nanalysis=%s\n%s\n",
"<<<test_start>>>",
running->cmd->name, running->mystime, running->cmd->cmdline, "",
"exit",
"<<<test_output>>>");
}
fflush(stdout);
}
static void
write_test_end(struct tag_pgrp *running, const char *init_status,
time_t exit_time, char *term_type, int stat_loc,
int term_id, struct tms *tms1, struct tms *tms2)
{
if (!strcmp(reporttype, "rts")) {
printf("%s\ninitiation_status=\"%s\"\nduration=%ld termination_type=%s "
"termination_id=%d corefile=%s\ncutime=%d cstime=%d\n%s\n",
"<<<execution_status>>>", init_status,
(long) (exit_time - running->mystime),
term_type, term_id, (stat_loc & 0200) ? "yes" : "no",
(int) (tms2->tms_cutime - tms1->tms_cutime),
(int) (tms2->tms_cstime - tms1->tms_cstime),
"<<<test_end>>>");
}
fflush(stdout);
}
/* The functions below are all debugging related */
static void
pids_running(struct tag_pgrp *running, int keep_active)
{
int i;
fprintf(stderr, "pids still running: ");
for (i = 0; i < keep_active; ++i) {
if (running[i].pgrp != 0)
fprintf(stderr, "%d ", running[i].pgrp);
}
fprintf(stderr, "\n");
}
static void
orphans_running(struct orphan_pgrp *orphans)
{
struct orphan_pgrp *orph;
fprintf(stderr, "orphans still running: ");
for (orph = orphans; orph != NULL; orph = orph->next) {
if (orph->pgrp != 0)
fprintf(stderr, "%d ", -(orph->pgrp));
}
fprintf(stderr, "\n");
}
static void
dump_coll(struct collection *coll)
{
int i;
for (i = 0; i < coll->cnt; ++i) {
fprintf(stderr, "coll %d\n", i);
fprintf(stderr, " name=%s cmdline=%s\n", coll->ary[i]->name,
coll->ary[i]->cmdline);
}
}
void
wait_handler( int sig )
{
static int lastsent = 0;
if (sig == 0) {
lastsent = 0;
} else {
rec_signal = sig;
if (sig == SIGUSR2)
return;
if (lastsent == 0)
send_signal = sig;
else if (lastsent == SIGUSR1)
send_signal = SIGINT;
else if (lastsent == sig)
send_signal = SIGTERM;
else if (lastsent == SIGTERM)
send_signal = SIGHUP;
else if (lastsent == SIGHUP)
send_signal = SIGKILL;
lastsent = send_signal;
}
}