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