[BACK]Return to pan.c CVS log [TXT][DIR] Up to [Development] / projects / ltp / pan

File: [Development] / projects / ltp / pan / pan.c (download)

Revision 1.1, Thu Sep 14 21:54:44 2000 UTC (17 years, 1 month ago) by nstraz
Branch: MAIN

Add pan and associated files.  This is a lightweight test harness.  It works a
lot like runtests.py did, but it is more powerful.  See the man page for
details.

/*
 * 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/
 *
 */
/* $Id: pan.c,v 1.1 2000/09/14 21:54:44 nstraz Exp $ */

#include <errno.h>
#include <string.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <string.h>
#include <time.h>
#include <math.h>		/* log10() for subst_pcnt_f */
#include <sys/times.h>

#define NANNY
#include "zoolib.h"

struct coll_entry
{
    char *name;
    char **argv;
    int argc;
    char *pcnt_f;
    struct coll_entry *next;
};

struct collection
{
    int cnt;
    struct coll_entry **ary;
};

struct active
{
    int pgrp;
    int stopping;
    time_t stime;
    struct coll_entry *cmd;
};

struct orphan_pgrp
{
    int pgrp;
    struct orphan_pgrp *next;
};

static pid_t run_child (struct coll_entry *colle, FILE * fp,
			struct active *active);
static char *slurp (char *file);
static struct collection *get_collection (char *file, int optind, int argc,
					  char **argv);
static void pids_running (struct active *running, int keep_active);
static int check_pids (struct active *running, int *num_active,
		       int keep_active, FILE * fp, FILE * logfile,
		       struct orphan_pgrp *orphans);
static void propagate_signal (struct active *running, int keep_active,
			      struct orphan_pgrp *orphans, FILE * fp);
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, FILE * fp, int sig);

char **splitstr (char *str, int *argc, char *sep);

static char *panname = NULL;
static char *errmsg;

/* Debug Bits */
int Debug = 0;
#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 */

main (int argc, char **argv)
{
    extern char *optarg;
    extern int optind;
    int c;
    char *active = NULL;
    char *filename = "/dev/null";
    char *logfilename = NULL;
    FILE *logfile = NULL;
    struct collection *coll = NULL;
    FILE *fp;
    pid_t cpid;
    struct active *running;
    struct orphan_pgrp *orphans, *orph;
    int keep_active = 1;
    int num_active = 0;
    int err, i;
    int starts = -1;
    int stop;
    int go_idle;
    int has_brakes = 0;
    int sequential = 0;
    int fork_in_road = 0;
    time_t t;
    int exit_stat;
    int track_exit_stats = 0;

    while ((c = getopt (argc, argv, "s:x:n:a:f:Ad:hSl:ye")) != -1) {
	switch (c) {
	case 'A':
	    has_brakes = 1;
	    track_exit_stats = 1;
	    break;
	case 'x':
	    keep_active = atoi (optarg);
	    break;
	case 's':
	    starts = atoi (optarg);
	    break;
	case 'n':
	    panname = (char *) malloc (strlen (optarg) + 1);
	    strcpy (panname, optarg);
	    break;
	case 'a':
	    active = (char *) malloc (strlen (optarg) + 1);
	    strcpy (active, optarg);
	    break;
	case 'f':
	    filename = (char *) malloc (strlen (optarg) + 1);
	    strcpy (filename, optarg);
	    break;
	case 'd':
	    sscanf (optarg, "%i", &Debug);
	    break;
	case 'S':
	    sequential = 1;
	    break;
	case 'l':
	    logfilename = optarg;
	    break;
	case 'y':
	    fork_in_road = 1;
	    break;
	case 'e':
	    track_exit_stats = 1;
	    break;
	case 'h':
	    printf
		("Usage: pan -n name [ -SyAeh ] [ -s starts ] [ -x nactive ] [ -l logfile ]\n\t[ -a active-file ] [ -f command-file ] [ -d debug-level ] [cmd]\n");
	    exit (0);
	}
    }

    if (panname == NULL) {
	fprintf (stderr, "pan: Must supply -n\n");
	exit (1);
    }
    if (active == NULL) {
	active = zoo_active ();
	if (active == NULL) {
	    fprintf (stderr,
		     "pan(%s): Must supply -a or set ZOO env variable\n",
		     panname);
	    exit (1);
	}
    }

    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';
	fprintf (logfile, "startup='%s'\n", s);
    }

    coll = get_collection (filename, optind, argc, argv);
    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 active *) malloc ((keep_active + 1) * sizeof (struct active));
    memset (running, 0, keep_active * sizeof (struct active));
    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 (starts == -1)
	if (sequential)
	    starts = coll->cnt;
	else
	    starts = 1;

    /* If starts is not infinite, but is less than keep_active,
     * then bump it up to keep_active.
     */

    if ((starts > 0) && (starts < keep_active))
	starts = keep_active;

    if (starts == 0)		/* The user's view of infinite starts. */
	starts = -1;		/* Our view of infinite starts. */

    if ((fp = open_file (active, "r+", &errmsg)) == NULL) {
	fprintf (stderr, "pan(%s): %s\n", panname, errmsg);
	exit (1);
    }
    if (write_active_args (fp, getpid (), panname, argc, argv, &errmsg) == -1) {
	fprintf (stderr, "pan(%s): %s\n", panname, errmsg);
	exit (1);
    }

    /* Allocate N spaces for max-arg commands.
     * this is an "active file cleanliness" thing
     */
    {
	char *av[2], bigarg[82];
	int t;

	t = 1;
	memset (bigarg, '.', 81);
	bigarg[81] = '\0';
	av[0] = bigarg;
	av[1] = NULL;

	for (c = 0; c < keep_active; c++) {
	    if (write_active_args (fp, t, panname, 1, av, &errmsg) == -1) {
		fprintf (stderr, "pan(%s): %s\n", panname, errmsg);
		exit (1);
	    }
	}
	for (c = 0; c < keep_active; c++) {
	    if (clear_active (fp, t, &errmsg) != 1) {
		fprintf (stderr, "pan(%s): %s\n", panname, errmsg);
		exit (1);
	    }
	}
    }

    rec_signal = send_signal = 0;
    signal (SIGINT, wait_handler);
    signal (SIGTERM, wait_handler);
    signal (SIGHUP, wait_handler);
    signal (SIGUSR1, wait_handler);	/* ignore fork_in_road */
    signal (SIGUSR2, wait_handler);	/* 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], fp, running + i);
	    if (cpid != -1) {
		++num_active;
		if (starts > 0)
		    --starts;
	    }

	    if (sequential)
		if (++c >= coll->cnt)
		    c = 0;

	}			/* while( (num_active < keep_active) && (starts != 0) ) */

	if (starts == 0)
	    ++stop;

	if (rec_signal) {
	    /* propagate everything except sigusr2 */

	    if (rec_signal == SIGUSR2) {
		if (fork_in_road)
		    ++go_idle;
		else
		    ++stop;
		signal (rec_signal, wait_handler);
		rec_signal = send_signal = 0;
	    } else {
		if (rec_signal == SIGUSR1)
		    fork_in_road = 0;
		propagate_signal (running, keep_active, orphans, fp);
		if (fork_in_road)
		    ++go_idle;
		else
		    ++stop;
	    }
	}

	err = check_pids (running, &num_active, keep_active, fp,
			  logfile, orphans);
	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) {
		printf ("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, fp);
	    if (Debug & Drunning)
		orphans_running (orphans);
	    break;
	}
	if (orph == NULL)
	    break;
    }

    signal (SIGINT, SIG_DFL);
    if (clear_active (fp, getpid (), &errmsg) != 1) {
	fprintf (stderr, "pan(%s): %s\n", panname, errmsg);
	++exit_stat;
    }
    fclose (fp);

    exit (exit_stat);
}


static void
pids_running (struct active *running, int keep_active)
{
    int i;

    printf ("pids still running: ");
    for (i = 0; i < keep_active; ++i) {
	if (running[i].pgrp != 0)
	    printf ("%d ", running[i].pgrp);
    }
    printf ("\n");
}


static void
propagate_signal (struct active *running, int keep_active,
		  struct orphan_pgrp *orphans, FILE * fp)
{
    int i;

    if (Debug & Dshutdown)
	printf ("pan was signaled with sig %d...\n", rec_signal);

    for (i = 0; i < keep_active; ++i) {
	if (running[i].pgrp == 0)
	    continue;

	if (Debug & Dshutdown)
	    printf ("  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, SYSERR);
	}
	running[i].stopping = 1;
    }

    check_orphans (orphans, fp, send_signal);

    signal (rec_signal, wait_handler);
    rec_signal = send_signal = 0;
}


static int
check_pids (struct active *running, int *num_active, int keep_active,
	    FILE * fp, FILE * logfile, struct orphan_pgrp *orphans)
{
    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, fp, 0);

    tck = times (&tms1);
    if (tck == -1) {
	fprintf (stderr, "pan(%s): times(&tms1) failed.  errno:%d  %s\n",
		 panname, errno, SYSERR);
    }
    cpid = wait (&stat_loc);
    tck = times (&tms2);
    if (tck == -1) {
	fprintf (stderr, "pan(%s): times(&tms2) failed.  errno:%d  %s\n",
		 panname, errno, SYSERR);
    }

    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, SYSERR);
	}
    } else if (cpid > 0) {

	if (WIFSIGNALED (stat_loc)) {
	    w = WTERMSIG (stat_loc);
	    status = "signaled";
	    if (Debug & Dexit)
		printf ("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)
		printf ("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)
			printf
			    ("pan(%s): tag=%s exited 130, known to be signaled; will give it an exit 0.\n",
			     panname, running[i].cmd->name);
		}
		if (logfile != NULL) {
		    time (&t);
		    fprintf (logfile,
			     "tag=%s stime=%d dur=%d exit=%s stat=%d core=%s cu=%d cs=%d\n",
			     running[i].cmd->name, running[i].stime,
			     t - running[i].stime, status, w,
			     (stat_loc & 0200) ? "yes" : "no",
			     tms2.tms_cutime - tms1.tms_cutime,
			     tms2.tms_cstime - tms1.tms_cstime);
		    fflush (logfile);
		}
		/* 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 (clear_active (fp, cpid, &errmsg) == -1) {
		    fprintf (stderr, "pan(%s): %s\n", panname, errmsg);
		    exit (1);
		}

		/* Check for orphaned pgrps */
		if ((kill (-cpid, 0) == 0) || (errno == EPERM)) {
		    if (write_active_args (fp, cpid, "panorphan",
					   running[i].cmd->argc,
					   running[i].cmd->argv,
					   &errmsg) == -1) {
			fprintf (stderr, "pan(%s): %s\n", panname, errmsg);
			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, FILE * fp, struct active *active)
{
    int cpid;

    if ((cpid = fork ()) < 0) {
	fprintf (stderr, "pan(%s): fork failed.  errno:%d  %s\n",
		 panname, errno, SYSERR);
	return -1;
    } else if (cpid == 0) {
	/* child */
	char **eargv;

	fclose (fp);
	setpgrp ();

	if (colle->pcnt_f != NULL) {
	    eargv = subst_pcnt_f (colle);
	} else {
	    eargv = colle->argv;
	}

	/* execute command */
	execvp (eargv[0], eargv);
	fprintf (stderr,
		 "pan(%s): execvp of '%s' (tag %s) failed.  errno:%d  %s\n",
		 panname, eargv[0], colle->name, errno, SYSERR);
	exit (errno);
    }

    /* parent */
    time (&active->stime);
    active->pgrp = cpid;
    active->stopping = 0;
    active->cmd = colle;

    if (write_active_args
	(fp, cpid, colle->name, colle->argc, colle->argv, &errmsg) == -1) {
	fprintf (stderr, "pan(%s): %s\n", panname, errmsg);
	exit (1);
    }

    if (Debug & Dstartup)
	printf ("started %s cpid=%d at %s",
		colle->name, cpid, ctime (&active->stime));

    if (Debug & Dstart) {
	int ac;
	printf ("Executing test = %s as ", colle->name);
	for (ac = 0; ac < colle->argc; ac++) {
	    printf ("%s ", colle->argv[ac]);
	}
	printf ("\n");
    }

    return cpid;
}


static char **
subst_pcnt_f (struct coll_entry *colle)
{
    char **eargv;
    char *p;
    int i;

    eargv = (char **) malloc ((colle->argc + 1) * sizeof (char *));

    for (i = 0; i < colle->argc; ++i) {
	if ((p = strstr (colle->argv[i], "%f")) != NULL) {
	    /* simple, for now */
	    static int counter = 1;
	    char *b, *p2;
	    int pidlen, counterlen;

	    *p = '\0';		/* cut off at % */
	    p2 = p + 2;		/* stuff that follows %f */

	    pidlen = 1 + (int) log10 ((double) getpid ());
	    counterlen = 1 + (int) log10 ((double) counter);

	    b = (char *) malloc (strlen (colle->argv[i]) +
				 pidlen + 1 + counterlen + strlen (p2) + 1);
	    sprintf (b, "%s%d_%d%s",
		     colle->argv[i], getpid (), counter++, p2);
	    *p = '%';		/* restore % */
	    eargv[i] = b;
	} else {
	    eargv[i] = colle->argv[i];
	}
    }
    eargv[i] = NULL;
    return eargv;
}

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);

    coll = (struct collection *) malloc (sizeof (struct collection));
    coll->cnt = 0;

    head = p = n = NULL;
    a = b = buf;
    while (*b != '\0') {
	if ((b = strchr (a, '\n')) != NULL)
	    *b = '\0';

	if ((*a != '#') && (*a != '\0') && (*a != ' ')) {
	    if (head == NULL) {
		head =
		    (struct coll_entry *) malloc (sizeof (struct coll_entry));
		head->pcnt_f = strstr (a, "%f");
		head->argv = splitstr (a, &head->argc, 0);
		head->name = head->argv[0];
		head->argv++;	/* remove name from command */
		head->argc--;
		head->next = NULL;
		p = head;
	    } else {
		n = (struct coll_entry *) malloc (sizeof (struct coll_entry));
		p->next = n;
		n->pcnt_f = strstr (a, "%f");
		n->argv = splitstr (a, &n->argc, 0);
		n->name = n->argv[0];
		n->argv++;	/* remove name from command */
		n->argc--;
		n->next = NULL;
		p = n;
	    }
	    coll->cnt++;
	}
	a += strlen (a) + 1;
	b = a;
    }
    free (buf);

    /* is there something on the commandline to be counted? */
    if (optind < argc) {
	char **args;
	char *pcnt_f = NULL;

	args = (char **) malloc ((argc - optind + 1) * sizeof (char *));
	/* fill arg list */
	for (i = 0; optind < argc; ++optind, ++i) {
	    args[i] = argv[optind];
	    if ((pcnt_f == NULL) && ((strstr (args[i], "%f")) != NULL)) {
		pcnt_f = args[i];
	    }
	}
	args[i] = NULL;

	if (head == NULL) {
	    head = (struct coll_entry *) malloc (sizeof (struct coll_entry));
	    head->pcnt_f = pcnt_f;
	    head->argv = args;
	    head->name = "cmdln";
	    head->argc = i;
	    head->next = NULL;
	} else {
	    n = (struct coll_entry *) malloc (sizeof (struct coll_entry));
	    p->next = n;
	    n->pcnt_f = pcnt_f;
	    n->argv = args;
	    n->name = "cmdln";
	    n->argc = i;
	    n->next = NULL;
	}
	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, SYSERR);
	exit (1);
    }

    if (fstat (fd, &sbuf) < 0) {
	fprintf (stderr, "pan(%s): fstat(%s) failed.  errno:%d  %s\n",
		 panname, file, errno, SYSERR);
	exit (1);
    }

    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, SYSERR);
	exit (1);
    }
    buf[sbuf.st_size] = '\0';

    close (fd);
    return buf;
}

static void
check_orphans (struct orphan_pgrp *orphans, FILE * fp, int sig)
{
    struct orphan_pgrp *orph;

    for (orph = orphans; orph != NULL; orph = orph->next) {
	if (orph->pgrp == 0)
	    continue;

	if (Debug & Dshutdown)
	    printf ("  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 (clear_active (fp, orph->pgrp, &errmsg) == -1) {
		    fprintf (stderr, "pan(%s): %s\n", panname, errmsg);
		}
		orph->pgrp = 0;
	    } else {
		fprintf (stderr,
			 "pan(%s): kill(%d,%d) on orphaned pgrp failed.  errno:%d  %s\n",
			 panname, -(orph->pgrp), sig, errno, SYSERR);
	    }
	}
    }
}


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
orphans_running (struct orphan_pgrp *orphans)
{
    struct orphan_pgrp *orph;

    printf ("orphans still running: ");
    for (orph = orphans; orph != NULL; orph = orph->next) {
	if (orph->pgrp != 0)
	    printf ("%d ", -(orph->pgrp));
    }
    printf ("\n");
}

static void
dump_coll (struct collection *coll)
{
    int x, i;

    for (i = 0; i < coll->cnt; ++i) {
	printf ("coll %d\n", i);
	printf ("  name=%s #args=%d\n", coll->ary[i]->name,
		coll->ary[i]->argc);
	for (x = 0; coll->ary[i]->argv[x]; ++x) {
	    printf ("  argv[%d] = (%s)\n", x, coll->ary[i]->argv[x]);
	}
    }
}