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.
diff --git a/Makefile b/Makefile
index 926df0b..73ca7fd 100644
--- a/Makefile
+++ b/Makefile
@@ -5,6 +5,7 @@
 all: libltp.a
 	@$(MAKE) -C doio $@
 	@$(MAKE) -C tests $@
+	@$(MAKE) -C pan $@
 
 libltp.a:
 	@$(MAKE) -C lib $@
@@ -13,3 +14,4 @@
 	@$(MAKE) -C lib $@
 	@$(MAKE) -C doio $@
 	@$(MAKE) -C tests $@
+	@$(MAKE) -C pan $@
diff --git a/README b/README
index 6743330..c7e2f8b 100644
--- a/README
+++ b/README
@@ -26,11 +26,11 @@
 structure is simple.
 
 doio/
-          The doio directory contains three tools: doio, iogen, and growfiles.
-          These are elaborate filesystem tests for stressing and testing the
-          functionality of the filesystem.  There is also a wrapper for doio
-          and iogen called rwtest.  Command examples for these tools can be found
-          in the cmdlines in the root of the ltp directory.
+	  The doio directory contains three tools: doio, iogen, and growfiles.
+	  These are elaborate filesystem tests for stressing and testing the
+	  functionality of the filesystem.  There is also a wrapper for doio
+	  and iogen called rwtest.  Command examples for these tools can be
+	  found in the cmdlines in the root of the ltp directory.
 
 include/
 lib/
@@ -38,13 +38,24 @@
           routines used by many of the tests.
 
 tests/
-          To date the tests directory contains a number of simple tests called
-          'quickhitters'.  These tests are designed to be simple and quick and be
-          run in conjunction with each other.  They have some use as stand alone
-          tests, but when run many-at-a-time, interesting issues can come up.
+	  To date the tests directory contains a number of simple tests called
+	  'quickhitters'.  These tests are designed to be simple and quick and
+	  be run in conjunction with each other.  They have some use as stand
+	  alone tests, but when run many-at-a-time, interesting issues can come
+	  up.
 
 doc/
-          The doc directory contains mainly man pages for mainly the library codes.
+	  The doc directory contains mainly man pages for mainly the library
+	  codes.
+
+runtest/
+	  The runtest directory contains a simple test harness that Aaron
+	  Laffin hacked up in Python.  It runs tests from a file in sequential
+	  order.  It is meant to be an example of a simple test harness.
+pan/
+	  The pan directory contains a simple, lightweight test harness.  pan
+	  has the ability to run tests randomly and in parallel.  See pan's man
+	  page for more information.
 
 
 Notes
diff --git a/doc/man1/bump.1 b/doc/man1/bump.1
new file mode 100644
index 0000000..744eb6a
--- /dev/null
+++ b/doc/man1/bump.1
@@ -0,0 +1,80 @@
+.\"
+.\" $Id: bump.1,v 1.1 2000/09/14 21:54:44 nstraz Exp $
+.\"
+.\" 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/
+.\"
+.TH BUMP 1 "14 Sep 2000" "LTP" "Linux Test Project"
+.SH NAME
+bump \- send signal to tags run by pan
+.SH SYNOPSIS
+\fBbump [-1] [-s \fIsig\fB] [\fI-a active-file\fB] [tags...]
+.SH DESCRIPTION
+
+Bump will send a SIGINT signal to processes, given that each process has a
+corresponding tag in an active-file.  The active-file is the same one that is
+used by the pan to start the processes.
+
+If the active file has multiple occurrences of a single tag name then only the
+first process will be signaled.  You may specify the tag name multiple times
+on the commandline if necessary.
+
+.TP 1i
+\fB-1\fP
+Send a SIGUSR1 signal.  By default a SIGINT will be sent.
+.TP 1i
+\fB-a \fIactive_file\fB
+A file containing the tagnames, pids, and commands being run by a pan.  If this
+is not specified then the ZOO environment variable will be read for the name of
+the directory where the active file can be found.
+.TP 1i
+\fB-s \fIsig\fB
+Used to specify a signal number to send.  By default a SIGINT will be sent.
+
+.in -1i
+
+.SH ENVIRONMENT
+.TP
+ZOO
+If set, should name the directory where the active file can be found.
+This is ignored if \fI-a\fP is specified.
+
+.SH FILES
+.TP
+active
+Default name of active file if \fI-a\fP is not specified.  This is prefixed
+by the directory name found in the ZOO environment variable.
+
+.SH "SEE ALSO"
+Zoo tools - pan(1)
+
+.SH DIAGNOSTICS
+Exits zero, unless it cannot find the active file or if there were no tags
+listed on the commandline.
diff --git a/doc/man1/pan.1 b/doc/man1/pan.1
new file mode 100644
index 0000000..227ed8a
--- /dev/null
+++ b/doc/man1/pan.1
@@ -0,0 +1,226 @@
+.\"
+.\" $Id: pan.1,v 1.1 2000/09/14 21:54:44 nstraz Exp $
+.\"
+.\" 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/
+.TH PAN 1 "14 Sep 2000" "LTP" "Linux Test Project"
+.SH NAME
+pan \- A light-weight driver to run tests and clean up their pgrps
+.SH SYNOPSIS
+\fBpan -n tagname [-SyAeh] [-s \fIstarts\fB] [\fI-x nactive\fB] [\fI-l logfile\fB] [\fI-a active-file\fB] [\fI-f command-file\fB] [\fI-d debug-level\fB] [cmd]
+.SH DESCRIPTION
+
+Pan will run a command, as specified on the commandline, or collection of
+commands from a command-file.  By default pan runs one command, choosing it at
+random from the whole set of commands available to it.  The pan's name in the
+active file is specified by the tagname.  When a command terminates pan will
+kill any orphans that may have been left behind in its pgrp.  If pan is
+signaled it will kill any active commands and, again, clean up any orphans.
+
+Pan uses the signal ratchet found in other zoo tools.  The first time pan is
+signaled it sends a SIGTERM to the active pgrps; the second time it sends
+SIGHUP; the third time a SIGINT; after that it always sends SIGKILL.
+
+Pan will not terminate until all the active commands and everything in their
+pgrps is dead.  It will loop around at 5 second intervals, triggering its own
+signal ratchet, until it succeeds in killing the pgrps.
+
+When the pan starts up it places its own tagname and commandline in the active
+file and begins scheduling commands.  After a command is started pan puts an
+entry for it into the active file with its indicated tagname.  If the command
+was specified on the command line, rather than in the command-file, then its
+tagname will be "cmdln".  When a process terminates pan frees the active file
+entry.  If a command terminates and leaves an orphaned pgrp then pan will put
+an entry into the active file called "panorphan" which will be removed only
+when the orphaned pgrp is cleaned up.  Before pan exits it will ensure that
+all orphaned pgrps are dead (see above) and then it will remove its own
+tagname from the active file.
+
+The command-file is a file containing tag/command pairs.  Each line in the
+file begins with a tag identifying the command, followed by white space, and
+then the command and its arguments.  A line beginning with the # character is
+a comment.  Pan recognizes the token "%f" in a command's arguments and
+replaces it with a unique identifier--add this to filename arguments to
+prevent two instances of the command from interfering with each other.
+
+When pan receives a SIGUSR2 it stops scheduling new tests and waits for the
+active tests to terminate.  If the \fB-y\fP option was used then it will begin
+scheduling again, otherwise it will exit.  It does not propagate the SIGUSR2.
+
+.TP 1i
+\fB-A\fP
+The all-stop flag.  If any command exits non-zero pan will shutdown its
+scheduler and signal any active pgrps.  The pan will exit non-zero after
+everything is shut down.  By default pan ignores command exit statuses.
+The \fI-e\fP option is implied when this option is used.
+.TP 1i
+\fB-a \fIactive_file\fB
+A file containing the tagnames, pids, and commands being run.  If this is
+not specified then the ZOO environment variable will be read for the name
+of a directory where the active file will be placed, and in this case the
+active file's name will be "active".  A single active file may be shared
+by any number of Zoo tools.
+.TP 1i
+\fB-d \fIdebug-level\fB
+See the source for settings.
+.TP 1i
+\fB-e\fP
+Pan will exit non-zero if any of its commands exited non-zero.  By default
+pan ignores command exit statuses.
+.TP 1i
+\fB-h\fP
+Print some simple help.
+.TP 1i
+\fB-l \fIlogfile\fB
+Name of a log file to be used to store exit information for each of the
+commands (tags) that are run.  This log file may not be shared with other Zoo
+tools or other pan processes.
+.TP 1i
+\fB-n \fItagname\fB
+The tagname by which this pan process will be known by the zoo tools.  This
+is a required argument.
+.TP 1i
+\fB-S\fP
+Causes pan to run commands (tags) sequentially, as they are listed in the
+command-file.  By default it chooses tags randomly.  If a command is specified
+on the commandline and a command-file is also specified, then the commandline
+tag will be the last command.  If this is specified and \fI-s\fP is not
+specified then the default setting for \fI-s\fP is equal to the total number
+of commands.
+.TP 1i
+\fB-s \fIstarts\fB
+Indicates the number of commands (tags) that should be run before terminating.
+Set this to zero to run forever.  By default this is set to 1 (but see
+\fI-S\fP for an exception).  If this is specified and is less than the value
+specified for \fI-x\fP then it is bumped up to be equal to the value of
+\fI-x\fP (in other words, \fI-x\fP is always satisfied).
+.TP 1i
+\fB-x \fInactive\fB
+Indicates the number of commands (tags) that should be kept active at any one
+time.  If this is greater than 1 then it is possible to have multiple
+instances of the same tag active at once.  By default this is 1.
+.TP 1i
+\fB-y\fP
+Causes the pan scheduler to go idle if a signal is received or if a command
+exits non-zero.  All active commands and their pgrps will be killed.  After
+everything is dead the scheduler will restart again where it left off.  If the
+signal is SIGUSR1 then pan will behave as if \fI-y\fP had not been specified.
+
+.in -1i
+
+.SH EXAMPLES
+
+In practice, the ZOO environment variable is generally prefered over the
+\fI-a\fP option.  All examples assume this is being set.
+
+The following creates a pan named "ex1" with an active file in /tmp/active.
+It runs the command "echo hello", keeping 3 copies running at all times,
+running 10 copies before terminating.
+
+$ export ZOO=/tmp
+.br
+$ pan -n ex1 -s 10 -x 3 echo hello
+
+The next example will use this command file.  Call this /tmp/cmds1.
+.br
+----------cut------
+.br
+fido    ls /bin
+.br
+rover   echo hello wally
+.br
+gidget  sleep 2
+.br
+lassie  ls /etc
+.br
+----------cut------
+.br
+
+Using the above command file, /tmp/cmds1, run one command at a time,
+sequentially, running each command only once.  If one command should fail then
+terminate immediately.  An exit log is kept for all the commands.
+
+$ pan -n ex3 -S -A -f /tmp/cmds1 -l ex3.log
+
+
+.SH LAYERING
+
+Pan is often used in layers.  This section extends the above examples to show
+how this is done.
+
+The next example will use this command file.  Call this /tmp/cmds2.  Note that
+the embedded pans inside this file have exit logs, and that %f is used to give
+each pan a unique log file name.
+.br
+----------cut------
+.br
+larry  pan -n ex4b -s10 -A -l ex4_%f.log echo hello
+.br
+curly  pan -n ex4c -S -A -f /tmp/cmds1 -l ex4_%f.log
+.br
+moe    echo done here
+.br
+----------cut------
+.br
+
+The following will run commands from the command file, keeping two at a time
+running, choosing them sequentially, and terminating if any of them exits
+non-zero.
+
+$ pan -n ex4 -x2 -A -S -f /tmp/cmds2
+
+Now run the commands in /tmp/cmds2, but this time we want to recover if one of
+the commands should exit non-zero.  In this example it is possible for the
+"larry" or "curly" tags to exit non-zero.  When this happens the pan will kill
+all active tags, making sure both larry and curly are dead, and then will
+continue scheduling--ensuring that our "done here" message comes out no matter
+what.
+
+$ pan -n ex5 -x2 -A -S -y -f /tmp/cmds2
+
+.SH ENVIRONMENT
+.TP
+ZOO
+If set, should name the directory where the active file should be placed.
+This is ignored if \fI-a\fP is specified.
+
+.SH FILES
+.TP
+active
+Default name of active file if \fI-a\fP is not specified.  This is prefixed
+by the directory name found in the ZOO environment variable.
+
+.SH "SEE ALSO"
+Zoo tools - bump(1)
+
+.SH DIAGNOSTICS
+By default it exits zero unless signaled, regardless of the exit status of any
+of the commands it is running.  If \fI-A\fP or \fI-e\fP are specified it exits non-zero if
+it is signaled or if any of the commands it is running should exit non-zero.
diff --git a/pan/Makefile b/pan/Makefile
new file mode 100644
index 0000000..a6fc395
--- /dev/null
+++ b/pan/Makefile
@@ -0,0 +1,12 @@
+
+CC = gcc
+LDFLAGS = -lm
+
+all: pan bump
+
+pan: pan.o zoolib.o splitstr.o
+
+bump: bump.o zoolib.o
+
+clean:
+	rm *.o pan bump
diff --git a/pan/bump.c b/pan/bump.c
new file mode 100644
index 0000000..2b83f97
--- /dev/null
+++ b/pan/bump.c
@@ -0,0 +1,170 @@
+/*
+ * 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: bump.c,v 1.1 2000/09/14 21:54:44 nstraz Exp $ */
+#include <stdio.h>
+#include <errno.h>
+#include <sys/signal.h>
+#include <string.h>
+
+#define NANNY /* not really */
+#include "zoolib.h"
+
+pid_t
+read_active( FILE *fp, char *name );
+
+static char *errmsg;
+
+main( int argc, char **argv ){
+	extern char *optarg;
+	extern int optind;
+	int c;
+	int x;
+	char *active = NULL;
+	pid_t nanny;
+	FILE *fp;
+	int sig = SIGINT;
+
+	while( (c = getopt(argc, argv, "a:s:12")) != -1 ){
+		switch(c){
+			case 'a':
+				active = (char*)malloc(strlen(optarg)+1);
+				strcpy( active, optarg );
+				break;
+			case 's':
+				sig = atoi( optarg );
+				break;
+			case '1':
+				sig = SIGUSR1;
+				break;
+			case '2':
+				sig = SIGUSR2;
+				break;
+		}
+	}
+
+	if( active == NULL ){
+		active = zoo_active();
+		if( active == NULL ){
+			fprintf(stderr, "bump: Must supply -a or set ZOO env variable\n");
+			exit(1);
+		}
+	}
+	if( optind == argc ){
+		fprintf( stderr, "bump: Must supply names\n");
+		exit(1);
+	}
+
+	/* need r+ here because we're using write-locks */
+	if( (fp = open_file( active, "r+", &errmsg )) == NULL ){
+		fprintf(stderr, "bump: %s\n", errmsg);
+		exit(1);
+	}
+	while( optind < argc ){
+		/*printf("argv[%d] = (%s)\n", optind, argv[optind] );*/
+		nanny = read_active( fp, argv[optind] );
+		if( nanny == -1 ){
+			fprintf(stderr, "bump: Did not find name '%s'\n",
+				argv[optind] );
+		}
+		else{
+			if( kill( nanny, sig ) == -1 ){
+				if( errno == ESRCH ){
+					fprintf(stderr,"bump: Did not find nanny '%s', pid %d\n",
+						argv[optind], nanny );
+					if( clear_active( fp, nanny, &errmsg ) != 1 )
+						fprintf(stderr,"bump: %s\n", errmsg);
+				}
+			}
+		}
+		++optind;
+	}
+	fclose( fp );
+
+	exit(0);
+}
+
+
+
+
+pid_t
+read_active( FILE *fp, char *name )
+{
+	char buf[1024];
+	pid_t nanny = -1;
+	char *n;
+	int len;
+
+	if( lock_file( fp, F_WRLCK, &errmsg ) == -1 ){
+		fprintf(stderr, "bump: %s\n", errmsg );
+		return(-1);
+	}
+	if( seek_file( fp, 0, SEEK_SET, &errmsg ) == -1 ){
+		fprintf(stderr, "bump: %s\n", errmsg );
+		return(-1);
+	}
+
+	len = strlen( name );
+	while(1){
+		if( fgets( buf, 1023, fp ) == NULL ){
+			/*printf("end of file\n");*/
+			break;
+		}
+
+		if( buf[0] == '#' ){
+			/*printf("skip line (%s)\n", buf );*/
+			continue;
+		}
+
+		/* get name */
+		if( (n = strchr( buf, ',' )) == NULL ){
+			/*printf("no comma (%s)\n", buf );*/
+			continue;
+		}
+
+		if( strncmp( n + 1, name, len ) != 0 ){
+			/*printf("not matching (%s) and (%s)\n",
+			       n+1, name );*/
+			continue;
+		}
+
+		nanny = atoi( buf );
+		break;
+	}/* while */
+
+	if( lock_file( fp, F_UNLCK, &errmsg ) == -1 ){
+		fprintf(stderr, "bump: %s\n", errmsg );
+		return(-1);
+	}
+	return nanny;
+}
+
diff --git a/pan/pan.c b/pan/pan.c
new file mode 100644
index 0000000..c135d6e
--- /dev/null
+++ b/pan/pan.c
@@ -0,0 +1,901 @@
+/*
+ * 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]);
+	}
+    }
+}
diff --git a/pan/splitstr.c b/pan/splitstr.c
new file mode 100644
index 0000000..4a63dff
--- /dev/null
+++ b/pan/splitstr.c
@@ -0,0 +1,91 @@
+/*
+ * 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: splitstr.c,v 1.1 2000/09/14 21:54:44 nstraz Exp $ */
+/*
+ * This is a heavily modified version of USC_parse_arg
+ *
+ * Synopsis
+ *
+ * char **splitstr(char *str, int *argcount, char *separator)
+ *
+ * Description
+ * This function splits a string (str) into components that are separated by
+ * one or more of the characters in the (separator) string.  An array of
+ * strings is returned, along with argcount being set to the number of strings
+ * found.
+ *
+ * To rid yourself of the memory allocated for the string:
+ *	free( SplitstrReturn[0] );
+ *	free( SplitstrReturn );
+ *
+ *#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#**/
+#include <stdio.h>
+#include <string.h>        /* for string functions */
+
+#define ARG_ARRAY_SIZE 1000
+
+char **
+splitstr(char *str, int *argcount, char *separator)
+{
+    char *arg_string;
+    char *arg_array[ARG_ARRAY_SIZE+1], **aa;
+
+    int num_toks = 0;  /* number of tokens found */
+    
+    /* copy str to not destroy the original */
+    arg_string = (char*)malloc( strlen(str) + 1 );
+    strcpy( arg_string, str );
+
+    if(separator==NULL)
+	separator = " \t";
+
+    /*
+     * Use strtok() to parse 'arg_string', placing pointers to the
+     * individual tokens into the elements of 'arg_array'.
+     */
+    arg_array[num_toks] = strtok(arg_string, separator);
+    while ( num_toks < ARG_ARRAY_SIZE && 
+	   (arg_array[++num_toks] = strtok(NULL, separator)) != NULL )
+	;
+    
+    arg_array[++num_toks] = NULL;
+
+    *argcount = num_toks-1;
+    aa = (char **) malloc(sizeof(char *) * num_toks);
+    memcpy(aa, arg_array, sizeof(char *) * num_toks);
+
+    /*
+     * Return the argument array.
+     */
+    return(aa);
+}
diff --git a/pan/zoolib.c b/pan/zoolib.c
new file mode 100644
index 0000000..174f338
--- /dev/null
+++ b/pan/zoolib.c
@@ -0,0 +1,352 @@
+/*
+ * 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: zoolib.c,v 1.1 2000/09/14 21:54:44 nstraz Exp $ */
+#include <stdlib.h> /* for getenv */
+#include "zoolib.h"
+
+#define A_BUF_SZ 100
+
+static char Errmsg[500];
+
+char *
+zoo_active()
+{
+	char *fname = "active";
+	static char *active = NULL;
+	char *zoo;
+
+	if( active == NULL ){
+		zoo = getenv( "ZOO" );
+		if( zoo != NULL ){
+			active = (char*)malloc( strlen(zoo) + 1 +
+					       strlen(fname) + 1 );
+			sprintf( active, "%s/%s", zoo, fname );
+		}
+	}
+	return active;
+}
+
+
+FILE *
+open_file( char *file, char *mode, char **errmsg )
+{
+	FILE *fp;
+
+	if( errmsg != NULL )
+		*errmsg = Errmsg;
+
+	if( strcmp( mode, "r+" ) == 0 ){
+		/* make sure there's a file */
+		if( (fp = fopen( file, "a" )) == NULL ){
+			sprintf(Errmsg, "Unable to create file '%s'.  %s/%d  errno:%d  %s\n",
+				file, __FILE__, __LINE__, errno, SYSERR );
+			return(NULL);
+		}
+		else
+			fclose( fp );
+	}
+	
+
+	if( (fp = fopen( file, mode )) == NULL ){
+		sprintf(Errmsg, "Unable to open(%s,%s).  %s/%d  errno:%d  %s\n",
+			file, mode, __FILE__, __LINE__, errno, SYSERR );
+		return(NULL);
+	}
+	return fp;
+}
+
+int
+seek_file( FILE *fp, long int offset, int whence, char **errmsg )
+{
+	if( errmsg != NULL )
+		*errmsg = Errmsg;
+
+	if( (whence != SEEK_SET) && (whence != SEEK_END) ){
+		sprintf(Errmsg, "whence is bad.  %s/%d\n", __FILE__, __LINE__ );
+		return(-1);
+	}
+
+	if( fseek( fp, offset, whence ) != 0 ){
+		sprintf(Errmsg,"fseek(%ld,%s) failed.  %s/%d  errno:%d  %s\n",
+			offset,
+			whence == SEEK_SET ? "SEEK_SET" : "SEEK_END",
+			__FILE__, __LINE__,
+			errno, SYSERR );
+		return(-1);
+	}
+	return whence;
+}
+
+
+int
+lock_file( FILE *fp, short ltype, char **errmsg )
+{
+	struct flock flk;
+	int ret=1;
+
+	if( errmsg != NULL )
+		*errmsg = Errmsg;
+
+	if( (ltype != F_WRLCK) && (ltype != F_UNLCK) ){
+		sprintf(Errmsg, "bad ltype, %s/%d\n", __FILE__, __LINE__ );
+		return(-1);
+	}
+
+	flk.l_whence = flk.l_start = flk.l_len = 0;
+	flk.l_type = ltype;
+/* XXX it's time to upgrade this code for sigaction */
+	sighold(SIGINT);
+	sighold(SIGTERM);
+	sighold(SIGHUP);
+	sighold(SIGUSR1);
+	sighold(SIGUSR2);
+	if( fcntl( fileno(fp), F_SETLKW, &flk ) == -1 ){
+		sprintf(Errmsg, "fcntl(%s) failed. %s/%d  errno:%d  %s\n",
+			ltype == F_WRLCK ? "F_WRLCK" : "F_UNLCK",
+			__FILE__, __LINE__,
+			errno, SYSERR );
+		ret=-1;
+	}
+	sigrelse(SIGINT);
+	sigrelse(SIGTERM);
+	sigrelse(SIGHUP);
+	sigrelse(SIGUSR1);
+	sigrelse(SIGUSR2);
+	return(ret);
+}
+
+
+int
+write_active( FILE *fp, char *name, char **errmsg )
+{
+	pid_t pid = getpid();
+	struct flock flk;
+	char *emsg;
+
+	if( fp == NULL )
+		return(1);
+
+	if( errmsg != NULL )
+		*errmsg = Errmsg;
+
+	if( lock_file( fp, F_WRLCK, &emsg ) == -1 )
+		return(-1);
+	if( seek_file( fp, 0, SEEK_END, &emsg ) == -1 )
+		return(-1);
+
+	/* Write pid first so update_active() can find it quickly.
+	 * update_active() will probably be used far more often than any
+	 * process that needs to search by name anyway.
+	 */
+	fprintf( fp, "%d,%s\n", pid, name );
+	fflush( fp );
+
+	if( lock_file( fp, F_UNLCK, &emsg ) == -1 )
+		return(-1);
+	return(1);
+}
+
+/*
+ * Write a command to the active file, with arguments.
+ * Uses a "first fit" algorithm to take the first available line
+ * Inactive commandlines are assumed to start with a "#"
+ */
+int
+write_active_args( FILE *fp, pid_t pid, char *name, int argc,
+		  char **argv, char **errmsg )
+{
+	struct flock flk;
+	char *args, *cat_args();
+	char active[81];		/* max length.. */
+	int len, l2, found;
+	long int pos;
+	char buf[A_BUF_SZ];
+	char *emsg;
+
+	if( fp == NULL )
+		return(1);
+
+	if( errmsg != NULL )
+		*errmsg = Errmsg;
+
+	if( (args = cat_args(argc, argv, &emsg)) == NULL )
+		return(-1);
+	len = sprintf(active, "%d,%s,", pid, name);
+	strncat(active, args, 80 - len);
+	len = strlen(active);
+	free(args);
+
+	if( lock_file( fp, F_WRLCK, &emsg ) == -1 )
+		return(-1);
+	if( seek_file( fp, 0, SEEK_SET, &emsg ) == -1 )
+		return(-1);
+
+	found=0;
+	while(1) {
+		pos = ftell( fp );
+		if( fgets( buf, A_BUF_SZ - 1, fp ) == NULL )
+			break;
+
+		if( buf[0] == '#' ) {
+		    /* "First Fit" */
+		    if( (l2=strlen(buf)) > len ) { /* can rewrite this one */
+			if( seek_file( fp, pos, SEEK_SET, &emsg ) == -1 )
+				return(-1);
+			fprintf( fp, "%s%*.*s\n", active, l2-len-1, l2-len-1,
+				"|");
+			found=1;
+			break;
+		    }
+		}
+	}/* while */
+
+	if(!found) {
+	    if( seek_file( fp, 0, SEEK_END, &emsg ) == -1 )
+		return(-1);
+
+	    /* Write pid first so update_active() can find it quickly.
+	     * update_active() will probably be used far more often than any
+	     * process that needs to search by name anyway.
+	     */
+	    fprintf( fp, "%s\n", active );
+	}
+
+	fflush( fp );
+	if( lock_file( fp, F_UNLCK, &emsg ) == -1 )
+		return(-1);
+
+	return(1);
+}
+
+
+int
+clear_active( FILE *fp, pid_t me, char **errmsg )
+{
+	char buf[A_BUF_SZ];
+	int pid;
+	long int pos;
+	int found = 0;
+	char *emsg;
+
+	if( fp == NULL )
+		return(1);
+
+	if( lock_file( fp, F_WRLCK, &emsg ) == -1 )
+		return(-1);
+	if( seek_file( fp, 0, SEEK_SET, &emsg ) == -1 )
+		return(-1);
+
+	while(1){
+		pos = ftell( fp );
+		if( fgets( buf, A_BUF_SZ - 1, fp ) == NULL )
+			break;
+
+		if( buf[0] == '#' )
+			continue;
+
+		/* is pid me? */
+		pid = atoi( buf );
+		if( pid != me )
+			continue;
+
+		if( seek_file( fp, pos, SEEK_SET, &emsg ) == -1 )
+			return(-1);
+		fprintf( fp, "#" );
+		found++;
+		break;
+	}/* while */
+
+	fflush( fp );
+	if( lock_file( fp, F_UNLCK, &emsg ) == -1 )
+		return(-1);
+
+	if( ! found ){
+		sprintf(Errmsg, "clear_active() did not find pid(%d).  %s/%d\n",
+			me, __FILE__, __LINE__ );
+		return(0);
+	}
+	return(1);
+}
+
+
+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;
+	}
+}
+
+char *
+cat_args(int argc, char **argv, char **errmsg)
+{
+    int a, size;
+    char *cmd;
+
+    for(size=a=0;a<argc;a++) {
+	size += strlen(argv[a]);
+	size++;
+    }
+
+    if( (cmd = (char *)malloc(size)) == NULL ) {
+	sprintf(Errmsg, "Malloc Error, %s/%d", __FILE__, __LINE__);
+	return(NULL);
+    }
+
+    *cmd='\0';
+    for(a=0;a<argc;a++) {
+	if(a != 0)
+	    strcat(cmd, " ");
+	strcat(cmd, argv[a]);
+    }
+
+    return(cmd);
+}
diff --git a/pan/zoolib.h b/pan/zoolib.h
new file mode 100644
index 0000000..6c76a9c
--- /dev/null
+++ b/pan/zoolib.h
@@ -0,0 +1,71 @@
+/*
+ * 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: zoolib.h,v 1.1 2000/09/14 21:54:44 nstraz Exp $ */
+#ifndef ZOOLIB_H
+#define ZOOLIB_H
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/signal.h>
+
+#ifdef NANNY
+#define EXTERN
+#else
+#define EXTERN extern
+#endif
+
+EXTERN int rec_signal;	/* received signal */
+EXTERN int send_signal;	/* signal to send */
+
+extern int errno;
+#ifndef linux
+extern char *sys_errlist[];
+#endif
+#define SYSERR sys_errlist[errno]
+
+int lock_file( FILE *fp, short ltype, char **errmsg );
+FILE *open_file( char *file, char *mode, char **errmsg );
+
+void wait_handler();
+
+char *zoo_active( void );
+int write_active( FILE *fp, char *name, char **errmsg );
+int clear_active( FILE *fp, pid_t me, char **errmsg );
+int write_active_args( FILE *fp, pid_t pid, char *name, int argc, char **argv, char **errmsg );
+int seek_file( FILE *fp, long int offset, int whence, char **errmsg );
+char *cat_args(int argc, char **argv, char **errmsg);
+
+#endif /* ZOOLIB_H */
diff --git a/runpan.sh b/runpan.sh
new file mode 100755
index 0000000..8bdeadf
--- /dev/null
+++ b/runpan.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+# This will only run the quickhit tests.  
+cd `dirname $0`
+LTPROOT=${PWD}
+
+mkdir /tmp/runalltests-$$
+cd /tmp/runalltests-$$
+
+export PATH="${PATH}:${LTPROOT}/doio:${LTPROOT}/tests"
+ 
+${LTPROOT}/pan/pan -e -S -a $$ -n $$ -f ${LTPROOT}/runtest/quickhit
+
+if [ $? -eq "0" ]; then
+  echo pan reported PASS
+else
+  echo pan reported FAIL
+fi
+
+rm -rf /tmp/runalltests-$$
diff --git a/tests/alarm02.c b/tests/alarm02.c
index e5cb0bb..425fb80 100644
--- a/tests/alarm02.c
+++ b/tests/alarm02.c
@@ -29,7 +29,7 @@
  * 
  * http://oss.sgi.com/projects/GenInfo/NoticeExplan/
  */
-/* $Id: alarm02.c,v 1.3 2000/08/30 18:43:38 nstraz Exp $ */
+/* $Id: alarm02.c,v 1.4 2000/09/14 21:54:44 nstraz Exp $ */
 /**********************************************************
  *
  *    OS Test - Silicon Graphics, Inc.
@@ -46,10 +46,6 @@
  *
  *    CPU TYPES         : ALL
  *
- *    BINARY LOCATION   : CUTS_BIN/rf_tests/sys
- *
- *    SOURCE LOCATION   : CUTS_SRC/src/tests/sys
- *
  *    AUTHOR            : Billy Jean Horne
  *
  *    CO-PILOT          : Kathy Olmsted
@@ -82,7 +78,7 @@
  *       Loop for each test case.
  *        Execute alarm (0) system call to clear previous alarm.
  *        Check return code, if system call failed (return=-1)
- *           Issue a BROK message and exit the test.
+ *           Issue a FAIL message and exit the test.
  *        Call alarm() with boundary values for seconds.
  *        Verify that returned value is as expected.
  *        Report results.