summaryrefslogtreecommitdiff
path: root/svc/src
diff options
context:
space:
mode:
Diffstat (limited to 'svc/src')
-rw-r--r--svc/src/dat.h85
-rw-r--r--svc/src/fs.c241
-rw-r--r--svc/src/init.c851
-rw-r--r--svc/src/mkfile9
4 files changed, 1186 insertions, 0 deletions
diff --git a/svc/src/dat.h b/svc/src/dat.h
new file mode 100644
index 0000000..f2cdec2
--- /dev/null
+++ b/svc/src/dat.h
@@ -0,0 +1,85 @@
+/*
+ * Shared between the supervisor (init.c) and the control
+ * filesystem (fs.c).
+ */
+
+enum
+{
+ /* states */
+ Sstopped,
+ Swaiting, /* wants to run; a needs= is not up yet */
+ Sstarting, /* started; not yet ready */
+ Srunning,
+ Sfailed,
+ Sdone, /* ready=exit service that finished cleanly */
+
+ /* kinds, from ready= */
+ Kexec, /* up as soon as started; watch the process */
+ Ksrv, /* up when /srv/x appears; watch that file */
+ Kdial, /* up when a dial succeeds; watch the process */
+ Kexit, /* oneshot; done when it exits cleanly */
+
+ Maxargs = 64,
+ Maxneeds = 32,
+ Maxrestarts = 5, /* within Restartwindow seconds */
+ Restartwindow = 60,
+ Restartwait = 1, /* seconds between restarts */
+ Readywait = 30, /* seconds to become ready */
+ Stopwait = 5, /* seconds before killing */
+ Tickms = 250, /* poll interval */
+};
+
+typedef struct Svc Svc;
+struct Svc
+{
+ char *name;
+ char *exec;
+ char *argv[Maxargs];
+ int argc;
+ char *env[Maxargs];
+ int envc;
+ char *restart;
+ int enable;
+
+ int kind;
+ char *readyarg;
+ char *needs[Maxneeds];
+ int nneeds;
+
+ int wanted; /* should it be running? */
+ int stopping; /* we asked it to go; not a crash */
+ int pid;
+ int state;
+ long tstart;
+ long tstop; /* when we asked it to stop */
+ long tretry; /* earliest time to start again */
+ int restarts;
+ long rtimes[Maxrestarts];
+ int rnext;
+ char *exits;
+
+ int mark; /* cycle detection */
+ Svc *next;
+};
+
+extern Svc *svcs;
+extern char *statename[];
+extern char *svcdir;
+extern char *logdir;
+extern int verbose;
+extern QLock statelock; /* covers svcs and the command queue */
+
+/* init.c */
+Svc* findname(char*);
+void startsvc(Svc*);
+void stopsvc(Svc*);
+char* statusline(Svc*); /* malloc'd ndb line, with newline */
+char* allstatus(void); /* malloc'd, every service */
+
+/* command queue: the filesystem asks, the supervisor acts */
+char* queuecmd(char *verb, char *name); /* nil ok, else error */
+void draincmds(void);
+int cmdspending(void);
+
+/* fs.c */
+void startfs(char *srvname, char *mtpt);
diff --git a/svc/src/fs.c b/svc/src/fs.c
new file mode 100644
index 0000000..63150fb
--- /dev/null
+++ b/svc/src/fs.c
@@ -0,0 +1,241 @@
+/*
+ * svcfs - the control filesystem init serves.
+ *
+ * This runs in a proc of its own, sharing memory with the supervisor,
+ * so everything it touches is under statelock. It never starts or
+ * stops anything itself: a service has to be a child of the supervisor
+ * for the supervisor to wait for it, so a write here is validated now
+ * and queued for the supervisor to carry out on its next tick.
+ */
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include "dat.h"
+
+enum
+{
+ Qctl,
+ Qstatus,
+ Qargs,
+ Qpid,
+ Qlog,
+};
+
+typedef struct Fref Fref;
+struct Fref
+{
+ int type;
+ Svc *svc; /* nil for the root ctl and status */
+};
+
+static char *rootctltext =
+ "start <name> start a service\n"
+ "stop <name> stop a service\n"
+ "restart <name> stop it, then start it\n"
+ "reset stop every service, then start them again\n";
+
+static char *svcctltext =
+ "start\n"
+ "stop\n"
+ "restart\n";
+
+static Fref*
+fref(int type, Svc *s)
+{
+ Fref *f;
+
+ f = mallocz(sizeof *f, 1);
+ if(f == nil)
+ sysfatal("out of memory");
+ f->type = type;
+ f->svc = s;
+ return f;
+}
+
+static void
+fsread(Req *r)
+{
+ Fref *f;
+ char *s;
+ char buf[64];
+ int fd, n;
+ char path[512];
+
+ f = r->fid->file->aux;
+ if(f == nil){
+ respond(r, "not a readable file");
+ return;
+ }
+
+ switch(f->type){
+ case Qctl:
+ readstr(r, f->svc == nil ? rootctltext : svcctltext);
+ break;
+
+ case Qstatus:
+ qlock(&statelock);
+ s = f->svc == nil ? allstatus() : statusline(f->svc);
+ qunlock(&statelock);
+ if(s == nil){
+ respond(r, "out of memory");
+ return;
+ }
+ readstr(r, s);
+ free(s);
+ break;
+
+ case Qargs:
+ qlock(&statelock);
+ s = smprint("%s", f->svc->exec);
+ qunlock(&statelock);
+ if(s == nil){
+ respond(r, "out of memory");
+ return;
+ }
+ readstr(r, s);
+ free(s);
+ break;
+
+ case Qpid:
+ qlock(&statelock);
+ if(f->svc->pid == 0)
+ buf[0] = '\0';
+ else
+ snprint(buf, sizeof buf, "%d\n", f->svc->pid);
+ qunlock(&statelock);
+ readstr(r, buf);
+ break;
+
+ case Qlog:
+ snprint(path, sizeof path, "%s/%s", logdir, f->svc->name);
+ fd = open(path, OREAD);
+ if(fd < 0){
+ /* no output yet is not an error */
+ r->ofcall.count = 0;
+ break;
+ }
+ seek(fd, r->ifcall.offset, 0);
+ n = read(fd, r->ofcall.data, r->ifcall.count);
+ close(fd);
+ if(n < 0){
+ responderror(r);
+ return;
+ }
+ r->ofcall.count = n;
+ break;
+
+ default:
+ respond(r, "not a readable file");
+ return;
+ }
+ respond(r, nil);
+}
+
+static void
+fswrite(Req *r)
+{
+ Fref *f;
+ char *buf, *err;
+ char *fld[3];
+ int nf;
+
+ f = r->fid->file->aux;
+ if(f == nil || f->type != Qctl){
+ respond(r, "not a writable file");
+ return;
+ }
+
+ buf = mallocz(r->ifcall.count+1, 1);
+ if(buf == nil){
+ respond(r, "out of memory");
+ return;
+ }
+ memmove(buf, r->ifcall.data, r->ifcall.count);
+ buf[r->ifcall.count] = '\0';
+
+ nf = tokenize(buf, fld, nelem(fld));
+ if(nf == 0){
+ free(buf);
+ respond(r, "no command");
+ return;
+ }
+
+ if(f->svc != nil){
+ /*
+ * A per-service ctl takes no name: which file you wrote to
+ * already said which service you meant.
+ */
+ if(nf != 1){
+ free(buf);
+ respond(r, "this ctl takes no arguments; "
+ "try start, stop or restart");
+ return;
+ }
+ err = queuecmd(fld[0], f->svc->name);
+ }else if(strcmp(fld[0], "reset") == 0)
+ err = queuecmd("reset", nil);
+ else if(nf != 2){
+ free(buf);
+ respond(r, "usage: start, stop or restart <service>, or reset");
+ return;
+ }else
+ err = queuecmd(fld[0], fld[1]);
+
+ free(buf);
+ if(err != nil){
+ respond(r, err);
+ return;
+ }
+ r->ofcall.count = r->ifcall.count;
+ respond(r, nil);
+}
+
+static Srv fs =
+{
+.read= fsread,
+.write= fswrite,
+};
+
+void
+startfs(char *srvname, char *mtpt)
+{
+ Svc *s;
+ File *d;
+ Tree *t;
+ char *me;
+
+ /*
+ * Files belong to whoever is running us, so that the host owner
+ * can write the ctl files. They are world readable: reading a
+ * ctl file is how you find out what it accepts, which is no use
+ * if only one user may do it. Writing is what wants restricting.
+ */
+ me = getuser();
+ if(me == nil)
+ me = "none";
+
+ t = alloctree(me, me, DMDIR|0555, nil);
+ if(t == nil)
+ sysfatal("alloctree: %r");
+ fs.tree = t;
+
+ createfile(t->root, "ctl", me, 0644, fref(Qctl, nil));
+ createfile(t->root, "status", me, 0444, fref(Qstatus, nil));
+
+ for(s = svcs; s != nil; s = s->next){
+ d = createfile(t->root, s->name, me, DMDIR|0555, nil);
+ if(d == nil){
+ fprint(2, "init: svcfs: create %s: %r\n", s->name);
+ continue;
+ }
+ createfile(d, "ctl", me, 0644, fref(Qctl, s));
+ createfile(d, "status", me, 0444, fref(Qstatus, s));
+ createfile(d, "args", me, 0444, fref(Qargs, s));
+ createfile(d, "pid", me, 0444, fref(Qpid, s));
+ createfile(d, "log", me, 0444, fref(Qlog, s));
+ }
+
+ postmountsrv(&fs, srvname, mtpt, MREPL);
+}
diff --git a/svc/src/init.c b/svc/src/init.c
new file mode 100644
index 0000000..7babe28
--- /dev/null
+++ b/svc/src/init.c
@@ -0,0 +1,851 @@
+/*
+ * init - run the services described in a directory of ndb files.
+ *
+ * Stage 3: load, start in dependency order, wait for readiness,
+ * supervise, log, and serve svcfs. No namespaces, no identity
+ * switching, no halt/reboot. Runs as an ordinary process; being
+ * pid 1 is not assumed anywhere.
+ */
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+#include "dat.h"
+
+char *statename[] =
+{
+[Sstopped] "stopped",
+[Swaiting] "waiting",
+[Sstarting] "starting",
+[Srunning] "running",
+[Sfailed] "failed",
+[Sdone] "done",
+};
+
+Svc *svcs;
+char *svcdir = "/lib/svc";
+/*
+ * Not /log: the root is mounted without create permission, so a new
+ * top level directory cannot be made from a running system. /sys/log
+ * already exists and is where Plan 9 keeps this.
+ */
+char *logdir = "/sys/log";
+int verbose;
+QLock statelock;
+
+static int tickpid;
+static int fsrunning;
+
+typedef struct Cmd Cmd;
+struct Cmd
+{
+ char verb[16];
+ char name[64];
+ Cmd *next;
+};
+static Cmd *cmdq;
+
+char *known[] =
+{
+ "svc", "exec", "args", "env", "restart", "enable", "ready", "needs",
+ "ns", "user", "stop", "adopt",
+ nil
+};
+
+char *unimpl[] = { "ns", "user", "stop", "adopt", nil };
+
+void*
+emalloc(ulong n)
+{
+ void *p;
+
+ p = mallocz(n, 1);
+ if(p == nil)
+ sysfatal("out of memory");
+ setmalloctag(p, getcallerpc(&n));
+ return p;
+}
+
+char*
+estrdup(char *s)
+{
+ char *p;
+
+ p = strdup(s);
+ if(p == nil)
+ sysfatal("out of memory");
+ return p;
+}
+
+int
+inlist(char **list, char *s)
+{
+ int i;
+
+ for(i = 0; list[i] != nil; i++)
+ if(strcmp(list[i], s) == 0)
+ return 1;
+ return 0;
+}
+
+Svc*
+findname(char *name)
+{
+ Svc *s;
+
+ for(s = svcs; s != nil; s = s->next)
+ if(strcmp(s->name, name) == 0)
+ return s;
+ return nil;
+}
+
+static Svc*
+findpid(int pid)
+{
+ Svc *s;
+
+ for(s = svcs; s != nil; s = s->next)
+ if(s->pid == pid)
+ return s;
+ return nil;
+}
+
+static void
+failsvc(Svc *s, char *why)
+{
+ s->state = Sfailed;
+ s->wanted = 0;
+ free(s->exits);
+ s->exits = estrdup(why);
+ fprint(2, "init: %s: %s\n", s->name, why);
+}
+
+static int
+parseready(Svc *s, char *v)
+{
+ if(strcmp(v, "exec") == 0)
+ s->kind = Kexec;
+ else if(strcmp(v, "exit") == 0)
+ s->kind = Kexit;
+ else if(strncmp(v, "srv:", 4) == 0){
+ s->kind = Ksrv;
+ s->readyarg = estrdup(v+4);
+ }else if(strncmp(v, "dial:", 5) == 0){
+ s->kind = Kdial;
+ s->readyarg = estrdup(v+5);
+ }else
+ return -1;
+ return 0;
+}
+
+static Svc*
+loadone(char *path, char *name)
+{
+ Ndb *db;
+ Ndbtuple *tu, *t;
+ Svc *s;
+
+ db = ndbopen(path);
+ if(db == nil){
+ fprint(2, "init: %s: %r\n", path);
+ return nil;
+ }
+ tu = ndbparse(db);
+ if(tu == nil){
+ fprint(2, "init: %s: no service defined\n", path);
+ ndbclose(db);
+ return nil;
+ }
+
+ s = emalloc(sizeof *s);
+ s->restart = "onfail";
+ s->state = Sstopped;
+ s->kind = Kexec;
+
+ for(t = tu; t != nil; t = t->entry){
+ if(!inlist(known, t->attr)){
+ fprint(2, "init: %s: unknown attribute %q\n", path, t->attr);
+ goto Bad;
+ }
+ if(inlist(unimpl, t->attr)){
+ fprint(2, "init: %s: %s not implemented yet, ignored\n",
+ path, t->attr);
+ continue;
+ }
+ if(strcmp(t->attr, "svc") == 0)
+ s->name = estrdup(t->val);
+ else if(strcmp(t->attr, "exec") == 0)
+ s->exec = estrdup(t->val);
+ else if(strcmp(t->attr, "args") == 0){
+ if(s->argc < Maxargs-2)
+ s->argv[++s->argc] = estrdup(t->val);
+ }else if(strcmp(t->attr, "env") == 0){
+ if(s->envc < Maxargs-1)
+ s->env[s->envc++] = estrdup(t->val);
+ }else if(strcmp(t->attr, "needs") == 0){
+ if(s->nneeds < Maxneeds)
+ s->needs[s->nneeds++] = estrdup(t->val);
+ }else if(strcmp(t->attr, "ready") == 0){
+ if(parseready(s, t->val) < 0){
+ fprint(2, "init: %s: ready=%q is not exec, exit, "
+ "srv:name or dial:addr\n", path, t->val);
+ goto Bad;
+ }
+ }else if(strcmp(t->attr, "restart") == 0)
+ s->restart = estrdup(t->val);
+ else if(strcmp(t->attr, "enable") == 0)
+ s->enable = strcmp(t->val, "yes") == 0;
+ }
+ ndbfree(tu);
+ ndbclose(db);
+
+ if(s->name == nil){
+ fprint(2, "init: %s: no svc= attribute\n", path);
+ goto Bad2;
+ }
+ if(strcmp(s->name, name) != 0){
+ fprint(2, "init: %s: svc=%s does not match file name\n", path, s->name);
+ goto Bad2;
+ }
+ if(s->exec == nil){
+ fprint(2, "init: %s: no exec= attribute\n", path);
+ goto Bad2;
+ }
+ if(strcmp(s->restart, "never") != 0 && strcmp(s->restart, "onfail") != 0
+ && strcmp(s->restart, "always") != 0){
+ fprint(2, "init: %s: restart=%q is not never, onfail or always\n",
+ path, s->restart);
+ goto Bad2;
+ }
+ if(s->kind == Kexit && strcmp(s->restart, "never") != 0){
+ fprint(2, "init: %s: restart=%s with ready=exit; a oneshot is "
+ "never restarted\n", path, s->restart);
+ goto Bad2;
+ }
+
+ s->argv[0] = strrchr(s->exec, '/');
+ if(s->argv[0] == nil)
+ s->argv[0] = s->exec;
+ else
+ s->argv[0]++;
+ s->argv[s->argc+1] = nil;
+
+ return s;
+
+Bad:
+ ndbfree(tu);
+ ndbclose(db);
+Bad2:
+ free(s);
+ return nil;
+}
+
+static int
+cyclic(Svc *s)
+{
+ Svc *d;
+ int i;
+
+ if(s->mark == 1)
+ return 1;
+ if(s->mark == 2)
+ return 0;
+ s->mark = 1;
+ for(i = 0; i < s->nneeds; i++){
+ d = findname(s->needs[i]);
+ if(d != nil && cyclic(d)){
+ fprint(2, "init: %s: needs= cycle through %s\n",
+ s->name, d->name);
+ return 1;
+ }
+ }
+ s->mark = 2;
+ return 0;
+}
+
+static void
+dropsvc(Svc *bad)
+{
+ Svc **p;
+
+ for(p = &svcs; *p != nil; p = &(*p)->next)
+ if(*p == bad){
+ *p = bad->next;
+ free(bad);
+ return;
+ }
+}
+
+static void
+checkdeps(void)
+{
+ Svc *s, *next;
+ int i;
+
+ for(s = svcs; s != nil; s = next){
+ next = s->next;
+ for(i = 0; i < s->nneeds; i++)
+ if(findname(s->needs[i]) == nil){
+ fprint(2, "init: %s: needs=%s, which is not a service\n",
+ s->name, s->needs[i]);
+ dropsvc(s);
+ break;
+ }
+ }
+ for(s = svcs; s != nil; s = next){
+ next = s->next;
+ if(cyclic(s))
+ dropsvc(s);
+ }
+}
+
+static void
+loadsvcs(void)
+{
+ Dir *d;
+ int fd, i, n;
+ char path[512];
+ Svc *s, **tail;
+
+ fd = open(svcdir, OREAD);
+ if(fd < 0)
+ sysfatal("open %s: %r", svcdir);
+ n = dirreadall(fd, &d);
+ close(fd);
+ if(n < 0)
+ sysfatal("read %s: %r", svcdir);
+
+ tail = &svcs;
+ for(i = 0; i < n; i++){
+ if(d[i].qid.type & QTDIR)
+ continue;
+ snprint(path, sizeof path, "%s/%s", svcdir, d[i].name);
+ s = loadone(path, d[i].name);
+ if(s == nil)
+ continue;
+ if(findname(s->name) != nil){
+ fprint(2, "init: %s: duplicate service name\n", path);
+ free(s);
+ continue;
+ }
+ *tail = s;
+ tail = &s->next;
+ }
+ free(d);
+ checkdeps();
+}
+
+static int
+toofast(Svc *s)
+{
+ long now, old;
+
+ now = time(nil);
+ old = s->rtimes[s->rnext];
+ s->rtimes[s->rnext] = now;
+ s->rnext = (s->rnext + 1) % Maxrestarts;
+ return old != 0 && now - old < Restartwindow;
+}
+
+static int
+srvexists(char *name)
+{
+ char path[256];
+
+ snprint(path, sizeof path, "/srv/%s", name);
+ return access(path, AEXIST) == 0;
+}
+
+static int
+dialok(char *addr)
+{
+ int fd;
+
+ fd = dial(addr, nil, nil, nil);
+ if(fd < 0)
+ return 0;
+ close(fd);
+ return 1;
+}
+
+static int
+isready(Svc *s)
+{
+ switch(s->kind){
+ case Ksrv:
+ return srvexists(s->readyarg);
+ case Kdial:
+ return dialok(s->readyarg);
+ }
+ return 1;
+}
+
+static int
+depsok(Svc *s)
+{
+ Svc *d;
+ int i;
+
+ for(i = 0; i < s->nneeds; i++){
+ d = findname(s->needs[i]);
+ if(d == nil)
+ return 0;
+ if(d->state != Srunning && d->state != Sdone)
+ return 0;
+ }
+ return 1;
+}
+
+void
+startsvc(Svc *s)
+{
+ int pid, fd, i;
+ char path[512];
+
+ if(s->pid != 0)
+ return;
+
+ switch(pid = rfork(RFPROC|RFFDG|RFNOTEG|RFENVG)){
+ case -1:
+ failsvc(s, "rfork failed");
+ return;
+ case 0:
+ snprint(path, sizeof path, "%s/%s", logdir, s->name);
+ fd = open(path, OWRITE);
+ if(fd < 0)
+ fd = create(path, OWRITE, 0644);
+ if(fd >= 0){
+ seek(fd, 0, 2);
+ dup(fd, 1);
+ dup(fd, 2);
+ if(fd > 2)
+ close(fd);
+ }
+ for(i = 0; i < s->envc; i++){
+ char *p;
+
+ p = strchr(s->env[i], '=');
+ if(p == nil)
+ continue;
+ *p = '\0';
+ putenv(s->env[i], p+1);
+ }
+ exec(s->exec, s->argv);
+ fprint(2, "init: exec %s: %r\n", s->exec);
+ exits("exec");
+ }
+ s->pid = pid;
+ s->wanted = 1;
+ s->tstart = time(nil);
+ free(s->exits);
+ s->exits = nil;
+ s->state = s->kind == Kexec ? Srunning : Sstarting;
+ if(verbose)
+ fprint(2, "init: started %s pid %d (%s)\n", s->name, pid,
+ statename[s->state]);
+}
+
+void
+stopsvc(Svc *s)
+{
+ s->wanted = 0;
+ s->tretry = 0;
+ if(s->pid == 0){
+ if(s->state != Sdone && s->state != Sfailed)
+ s->state = Sstopped;
+ return;
+ }
+ s->tstop = time(nil);
+ s->stopping = 1;
+ if(postnote(PNGROUP, s->pid, "hangup") < 0)
+ postnote(PNPROC, s->pid, "hangup");
+ if(verbose)
+ fprint(2, "init: stopping %s pid %d\n", s->name, s->pid);
+}
+
+/*
+ * The filesystem runs in another proc and cannot fork services itself,
+ * so it leaves them here. Errors are constant strings: several procs
+ * may be in here at once.
+ */
+char*
+queuecmd(char *verb, char *name)
+{
+ Cmd *c;
+ Svc *s;
+
+ if(strcmp(verb, "start") != 0 && strcmp(verb, "stop") != 0
+ && strcmp(verb, "restart") != 0 && strcmp(verb, "reset") != 0)
+ return "unknown command; try start, stop, restart or reset";
+
+ qlock(&statelock);
+ if(name != nil){
+ s = findname(name);
+ if(s == nil){
+ qunlock(&statelock);
+ return "no such service";
+ }
+ }
+ c = mallocz(sizeof *c, 1);
+ if(c == nil){
+ qunlock(&statelock);
+ return "out of memory";
+ }
+ strecpy(c->verb, c->verb+sizeof c->verb, verb);
+ if(name != nil)
+ strecpy(c->name, c->name+sizeof c->name, name);
+ c->next = cmdq;
+ cmdq = c;
+ qunlock(&statelock);
+ return nil;
+}
+
+int
+cmdspending(void)
+{
+ return cmdq != nil;
+}
+
+/* called with statelock held */
+void
+draincmds(void)
+{
+ Cmd *c, *next;
+ Svc *s;
+
+ c = cmdq;
+ cmdq = nil;
+ for(; c != nil; c = next){
+ next = c->next;
+ if(strcmp(c->verb, "reset") == 0){
+ for(s = svcs; s != nil; s = s->next)
+ if(s->pid != 0 || s->state == Srunning)
+ stopsvc(s);
+ for(s = svcs; s != nil; s = s->next)
+ if(s->enable){
+ s->wanted = 1;
+ s->state = Swaiting;
+ }
+ free(c);
+ continue;
+ }
+ s = findname(c->name);
+ if(s == nil){
+ free(c);
+ continue;
+ }
+ if(strcmp(c->verb, "start") == 0){
+ if(s->pid == 0 && s->state != Srunning){
+ s->wanted = 1;
+ s->tretry = 0;
+ s->state = Swaiting;
+ }
+ }else if(strcmp(c->verb, "stop") == 0)
+ stopsvc(s);
+ else if(strcmp(c->verb, "restart") == 0){
+ stopsvc(s);
+ s->wanted = 1;
+ s->state = Swaiting;
+ }
+ free(c);
+ }
+}
+
+static void
+startall(void)
+{
+ Svc *s;
+
+ for(s = svcs; s != nil; s = s->next)
+ if(s->enable){
+ s->wanted = 1;
+ s->state = Swaiting;
+ }
+}
+
+static int
+needtick(void)
+{
+ Svc *s;
+
+ if(fsrunning) /* commands may arrive at any time */
+ return 1;
+ for(s = svcs; s != nil; s = s->next){
+ if(s->state == Swaiting || s->state == Sstarting)
+ return 1;
+ if(s->state == Srunning && s->kind == Ksrv)
+ return 1;
+ if(s->pid != 0 && s->wanted == 0)
+ return 1; /* waiting for it to go */
+ }
+ return 0;
+}
+
+static void
+armtick(void)
+{
+ int pid;
+
+ if(tickpid != 0 || !needtick())
+ return;
+ switch(pid = rfork(RFPROC|RFNOTEG)){
+ case -1:
+ return;
+ case 0:
+ sleep(Tickms);
+ _exits(nil);
+ }
+ tickpid = pid;
+}
+
+/* called with statelock held */
+static void
+ontick(void)
+{
+ Svc *s;
+ long now;
+
+ now = time(nil);
+ for(s = svcs; s != nil; s = s->next){
+ /* a service we asked to stop that will not go */
+ if(s->pid != 0 && s->wanted == 0 && now - s->tstop >= Stopwait){
+ if(verbose)
+ fprint(2, "init: %s did not stop, killing\n", s->name);
+ postnote(PNGROUP, s->pid, "kill");
+ postnote(PNPROC, s->pid, "kill");
+ s->tstop = now;
+ }
+ switch(s->state){
+ case Swaiting:
+ if(s->wanted && now >= s->tretry && depsok(s) && s->pid == 0)
+ startsvc(s);
+ break;
+ case Sstarting:
+ if(isready(s)){
+ s->state = Srunning;
+ if(verbose)
+ fprint(2, "init: %s ready\n", s->name);
+ }else if(now - s->tstart >= Readywait)
+ failsvc(s, "not ready in time");
+ break;
+ case Srunning:
+ if(s->kind == Ksrv && s->wanted && !srvexists(s->readyarg)){
+ if(verbose)
+ fprint(2, "init: %s: /srv/%s went away\n",
+ s->name, s->readyarg);
+ s->pid = 0;
+ if(strcmp(s->restart, "never") == 0){
+ s->state = Sstopped;
+ s->wanted = 0;
+ break;
+ }
+ if(toofast(s)){
+ failsvc(s, "restarting too fast");
+ break;
+ }
+ s->restarts++;
+ s->state = Swaiting;
+ s->tretry = now + Restartwait;
+ }
+ break;
+ }
+ }
+}
+
+/* called with statelock held */
+static void
+reap(Waitmsg *w)
+{
+ Svc *s;
+ int failed;
+
+ s = findpid(w->pid);
+ if(s == nil)
+ return;
+ s->pid = 0;
+ failed = w->msg[0] != '\0';
+ free(s->exits);
+ s->exits = failed ? estrdup(w->msg) : nil;
+
+ if(verbose || failed)
+ fprint(2, "init: %s exited%s%s\n", s->name,
+ failed ? ": " : " cleanly", failed ? w->msg : "");
+
+ if(s->kind == Kexit){
+ s->wanted = 0;
+ s->state = failed ? Sfailed : Sdone;
+ return;
+ }
+ if(s->kind == Ksrv){
+ /*
+ * Its process exiting is normal: the service is whatever is
+ * behind /srv, and the tick watches that.
+ */
+ if(failed && s->state == Sstarting)
+ failsvc(s, w->msg);
+ return;
+ }
+ /*
+ * If we asked it to go, this is not a crash: do not count it
+ * against the restart rate, or restarting a service by hand a
+ * few times would mark it failed.
+ */
+ if(s->stopping){
+ s->stopping = 0;
+ s->state = s->wanted ? Swaiting : Sstopped;
+ return;
+ }
+ if(!s->wanted){
+ s->state = Sstopped;
+ return;
+ }
+ if(strcmp(s->restart, "always") == 0
+ || (strcmp(s->restart, "onfail") == 0 && failed)){
+ if(toofast(s)){
+ failsvc(s, "restarting too fast");
+ return;
+ }
+ s->restarts++;
+ s->state = Swaiting;
+ s->tretry = time(nil) + Restartwait;
+ return;
+ }
+ s->wanted = 0;
+ s->state = failed ? Sfailed : Sstopped;
+}
+
+/* called with statelock held */
+char*
+statusline(Svc *s)
+{
+ char buf[512], *p, *e;
+ Svc *d;
+ int i;
+
+ p = buf;
+ e = buf + sizeof buf;
+ p = seprint(p, e, "svc=%s state=%s", s->name, statename[s->state]);
+ if(s->pid != 0)
+ p = seprint(p, e, " pid=%d", s->pid);
+ if(s->restarts != 0)
+ p = seprint(p, e, " restarts=%d", s->restarts);
+ if(s->state == Swaiting)
+ for(i = 0; i < s->nneeds; i++){
+ d = findname(s->needs[i]);
+ if(d != nil && d->state != Srunning && d->state != Sdone)
+ p = seprint(p, e, " needs=%s", s->needs[i]);
+ }
+ if(s->enable)
+ p = seprint(p, e, " enable=yes");
+ if(s->exits != nil)
+ p = seprint(p, e, " exit=%q", s->exits);
+ seprint(p, e, "\n");
+ return strdup(buf);
+}
+
+/* called with statelock held */
+char*
+allstatus(void)
+{
+ Svc *s;
+ char *all, *one, *t;
+
+ all = strdup("");
+ if(all == nil)
+ return nil;
+ for(s = svcs; s != nil; s = s->next){
+ one = statusline(s);
+ if(one == nil)
+ break;
+ t = smprint("%s%s", all, one);
+ free(one);
+ if(t == nil)
+ break;
+ free(all);
+ all = t;
+ }
+ return all;
+}
+
+static void
+usage(void)
+{
+ fprint(2, "usage: init [-v] [-d svcdir] [-l logdir] "
+ "[-s srvname] [-m mtpt]\n");
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ Waitmsg *w;
+ char *srvname, *mtpt;
+
+ quotefmtinstall();
+ srvname = nil;
+ mtpt = nil;
+
+ ARGBEGIN{
+ case 'd':
+ svcdir = EARGF(usage());
+ break;
+ case 'l':
+ logdir = EARGF(usage());
+ break;
+ case 's':
+ srvname = EARGF(usage());
+ break;
+ case 'm':
+ mtpt = EARGF(usage());
+ break;
+ case 'v':
+ verbose++;
+ break;
+ default:
+ usage();
+ }ARGEND;
+
+ if(argc != 0)
+ usage();
+
+ loadsvcs();
+ if(svcs == nil)
+ sysfatal("no services loaded from %s", svcdir);
+
+ qlock(&statelock);
+ startall();
+ ontick();
+ qunlock(&statelock);
+
+ /*
+ * The control plane is optional: if it will not come up we say so
+ * and carry on supervising. A machine must not fail to boot over
+ * a filesystem nobody may ever mount.
+ */
+ if(srvname != nil || mtpt != nil){
+ startfs(srvname != nil ? srvname : "svc", mtpt);
+ fsrunning = 1;
+ }
+
+ for(;;){
+ armtick();
+ w = wait();
+ if(w == nil){
+ fprint(2, "init: wait: %r\n");
+ sleep(1000);
+ continue;
+ }
+ if(w->pid == tickpid){
+ tickpid = 0;
+ qlock(&statelock);
+ draincmds();
+ ontick();
+ qunlock(&statelock);
+ }else{
+ qlock(&statelock);
+ reap(w);
+ qunlock(&statelock);
+ }
+ free(w);
+ }
+}
diff --git a/svc/src/mkfile b/svc/src/mkfile
new file mode 100644
index 0000000..5e1326e
--- /dev/null
+++ b/svc/src/mkfile
@@ -0,0 +1,9 @@
+</$objtype/mkfile
+
+TARG=init
+OFILES=init.$O fs.$O
+HFILES=dat.h
+LIB=/$objtype/lib/lib9p.a /$objtype/lib/libthread.a /$objtype/lib/libndb.a /$objtype/lib/libbio.a
+BIN=/$objtype/bin
+
+</sys/src/cmd/mkone