/* #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);
}