void zt_add(zoo_t z, int n) { char cmdline[200]; char tag[10]; snprintf(tag, 10, "%s%d", "test", n); snprintf(cmdline, 200, "%s%d %s %s %s", "runtest", n, "one", "two", "three"); zoo_mark_cmdline(z, n, tag, cmdline); }
int zoo_mark_args(zoo_t z, pid_t p, char *tag, int ac, char **av) { char *cmdline; int ret; cmdline = cat_args(ac, av); ret = zoo_mark_cmdline(z, p, tag, cmdline); free(cmdline); return ret; }
static pid_t run_child(struct coll_entry *colle, struct tag_pgrp *active, int quiet_mode, int *failcnt, int fmt_print, FILE * logfile) { 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, NULL); 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 }; if (read(errpipe[0], errbuf, errlen) < 0) fprintf(stderr, "Failed to read from errpipe[0]\n"); 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 (logfile != NULL) { if (!fmt_print) { fprintf(logfile, "tag=%s stime=%d dur=%d exit=%s " "stat=%d core=%s cu=%d cs=%d\n", colle->name, (int)(active->mystime), (int)(end_time - active->mystime), termtype, termid, (status & 0200) ? "yes" : "no", 0, 0); } else { if (termid != 0) ++ * failcnt; fprintf(logfile, "%-30.30s %-10.10s %-5d\n", colle->name, ((termid != 0) ? "FAIL" : "PASS"), termid); } fflush(logfile); } if (!quiet_mode) { //write_test_start(active, errbuf); write_test_end(active, errbuf, end_time, termtype, status, termid, ¬ime, ¬ime); } 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 int check_pids(struct tag_pgrp *running, int *num_active, int keep_active, FILE *logfile, FILE *failcmdfile, FILE *tconfcmdfile, struct orphan_pgrp *orphans, int fmt_print, int *failcnt, int *tconfcnt, int quiet_mode) { int w; pid_t cpid; int stat_loc; int ret = 0; int i; time_t t; char *status; char *result_str; 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 && w != TCONF) 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 (strcmp(status, "exited") == 0 && w == TCONF) { ++*tconfcnt; result_str = "CONF"; } else if (w != 0) { ++*failcnt; result_str = "FAIL"; } else { result_str = "PASS"; } fprintf(logfile, "%-30.30s %-10.10s %-5d\n", running[i].cmd->name, result_str, w); } fflush(logfile); } if (w != 0) { if (tconfcmdfile != NULL && w == TCONF) { fprintf(tconfcmdfile, "%s %s\n", running[i].cmd->name, running[i].cmd->cmdline); } else if (failcmdfile != NULL) { 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; }
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 *tconfcmdfilename = 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; FILE *tconfcmdfile = NULL; int keep_active = 1; int num_active = 0; int failcnt = 0; /* count of total testcases that failed. */ int tconfcnt = 0; /* count of total testcases that return TCONF */ 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:T: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 'T': /* * test cases that are not fully tested will be recorded * in this file */ tconfcmdfilename = 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", "--------", "------", "------------"); } fflush(logfile); } 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 = 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 = 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 (tconfcmdfilename) { tconfcmdfile = fopen(tconfcmdfilename, "a+"); if (!tconfcmdfile) { fprintf(stderr, "pan(%s): Error %s (%d) opening " "tconf cmd file '%s'\n", panname, strerror(errno), errno, tconfcmdfilename); 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 */ { 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, &failcnt, fmt_print, logfile); 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, tconfcmdfile, orphans, fmt_print, &failcnt, &tconfcnt, 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 Skipped Tests: %d\n", tconfcnt); 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); if (failcmdfile) fclose(failcmdfile); if (tconfcmdfile) fclose(tconfcmdfile); exit(exit_stat); }