supervise.c


/* #include は省略 */

#define FATAL "supervise: fatal: "
#define WARNING "supervise: warning: "

char *dir;
int selfpipe[2];	/* 自分自身のトリガ用 */
int fdok;		/* supervise/ok */
int fdlock;		/* supervise/lock */
int fdcontrol;		/* supervise/control */
int fdcontrolwrite;	/* supervise/control の書きこみ用。謎。 */

/* 現在の supervise の状態 */
int flagexit = 0;
int flagwant = 1;
int flagwantup = 1;
int pid = 0; /* 0 means down */
int flagpaused; /* defined if(pid) */

/* 
   status: ステータス情報のバッファ。
   これは supervise/status に書きこまれ、svstat が読む。
*/
char status[18];

/* 
   pidchange: pid が変わったときの時刻をstatusに記録しておく。
*/
void pidchange(void)
{
  /*
    taia は時刻をいれる型。
  */
  struct taia now;
  unsigned long u;

  taia_now(&now);
  taia_pack(status,&now);
  u = (unsigned long) pid;
  status[12] = u; u >>= 8;
  status[13] = u; u >>= 8;
  status[14] = u; u >>= 8;
  status[15] = u;
}

/*
  announce: 現在の supervise の状態(status変数の内容)を
  supervise/status に書きこむ。
*/
void announce(void)
{
  int fd; int r;
  status[16] = (pid ? flagpaused : 0);
  status[17] = (flagwant ? (flagwantup ? 'u' : 'd') : 0);

  /*
    djb が(cdbなどで)よく使っている排他制御の方法。
    まず別名(status.new)でファイルをオープンし、ぜんぶ完成したあとmvする。
  */
  fd = open_trunc("supervise/status.new");
  if (fd == -1) {
    strerr_warn4(WARNING,"unable to open ",dir,"/supervise/status.new: ",&strerr_sys);
    return;
  }

  r = write(fd,status,sizeof status);
  if (r == -1) {
    strerr_warn4(WARNING,"unable to write ",dir,"/supervise/status.new: ",&strerr_sys);
    close(fd);
    return;
  }
  close(fd);
  if (r < sizeof status) {
    strerr_warn4(WARNING,"unable to write ",dir,"/supervise/status.new: partial write",0);
    return;
  }

  /* 
     完成したので mv (rename)。
     UNIX では、rename はディレクトリのエントリを変えるだけで、unlink する
     わけではない (結果的にそのファイルへの参照カウントが 0 になれば
     そのファイルは消えるわけだが)。そのためもし古い status をどこかの
     プロセスがよみこみ中だったと、そのプロセスは自分が終了するまでは
     同一のファイル (i-node) にアクセスできる。
  */
  if (rename("supervise/status.new","supervise/status") == -1)
    strerr_warn4(WARNING,"unable to rename ",dir,"/supervise/status.new to status: ",&strerr_sys);
}

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

void trystart(void)
{
  int f; char *run[2] = { "./run", 0 };

  /* 子プロセス "run" を起動 */
  switch(f = fork()) {
    case -1:
      strerr_warn4(WARNING,"unable to fork for ",dir,", sleeping 60 seconds: ",&strerr_sys);
      sleep(60);
      /* 自分自身に状態変化を通知 */
      trigger();
      return;
    case 0:
      execve(*run,run,environ);
      strerr_die4sys(111,FATAL,"unable to start ",dir,"/run: ");
  }
  flagpaused = 0;
  pid = f;
  /*
    状態変化をアナウンス
  */
  pidchange();
  announce();
  /*
    問: 実際には、この sleep はほとんど効いていない。
    これは switch の前にもっていってもいいのではないか。
    このせいでときに supervise が負荷を上げてしまう。
    (この話は最近 log@list.cr.yp.to でがいしゅつ)
  */
  sleep(1);
}

void doit(void)
{
/*
  iopause はようするに djb 版 select。
  指定された fd が読み込み (あるいは書き込み) 可能になるまで
  待つか、あるいは timeout する。
*/
  iopause_fd x[2];
  struct taia deadline;
  struct taia stamp;
  int wstat;
  int r;
  char ch;

  for (;;) {
/*
  状態変化をアナウンス
*/
    announce();

    if (flagexit && !pid) return;

/* 自分自身のトリガーか、
   あるいは supervise/control が変化するまで待つ 

   問: 自分自身のトリガは本当に必要なのだろうか。
   supervise/control を共用するんじゃだめなのか。
*/
    x[0].fd = selfpipe[0];
    x[0].events = IOPAUSE_READ;
    x[1].fd = fdcontrol;
    x[1].events = IOPAUSE_READ;
    taia_now(&stamp);
    taia_uint(&deadline,3600);
    taia_add(&deadline,&stamp,&deadline);
    iopause(x,2,&deadline,&stamp);
    
    /* たまったトリガーは捨てておく */
    while (read(selfpipe[0],&ch,1) == 1)
      ;

    for (;;) {
/*
  子プロセスが終了しているかどうかを検査する。
  (これはブロックされない)
*/
      r = wait_nohang(&wstat);
      if (!r) break;
      if ((r == -1) && (errno != error_intr)) break;
      if (r == pid) {
	pid = 0;
	pidchange();
/* 
   子が終了して、もし supervise も終了するように
   言われている (svc -x) のなら exit。
   まだ続けるのなら再起動。
*/
	if (flagexit) return;
	if (flagwant && flagwantup) trystart();
	break;
      }
    }

/* 
   supervise/control の FIFO から 1文字よんで、
   それに応じた動作をする。
*/
    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();
	  break;
	case 'o':
	  flagwant = 0;
	  if (!pid) trystart();
	  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 'x':
	  flagexit = 1;
	  break;
      default:
	  /* trigger() が呼びだされたときはここに落ちる。
	     djb はなぜか default を書いてないが… 
	  */
      }
  }
}

/*
  ここでやってることは前準備のみ
*/
main(int argc,char **argv)
{
  struct stat st;

  dir = argv[1];
  if (!dir || argv[2])
    strerr_die1x(100,"supervise: usage: supervise dir");

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

  sig_catch(sig_child,trigger);

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

/*
  supervise/down があれば最初はプロセス上げない。
  (これは最初の1回しか見ないことに注意)
*/
  if (stat("down",&st) != -1)
    flagwantup = 0;
  else
    if (errno != error_noent)
      strerr_die4sys(111,FATAL,"unable to stat ",dir,"/down: ");

  /* この mkdir は失敗してもいい */
  mkdir("supervise",0700);

/* 
   排他的オープン。
   これができなければロック失敗。
   coe を実行しているので、プロセスが終了すると自動的にロックは解除される。
*/
  fdlock = open_append("supervise/lock");
  if ((fdlock == -1) || (lock_exnb(fdlock) == -1))
    strerr_die4sys(111,FATAL,"unable to acquire ",dir,"/supervise/lock: ");
  coe(fdlock);

  fifo_make("supervise/control",0600);
  fdcontrol = open_read("supervise/control");
  if (fdcontrol == -1)
    strerr_die4sys(111,FATAL,"unable to read ",dir,"/supervise/control: ");
  coe(fdcontrol);
  ndelay_on(fdcontrol); /* shouldn't be necessary */

/*
  この動作は何を意味するのか?
*/
  fdcontrolwrite = open_write("supervise/control");
  if (fdcontrolwrite == -1)
    strerr_die4sys(111,FATAL,"unable to write ",dir,"/supervise/control: ");
  coe(fdcontrolwrite);

/*
  状態変化をアナウンス
*/
  pidchange();
  announce();

  fifo_make("supervise/ok",0600);
  fdok = open_read("supervise/ok");
  if (fdok == -1)
    strerr_die4sys(111,FATAL,"unable to read ",dir,"/supervise/ok: ");
  coe(fdok);

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

/* ループに入る */
  doit();

/* svc -x が実行されたとき、flagexit = 1 でここにくる */
  announce();
  _exit(0);
}