#include <sys/types.h>
#include <sys/time.h>
#include <signal.h>
#include "now.h"
#include "sig.h"
#include "coe.h"
#include "open.h"
#include "wait.h"
#include "fork.h"
#include "lock.h"
#include "fifo.h"
#include "error.h"
#include "select.h"
#include "strerr.h"
#include "sgetopt.h"
#include "substdio.h"
#include "readwrite.h"

#define FATAL "supervise: fatal: "
#define WARNING "supervise: warning: "
void die_usage()
{
  strerr_die1x(100,"supervise: usage: supervise [ -rsudox ] dir program args ...");
}

int flagexit = 0;
int flagnormallyup = 1;
int flagwant = 1;
int flagwantup = 1;
int pid = 0; /* 0 means down */
int flagpaused; /* defined if(pid) */

int fdexecdir;
int fdlock;
int fdcontrolwrite;
int fdcontrol;
int selfpipe[2];

unsigned char status[23];

void trigger()
{
  write(selfpipe[1],"",1);
}

void statusmark()
{
  struct timeval tv;
  unsigned long u;

  gettimeofday(&tv,(struct timezone *) 0);
  u = tv.tv_sec;
  status[7] = u; u >>= 8;
  status[6] = u; u >>= 8;
  status[5] = u; u >>= 8;
  status[4] = u; u >>= 8;
  status[3] = u; u >>= 8;
  status[2] = u; u >>= 8;
  status[1] = u; u >>= 8;
  status[0] = u + 64; /* TAI64 */
  u = tv.tv_usec * 1000;
  status[11] = u; u >>= 8;
  status[10] = u; u >>= 8;
  status[9] = u; u >>= 8;
  status[8] = u;
  status[15] = status[14] = status[13] = status[12] = 0;
  u = (unsigned long) pid;
  status[16] = u; u >>= 8;
  status[17] = u; u >>= 8;
  status[18] = u; u >>= 8;
  status[19] = u;
}

void announce()
{
  int fd;
  int r;

  status[20] = flagnormallyup;
  status[21] = (pid ? flagpaused : 0);
  status[22] = (flagwant ? (flagwantup ? 'u' : 'd') : 0);

  fd = open_trunc("status.new");
  if (fd == -1) {
    strerr_warn2(WARNING,"unable to open status.new: ",&strerr_sys);
    return;
  }

  r = write(fd,status,sizeof status);
  if (r == -1)
    strerr_warn2(WARNING,"unable to write status.new: ",&strerr_sys);
  else if (r < sizeof status)
    strerr_warn2(WARNING,"unable to write status.new: partial write",0);
  close(fd);

  if (r == sizeof status)
    if (rename("status.new","status") == -1)
      strerr_warn2(WARNING,"unable to rename status.new to status: ",&strerr_sys);
}

void trystart(argv)
char **argv;
{
  int f;

  switch(f = fork()) {
    case -1:
      strerr_warn2(WARNING,"unable to fork, sleeping 60 seconds: ",&strerr_sys);
      sleep(60);
      trigger();
      return;
    case 0:
      sleep(1);
      if (fchdir(fdexecdir) == -1)
	strerr_die2sys(111,FATAL,"unable to set directory: ");
      execvp(*argv,argv);
      strerr_die4sys(111,FATAL,"unable to run ",*argv,": ");
    default:
      pid = f;
      flagpaused = 0;
      statusmark();
  }
}

void main(argc,argv)
int argc;
char **argv;
{
  int opt;
  char *dir;

  while ((opt = getopt(argc,argv,"rsudox")) != opteof)
    switch(opt) {
      case 'r': flagnormallyup = 1;
      case 'u': flagwant = 1; flagwantup = 1; break;
      case 's': flagnormallyup = 0;
      case 'd': flagwant = 1; flagwantup = 0; break;
      case 'o': flagwant = 0; break;
      case 'x': flagexit = 1; break;
      default: die_usage();
    }
  argv += optind;

  if (!*argv) die_usage();
  dir = *argv++;
  if (!*argv) die_usage();

  fdexecdir = open_read(".");
  if (fdexecdir == -1)
    strerr_die2sys(111,FATAL,"unable to open current directory: ");
  coe(fdexecdir);

  if (chdir(dir) == -1)
    strerr_die4sys(111,FATAL,"unable to chdir to ",dir,": ");

  if (pipe(selfpipe) == -1)
    strerr_die2sys(111,FATAL,"unable to create pipe: ");
  coe(selfpipe[0]);
  coe(selfpipe[1]);
  ndelay_on(selfpipe[0]);
  ndelay_on(selfpipe[1]);

  sig_childcatch(trigger);

  fdlock = open_append("lock");
  if (fdlock == -1)
    strerr_die2sys(111,FATAL,"unable to open lock: ");
  coe(fdlock);
  if (lock_exnb(fdlock) == -1)
    strerr_die2sys(111,FATAL,"unable to acquire lock: ");

  fifo_make("svcontrol",0600);

  fdcontrol = open_read("svcontrol");
  if (fdcontrol == -1)
    strerr_die2sys(111,FATAL,"unable to open svcontrol: ");
  coe(fdcontrol);
  ndelay_on(fdcontrol); /* shouldn't be necessary */
  fdcontrolwrite = open_write("svcontrol");
  if (fdcontrolwrite == -1)
    strerr_die2sys(111,FATAL,"unable to open svcontrol for writing: ");
  coe(fdcontrolwrite);

  statusmark();
  if (!flagwant || flagwantup) trystart(argv);

  for (;;) {
    fd_set rfds;
    int nfds;
    int wstat;
    int r;
    char ch;

    announce();

    if (flagexit && !pid) _exit(0);

    FD_ZERO(&rfds);
    FD_SET(selfpipe[0],&rfds);
    nfds = selfpipe[0] + 1;
    FD_SET(fdcontrol,&rfds);
    if (fdcontrol >= nfds) nfds = fdcontrol + 1;

    select(nfds,&rfds,(fd_set *) 0,(fd_set *) 0,(struct timeval *) 0);

    if (read(selfpipe[0],&ch,1) == 1) {
      for (;;) {
	r = wait_nohang(&wstat);
	if (!r || ((r == -1) && (errno != error_intr))) break;
	if (r == pid) { pid = 0; statusmark(); }
      }
      if (flagexit && !pid) { announce(); _exit(0); }
      if (!pid) if (flagwant) if (flagwantup) trystart(argv);
    }

    if (read(fdcontrol,&ch,1) == 1)
      switch(ch) {
	case 'd':
	  flagwant = 1; flagwantup = 0;
	  if (pid) { kill(pid,SIGTERM); kill(pid,SIGCONT); flagpaused = 0; }
	  break;
	case 'u': flagwant = 1; flagwantup = 1; if (!pid) trystart(argv); break;
	case 'o': flagwant = 0; if (!pid) trystart(argv); break;
	case 'a': if (pid) kill(pid,SIGALRM); break;
	case 'h': if (pid) kill(pid,SIGHUP); break;
	case 'k': if (pid) kill(pid,SIGKILL); break;
	case 't': if (pid) kill(pid,SIGTERM); break;
	case 'i': if (pid) kill(pid,SIGINT); break;
	case 'p': if (pid) kill(pid,SIGSTOP); flagpaused = 1; break;
	case 'c': if (pid) kill(pid,SIGCONT); flagpaused = 0; break;
	case 'r': flagnormallyup = 1; break;
	case 's': flagnormallyup = 0; break;
	case 'x': flagexit = 1; break;
      }
  }
}


syntax highlighted by Code2HTML, v. 0.9.1