Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/CheckController.c
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/CheckController.c	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/CheckController.c	(revision 21930)
@@ -0,0 +1,120 @@
+# include "psched.h"
+
+static struct timeval start;
+void TimerMark ();
+float TimerElapsed (int reset);
+
+int CheckController () {
+
+  char *p, *q;
+  int i, Njobs, status, JobID;
+  Job *job;
+  IOBuffer buffer;
+
+  /* get the list of completed jobs (exit / crash), update the job status */
+  if (!CheckControllerStatus()) return (TRUE);
+
+  /*** check EXIT jobs ***/
+  InitIOBuffer (&buffer, 0x100);
+  // TimerMark ();
+  // status = ControllerCommand ("stop", CONTROLLER_PROMPT, &buffer); 
+  // if (VerboseMode()) fprintf (stderr, "stop controller %f\n", TimerElapsed(TRUE));
+
+  TimerMark ();
+  FlushIOBuffer (&buffer);
+  status = ControllerCommand ("jobstack exit", CONTROLLER_PROMPT, &buffer);
+  if (VerboseMode()) fprintf (stderr, "check exit stack %f\n", TimerElapsed(TRUE));
+  if (!status) goto escape;
+
+  /** programming error **/
+  p = memstr (buffer.buffer, "USAGE: jobstack", buffer.Nbuffer);
+  if (p != NULL) goto escape;
+
+  /** parse job list **/
+  status = sscanf (buffer.buffer, "%*s %d", &Njobs);
+  if (status != 1) goto escape;
+  if (VerboseMode()) fprintf (stderr, "parse %d jobs on stack %f\n", Njobs, TimerElapsed(TRUE));
+
+  p = buffer.buffer;
+  for (i = 0; (i < Njobs) && !TestElapsedCheck(); i++) {
+    q = strchr (p, '\n');
+    if (q == NULL) {
+      fprintf (stderr, "controller message error: incomplete job list\n");
+      break;
+    }
+    p = q + 1;
+    status = sscanf (p, "%d", &JobID);
+
+    job = FindControllerJob (JobID);
+    if (job == NULL) {
+      fprintf (stderr, "misplaced job? %d not in EXIT job list\n", JobID);
+      continue;
+    }
+    /* this checks the individual job status, grabs stdout/stderr */
+    CheckControllerJob (job);
+  }
+  if (VerboseMode()) fprintf (stderr, "clear %d exit jobs %f\n", i, TimerElapsed(TRUE));
+
+  if (TestElapsedCheck()) goto finish;
+  /* this will prevent us from ever checking crashed jobs... */
+
+  /*** check CRASH jobs ***/
+  FlushIOBuffer (&buffer);
+  status = ControllerCommand ("jobstack crash", CONTROLLER_PROMPT, &buffer);
+  if (!status) goto escape;
+
+  p = memstr (buffer.buffer, "USAGE: jobstack", buffer.Nbuffer);
+  if (p != NULL) goto escape;
+
+  /** parse job list **/
+  status = sscanf (buffer.buffer, "%*s %d", &Njobs);
+  if (status != 1) goto escape;
+  if (VerboseMode()) fprintf (stderr, "check crash stack %f\n", TimerElapsed(TRUE)); 
+
+  p = buffer.buffer;
+  for (i = 0; (i < Njobs) && !TestElapsedCheck(); i++) {
+    q = strchr (p, '\n');
+    if (q == NULL) {
+      fprintf (stderr, "controller message error: incomplete job list\n");
+      break;
+    }
+    p = q + 1;
+    
+    status = sscanf (p, "%d", &JobID);
+    job = FindControllerJob (JobID);
+    if (job == NULL) {
+      fprintf (stderr, "misplaced job? %d not in CRASH job list\n", JobID);
+      continue;
+    }
+    /* this checks the individual job status, grabs stdout/stderr */
+    CheckControllerJob (job);
+  }
+  if (VerboseMode()) fprintf (stderr, "clear %d crash jobs %f\n", i, TimerElapsed(TRUE)); 
+
+ finish:
+  FlushIOBuffer (&buffer);
+  // status = ControllerCommand ("run", CONTROLLER_PROMPT, &buffer);
+  FreeIOBuffer (&buffer);
+  return (TRUE);
+
+ escape:
+  FlushIOBuffer (&buffer);
+  // status = ControllerCommand ("run", CONTROLLER_PROMPT, &buffer); 
+  FreeIOBuffer (&buffer);
+  return (FALSE);
+}
+
+void TimerMark () {
+    gettimeofday (&start, (void *) NULL);
+}
+
+float TimerElapsed (int reset) {
+
+  float dtime;
+  struct timeval stop;
+
+  gettimeofday (&stop, (void *) NULL);
+  dtime = DTIME (stop, start);
+  if (reset) gettimeofday (&start, (void *) NULL);
+  return (dtime);
+}
Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/CheckJobs.c
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/CheckJobs.c	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/CheckJobs.c	(revision 21930)
@@ -0,0 +1,110 @@
+# include "psched.h"
+
+int CheckJobs () {
+
+  Job *job;
+  Macro *macro;
+  int i, status;
+
+  /** test all jobs: ready to test?  finished? **/
+  while ((job = NextJob ()) != NULL) {
+
+    /* check poll period (ready to ask for status?) */
+    if (GetTaskTimer(job[0].last) < job[0].task[0].poll_period) continue;
+
+    /* check current status */
+    status = CheckJob (job);
+    switch (status) {
+      case JOB_PENDING:
+	/* if (VerboseMode()) fprintf (stderr, "job %s (%d) pending\n", job[0].task[0].name, job[0].JobID); */
+	break;
+
+      case JOB_BUSY:
+	/* if (VerboseMode()) fprintf (stderr, "job %s (%d) busy\n", job[0].task[0].name, job[0].JobID); */
+	break;
+
+      case JOB_CRASH:
+	/* run task[0].crash macro, if it exists */
+	/* push output buffer data to the stdout and stderr queues */
+	if (VerboseMode()) fprintf (stderr, "job %s (%d) crash\n", job[0].task[0].name, job[0].JobID);
+	PushNamedQueue ("stdout", job[0].stdout.buffer);
+	PushNamedQueue ("stderr", job[0].stderr.buffer);
+	/* XXX this will break on 0 values in output streams */
+	/* perhaps define PushNamedQueueBuffer */
+	if (job[0].task[0].crash != NULL) {
+	  exec_loop (job[0].task[0].crash);
+	}
+	DeleteJob (job);
+	continue;
+	break;
+
+      case JOB_EXIT:
+	if (VerboseMode()) fprintf (stderr, "job %s (%d) exit\n", job[0].task[0].name, job[0].JobID);
+	PushNamedQueue ("stdout", job[0].stdout.buffer);
+	PushNamedQueue ("stderr", job[0].stderr.buffer);
+	/* run corresponding task[0].exit macro, if it exists */
+	macro = job[0].task[0].defexit;
+	for (i = 0; i < job[0].task[0].Nexit; i++) {
+	  if (job[0].exit_status == atoi(job[0].task[0].exit[i][0].name)) {
+	    macro = job[0].task[0].exit[i];
+	    break;
+	  }
+	}
+	if (macro != NULL) exec_loop (macro);
+	DeleteJob (job);
+	continue;
+	break;
+
+      default:
+	if (VerboseMode()) fprintf (stderr, "unknown exit status: %d\n", status);
+	/** do something more useful here ?? **/
+	break;
+    }
+
+    /* check for timeout - (local jobs only) 
+       we only check timeout after a poll (forces at least one poll)
+     */
+    if (job[0].mode == JOB_LOCAL) {
+      if (GetTaskTimer(job[0].start) < job[0].task[0].timeout_period) continue;
+      if (VerboseMode()) fprintf (stderr, "timeout on %s\n", job[0].task[0].name);
+      if (!KillLocalJob (job)) {
+	job[0].state = JOB_HUNG;
+	if (VerboseMode()) fprintf (stderr, "child process %d is hung, cannot kill\n", job[0].pid);
+	continue;
+      }
+      /* run task[0].timeout macro, if it exists */
+      if (job[0].task[0].timeout != NULL) {
+	exec_loop (job[0].task[0].timeout);
+      }
+      DeleteJob (job);
+      continue;
+    }
+
+    /* reset polling clock */
+    SetTaskTimer (&job[0].last);
+    if (TestElapsedCheck()) return (TRUE);
+  }
+  return (TRUE);
+}
+
+/* 
+
+  job / task timeline:
+
+  task:
+  0           exec     
+  start       create
+  task clock  new job
+
+  job:
+  0           1xpoll     2xpoll     3xpoll
+  start       check      check      check 
+  job clock   status     status     status
+
+  .           .          .          timeout
+                                    run
+				    timeout
+
+  must be at least one poll before timeout 
+  (timeout >= poll)
+*/
Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/CheckSystem.c
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/CheckSystem.c	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/CheckSystem.c	(revision 21930)
@@ -0,0 +1,37 @@
+# include "psched.h"
+
+# define MAX_DELAY 0.1
+struct timeval start, last;
+static int Ncheck = 0;
+
+int CheckSystem () {
+
+  gettimeofday (&start, (void *) NULL);
+
+  if (Ncheck < 100) {
+    CheckTasks ();
+    CheckJobs ();
+    Ncheck ++;
+  } else {
+    CheckController ();
+    CheckControllerOutput ();
+    Ncheck = 0;
+  }
+  /* ohana_memcheck (FALSE); */
+  return (TRUE);
+}
+
+int TestElapsedCheck () {
+
+  struct timeval stop;
+  float dtime;
+
+  gettimeofday (&stop, (void *) NULL);
+  dtime = DTIME (stop, start);
+  if (dtime > MAX_DELAY) return (TRUE);
+  return (FALSE);
+}
+
+  /* do we really need to check the controller on every call?
+     or perhaps alternate?
+  */
Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/CheckTasks.c
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/CheckTasks.c	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/CheckTasks.c	(revision 21930)
@@ -0,0 +1,39 @@
+# include "psched.h"
+
+int CheckTasks () {
+
+  Job *job;
+  Task *task;
+  int status;
+
+  /** test all tasks: ready to test? ready to run? **/
+  while ((task = NextTask ()) != NULL) {
+
+    /* ready to test? : check exec period */
+    if (GetTaskTimer(task[0].last) < task[0].exec_period) continue;
+
+    /* need to check if the current time is within valid/invalid periods */
+    if (!CheckTimeRanges (task[0].ranges, task[0].Nranges)) continue;
+    if (task[0].Nmax && (task[0].Njobs >= task[0].Nmax)) continue;
+
+    /* ready to run? : run task.exec macro */
+    if (task[0].exec != NULL) {
+      status = exec_loop (task[0].exec);
+      if (!status) continue;
+    }
+    if (!ValidateTask (task, TRUE)) continue;
+
+    /* construct job from task */
+    job = CreateJob (task);
+
+    /* execute job - XXX add status test */
+    SubmitJob (job);
+
+    /* reset timer on task (don't do this if Create/Submit fails)*/
+    gettimeofday (&task[0].last, (void *) NULL);
+    task[0].Njobs ++;
+
+    if (TestElapsedCheck()) return (TRUE);
+  }
+  return (TRUE);
+}
Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/CheckTimeRanges.c
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/CheckTimeRanges.c	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/CheckTimeRanges.c	(revision 21930)
@@ -0,0 +1,35 @@
+# include "psched.h"
+
+int CheckTimeRanges (TimeRange *ranges, int Nranges) {
+
+  int i, intime, valid;
+  time_t daytime, weektime, abstime;
+  struct timeval now;
+  struct tm Now;
+
+  gettimeofday (&now, NULL);
+  gmtime_r (&now.tv_sec, &Now);
+  daytime = Now.tm_sec + Now.tm_min*60 + Now.tm_hour*3600;
+  weektime = Now.tm_sec + Now.tm_min*60 + Now.tm_hour*3600 + Now.tm_wday*86400;
+  abstime = now.tv_sec;
+
+  valid = TRUE;
+  for (i = 0; (i < Nranges) && valid; i++) {
+    switch (ranges[i].type) {
+      case RANGE_ABS:
+	intime = (abstime >= ranges[i].start) && (abstime <= ranges[i].stop);
+	valid = ranges[i].keep ? intime : !intime;
+	break;
+      case RANGE_DAY:
+	intime = (daytime >= ranges[i].start) && (daytime <= ranges[i].stop);
+	valid = ranges[i].keep ? intime : !intime;
+	break;
+      case RANGE_WEEK:
+	intime = (weektime >= ranges[i].start) && (weektime <= ranges[i].stop);
+	valid = ranges[i].keep ? intime : !intime;
+	break;
+    }
+    if (!valid) return (FALSE);
+  }
+  return (valid);
+}  
Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/ControllerOps.c
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/ControllerOps.c	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/ControllerOps.c	(revision 21930)
@@ -0,0 +1,473 @@
+# include "psched.h"
+/* adding a new host can delay controller up to a second or so */
+# define CONTROLLER_TIMEOUT 500
+# define CONNECT_TIMEOUT 300
+
+/* local static variables to hold the connection to the controller */
+static int ControllerStatus = FALSE;
+static int stdin_cntl, stdout_cntl, stderr_cntl;
+static IOBuffer stdout_buffer;
+static IOBuffer stderr_buffer;
+static int ControllerPID = 0;
+
+/* test if the controller is running */
+int CheckControllerStatus () {
+  return (ControllerStatus);
+}
+
+/* check job / get output if done */
+int CheckControllerJob (Job *job) {
+  struct timeval start, stop;
+  float dtime;
+
+  gettimeofday (&start, (void *) NULL);
+  CheckControllerJobStatus (job);
+  gettimeofday (&stop, (void *) NULL);
+  dtime = DTIME (stop, start);
+  /* if (VerboseMode()) fprintf (stderr, "check job status %f\n", dtime); */
+
+  if ((job[0].state == JOB_EXIT) || (job[0].state == JOB_CRASH)) {
+    gettimeofday (&start, (void *) NULL);
+    GetJobOutput ("stdout", job[0].pid, &job[0].stdout, job[0].stdout_size);
+    gettimeofday (&stop, (void *) NULL);
+    dtime = DTIME (stop, start);
+    /* if (VerboseMode()) fprintf (stderr, "get stdout %f\n", dtime); */
+
+    gettimeofday (&start, (void *) NULL);
+    GetJobOutput ("stderr", job[0].pid, &job[0].stderr, job[0].stderr_size);
+    gettimeofday (&stop, (void *) NULL);
+    dtime = DTIME (stop, start);
+    /* if (VerboseMode()) fprintf (stderr, "get stderr %f\n", dtime); */
+
+    gettimeofday (&start, (void *) NULL);
+    DeleteControllerJob (job);
+    gettimeofday (&stop, (void *) NULL);
+    dtime = DTIME (stop, start);
+    /* if (VerboseMode()) fprintf (stderr, "delete job %f\n", dtime); */
+  }  
+  return (TRUE);
+}
+
+int DeleteControllerJob (Job *job) {
+
+  int status;
+  char cmd[128]; 
+  IOBuffer buffer;
+
+  sprintf (cmd, "delete %d", job[0].pid);
+  InitIOBuffer (&buffer, 0x100);
+  status = ControllerCommand (cmd, CONTROLLER_PROMPT, &buffer);
+  FreeIOBuffer (&buffer);
+  return (status);
+}
+  
+/* ask controller about job status */
+int CheckControllerJobStatus (Job *job) {
+
+  int outstate, status;
+  char cmd[128], status_string[64];
+  char *p;
+  IOBuffer buffer;
+
+  sprintf (cmd, "check job %d", job[0].pid);
+  InitIOBuffer (&buffer, 0x100);
+  status = ControllerCommand (cmd, CONTROLLER_PROMPT, &buffer);
+  if (!status) {
+    FreeIOBuffer (&buffer);
+    return (FALSE);
+  }
+
+  /** was this a valid job? **/
+  p = memstr (buffer.buffer, "job not found", buffer.Nbuffer);
+  if (p != NULL) {
+    fprintf (stderr, "unknown job %d\n", job[0].pid);
+    FreeIOBuffer (&buffer);
+    return (FALSE);
+  }
+
+  /** parse status message **/
+  p = memstr (buffer.buffer, "STATUS", buffer.Nbuffer);
+  sscanf (p, "%*s %s", status_string);
+  p = memstr (buffer.buffer, "EXITST", buffer.Nbuffer);
+  sscanf (p, "%*s %d", &job[0].exit_status);
+  p = memstr (buffer.buffer, "STDOUT", buffer.Nbuffer);
+  sscanf (p, "%*s %d", &job[0].stdout_size);
+  p = memstr (buffer.buffer, "STDERR", buffer.Nbuffer);
+  sscanf (p, "%*s %d", &job[0].stderr_size);
+  FreeIOBuffer (&buffer);
+
+  /* possible exit status values */
+  outstate = -1;
+  if (!strcmp(status_string, "BUSY"))    outstate = JOB_BUSY;
+  if (!strcmp(status_string, "DONE"))    outstate = JOB_BUSY;
+  if (!strcmp(status_string, "PENDING")) outstate = JOB_PENDING;
+  if (!strcmp(status_string, "EXIT"))    outstate = JOB_EXIT;
+  if (!strcmp(status_string, "CRASH"))   outstate = JOB_CRASH;
+  if (outstate == -1) {
+    fprintf (stderr, "programming error?\n");
+    exit (1);
+  }
+  job[0].state = outstate;
+  return (TRUE);
+}
+
+/* we read Nbytes from the host, then watch for the prompt */ 
+int GetJobOutput (char *cmd, int pid, IOBuffer *buffer, int Nbytes) {
+  
+  int i, status, Nstart;
+  char *line;
+  struct timespec request, remain;
+
+  /* avoid blocking on waitpid, test every 100 usec, up to 50 msec */
+  request.tv_sec = 0;
+  request.tv_nsec = 100000;
+
+  /* flush any earlier messages */
+  ReadtoIOBuffer (buffer, stdout_cntl);
+  FlushIOBuffer (buffer);
+  Nstart = buffer[0].Nbuffer;
+
+  /* send command to get appropriate channel */
+  status = write_fmt (stdin_cntl, "%s %d\n", cmd, pid);
+
+  /* is pipe still open? */
+  if ((status == -1) && (errno == EPIPE)) return (CONTROLLER_DOWN);
+
+  /* read at least Nbytes, then watch for CONTROLLER_PROMPT */
+  line = NULL;
+  status = -1;
+  for (i = 0; (i < CONTROLLER_TIMEOUT) && (status != 0) && (line == NULL); i++) {
+    status = ReadtoIOBuffer (buffer, stdout_cntl);
+    if ((buffer[0].Nbuffer - Nstart) >= Nbytes) {
+      line = memstr (buffer[0].buffer, CONTROLLER_PROMPT, buffer[0].Nbuffer);
+    }
+    if (status == -1) nanosleep (&request, &remain);
+  }
+  if (status ==  0) return (CONTROLLER_DOWN);
+  if (status == -1) return (CONTROLLER_HUNG);
+
+  /* if (VerboseMode()) fprintf (stderr, "message received (GetJobOutput : %s)\n", cmd);   */
+  /* drop extra bytes from pcontrol (not pclient:job) */
+  buffer[0].Nbuffer = Nstart + Nbytes;
+  if (buffer[0].Nalloc > buffer[0].Nbuffer) {
+    bzero (buffer[0].buffer + buffer[0].Nbuffer, buffer[0].Nalloc - buffer[0].Nbuffer);
+  }
+  return (CONTROLLER_GOOD);
+}
+
+/* submitting a job to the controller automatically starts controller */
+int SubmitControllerJob (Job *job) {
+
+  int i, Nchar, status;
+  char *cmd, *p, string[64];
+  IOBuffer buffer;
+
+  if (job[0].task[0].host == NULL) return (FALSE); 
+
+  StartController ();   // XXX check for success 
+
+  /** construct the line to be sent to the controller **/
+
+  /* determine the total line length */
+  Nchar = 0;
+  for (i = 0; i < job[0].argc; i++) {
+    Nchar += strlen (job[0].argv[i]) + 1;
+  }
+  if (job[0].task[0].host) {
+    Nchar += strlen (job[0].task[0].host) + 1;
+  }
+  Nchar += 10;
+  ALLOCATE (cmd, char, Nchar);
+  bzero (cmd, Nchar);
+
+  /* construct the controller command portion */
+  if (!strcasecmp (job[0].task[0].host, "ANYHOST")) {
+    sprintf (cmd, "job");
+  } else {
+    if (job[0].task[0].host_required) {
+      sprintf (cmd, "job +host %s", job[0].task[0].host);
+    } else {
+      sprintf (cmd, "job -host %s", job[0].task[0].host);
+    }
+  }
+
+  /* add the command arguments */
+  for (i = 0; i < job[0].task[0].argc; i++) {
+    strcat (cmd, " ");
+    strcat (cmd, job[0].task[0].argv[i]);
+  }
+
+  InitIOBuffer (&buffer, 0x100);
+  status = ControllerCommand (cmd, CONTROLLER_PROMPT, &buffer);
+  free (cmd);
+
+  /* extract the job PID from the controller response */
+  p = memstr (buffer.buffer, "JobID", buffer.Nbuffer);
+  if (p == NULL) {
+    fprintf (stderr, "missing PID in pcontrol message : programming error\n");
+    exit (1);
+  }
+  sscanf (p, "%*s %s", string);
+  job[0].pid = atoi (string);
+  FreeIOBuffer (&buffer);
+  return (TRUE);
+}
+
+int StartController () {
+
+  char *p;
+  char **argv, cmd[128];
+  int i, pid, status;
+  int stdin_fd[2], stdout_fd[2], stderr_fd[2];
+  IOBuffer buffer;
+
+  if (ControllerStatus) return (TRUE);
+
+  if (VarConfig ("CONTROLLER", "%s", cmd) == NULL) strcpy (cmd, "pcontrol");
+
+  bzero (stdin_fd,  2*sizeof(int));
+  bzero (stdout_fd, 2*sizeof(int));
+  bzero (stderr_fd, 2*sizeof(int));
+
+  if (pipe (stdin_fd)  < 0) goto pipe_error;
+  if (pipe (stdout_fd) < 0) goto pipe_error;
+  if (pipe (stderr_fd) < 0) goto pipe_error;
+
+  ALLOCATE (argv, char *, 2);
+  argv[0] = cmd;
+  argv[1] = 0;
+
+  pid = fork ();
+  if (!pid) { /* must be child process */
+    fprintf (stderr, "starting controller connection\n");
+
+    /* close the other ends of the pipes */
+    close (stdin_fd[1]);
+    close (stdout_fd[0]);
+    close (stderr_fd[0]);
+
+    /* tie our ends of the pipes to stdin, stdout, stderr */
+    dup2 (stdin_fd[0],  STDIN_FILENO);
+    dup2 (stdout_fd[1], STDOUT_FILENO);
+    dup2 (stderr_fd[1], STDERR_FILENO);
+
+    /* set all three unblocking */
+    setvbuf (stdin,  (char *) NULL, _IONBF, BUFSIZ);
+    setvbuf (stdout, (char *) NULL, _IONBF, BUFSIZ);
+    setvbuf (stderr, (char *) NULL, _IONBF, BUFSIZ);
+
+    status = execvp (argv[0], argv); 
+    exit (1);
+  }
+  free (argv);
+
+  /* close the other ends of the pipes */
+  close (stdin_fd[0]);  stdin_fd[0]  = 0;
+  close (stdout_fd[1]); stdout_fd[1] = 0;
+  close (stderr_fd[1]); stderr_fd[1] = 0;
+
+  /* make the pipes non-blocking */
+  fcntl (stdin_fd[1],  F_SETFL, O_NONBLOCK);
+  fcntl (stdout_fd[0], F_SETFL, O_NONBLOCK);
+  fcntl (stderr_fd[0], F_SETFL, O_NONBLOCK);
+
+  /* perform handshake with controller to verify alive & running */
+  /** this handshake is similar to ControllerCommand, but has important differences **/
+  InitIOBuffer (&buffer, 0x100);
+
+  /* send handshake command */
+  status = write_fmt (stdin_fd[1], "echo CONNECTED\n");
+  if ((status == -1) && (errno == EPIPE)) goto pipe_error;
+
+  /* try to get evidence connection is alive - wait upto a few seconds */
+  /* connection is likely slow; don't bother with nanosleep here */
+  p = NULL;
+  status = -1;
+  for (i = 0; (i < CONNECT_TIMEOUT) && (status != 0) && (p == NULL); i++) {
+    status = ReadtoIOBuffer (&buffer, stdout_fd[0]);
+    p = memstr (buffer.buffer, "CONNECTED", buffer.Nbuffer);
+    usleep (10000);
+  }
+  if (status == 0) goto pipe_error;
+  if (status == -1) goto io_error;
+  FreeIOBuffer (&buffer);
+
+  /* set local static vars to pipe connections */
+  stdin_cntl  = stdin_fd[1];
+  stdout_cntl = stdout_fd[0];
+  stderr_cntl = stderr_fd[0];
+
+  InitIOBuffer (&stdout_buffer, 0x100);
+  InitIOBuffer (&stderr_buffer, 0x100);
+
+  ControllerPID = pid;
+  ControllerStatus = TRUE;
+  fprintf (stderr, "Connected\n");
+  return (TRUE);
+
+pipe_error:
+  perror ("pipe error:");
+  goto close_pipes;
+
+io_error:
+  fprintf (stderr, "timeout while connecting\n");
+  goto close_pipes;
+
+close_pipes:
+  if (stdin_fd[0]  != 0) close (stdin_fd[0]);
+  if (stdin_fd[1]  != 0) close (stdin_fd[1]);
+  if (stdout_fd[0] != 0) close (stdout_fd[0]);
+  if (stdout_fd[1] != 0) close (stdout_fd[1]);
+  if (stderr_fd[0] != 0) close (stderr_fd[0]);
+  if (stderr_fd[1] != 0) close (stderr_fd[1]);
+  return (FALSE);
+}
+
+int ControllerCommand (char *cmd, char *response, IOBuffer *buffer) {
+
+  int i, status;
+  char *line;
+  struct timespec request, remain;
+
+  /* avoid blocking on waitpid, test every 100 usec, up to 50 msec */
+  request.tv_sec = 0;
+  request.tv_nsec = 100000;
+
+  ReadtoIOBuffer (buffer, stdout_cntl);
+  FlushIOBuffer (buffer);
+
+  /* send command, is pipe still open? */
+  status = write_fmt (stdin_cntl, "%s\n", cmd);
+  if ((status == -1) && (errno == EPIPE)) {
+    StopController ();
+    if (VerboseMode()) fprintf (stderr, "controller is down\n");
+    return (FALSE);
+  }
+  
+  /* watch for response - wait up to 1 second */
+  line = NULL;
+  status = -1;
+  for (i = 0; (i < CONTROLLER_TIMEOUT) && (status != 0) && (line == NULL); i++) {
+    status = ReadtoIOBuffer (buffer, stdout_cntl);
+    line = memstr (buffer[0].buffer, response, buffer[0].Nbuffer);
+    if (status == -1) nanosleep (&request, &remain);
+  }
+  if (status ==  0) {
+    StopController ();
+    if (VerboseMode()) fprintf (stderr, "controller is down\n");
+    return (FALSE);
+  }
+  if (status == -1) {
+    if (VerboseMode()) fprintf (stderr, "controller is not responding\n");
+    return (FALSE);
+  }
+
+  /* need to strip off the prompt */
+  line = memstr (buffer[0].buffer, response, buffer[0].Nbuffer);
+  if (line != NULL) {
+    buffer[0].Nbuffer = line - buffer[0].buffer;
+    bzero (buffer[0].buffer + buffer[0].Nbuffer, buffer[0].Nalloc - buffer[0].Nbuffer);
+  }
+  /* if (VerboseMode()) fprintf (stderr, "message received, %d cycles\n", i); */
+  return (TRUE);
+}
+
+int CheckControllerOutput () {
+
+  int Nread;
+
+  if (!ControllerStatus) return (TRUE);
+
+  /* read stdout buffer */
+  Nread = ReadtoIOBuffer (&stdout_buffer, stdout_cntl);
+  switch (Nread) {
+    case -2:  /* error in read (programming error?  system level error?) */
+      fprintf (stderr, "serious IO error\n");
+      exit (2);
+    case -1:  /* no data in pipe */
+    case 0:   /* pipe is closed, change child state? **/
+    default:  /* data in pipe */
+      break;
+  }
+  
+  /* read stderr buffer */
+  Nread = ReadtoIOBuffer (&stderr_buffer, stderr_cntl);
+  switch (Nread) {
+    case -2:  /* error in read (programming error?  system level error?) */
+      fprintf (stderr, "serious IO error\n");
+      exit (2);
+    case -1:  /* no data in pipe */
+    case 0:   /* pipe is closed, change child state? **/
+    default:  /* data in pipe */
+      break;
+  }
+  return (TRUE);
+}
+
+int PrintControllerOutput () {
+
+  fprintf (stderr, "--- stdout ---\n");
+  fwrite (stdout_buffer.buffer, 1, stdout_buffer.Nbuffer, stderr);
+  fprintf (stderr, "--- stdout ---\n");
+  fwrite (stderr_buffer.buffer, 1, stderr_buffer.Nbuffer, stderr);
+  fprintf (stderr, "---  done  ---\n");
+  return (TRUE);
+}
+
+
+int KillControllerJob (Job *job) {
+
+  int status;
+  char cmd[128];
+  IOBuffer buffer;
+
+  if (!ControllerStatus) return (TRUE);
+
+  sprintf (cmd, "kill %d", job[0].pid);
+  InitIOBuffer (&buffer, 0x100);
+  status = ControllerCommand (cmd, CONTROLLER_PROMPT, &buffer);
+  FreeIOBuffer (&buffer);
+  return (status);
+
+  /** need to interpret output message & free things **/
+}
+
+int QuitController () {
+
+  int status;
+  char cmd[128];
+  IOBuffer buffer;
+
+  if (!ControllerStatus) return (TRUE);
+
+  sprintf (cmd, "quit");
+  InitIOBuffer (&buffer, 0x100);
+  status = ControllerCommand (cmd, "", &buffer);
+  FreeIOBuffer (&buffer);
+
+  /* the quit command does not return a prompt, so we always 
+     get an error on the controller here */
+  StopController ();
+  return (TRUE);
+}
+
+int StopController () {
+
+  int i, waitstatus, result;
+
+  if (!ControllerStatus) return (TRUE);
+
+  ControllerStatus = FALSE;
+  result = waitpid (ControllerPID, &waitstatus, WNOHANG);
+  for (i = 0; (i < 10) && (result == 0); i++) {
+    usleep (10000);  /* 10 ms is min */
+    result = waitpid (ControllerPID, &waitstatus, WNOHANG);
+  }
+  ControllerPID = 0;
+  close (stdin_cntl);
+  close (stdout_cntl);
+  close (stderr_cntl);
+  FreeIOBuffer (&stdout_buffer);
+  FreeIOBuffer (&stderr_buffer);
+  return (TRUE);
+}
Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/JobIDOps.c
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/JobIDOps.c	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/JobIDOps.c	(revision 21930)
@@ -0,0 +1,41 @@
+# include "psched.h"
+
+# define MAX_N_JOBS 1000
+static char *JobIDList;
+static int   JobIDPtr;
+
+int InitJobIDs () {
+
+  JobIDPtr = 0;
+  ALLOCATE (JobIDList, char, MAX_N_JOBS);
+  bzero (JobIDList, MAX_N_JOBS*sizeof(char));
+  return (TRUE);
+}  
+
+/* return next unique ID, recycle every MAX_N_JOBS */
+int NextJobID () {
+
+  int Ntry;
+
+  JobIDPtr ++;
+  if (JobIDPtr >= MAX_N_JOBS) JobIDPtr = 0;
+
+  Ntry = 0;
+  while (JobIDList[JobIDPtr]) {
+    Ntry ++;
+    JobIDPtr ++;
+    if (JobIDPtr >= MAX_N_JOBS) JobIDPtr = 0;
+    if (Ntry == MAX_N_JOBS) return (-1);
+  }
+  JobIDList[JobIDPtr] = TRUE;
+  return (JobIDPtr);
+}
+
+int FreeJobID (int JobID) {
+
+  if (JobID < 0) return (FALSE);
+  if (JobID >= MAX_N_JOBS) return (FALSE);
+
+  JobIDList[JobID] = FALSE;
+  return (TRUE);
+}
Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/JobOps.c
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/JobOps.c	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/JobOps.c	(revision 21930)
@@ -0,0 +1,200 @@
+# include "psched.h"
+
+static Job **jobs;
+static int   Njobs;
+static int   NJOBS;
+
+/* counter marking job being visited by the run loop */
+static int   ActiveJob;
+
+/* set up the jobs list */
+void InitJobs () {
+  NJOBS = 20;
+  Njobs = 0;
+  ALLOCATE (jobs, Job *, NJOBS);
+  ActiveJob = -1;
+}
+
+/* provide a mechanism to loop over the list of jobs */
+Job *NextJob () {
+  
+  Job *job;
+
+  ActiveJob ++;
+  if (ActiveJob < 0) ActiveJob = 0;
+  if (ActiveJob >= Njobs) {
+    ActiveJob = -1;
+    return (NULL);
+  }
+  job = jobs[ActiveJob];
+  return (job);
+}
+
+/* return job with given ID */
+Job *FindJob (int JobID) {
+
+  int i;
+
+  /* return job with matching JobID */
+  for (i = 0; i < Njobs; i++) {
+    if (jobs[i][0].JobID == JobID) {
+      return (jobs[i]);
+    }
+  }
+  return (NULL);
+}  
+
+/* return job with given controller Job ID */
+Job *FindControllerJob (int JobID) {
+
+  int i;
+
+  /* return job with matching JobID */
+  for (i = 0; i < Njobs; i++) {
+    if (jobs[i][0].mode == JOB_LOCAL) continue;
+    if (jobs[i][0].pid  == JobID) {
+      return (jobs[i]);
+    }
+  }
+  return (NULL);
+}  
+
+/* list known jobs */
+void ListJobs () {
+
+  int i;
+
+  fprintf (stderr, "\n");
+  if (Njobs == 0) {
+    fprintf (stderr, " no defined jobs\n");
+    return;
+  }
+
+  fprintf (stderr, " Jobs\n");
+  for (i = 0; i < Njobs; i++) {
+    fprintf (stderr, " %d: %-15s %5d %20s (%lx)\n", Njobs, jobs[i][0].task[0].name, jobs[i][0].JobID, jobs[i][0].argv[0], (long) jobs[i][0].argv);
+  }
+  return;
+}
+
+/* make a new job from a task */
+Job *CreateJob (Task *task) {
+  
+  int i;
+  Job *job;
+  
+  ALLOCATE (job, Job, 1);
+
+  job[0].JobID = NextJobID ();
+  job[0].pid = 0;
+  job[0].mode = JOB_LOCAL;
+  if (task[0].host != NULL) {
+    job[0].mode = JOB_CONTROLLER;
+  }
+
+  /* we need our own copy of task[0].argv argc is the number of valid args, like the usual command line.  we
+   *  allocate one extra element, with value 0 to be passed to execvp
+   */
+  job[0].argc = task[0].argc;
+  ALLOCATE (job[0].argv, char *, MAX (task[0].argc + 1, 1));
+  for (i = 0; i < task[0].argc; i++) {
+    job[0].argv[i] = strcreate (task[0].argv[i]);
+  }
+  job[0].argv[i] = 0;
+
+  /* Other data from the task is needed by the job. We carry a pointer back to the task.  Changes to an
+     executing task are applied to the existing jobs (exit macros, poll_period, timeout) */
+
+  job[0].task = task;
+  
+  /* if we decide we need to be able to dynamically set task qualities (like host, timeouts, etc), the we will
+     need to have matched entries to these quantites in the job structure */
+
+  InitIOBuffer (&job[0].stdout, 0x100);
+  InitIOBuffer (&job[0].stderr, 0x100);
+
+  jobs[Njobs] = job;
+  Njobs ++;
+  if (Njobs == NJOBS) {
+    NJOBS += 20;
+    REALLOCATE (jobs, Job *, NJOBS);
+  }
+  return (jobs[Njobs-1]);
+}
+
+void FreeJob (Job *job) {
+  
+  int i;
+
+  if (job == NULL) return;
+  
+  FreeJobID (job[0].JobID);
+
+  for (i = 0; i < job[0].argc; i++) {
+    free (job[0].argv[i]);
+  }
+  free (job[0].argv);
+
+  FreeIOBuffer (&job[0].stdout);
+  FreeIOBuffer (&job[0].stderr);
+  free (job);
+  return;
+}
+
+/* delete the job from the job list & adjust ActiveJob counter */
+int DeleteJob (Job *job) {
+
+  int i, Nm;
+
+  Nm = -1;
+  for (i = 0; i < Njobs; i++) {
+    if (job == jobs[i]) {
+      Nm = i;
+      break;
+    }
+  }
+  if (Nm == -1) {
+    fprintf (stderr, "programming error: job not found\n");
+    return (FALSE);
+  }
+
+  FreeJob (jobs[Nm]);
+  for (i = Nm; i < Njobs - 1; i++) {
+    jobs[i] = jobs[i + 1];
+  }
+  Njobs --;
+
+  /* adjust active job number */
+  if (ActiveJob >= Nm) {
+    ActiveJob --;
+  }
+  return (TRUE);
+}
+
+int SubmitJob (Job *job) {
+
+  if (job[0].mode == JOB_LOCAL) {
+    SubmitLocalJob (job);
+  } else {
+    SubmitControllerJob (job);
+  }
+
+  /* reset clock for start and poll-test */
+  gettimeofday (&job[0].start, (void *) NULL);
+  job[0].last = job[0].start;
+  job[0].state = JOB_PENDING;
+  return (TRUE);
+}
+
+int CheckJob (Job *job) {
+
+  /* add checks for timeouts */
+
+  if (job[0].mode == JOB_LOCAL) {
+    CheckLocalJob (job);
+  } else {
+    /* controller jobs are now checked en masse by CheckController */
+    /* CheckControllerJob (job); */
+  }
+  return (job[0].state);
+}
Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/LocalJob.c
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/LocalJob.c	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/LocalJob.c	(revision 21930)
@@ -0,0 +1,185 @@
+# include "psched.h"
+
+/* local jobs are forked in the background 
+   we might need to limit the maximum number of local jobs.
+   should we have a queue/stack of pending local jobs, much
+   like controller? */
+
+/* update current state, drain stdout/stderr buffers */
+int CheckLocalJob (Job *job) {
+
+  int Nread;
+
+  // XXX do something useful with exit status?
+  CheckLocalJobStatus (job);
+
+  if ((job[0].state == JOB_EXIT) || (job[0].state == JOB_CRASH)) {
+    EmptyIOBuffer (&job[0].stdout, 10, job[0].stdout_fd);
+    EmptyIOBuffer (&job[0].stderr, 10, job[0].stderr_fd);
+    close (job[0].stdout_fd);
+    close (job[0].stderr_fd);
+  } else {
+    /* read stdout buffer */
+    Nread = ReadtoIOBuffer (&job[0].stdout, job[0].stdout_fd);
+    switch (Nread) {
+      case -2:  /* error in read (programming error?  system level error?) */
+	fprintf (stderr, "serious IO error\n");
+	exit (2);
+      case -1:  /* no data in pipe */
+      case 0:   /* pipe is closed, change child state? **/
+      default:  /* data in pipe */
+	break;
+    }
+  
+    /* read stderr buffer */
+    Nread = ReadtoIOBuffer (&job[0].stderr, job[0].stderr_fd);
+    switch (Nread) {
+      case -2:  /* error in read (programming error?  system level error?) */
+	fprintf (stderr, "serious IO error\n");
+	exit (2);
+      case -1:  /* no data in pipe */
+      case 0:   /* pipe is closed, change child state? **/
+      default:  /* data in pipe */
+	break;
+    }
+  }
+  return (TRUE);
+}
+
+int CheckLocalJobStatus (Job *job) {
+
+  int result, waitstatus;
+
+  /* check local job status */
+  result = waitpid (job[0].pid, &waitstatus, WNOHANG);
+  switch (result) {
+    case -1:  /* error with waitpid */
+      switch (errno) {
+	case ECHILD:
+	  fprintf (stderr, "unknown PID, not a child proc\n");
+	  fprintf (stderr, "did process already exit?  programming error?\n");
+	  job[0].state = JOB_NONE;
+	  job[0].exit_status = 0;
+	  return (FALSE);
+	case EINVAL:
+	  fprintf (stderr, "error EINVAL (waitpid): programming error\n");
+	  exit (1);
+	case EINTR:
+	  fprintf (stderr, "error EINTR (waitpid): programming error\n");
+	  exit (1);
+	default:
+	  fprintf (stderr, "unknown error for waitpid (%d): programming error\n", errno);
+	  exit (1);
+      }
+      break;
+      
+    case 0:  /* process not exited */
+      job[0].state = JOB_BUSY;
+      job[0].exit_status = 0;
+      return (TRUE);
+
+    default:
+      if (result != job[0].pid) {
+	fprintf (stderr, "waitpid error: mis-matched PID (%d vs %d).  programming error\n", result, job[0].pid);
+	exit (1);
+      }
+      if (WIFEXITED(waitstatus)) {
+	job[0].state = JOB_EXIT;
+	job[0].exit_status = WEXITSTATUS(waitstatus);
+      }
+      if (WIFSIGNALED(waitstatus)) {
+	job[0].state = JOB_CRASH;
+	job[0].exit_status = WTERMSIG(waitstatus);
+      }
+      if (WIFSTOPPED(waitstatus)) {
+	fprintf (stderr, "waitpid returns 'stopped': programming error\n");
+	exit (1);
+      }
+  }
+  return (FALSE);
+}
+
+/* this could be written a just a one-way pipe */
+int SubmitLocalJob (Job *job) {
+
+  int status, pid;
+  int stdout_fd[2], stderr_fd[2];
+
+  bzero (stdout_fd, 2*sizeof(int));
+  bzero (stderr_fd, 2*sizeof(int));
+
+  if (pipe (stdout_fd) < 0) goto pipe_error;
+  if (pipe (stderr_fd) < 0) goto pipe_error;
+
+  pid = fork ();
+  if (!pid) { /* must be child process */
+    if (VerboseMode()) fprintf (stderr, "starting local job\n");
+
+    /* close the other ends of the pipes */
+    close (stdout_fd[0]);
+    close (stderr_fd[0]);
+
+    /* tie our ends of the pipes to stdin, stdout, stderr */
+    dup2 (stdout_fd[1], STDOUT_FILENO);
+    dup2 (stderr_fd[1], STDERR_FILENO);
+
+    /* set all three unblocking */
+    setvbuf (stdout, (char *) NULL, _IONBF, BUFSIZ);
+    setvbuf (stderr, (char *) NULL, _IONBF, BUFSIZ);
+
+    status = execvp (job[0].argv[0], job[0].argv); 
+    exit (1);
+  }
+
+  /* close the other ends of the pipes */
+  close (stdout_fd[1]); stdout_fd[1] = 0;
+  close (stderr_fd[1]); stderr_fd[1] = 0;
+
+  /* make the pipes non-blocking */
+  fcntl (stdout_fd[0], F_SETFL, O_NONBLOCK);
+  fcntl (stderr_fd[0], F_SETFL, O_NONBLOCK);
+
+  job[0].stdout_fd = stdout_fd[0];
+  job[0].stderr_fd = stderr_fd[0];
+  job[0].pid = pid;
+
+  return (TRUE);
+
+pipe_error:
+  perror ("pipe error:");
+  if (stdout_fd[0] != 0) close (stdout_fd[0]);
+  if (stdout_fd[1] != 0) close (stdout_fd[1]);
+  if (stderr_fd[0] != 0) close (stderr_fd[0]);
+  if (stderr_fd[1] != 0) close (stderr_fd[1]);
+  return (FALSE);
+}
+
+/* should this function close the fd's? */
+int KillLocalJob (Job *job) {
+
+  int i, result, waitstatus;
+
+  if (job[0].state != JOB_BUSY) return (TRUE);
+
+  /* send SIGTERM signal to job */
+  kill (job[0].pid, SIGTERM);
+  result = 0;
+  for (i = 0; (i < 10) && (result == 0); i++) {
+    usleep (10000);  /* 10 ms is min */
+    result = waitpid (job[0].pid, &waitstatus, WNOHANG);
+  }
+  if (result) return (TRUE);
+
+  /* send SIGKILL signal to job */
+  kill (job[0].pid, SIGKILL);
+  result = 0;
+  for (i = 0; (i < 10) && (result == 0); i++) {
+    usleep (10000);  /* 10 ms is min */
+    result = waitpid (job[0].pid, &waitstatus, WNOHANG);
+  }
+  if (result) return (TRUE);
+
+  /* total failure, don't reset */
+  return (FALSE);
+}
+
Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/Makefile
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/Makefile	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/Makefile	(revision 21930)
@@ -0,0 +1,80 @@
+include ../../../Configure
+
+HOME    =       $(ROOT)/src/opihi
+BIN     =       $(HOME)/bin
+LIB     =       $(HOME)/lib
+INC     =       $(HOME)/include
+SDIR    =       $(HOME)/psched
+#
+DESTBIN =       $(LBIN)
+DESTLIB =       $(LLIB)
+DESTINC =       $(LINC)
+DESTMAN =       $(LMAN)
+DESTHLP =       $(LHLP)/psched
+#  
+INCS    =       -I$(INC) -I$(LINC) -I$(XINC)
+LFLAGS  =       -L$(LLIB) -L$(LIB)
+LIBS1   =       -lsocket -lnsl -lreadline -ltermcap -ldvo -lFITS -lohana -lm
+LIBS2   =       -lbasiccmd -ldatacmd -lastrocmd -lshell -ldata 
+LIBS    =       $(LIBS2) $(LIBS1) 
+CFLAGS  =       $(INCS) -DHELPDIR_DEFAULT=$(DESTHLP)
+CCFLAGS =       $(LIBS) 
+
+# sched user commands and support functions ########################
+
+funcs = \
+$(SDIR)/init.$(ARCH).o \
+$(SDIR)/psched.$(ARCH).o \
+$(SDIR)/CheckJobs.$(ARCH).o \
+$(SDIR)/CheckSystem.$(ARCH).o \
+$(SDIR)/CheckController.$(ARCH).o \
+$(SDIR)/CheckTasks.$(ARCH).o \
+$(SDIR)/CheckTimeRanges.$(ARCH).o \
+$(SDIR)/ControllerOps.$(ARCH).o \
+$(SDIR)/LocalJob.$(ARCH).o \
+$(SDIR)/JobOps.$(ARCH).o \
+$(SDIR)/JobIDOps.$(ARCH).o \
+$(SDIR)/TaskOps.$(ARCH).o
+
+cmds = \
+$(SDIR)/run.$(ARCH).o \
+$(SDIR)/stop.$(ARCH).o \
+$(SDIR)/pulse.$(ARCH).o \
+$(SDIR)/status.$(ARCH).o \
+$(SDIR)/kill.$(ARCH).o \
+$(SDIR)/delete.$(ARCH).o \
+$(SDIR)/verbose.$(ARCH).o \
+$(SDIR)/controller.$(ARCH).o \
+$(SDIR)/controller_host.$(ARCH).o \
+$(SDIR)/controller_exit.$(ARCH).o \
+$(SDIR)/controller_check.$(ARCH).o \
+$(SDIR)/controller_status.$(ARCH).o \
+$(SDIR)/controller_output.$(ARCH).o \
+$(SDIR)/controller_pulse.$(ARCH).o \
+$(SDIR)/task.$(ARCH).o \
+$(SDIR)/task_host.$(ARCH).o \
+$(SDIR)/task_nmax.$(ARCH).o \
+$(SDIR)/task_macros.$(ARCH).o \
+$(SDIR)/task_trange.$(ARCH).o \
+$(SDIR)/task_periods.$(ARCH).o \
+$(SDIR)/task_command.$(ARCH).o \
+$(SDIR)/version.$(ARCH).o
+
+libs = \
+$(DESTLIB)/libshell.a \
+$(DESTLIB)/libdata.a \
+$(DESTLIB)/libbasiccmd.a \
+$(DESTLIB)/libastrocmd.a \
+$(DESTLIB)/libdatacmd.a
+
+psched: $(BIN)/psched.$(ARCH)
+
+$(BIN)/psched.$(ARCH) : $(funcs) $(cmds) $(libs)
+
+install: $(DESTBIN)/psched help
+
+help: cmd.basic.help cmd.data.help cmd.astro.help psched.help
+
+.PHONY: psched
+
+include ../Makefile.Common
Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/TaskOps.c
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/TaskOps.c	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/TaskOps.c	(revision 21930)
@@ -0,0 +1,378 @@
+# include "psched.h"
+
+static Task **tasks;
+static int    Ntasks;
+static int    NTASKS;
+
+/* counter marking task being visited by the run loop */
+static int   ActiveTask;
+
+/* temporary holder for a new task */
+static Task *NewTask = NULL;
+
+/* set up the task list system */
+void InitTasks () {
+  NTASKS = 20;
+  Ntasks = 0;
+  ALLOCATE (tasks, Task *, NTASKS);
+  ActiveTask = -1;
+}
+
+/* provide a mechanism to loop over the list of tasks */
+Task *NextTask () {
+  
+  Task *task;
+
+  /* move to the next task and return it */
+  ActiveTask ++;
+  if (ActiveTask < 0) ActiveTask = 0;
+  if (ActiveTask >= Ntasks) {
+    ActiveTask = -1;
+    return (NULL);
+  }
+  task = tasks[ActiveTask];
+  return (task);
+}
+
+/* return task with given name */
+Task *FindTask (char *name) {
+
+  int i;
+
+  /* try for an exact match first */
+  for (i = 0; i < Ntasks; i++) {
+    if (!strcmp (tasks[i][0].name, name)) {
+      return (tasks[i]);
+    }
+  }
+  return (NULL);
+}  
+
+/* list known tasks */
+void ListTasks (int verbose) {
+
+  int i, valid;
+
+  fprintf (stderr, "\n");
+  if (Ntasks == 0) {
+    fprintf (stderr, " no defined tasks\n");
+    return;
+  }
+
+  fprintf (stderr, " Task Status\n");
+  fprintf (stderr, "  * Name            Njobs  Command\n");
+  for (i = 0; i < Ntasks; i++) {
+    valid = CheckTimeRanges (tasks[i][0].ranges, tasks[i][0].Nranges);
+    if (verbose) fprintf (stderr, "\n");
+    if (valid) {
+      fprintf (stderr, "  + ");
+    } else {
+      fprintf (stderr, "  - ");
+    }
+    if (tasks[i][0].argv == NULL) {
+      fprintf (stderr, "%-15s %5d  %-20s\n", tasks[i][0].name, tasks[i][0].Njobs, "(dynamic)");
+    } else {
+      fprintf (stderr, "%-15s %5d  %-20s\n", tasks[i][0].name, tasks[i][0].Njobs, tasks[i][0].argv[0]);
+    }
+    if (verbose) {
+      fprintf (stderr, "    spawn period: %f, polling period: %f, timeout period: %f\n", 
+	       tasks[i][0].exec_period, tasks[i][0].poll_period, tasks[i][0].timeout_period);
+      if (tasks[i][0].host == NULL) {
+	fprintf (stderr, "    task runs locally\n");
+	continue;
+      }
+      if (!strcasecmp(tasks[i][0].host, "ANYHOST")) {
+	fprintf (stderr, "    task host selected by controller\n");
+	continue;
+      }
+      if (tasks[i][0].host_required) {
+	fprintf (stderr, "    host %s (required)\n", tasks[i][0].host);
+      } else {
+	fprintf (stderr, "    host %s (desired)\n", tasks[i][0].host);
+      }
+    }
+  }
+  return;
+}
+
+/* show details of a task */
+int ShowTask (char *name) {
+
+  int i;
+  Task *task;
+
+  task = FindTask (name);
+  if (task == NULL) {
+    fprintf (stderr, "task %s not found\n", name);
+    return (FALSE);
+  }
+
+  fprintf (stderr, "\n macro %s\n", task[0].name);
+
+  fprintf (stderr, "\n command: ");
+  for (i = 0; i < task[0].argc; i++) {
+    fprintf (stderr, "%s ", task[0].argv[i]);
+  }
+  fprintf (stderr, "\n\n");
+
+  if (task[0].host == NULL) {
+    fprintf (stderr, " task runs locally\n");
+    goto periods;
+  }
+  if (!strcasecmp(task[0].host, "ANYHOST")) {
+    fprintf (stderr, " task host selected by controller\n");
+    goto periods;
+  }
+  if (task[0].host_required) {
+    fprintf (stderr, " host %s (required)\n", task[0].host);
+  } else {
+    fprintf (stderr, " host %s (desired)\n", task[0].host);
+  }
+
+periods:
+  fprintf (stderr, " time periods: exec: %f  poll: %f  timeout: %f\n", 
+	   task[0].exec_period, task[0].poll_period, task[0].timeout_period);
+
+  fprintf (stderr, "\n pre-execute macro\n");
+  ListMacro (task[0].exec);
+
+  fprintf (stderr, "\n timeout macro\n");
+  ListMacro (task[0].timeout);
+
+  fprintf (stderr, "\n crash macro\n");
+  ListMacro (task[0].crash);
+
+  fprintf (stderr, "\n default exit macro\n");
+  ListMacro (task[0].defexit);
+
+  for (i = 0; i < task[0].Nexit; i++) {
+    fprintf (stderr, "\n exit macro (status == %d)\n", atoi(task[0].exit[i][0].name));
+    ListMacro (task[0].exit[i]);
+  }
+
+  return (TRUE);
+}
+
+/* make a new named task */
+int FreeTask (Task *task) {
+  
+  int i;
+
+  if (task == NULL) return (FALSE);
+  
+  if (task[0].name != NULL) free (task[0].name);
+  if (task[0].host != NULL) free (task[0].host);
+  if (task[0].argv != NULL) {
+    for (i = 0; i < task[0].argc; i++) {
+      free (task[0].argv[i]);
+    }
+    free (task[0].argv);
+  }
+  if (task[0].exec != NULL) {
+    FreeMacro (task[0].exec);
+    free (task[0].exec);
+  }
+  if (task[0].crash != NULL) {
+    FreeMacro (task[0].crash);
+    free (task[0].crash);
+  }
+  if (task[0].timeout != NULL) {
+    FreeMacro (task[0].timeout);
+    free (task[0].timeout);
+  }
+  for (i = 0; i < task[0].Nexit; i++) {
+    if (task[0].exit[i] != NULL) {
+      FreeMacro (task[0].exit[i]);
+    }
+    free (task[0].exit[i]);
+  }
+  free (task[0].exit);
+
+  if (task[0].ranges != NULL) {
+    free (task[0].ranges);
+  }
+  return (TRUE);
+}
+
+/**** new task functions ***/
+
+/* make a new named task */
+Task *CreateTask (char *name) {
+  
+  ALLOCATE (NewTask, Task, 1);
+
+  NewTask[0].name = strcreate (name);;
+
+  NewTask[0].host = NULL;
+  NewTask[0].host_required = FALSE;
+
+  NewTask[0].argc = 0;
+  NewTask[0].argv = NULL;
+
+  NewTask[0].exec = NULL;
+  NewTask[0].crash = NULL;
+  NewTask[0].timeout = NULL;
+  NewTask[0].defexit = NULL;
+
+  NewTask[0].Nexit = 0;
+  NewTask[0].NEXIT = 10;
+  ALLOCATE (NewTask[0].exit, Macro *, NewTask[0].NEXIT);
+  /* don't free tasks[0].exit, keep at least 1 allocated */
+
+  NewTask[0].exec_period = 1.0;
+  NewTask[0].poll_period = 1.0;
+  NewTask[0].timeout_period = 1.0;
+
+  NewTask[0].Nranges = 0;
+  ALLOCATE (NewTask[0].ranges, TimeRange, 1);
+
+  /* init task timer (is reset by 'run') */  
+  gettimeofday (&NewTask[0].last, (void *) NULL);
+  NewTask[0].Njobs = 0;
+  NewTask[0].Nmax = 0;  /* default value means 'no limit' */
+
+  return (NewTask);
+}
+
+/* remove the task from the task list */
+int RemoveTask (Task *task) {
+  
+  int i, Nt;
+
+  /* find task in task list */
+  Nt = -1;
+  for (i = 0; i < Ntasks; i++) {
+    if (task == tasks[i]) {
+      Nt = i;
+      break;
+    }
+  }
+  if (Nt == -1) {
+    fprintf (stderr, "programming error: task not found\n");
+    return (FALSE);
+  }
+  for (i = Nt; i < Ntasks - 1; i++) {
+    tasks[i] = tasks[i+1];
+  }
+  Ntasks --;
+  return (TRUE);
+}
+
+int ValidateTask (Task *task, int RequireStatic) {
+
+  int i, hash;
+
+  /* is a static command defined? */
+  if (task[0].argc != 0) {
+    if (task[0].argv == NULL) {
+      fprintf (stderr, "task command arguments not defined (programming error)\n");
+      return (FALSE);
+    }
+    return (TRUE);
+  }
+  if (RequireStatic) {
+    fprintf (stderr, "task command not defined\n");
+    return (FALSE);
+  }
+
+  /* no static command; dynamic command? */
+  if (task[0].exec != NULL) {
+    for (i = 0; i < task[0].exec[0].Nlines; i++) {
+      hash = TaskHash (task[0].exec[0].line[i]);
+      if (hash == TASK_COMMAND) return (TRUE);
+    }
+  }
+  fprintf (stderr, "task command not defined\n");
+  return (FALSE);
+}
+
+int RegisterNewTask () {
+  
+  int N;
+
+  N = Ntasks;
+  Ntasks ++;
+  if (Ntasks == NTASKS) {
+    NTASKS += 20;
+    REALLOCATE (tasks, Task *, NTASKS);
+  }
+  tasks[N] = NewTask;
+  NewTask = NULL;
+  return (TRUE);
+}
+
+int DeleteNewTask () {
+  if (NewTask != NULL) {
+    FreeTask (NewTask);
+    free (NewTask);
+    NewTask = NULL;
+  }
+  return (TRUE);
+}
+
+Task *SetNewTask (Task *task) {
+  NewTask = task;
+  return (task);
+}
+
+Task *GetNewTask () {
+  return (NewTask);
+}
+
+Task *GetActiveTask () {
+  Task *task;
+  if (ActiveTask < 0) return (NULL);
+  task = tasks[ActiveTask];
+  return (task);
+}
+
+int TaskHash (char *input) {
+  
+  int hash;
+  char *command;
+
+  hash = TASK_NONE;
+
+  command = thisword (input);
+  if (command == NULL) return (TASK_EMPTY);
+
+  if (command[0] == '#')                  hash = TASK_COMMENT;
+  if (!strcasecmp (command, "END"))       hash = TASK_END;
+  if (!strcasecmp (command, "HOST"))      hash = TASK_HOST;
+  if (!strcasecmp (command, "NMAX"))      hash = TASK_NMAX;
+  if (!strcasecmp (command, "TRANGE"))    hash = TASK_TRANGE;
+  if (!strcasecmp (command, "COMMAND"))   hash = TASK_COMMAND;
+  if (!strcasecmp (command, "PERIODS"))   hash = TASK_PERIODS;
+  if (!strcasecmp (command, "TASK.EXIT")) hash = TASK_EXIT;
+  if (!strcasecmp (command, "TASK.EXEC")) hash = TASK_EXEC;
+
+  free (command);
+  return (hash);
+}
+
+/*** task timer functions ***/
+
+double GetTaskTimer (struct timeval start) {
+
+  double dtime;
+  struct timeval now;
+  
+  gettimeofday (&now, (void *) NULL);
+  dtime = DTIME (now, start);
+  
+  return (dtime);
+}
+
+void SetTaskTimer (struct timeval *timer) {
+  gettimeofday (timer, (void *) NULL);
+}
+
+/* start the clock for all tasks */
+void InitTaskTimers () {
+
+  Task *task;
+
+  while ((task = NextTask ()) != NULL) {
+    gettimeofday (&task[0].last, (void *) NULL);
+ }
+}
Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/controller.c
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/controller.c	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/controller.c	(revision 21930)
@@ -0,0 +1,51 @@
+# include "psched.h"
+
+int controller_host    PROTO((int, char **));
+int controller_exit    PROTO((int, char **));
+int controller_status  PROTO((int, char **));
+int controller_check   PROTO((int, char **));
+int controller_output  PROTO((int, char **));
+int controller_pulse   PROTO((int, char **));
+
+static Command controller_cmds[] = {
+  {"exit",   controller_exit,   "shutdown controller"},
+  {"host",   controller_host,   "define host for controller"},
+  {"check",  controller_check,  "check controller host/job"},
+  {"status", controller_status, "check controller status"},
+  {"output", controller_output, "print controller output"},
+  {"pulse",  controller_pulse,  "set controller pulse"},
+};
+
+int controller (int argc, char **argv) {
+
+  int status;
+  CommandF *func;
+
+  if (argc < 2) {
+    fprintf (stderr, "USAGE: controller (command) ... \n");
+    return (FALSE);
+  }
+
+  func = FindControllerCommand (argv[1]);
+  if (func == NULL) {
+    fprintf (stderr, "invalid controller command\n");
+    return (FALSE);
+  }
+
+  status = (*func)(argc - 1, argv + 1);
+  return (status);
+}
+
+CommandF *FindControllerCommand (char *cmd) {
+
+  int i, N;
+
+  N = sizeof (controller_cmds) / sizeof (Command);
+
+  for (i = 0; i < N; i++) {
+    if (!strcmp (controller_cmds[i].name, cmd)) {
+      return (controller_cmds[i].func);
+    }
+  }
+  return (NULL);
+}
Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/controller_check.c
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/controller_check.c	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/controller_check.c	(revision 21930)
@@ -0,0 +1,35 @@
+# include "psched.h"
+
+int controller_check (int argc, char **argv) {
+
+  int status;
+  char command[1024];
+  IOBuffer buffer;
+
+  if (argc != 3) goto usage;
+  if (strcasecmp (argv[1], "JOB") && 
+      strcasecmp (argv[1], "HOST")) goto usage;
+
+  /* check if controller is running */
+  status = CheckControllerStatus ();
+  if (!status) {
+    fprintf (stderr, "controller is not running\n");
+    return (TRUE);
+  }
+
+  sprintf (command, "check %s %s", argv[1], argv[2]);
+  InitIOBuffer (&buffer, 0x100);
+  status = ControllerCommand (command, CONTROLLER_PROMPT, &buffer);
+  if (VerboseMode()) {
+    fprintf (stderr, "controller command sent\n");  
+    fwrite (buffer.buffer, 1, buffer.Nbuffer, stderr);
+    fprintf (stderr, "\n Nbytes received: %d\n", buffer.Nbuffer);  
+  }
+  FreeIOBuffer (&buffer);
+  return (TRUE);
+
+usage:
+  fprintf (stderr, "USAGE: controller check job (jobID)\n");
+  fprintf (stderr, "USAGE: controller check host (hostID)\n");
+  return (FALSE);
+}
Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/controller_exit.c
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/controller_exit.c	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/controller_exit.c	(revision 21930)
@@ -0,0 +1,14 @@
+# include "psched.h"
+
+int controller_exit (int argc, char **argv) {
+
+  if (argc != 2) {
+    fprintf (stderr, "USAGE: controller exit TRUE\n");
+    return (FALSE);
+  }
+
+  if (strcasecmp (argv[1], "TRUE")) return (FALSE);
+
+  QuitController ();
+  return (TRUE);
+}
Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/controller_host.c
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/controller_host.c	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/controller_host.c	(revision 21930)
@@ -0,0 +1,29 @@
+# include "psched.h"
+
+int controller_host (int argc, char **argv) {
+
+  int status;
+  char command[1024];
+  IOBuffer buffer;
+
+  if (argc != 3) {
+    fprintf (stderr, "USAGE: controller host (command) (hostname)\n");
+    return (FALSE);
+  }
+
+  /* start controller connection (if needed) */
+  StartController ();
+
+  sprintf (command, "host %s %s", argv[1], argv[2]);
+  InitIOBuffer (&buffer, 0x100);
+  status = ControllerCommand (command, CONTROLLER_PROMPT, &buffer);
+  if (status) fwrite (buffer.buffer, 1, buffer.Nbuffer, stderr);
+  FreeIOBuffer (&buffer);
+  return (TRUE);
+}
+
+/* should I keep an internal host table so I can reload the 
+   hosts if the controller exits?
+
+   alternatively, that could be a user-level choice
+*/
Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/controller_output.c
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/controller_output.c	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/controller_output.c	(revision 21930)
@@ -0,0 +1,13 @@
+# include "psched.h"
+
+int controller_output (int argc, char **argv) {
+
+  if (argc != 1) {
+    fprintf (stderr, "USAGE: controller status\n");
+    return (FALSE);
+  }
+
+  CheckControllerOutput ();
+  PrintControllerOutput ();
+  return (TRUE);
+}
Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/controller_pulse.c
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/controller_pulse.c	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/controller_pulse.c	(revision 21930)
@@ -0,0 +1,23 @@
+# include "psched.h"
+
+int controller_pulse (int argc, char **argv) {
+
+  int status;
+  char command[1024];
+  IOBuffer buffer;
+
+  if (argc != 2) {
+    fprintf (stderr, "USAGE: controller pulse (usec)\n");
+    return (FALSE);
+  }
+
+  /* start controller connection (if needed) */
+  StartController ();
+
+  sprintf (command, "pulse %d", atoi(argv[1]));
+  InitIOBuffer (&buffer, 0x100);
+  status = ControllerCommand (command, CONTROLLER_PROMPT, &buffer);
+  if (status) fwrite (buffer.buffer, 1, buffer.Nbuffer, stderr);
+  FreeIOBuffer (&buffer);
+  return (TRUE);
+}
Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/controller_status.c
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/controller_status.c	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/controller_status.c	(revision 21930)
@@ -0,0 +1,31 @@
+# include "psched.h"
+
+int controller_status (int argc, char **argv) {
+
+  int status;
+  char command[1024];
+  IOBuffer buffer;
+
+  if (argc != 1) {
+    fprintf (stderr, "USAGE: controller status\n");
+    return (FALSE);
+  }
+
+  /* check if controller is running */
+  status = CheckControllerStatus ();
+  if (!status) {
+    fprintf (stderr, "controller is not running\n");
+    return (TRUE);
+  }
+
+  sprintf (command, "status");
+  InitIOBuffer (&buffer, 0x100);
+  status = ControllerCommand (command, CONTROLLER_PROMPT, &buffer);
+  if (status) {
+    fwrite (buffer.buffer, 1, buffer.Nbuffer, stderr);
+  } else {
+    fprintf (stderr, "controller is down\n");
+  }
+  FreeIOBuffer (&buffer);
+  return (TRUE);
+}
Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/delete.c
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/delete.c	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/delete.c	(revision 21930)
@@ -0,0 +1,18 @@
+# include "psched.h"
+
+int delete_job (int argc, char **argv) {
+
+  int JobID;
+
+  if (argc < 2) {
+    fprintf (stderr, "USAGE: delete (JobID)\n");
+    return (FALSE);
+  }
+  JobID = atoi (argv[1]);
+  /* use a string interp to convert JobIDs to ints ? */
+
+  fprintf (stderr, "this function is not yet implemented\n");
+  return (TRUE);
+}
+
+/* find job from jobID, kill and delete */
Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/help/controller
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/help/controller	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/help/controller	(revision 21930)
@@ -0,0 +1,29 @@
+
+   controller (command) ...
+
+   Interact with the parallel controller 'pcontrol'.  This command
+   groups together several controller sub-commands, listed below.
+
+
+   controller host (hostname)
+
+   Define a new machine available to the parallel controller.  
+
+
+   controller check (host) (HostID)
+   controller check (job) (JobID)
+
+   Check on the status of a specific host or job known to the parallel
+   controller.
+
+
+   controller status
+
+   Query the parallel controller for its current status
+
+
+   controller output
+
+   Dump the contents of the parallel controller message buffers.
+   These are the messages from the controller itself, rather than the
+   messages generated by jobs run by the controller.
Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/help/task
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/help/task	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/help/task	(revision 21930)
@@ -0,0 +1,27 @@
+
+   task (name)
+
+   Define or modify a task.  A task is defined by issuing a series of
+   task commands, listed below (see their individual help pages).  A
+   task describes the rules for constructing jobs.  At a minimum, a
+   task must include a command, defining the UNIX command to run for
+   each job.  Other task commands define optional concepts for the
+   task.  These include: 
+
+   - a macro to be run before the job is constructed (task.exec).
+   - a set of macros to be run when the job is completed (task.exit)
+   - time ranges in which the task should be run (or not) (trange) 
+   - required / desired parallel controller hosts (host)
+   - job contruction time interval (periods -exec)
+   - job polling time interval (periods -poll)
+   - job timeouts (periods -timeout)
+   - maximum number of jobs to construct
+
+   See also:
+   task.command
+   task.exit
+   task.exec
+   task.trange
+   task.nmax
+   task.host
+   task.periods
Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/help/task.command
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/help/task.command	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/help/task.command	(revision 21930)
@@ -0,0 +1,15 @@
+
+   command (arg0) (arg1) ...
+
+   Define the rule for constructing a job command for a task.  The
+   arguments are UNIX command and the collection of command arguments.
+   The arguments may include references to Opihi variables.  The task
+   command may be included at the top level of the task, or it may be
+   defined within the task exec macro.  In the latter case, the
+   command is constructed only when the exec macro is run, every job
+   construction interval.  Thus, the command arguments may depend on
+   the result of the exec macro operation.   
+
+   See also:
+   task
+   task.exec
Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/help/task.exec
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/help/task.exec	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/help/task.exec	(revision 21930)
@@ -0,0 +1,17 @@
+
+   task.exec
+
+   A special macro to be executed before constructing the job command.
+   The macro is defined by commands following the 'task.exec' command,
+   ending with the word 'end'.  If the exit status of the task.exec
+   macro is FALSE, then the job is NOT constructed.  This allows the
+   construction of a job to be conditional on other, external
+   quantites.  (Note: the exit status of the macro is false if 'break'
+   is called).  The task command may be defined within the task.exec
+   macro, in which case the command is constructed only when the exec
+   macro is run, every job construction interval.  Thus, the command
+   arguments may depend on the result of the exec macro operation.
+
+   See also:
+   task
+   task.command
Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/init.c
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/init.c	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/init.c	(revision 21930)
@@ -0,0 +1,65 @@
+# include "psched.h"
+
+int controller      PROTO((int, char **));
+int task	    PROTO((int, char **));
+int task_host	    PROTO((int, char **));
+int task_nmax	    PROTO((int, char **));
+int task_trange	    PROTO((int, char **));
+int task_macros	    PROTO((int, char **));
+int task_command    PROTO((int, char **));
+int task_periods    PROTO((int, char **));
+int run             PROTO((int, char **));
+int stop            PROTO((int, char **));
+int pulse           PROTO((int, char **));
+int status_sys      PROTO((int, char **));
+int kill_job        PROTO((int, char **));
+int delete_job      PROTO((int, char **));
+int verbose         PROTO((int, char **));
+int version         PROTO((int, char **));
+
+static Command cmds[] = {  
+  {"controller", controller,   "controller commands"},
+  {"task",       task,         "define a schedulable task"},
+  {"host",       task_host,    "define host machine for a task"},
+  {"nmax",       task_nmax,    "define maximum number of jobs for a task"},
+  {"trange",     task_trange,  "define valid/invalid time periods for a task"},
+  {"task.exit",  task_macros,  "define exit macros for a task"},
+  {"task.exec",  task_macros,  "define pre-exec macro for a task"},
+  {"command",    task_command, "define executed command for a task"},
+  {"periods",    task_periods, "define time scales for a task"},
+  {"run",        run,          "run the scheduler"},
+  {"stop",       stop,         "stop the scheduler"},
+  {"pulse",      pulse,        "set the scheduler update period"},
+  {"status",     status_sys,   "get system status"},
+  {"kill",       kill_job,     "kill job"},
+  {"delete",     delete_job,   "delete job"},
+  {"version",     version,      "show version information"},
+  {"verbose",    verbose,      "set/toggle verbose mode"},
+}; 
+
+/* these are functions which duplicate actions on the 
+   controller.  these could simply pass the command along 
+   to the controller (and should then be in 'controller'),
+   or they could add / modify the jobs in the scheduler
+   job stack 
+
+  {"job",        job,          "add job"},
+  {"kill",       kill_sh,      "kill job"},
+  {"delete",     delete,       "delete job"},
+  {"stdout",     stdout_sh,    "get stdout buffer for job"},
+  {"stderr",     stderr_sh,    "get stderr buffer for job"},
+*/
+
+void InitPsched () {
+  
+  int i;
+
+  InitTasks ();
+  InitJobs ();
+  InitJobIDs ();
+
+  for (i = 0; i < sizeof (cmds) / sizeof (Command); i++) {
+    AddCommand (&cmds[i]);
+  }
+
+}
Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/job.c
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/job.c	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/job.c	(revision 21930)
@@ -0,0 +1,68 @@
+# include "psched.h"
+
+/* this will require a bit of care: to define a job, we need
+   to specify:
+   - the command 
+     this is defined in the job command: job argv0 argv1 argv2...
+   - timeout (an option to the command)
+   - exit macros?
+     do nothing by default?
+   - stderr / stdout disposal?
+     save in a specific buffer by default?
+     send to a file?
+
+   the command can look just like the controller equivalent one:
+   job [-host host] [-timeout timeout] args...
+
+*/
+
+int job (int argc, char **argv) {
+
+  char *Host, **targv;
+  int i, N, Mode, targc, Timeout;
+  IDtype JobID;
+
+  Host = NULL;
+  Mode = PCONTROL_JOB_ANYHOST;
+  if ((N = get_argument (argc, argv, "-host"))) {
+    remove_argument (N, &argc, argv);
+    Host = strcreate (argv[N]);
+    remove_argument (N, &argc, argv);
+    Mode = PCONTROL_JOB_WANTHOST;
+  }
+  if ((N = get_argument (argc, argv, "+host"))) {
+    if (Mode == PCONTROL_JOB_WANTHOST) {
+      fprintf (stderr, "ERROR: -host and +host are incompatible\n");
+      FREE (Host);
+      return (FALSE);
+    }
+    remove_argument (N, &argc, argv);
+    Host = strcreate (argv[N]);
+    remove_argument (N, &argc, argv);
+    Mode = PCONTROL_JOB_NEEDHOST;
+  }
+  if (Host == NULL) Host = strcreate ("anyhost");
+ 
+  Timeout = 100;
+  if ((N = get_argument (argc, argv, "-timeout"))) {
+    remove_argument (N, &argc, argv);
+    Timeout = atoi (argv[N]);
+    remove_argument (N, &argc, argv);
+  }
+
+  if (argc < 2) {
+    fprintf (stderr, "USAGE: job [options] (arg0) (arg1) ... (argN)\n");
+    FREE (Host);
+    return (FALSE);
+  }
+  
+  targc = argc - 1;
+  ALLOCATE (targv, char *, targc);
+  for (i = 1; i < argc; i++) {
+    targv[i-1] = strcreate (argv[i]);
+  }
+
+  JobID = AddJob (Host, Mode, Timeout, targc, targv);
+  fprintf (stdout, "JobID: %d\n", JobID);
+  return (TRUE);
+}
Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/kill.c
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/kill.c	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/kill.c	(revision 21930)
@@ -0,0 +1,32 @@
+# include "psched.h"
+
+int kill_job (int argc, char **argv) {
+
+  Job *job;
+  int JobID;
+
+  if (argc < 2) {
+    fprintf (stderr, "USAGE: kill (JobID)\n");
+    return (FALSE);
+  }
+  JobID = atoi (argv[1]);
+
+  job = FindJob (JobID);
+
+  if (job[0].mode == JOB_LOCAL) {
+    if (!KillLocalJob (job)) {
+      job[0].state = JOB_HUNG;
+      if (VerboseMode()) fprintf (stderr, "child process %d is hung, cannot kill\n", job[0].pid);
+      return (FALSE);
+    }
+  } else {
+    if (!KillControllerJob (job)) {
+      job[0].state = JOB_HUNG;
+      if (VerboseMode()) fprintf (stderr, "child process %d is hung, cannot kill\n", job[0].pid);
+      return (FALSE);
+    }
+  }    
+    
+  fprintf (stderr, "this function is not yet implemented\n");
+  return (TRUE);
+}
Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/psched.c
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/psched.c	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/psched.c	(revision 21930)
@@ -0,0 +1,66 @@
+# include "psched.h"
+
+# define opihi_name "psched"
+# define opihi_prompt "psched: "
+# define opihi_description "parallel task scheduler\n"
+# define opihi_history ".psched"
+# define opihi_rcfile ".pschedrc"
+
+/* program-dependent initialization */
+void initialize (int argc, char **argv) {
+  
+  auto_break = TRUE;
+
+  /* load the commands used by this implementation */
+  InitBasic ();
+  InitData ();
+  InitPsched ();
+
+  rl_readline_name = opihi_name;
+  rl_attempted_completion_function = command_completer;
+  rl_event_hook = NULL;
+  rl_set_keyboard_input_timeout (100000); 
+
+  set_str_variable ("HISTORY", opihi_history);
+  set_str_variable ("PROMPT", opihi_prompt);
+  set_str_variable ("RCFILE", opihi_rcfile);
+# ifdef HELPDIR_DEFAULT
+  set_str_variable ("HELPDIR", MACRO_NAME(HELPDIR_DEFAULT));
+# endif
+
+  { /* check history file permission */
+    FILE *f;
+    f = fopen (opihi_history, "a");
+    if (f == NULL) /* no current history file here */
+      fprintf (stderr, "can't save history.\n");
+    else
+      fclose (f);
+    stifle_history (200);
+    read_history (opihi_history);
+  }
+
+  signal (SIGINT, SIG_IGN);
+  signal (SIGPIPE, gotsignal);
+  signal (SIGTSTP, gotsignal);
+  signal (SIGTTIN, gotsignal);
+  return;
+}
+
+/* standard welcome message */
+void welcome () {
+  fprintf (stderr, "\n");
+  fprintf (stderr, "Welcome to %s - %s\n\n", opihi_name, opihi_description);
+}
+
+/* add program-dependent exit functions here */
+void cleanup () {
+  QuitImage ();
+  QuitGraph ();
+  QuitController ();
+  return;
+}
+
+void gotsignal (int signum) {
+  fprintf (stderr, "got signal : %d\n", signum);
+  return;
+}
Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/pulse.c
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/pulse.c	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/pulse.c	(revision 21930)
@@ -0,0 +1,16 @@
+# include "psched.h"
+
+int pulse (int argc, char **argv) {
+
+  int Nusec;
+
+  if (argc != 2) {
+    fprintf (stderr, "USAGE: pulse (microseconds)\n");
+    return (FALSE);
+  }
+
+  Nusec = atoi (argv[1]);
+  rl_set_keyboard_input_timeout (Nusec); 
+
+  return (TRUE);
+}
Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/run.c
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/run.c	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/run.c	(revision 21930)
@@ -0,0 +1,14 @@
+# include "psched.h"
+
+int run (int argc, char **argv) {
+
+  if (argc != 1) {
+    fprintf (stderr, "USAGE: run\n");
+    return (FALSE);
+  }
+
+  InitTaskTimers ();
+  rl_event_hook = CheckSystem;
+
+  return (TRUE);
+}
Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/scheduler.c
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/scheduler.c	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/scheduler.c	(revision 21930)
@@ -0,0 +1,77 @@
+# include "scheduler.h"
+
+# define opihi_name "scheduler"
+# define opihi_prompt "sched: "
+# define opihi_description "parallel task scheduler\n"
+# define opihi_history ".sched"
+# define opihi_rcfile ".schedrc"
+
+/* program-dependent initialization */
+void initialize (int argc, char **argv) {
+  
+  auto_break = TRUE;
+
+  Nlists = 0;
+  ALLOCATE (lists, List, 1); 
+
+  /* init functions required by libraries */
+  /* -libopihi */
+  InitCommands ();
+  InitMacros ();
+  InitBuffers ();
+  InitVectors ();
+  InitVariables ();
+
+  /* -libdisplay */
+  InitGraph ();
+  InitImage ();
+
+  /* load the commands used by this implementation */
+  InitBasic ();
+  InitData ();
+  InitOutfile ();
+  InitSched ();
+  InitTasks ();
+  InitJobs ();
+  InitJobIDs ();
+
+  rl_readline_name = opihi_name;
+  rl_attempted_completion_function = command_completer;
+  rl_event_hook = NULL;
+  rl_set_keyboard_input_timeout (1000000); 
+
+  set_str_variable ("HISTORY", opihi_history);
+  set_str_variable ("PROMPT", opihi_prompt);
+  set_str_variable ("RCFILE", opihi_rcfile);
+# ifdef HELPDIR_DEFAULT
+  set_str_variable ("HELPDIR", MACRO_NAME(HELPDIR_DEFAULT));
+# endif
+
+  { /* check history file permission */
+    FILE *f;
+    f = fopen (opihi_history, "a");
+    if (f == NULL) /* no current history file here */
+      fprintf (stderr, "can't save history.\n");
+    else
+      fclose (f);
+    stifle_history (200);
+    read_history (opihi_history);
+  }
+
+  signal (SIGINT, SIG_IGN);
+  return;
+}
+
+/* standard welcome message */
+void welcome () {
+  fprintf (stderr, "\n");
+  fprintf (stderr, "Welcome to %s - %s\n\n", opihi_name, opihi_description);
+}
+
+/* add program-dependent exit functions here */
+void cleanup () {
+  QuitImage ();
+  QuitGraph ();
+  QuitController ();
+  return;
+}
Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/status.c
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/status.c	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/status.c	(revision 21930)
@@ -0,0 +1,20 @@
+# include "psched.h"
+
+int status_sys (int argc, char **argv) {
+
+  fprintf (stderr, "\n");
+  if (rl_event_hook == NULL) {
+    fprintf (stderr, " Scheduler is stopped\n");
+  } else {
+    fprintf (stderr, " Scheduler is running\n");
+  }
+  if (CheckControllerStatus ()) {
+    fprintf (stderr, " Controller is running\n");
+  } else {
+    fprintf (stderr, " Controller is stopped\n");
+  }
+  ListTasks (FALSE);
+  ListJobs ();
+  return (TRUE);
+}
+
Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/stop.c
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/stop.c	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/stop.c	(revision 21930)
@@ -0,0 +1,13 @@
+# include "psched.h"
+
+int stop (int argc, char **argv) {
+
+  if (argc != 1) {
+    fprintf (stderr, "USAGE: stop\n");
+    return (FALSE);
+  }
+
+  rl_event_hook = NULL;
+
+  return (TRUE);
+}
Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/task.c
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/task.c	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/task.c	(revision 21930)
@@ -0,0 +1,111 @@
+# include "psched.h"
+# define prompt "> "
+
+int task (int argc, char **argv) {
+
+  int N, hash;
+  int ThisList, status;
+  char *input, *outline;
+  Task *task;
+
+  if ((N = get_argument (argc, argv, "-list"))) {
+    remove_argument (N, &argc, argv);
+    ListTasks (FALSE);
+    return (TRUE);
+  }
+
+  if ((N = get_argument (argc, argv, "-longlist"))) {
+    remove_argument (N, &argc, argv);
+    ListTasks (TRUE);
+    return (TRUE);
+  }
+
+  if ((N = get_argument (argc, argv, "-show"))) {
+    remove_argument (N, &argc, argv);
+    ShowTask (argv[N]);
+    return (TRUE);
+  }
+
+  if (argc != 2) {
+    fprintf (stderr, "USAGE: task <name>\n");
+    fprintf (stderr, "  (enter commands & task functions; end with the word 'END')\n");
+    return (FALSE);
+  }
+
+  task = FindTask (argv[1]);
+  if (task == NULL) { /**** new task ****/
+    task = CreateTask (argv[1]);
+  } else {
+    RemoveTask (task);
+    SetNewTask (task);
+  }
+  /* While a task is being defined, it is removed from the task list.  The new task is added to the task list
+     when the definition process is complete.  
+     XXX If an outstanding job has a task deleted, it will not be able to complete... */
+
+  /* read in task from appropriate source (keyboard or list) until end */
+
+  /* allowed tokens: command, host, stderr, periods, trange, nmax, task.exit, task.exec, end */
+
+  ThisList = Nlists;
+  while (1) {
+
+    /* get the next line (from correct place) */
+    if (ThisList == 0) 
+      input = readline (prompt);
+    else 
+      input = get_next_listentry (ThisList);
+
+    if ((ThisList == 0) && (input == (char *) NULL)) {
+      fprintf (stderr, "end task with 'END'\n");
+      continue;
+    }
+    if ((ThisList > 0) && (input == (char *) NULL)) {
+      fprintf (stderr, "missing 'END' in task definition\n");
+      input = strcreate ("end");
+    }
+    if (ThisList == 0) ohana_memregister (input);
+
+    stripwhite (input);
+    hash = TaskHash (input);
+    switch (hash) {
+
+      case TASK_EMPTY:
+      case TASK_COMMENT:
+	free (input);
+	break;
+
+      case TASK_END:
+	/* I need to add in a test here to see if all task elements 
+	   have been defined.  delete the task if not */
+	free (input);
+	/* validate the new task: all mandatory elements defined? */ 
+	if (!ValidateTask (task, FALSE)) {
+	  DeleteNewTask ();
+	  return (FALSE);
+	}
+	RegisterNewTask ();
+	return (TRUE);
+	break;
+
+      case TASK_TRANGE:
+      case TASK_NMAX:
+      case TASK_HOST:
+      case TASK_EXIT:
+      case TASK_EXEC:
+      case TASK_COMMAND:
+      case TASK_PERIODS:
+	status = command (input, &outline);
+	if (outline != NULL) free (outline);
+	/* what to do if command is invalid?
+	   if (!status) return (FALSE); */
+	break;
+
+      case TASK_NONE:
+	fprintf (stderr, "unknown task command %s\n", input);
+	break;
+    }
+  }
+  /* cannot ever reach here */
+  return (FALSE);
+}
Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/task_command.c
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/task_command.c	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/task_command.c	(revision 21930)
@@ -0,0 +1,38 @@
+# include "psched.h"
+
+int task_command (int argc, char **argv) {
+
+  int i;
+  Task *task;
+
+  if (argc < 2) {
+    fprintf (stderr, "USAGE: command <command> <arg>. ..\n");
+    fprintf (stderr, "  (define command for this task)\n");
+    return (FALSE);
+  }
+
+  task = GetNewTask ();
+  if (task == NULL) {
+    task = GetActiveTask ();
+    if (task == NULL) {
+      fprintf (stderr, "ERROR: not defining or running a task\n");
+      return (FALSE);
+    }
+  }
+
+  /* free existing memory used by argv */
+  if (task[0].argc != 0) {
+    for (i = 0; i < task[0].argc; i++) {
+      if (task[0].argv[i] != NULL) free (task[0].argv[i]);
+    }
+    free (task[0].argv);
+  }
+
+  /* create new memory for argv */
+  task[0].argc = argc - 1;
+  ALLOCATE (task[0].argv, char *, MAX (task[0].argc, 1));
+  for (i = 0; i < task[0].argc; i++) {
+    task[0].argv[i] = strcreate (argv[i+1]);
+  }
+  return (TRUE);
+}
Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/task_host.c
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/task_host.c	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/task_host.c	(revision 21930)
@@ -0,0 +1,42 @@
+# include "psched.h"
+
+int task_host (int argc, char **argv) {
+
+  int N, RequiredHost;
+  Task *task;
+
+  RequiredHost = FALSE;
+  if ((N = get_argument (argc, argv, "-required"))) {
+    remove_argument (N, &argc, argv);
+    RequiredHost = TRUE;
+  }
+
+  if (argc != 2) {
+    fprintf (stderr, "USAGE: host <name> [-required]\n");
+    fprintf (stderr, "  define host machine for this task\n");
+    fprintf (stderr, "  -required flags indicates controller must use this host\n");
+    fprintf (stderr, "  value of 'local' for host indicates process not using controller\n");
+    fprintf (stderr, "  value of 'anyhost' for host indicates controller may assign at will\n");
+    return (FALSE);
+  }
+
+  task = GetNewTask ();
+  if (task == NULL) {
+    task = GetActiveTask ();
+    if (task == NULL) {
+      fprintf (stderr, "ERROR: not defining or running a task\n");
+      return (FALSE);
+    }
+  }
+  task[0].host_required = RequiredHost;
+
+  if (task[0].host != NULL) free (task[0].host);
+  task[0].host = NULL;
+
+  if (!strcasecmp (argv[1], "LOCAL")) return (TRUE);
+
+  task[0].host = strcreate (argv[1]);
+  return (TRUE);
+}
+
+/* apparently, local is the default! */
Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/task_macros.c
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/task_macros.c	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/task_macros.c	(revision 21930)
@@ -0,0 +1,151 @@
+# include "psched.h"
+# define D_NLINES 100
+# define prompt "> "
+
+int task_macros (int argc, char **argv) {
+
+  int i, N, NLINES, done, depth, ThisList;
+  char *input;
+  Macro *macro;
+  Task *task;
+
+  if (!strcmp (argv[0], "task.exec") && (argc != 1)) {
+    fprintf (stderr, "USAGE: task.exec\n");
+    fprintf (stderr, "  (define pre-exec macro for this task)\n");
+    return (FALSE);
+  }
+
+  if (!strcmp (argv[0], "task.exit") && (argc != 2)) {
+    fprintf (stderr, "USAGE: task.exit (state)\n");
+    fprintf (stderr, "  (define exit state macro for this task)\n");
+    return (FALSE);
+  }
+
+  task = GetNewTask ();
+  if (task == NULL) {
+    fprintf (stderr, "ERROR: not defining or running a task\n");
+    return (FALSE);
+  }
+
+  macro = NULL;
+
+  /*** identify which Macro in Task this particular macro goes with ***/ 
+  if (!strcmp (argv[0], "task.exec")) {
+    if (task[0].exec != NULL) {
+      FreeMacro (task[0].exec);
+      free (task[0].exec);
+    }
+    ALLOCATE (task[0].exec, Macro, 1);
+    macro = task[0].exec;
+    macro[0].name = strcreate ("exec");
+    goto found;
+  }
+  if (!strcmp (argv[0], "task.exit") && !strcmp (argv[1], "timeout")) {
+    if (task[0].timeout != NULL) {
+      FreeMacro (task[0].timeout);
+      free (task[0].timeout);
+    }
+    ALLOCATE (task[0].timeout, Macro, 1);
+    macro = task[0].timeout;
+    macro[0].name = strcreate ("timeout");
+    goto found;
+  }
+  if (!strcmp (argv[0], "task.exit") && !strcmp (argv[1], "crash")) {
+    if (task[0].crash != NULL) {
+      FreeMacro (task[0].crash);
+      free (task[0].crash);
+    }
+    ALLOCATE (task[0].crash, Macro, 1);
+    macro = task[0].crash;
+    macro[0].name = strcreate ("crash");
+    goto found;
+  }
+  if (!strcmp (argv[0], "task.exit") && !strcmp (argv[1], "default")) {
+    if (task[0].defexit != NULL) {
+      FreeMacro (task[0].defexit);
+      free (task[0].defexit);
+    }
+    ALLOCATE (task[0].defexit, Macro, 1);
+    macro = task[0].defexit;
+    macro[0].name = strcreate ("default");
+    goto found;
+  }
+  if (!strcmp (argv[0], "task.exit")) {	/* generic exit function */
+    for (i = 0; i < task[0].Nexit; i++) { /* find exiting exit function with same name */
+      if (!strcmp (task[0].exit[i][0].name, argv[1])) {	/*** replace this one ***/
+	FreeMacro (task[0].exit[i]);
+	free (task[0].exit[i]);
+	ALLOCATE (task[0].exit[i], Macro, 1);
+	macro = task[0].exit[i];
+	macro[0].name = strcreate (argv[1]);
+	goto found;
+      }
+    }
+    /** create a new exit status macro **/
+    N = task[0].Nexit;
+    ALLOCATE (task[0].exit[N], Macro, 1);
+    task[0].Nexit ++;
+    if (task[0].Nexit == task[0].NEXIT) {
+      task[0].NEXIT += 10;
+      REALLOCATE (task[0].exit, Macro *, task[0].NEXIT);
+    }
+    macro = task[0].exit[N];
+    macro[0].name = strcreate (argv[1]);
+    goto found;
+  }
+
+found:
+
+  macro[0].Nlines = 0;
+  NLINES = D_NLINES;
+  ALLOCATE (macro[0].line, char *, NLINES);
+
+  /* load macro lines from appropriate source (readline / list) */
+
+  depth = 0;
+  done = FALSE;
+  ThisList = Nlists;
+  while (!done) {
+
+    /* get the next line (from correct place) */
+    if (ThisList == 0) 
+      input = readline (prompt);
+    else 
+      input = get_next_listentry (ThisList);
+
+    if ((ThisList == 0) && (input == (char *) NULL)) {
+      fprintf (stderr, "end macro with 'END'\n");
+      continue;
+    }
+    if ((ThisList > 0) && (input == (char *) NULL)) {
+      fprintf (stderr, "missing 'END' in macro definition\n");
+      input = strcreate ("end");
+    }
+    if (ThisList == 0) ohana_memregister (input);
+
+    stripwhite (input);
+
+    /* test for new macro (or other list, in the future?) */
+    if (is_list (input)) depth ++;
+
+    /* test for end of nested list -- if not nested, END refers to this macro */
+    if (!strncasecmp (input, "END", 3)) {
+      depth --;
+      if (depth < 0) { /* we hit the last "END", macro is done */
+	free (input);
+	REALLOCATE (macro[0].line, char *, MAX (1, macro[0].Nlines));
+	return (TRUE);
+      }
+    }
+
+    if (*input) { 
+      macro[0].line[macro[0].Nlines] = input;
+      macro[0].Nlines ++;
+      if (macro[0].Nlines == NLINES - 1) {
+	NLINES += D_NLINES;
+	REALLOCATE (macro[0].line, char *, NLINES);
+      }
+    }
+  }
+  return (TRUE);
+}
Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/task_nmax.c
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/task_nmax.c	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/task_nmax.c	(revision 21930)
@@ -0,0 +1,21 @@
+# include "psched.h"
+
+int task_nmax (int argc, char **argv) {
+
+  Task *task;
+
+  if (argc != 2) goto usage;
+
+  task = GetNewTask ();
+  if (task == NULL) {
+    fprintf (stderr, "ERROR: not defining or running a task\n");
+    return (FALSE);
+  }
+
+  task[0].Nmax = atoi (argv[1]);
+  return (TRUE);
+
+usage:
+  fprintf (stderr, "USAGE: nmax N\n");
+  return (FALSE);
+}
Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/task_periods.c
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/task_periods.c	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/task_periods.c	(revision 21930)
@@ -0,0 +1,53 @@
+# include "psched.h"
+
+int task_periods (int argc, char **argv) {
+
+  int N, Poll, Exec, Timeout;
+  float PollValue, ExecValue, TimeoutValue;
+  Task *task;
+
+  PollValue = 0;
+  Poll = FALSE;
+  if ((N = get_argument (argc, argv, "-poll"))) {
+    Poll = TRUE;
+    remove_argument (N, &argc, argv);
+    PollValue = atof(argv[N]);
+    remove_argument (N, &argc, argv);
+  }
+
+  ExecValue = 0;
+  Exec = FALSE;
+  if ((N = get_argument (argc, argv, "-exec"))) {
+    Exec = TRUE;
+    remove_argument (N, &argc, argv);
+    ExecValue = atof(argv[N]);
+    remove_argument (N, &argc, argv);
+  }
+
+  TimeoutValue = 0;
+  Timeout = FALSE;
+  if ((N = get_argument (argc, argv, "-timeout"))) {
+    Timeout = TRUE;
+    remove_argument (N, &argc, argv);
+    TimeoutValue = atof(argv[N]);
+    remove_argument (N, &argc, argv);
+  }
+
+  if (argc != 1) {
+    fprintf (stderr, "USAGE: periods [-poll value] [-exec value] [-timeout value]\n");
+    fprintf (stderr, "  define time periods for this task\n");
+    return (FALSE);
+  }
+
+  task = GetNewTask ();
+  if (task == NULL) {
+    fprintf (stderr, "ERROR: not defining or running a task\n");
+    return (FALSE);
+  }
+
+  if (Poll) task[0].poll_period = PollValue;
+  if (Exec) task[0].exec_period = ExecValue;
+  if (Timeout) task[0].timeout_period = TimeoutValue;
+
+  return (TRUE);
+}
Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/task_trange.c
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/task_trange.c	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/task_trange.c	(revision 21930)
@@ -0,0 +1,65 @@
+# include "psched.h"
+
+int task_trange (int argc, char **argv) {
+
+  int N;
+  Task *task;
+  TimeRange range;
+
+  /* add a -reset option? 
+  if (N = get_argument (argc, argv, "-reset")) {
+    remove_argument (N, &argc, argv);
+  } */
+
+
+  /* keep = true means the time range defines a valid time range
+     keep = false means the time range defines an invalid range */
+  range.keep = TRUE;
+  if ((N = get_argument (argc, argv, "-exclude"))) {
+    remove_argument (N, &argc, argv);
+    range.keep = FALSE;
+  }
+
+  if (argc != 3) goto usage;
+
+  task = GetNewTask ();
+  if (task == NULL) {
+    fprintf (stderr, "ERROR: not defining or running a task\n");
+    return (FALSE);
+  }
+
+  /* test for Mon[@HH:MM:SS] - both must match */
+  if (day_to_sec (argv[1], &range.start)) {
+    if (!day_to_sec (argv[2], &range.stop)) goto usage;
+    range.type = RANGE_WEEK;
+    goto valid;
+  }
+
+  /* test for HH:MM:SS */
+  if (hms_to_sec (argv[1], &range.start)) {
+    if (!hms_to_sec (argv[2], &range.stop)) goto usage;
+    range.type = RANGE_DAY;
+    goto valid;
+  }
+
+  /* this test does not fail sufficiently robustly for invalid inputs */
+  /* test for YYYY/MM/DD - both must match */
+  if (str_to_time (argv[1], &range.start)) {
+    if (!str_to_time (argv[2], &range.stop)) goto usage;
+    range.type = RANGE_ABS;
+    goto valid;
+  }
+  goto usage;
+
+valid:
+  N = task[0].Nranges;
+  task[0].Nranges ++;
+  REALLOCATE (task[0].ranges, TimeRange, task[0].Nranges);
+  
+  task[0].ranges[N] = range;
+  return (TRUE);
+
+usage:
+  fprintf (stderr, "USAGE: trange start end\n");
+  return (FALSE);
+}
Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/verbose.c
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/verbose.c	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/verbose.c	(revision 21930)
@@ -0,0 +1,37 @@
+# include "psched.h"
+
+static int VERBOSE = FALSE;
+
+int verbose (int argc, char **argv) {
+
+  if (argc == 1) {
+    if (VERBOSE) {
+      fprintf (stderr, "verbose mode ON\n");
+    } else {
+      fprintf (stderr, "verbose mode OFF\n");
+    }
+    return (TRUE);
+  }
+
+  if (argc == 2) {
+    if (!strcasecmp (argv[1], "ON")) {
+      VERBOSE = TRUE;
+      return (TRUE);
+    }
+    if (!strcasecmp (argv[1], "OFF")) {
+      VERBOSE = FALSE;
+      return (TRUE);
+    }
+    if (!strcasecmp (argv[1], "TOGGLE")) {
+      VERBOSE = ~VERBOSE;
+      return (TRUE);
+    }
+  }
+
+  fprintf (stderr, "USAGE: verbose (on/off/toggle)\n");
+  return (FALSE);
+}
+
+int VerboseMode () {
+  return (VERBOSE);
+}
Index: /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/version.c
===================================================================
--- /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/version.c	(revision 21930)
+++ /tags/ohana/psched-0-2/Ohana/src/opihi/pantasks/version.c	(revision 21930)
@@ -0,0 +1,14 @@
+# include "psched.h"
+static char *name = "$Name: not supported by cvs2svn $";
+
+int version (int argc, char **argv) {
+
+  fprintf (stderr, "%s\n", name);
+
+  fprintf (stderr, "%s\n", opihi_version());
+  fprintf (stderr, "%s\n", ohana_version());
+  fprintf (stderr, "%s\n", fits_version());
+
+  fprintf (stderr, "compiled on %s %s\n", __DATE__, __TIME__);
+  return (TRUE);
+}
