summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorGerrit Pape <pape@smarden.org>2002-09-23 10:12:50 +0000
committerGerrit Pape <pape@smarden.org>2002-09-23 10:12:50 +0000
commit7aef323f749e96b290afde337762f7cafff17c41 (patch)
treeaacee902f6fc57cbe8447c482107e08bd8d68832 /src
parent70af2b4add8297968bfa6bf02fe7b462b4353d90 (diff)
downloadrunit-7aef323f749e96b290afde337762f7cafff17c41.tar.gz
runit-7aef323f749e96b290afde337762f7cafff17c41.tar.xz
runit-7aef323f749e96b290afde337762f7cafff17c41.zip
* runsv, runsvdir: new; svscan/supervise replacement.
0.5.2.
Diffstat (limited to 'src')
-rw-r--r--src/Makefile16
-rw-r--r--src/TARGETS4
-rw-r--r--src/runsv.c470
-rw-r--r--src/runsvdir.c242
4 files changed, 730 insertions, 2 deletions
diff --git a/src/Makefile b/src/Makefile
index 62e3fda..2f8f043 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -1,4 +1,4 @@
-IT=runit runit-init svwaitup svwaitdown utmpset sysdeps
+IT=sysdeps runit runit-init runsv runsvdir svwaitup svwaitdown utmpset
 
 default: $(IT)
 
@@ -8,18 +8,30 @@ runit: load runit.o unix.a byte.a
 runit-init: load runit-init.o unix.a byte.a
 	./load runit-init unix.a byte.a -static
 
+runsv: load runsv.o unix.a byte.a time.a
+	./load runsv unix.a byte.a time.a
+
+runsvdir: load runsvdir.o unix.a byte.a time.a
+	./load runsvdir unix.a byte.a time.a
+
 svwaitup: load svwaitup.o unix.a byte.a time.a
 	./load svwaitup unix.a byte.a time.a
 
 svwaitdown: load svwaitdown.o unix.a byte.a time.a
 	./load svwaitdown unix.a byte.a time.a
 
-runit.o: compile runit.c iopause.h uint64.h
+runit.o: compile sysdeps runit.c
 	./compile runit.c
 
 runit-init.o: compile runit-init.c
 	./compile runit-init.c
 
+runsv.o: compile sysdeps runsv.c
+	./compile runsv.c
+
+runsvdir.o: compile runsvdir.c direntry.h iopause.h uint64.h
+	./compile runsvdir.c
+
 svwaitup.o: compile svwaitup.c uint64.h
 	./compile svwaitup.c
 
diff --git a/src/TARGETS b/src/TARGETS
index 83dd880..7a67a57 100644
--- a/src/TARGETS
+++ b/src/TARGETS
@@ -2,6 +2,10 @@ runit
 runit.o
 runit-init
 runit-init.o
+runsv
+runsv.o
+runsvdir
+runsvdir.o
 svwaitdown
 svwaitdown.o
 svwaitup
diff --git a/src/runsv.c b/src/runsv.c
new file mode 100644
index 0000000..04a3cc6
--- /dev/null
+++ b/src/runsv.c
@@ -0,0 +1,470 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <signal.h>
+#include "strerr.h"
+#include "error.h"
+#include "taia.h"
+#include "sig.h"
+#include "env.h"
+#include "coe.h"
+#include "ndelay.h"
+#include "fifo.h"
+#include "open.h"
+#include "lock.h"
+#include "iopause.h"
+#include "wait.h"
+#include "fd.h"
+#include "buffer.h"
+#include "fmt.h"
+
+#define USAGE " dir"
+
+#define VERSION "$Id$"
+
+char *progname;
+int selfpipe[2];
+
+/* state */
+#define S_DOWN 0
+#define S_RUN 1
+#define S_FINISH 2
+/* ctrl */
+#define C_NOOP 0
+#define C_TERM 1
+#define C_PAUSE 2
+/* want */
+#define W_UP 0
+#define W_DOWN 1
+#define W_EXIT 2
+
+struct svdir {
+  int pid;
+  int state;
+  int ctrl;
+  int want;
+  struct taia start;
+  int fdlock;
+  int fdcontrol;
+  int fdcontrolwrite;
+  //  int fdok;
+  int islog;
+};
+struct svdir svd[2];
+
+int haslog =0;
+int logpipe[2];
+char *dir;
+
+void usage () {
+  strerr_die4x(1, "usage: ", progname, USAGE, "\n");
+}
+void fatal(char *m) {
+  strerr_die5sys(111, "runsv ", dir, ": fatal: ", m, ": ");
+}
+void fatal2(char *m1, char *m2) {
+  strerr_die6sys(111, "runsv ", dir, ": fatal: ", m1, m2, ": ");
+}
+void warn(char *m) {
+  strerr_warn5("runsv ", dir, ": warning: ", m, ": ", &strerr_sys);
+}
+void warnx(char *m) {
+  strerr_warn4("runsv ", dir, ": warning: ", m, 0);
+}
+
+void stopservice(struct svdir *);
+
+void s_child() {
+  write(selfpipe[1], "", 1);
+}
+void s_term() {
+  svd[0].want =W_EXIT;
+  stopservice(&svd[0]);
+  write(selfpipe[1], "", 1);
+}
+
+void update_status(struct svdir *s) {
+  unsigned long l;
+  int fd;
+  char status[19];
+  char bspace[64];
+  buffer b;
+  char spid[FMT_ULONG];
+  
+  if (s->islog) {
+    if ((fd =open_trunc("log/supervise/state")) == -1)
+      fatal("unable to open log/supervise/state");
+  }
+  else {
+    if ((fd =open_trunc("supervise/state")) == -1)
+      fatal("unable to open supervise/state");
+  }
+  buffer_init(&b, buffer_unixwrite, fd, bspace, sizeof bspace);
+  spid[fmt_ulong(spid, (unsigned long)s->pid)] =0;
+  switch (s->state) {
+  case S_DOWN:
+    buffer_puts(&b, "down");
+    break;
+  case S_RUN:
+    buffer_puts(&b, "run");
+    break;
+  case S_FINISH:
+    buffer_puts(&b, "finish");
+    break;
+  }
+  if (s->pid) {
+    buffer_puts(&b, " pid (");
+    buffer_puts(&b, spid);
+    buffer_puts(&b, ")");
+  }
+  if (s->ctrl & C_PAUSE)
+    buffer_puts(&b, ", paused");
+  if (s->ctrl & C_TERM)
+    buffer_puts(&b, ", got TERM");
+  switch(s->want) {
+  case W_DOWN:
+    if (s->state != S_DOWN) buffer_puts(&b, ", want down");
+    break;
+  case W_EXIT:
+    buffer_puts(&b, ", want exit");
+    break;
+  }
+  buffer_putsflush(&b, "\n");
+  close(fd);
+
+  /* supervise compatibility */
+  taia_pack(status, &s->start);
+  l =(unsigned long)s->pid;
+  status[12] =l; l >>=8;
+  status[13] =l; l >>=8;
+  status[14] =l; l >>=8;
+  status[15] =l;
+  if (s->ctrl & C_PAUSE)
+    status[16] =1;
+  else
+    status[16] =0;
+  if (s->want == W_UP)
+    status[17] ='u';
+  else
+    status[17] ='d';
+  if (s->ctrl & C_TERM)
+    status[18] =1;
+  else
+    status[18] =0;
+  if ((fd =open_trunc("supervise/status.new")) == -1) {
+    warn("unable to open supervise/status.new");
+    return;
+  }
+  if ((l =write(fd, status, sizeof status)) == -1) {
+    warn("unable to write supervise/status.new");
+    close(fd);
+    unlink("supervise/status.new");
+    return;
+  }
+  close(fd);
+  if (l < sizeof status) {
+    warnx("unable to write supervise/status.new: partial write.");
+    return;
+  }
+  if (s->islog) {
+    if (rename("supervise/status.new", "log/supervise/status") == -1)
+      warn("unable to rename supervise/status.new to log/supervise/status");
+  }
+  else {
+    if (rename("supervise/status.new", "supervise/status") == -1)
+      warn("unable to rename supervise/status.new to supervise/status");
+  }
+}
+
+void stopservice(struct svdir *s) {
+  if (s->pid) kill(s->pid, SIGTERM);
+  s->ctrl |=C_TERM;
+  update_status(s);
+}
+
+void startservice(struct svdir *s) {
+  int p;
+  char *run[2];
+  
+  if (s->state == S_FINISH)
+    run[0] ="./finish";
+  else
+    run[0] ="./run";
+  run[1] =0;
+
+  if (s->pid != 0) stopservice(s); /* should never happen */
+  while ((p =fork()) == -1) {
+    warn("unable to fork, sleeping");
+    sleep(5);
+  }
+  if (p == 0) {
+    /* child */
+    if (haslog) {
+      if (s->islog) {
+	if (fd_copy(0, logpipe[0]) == -1)
+	  fatal("unable to setup filedescriptor for ./log/run");
+	if (chdir("./log") == -1)
+	  fatal("unable to change directory ./log");
+      }
+      else {
+	if (fd_copy(1, logpipe[1]) == -1)
+	  fatal("unable to setup filedescriptor for ./run");
+      }
+    }
+    sig_uncatch(sig_child);
+    sig_unblock(sig_child);
+    sig_uncatch(sig_term);
+    sig_unblock(sig_term);
+    execve(*run, run, environ);
+    if (s->islog)
+      fatal2("unable to start log/", *run);
+    else
+      fatal2("unable to start ", *run);
+  }
+  if (s->state != S_FINISH)
+    taia_now(&s->start);
+  s->pid =p;
+  s->ctrl =C_NOOP;
+  if (s->state != S_FINISH) s->state =S_RUN;
+  update_status(s);
+  sleep(1);
+}
+
+int ctrl(struct svdir *s, char c) {
+  switch(c) {
+  case 'd': /* down */
+    s->want =W_DOWN;
+    if (s->pid && s->state != S_FINISH) stopservice(s);
+    else update_status(s);
+    break;
+  case 'u': /* up */
+    s->want =W_UP;
+    if (s->pid == 0) startservice(s);
+    else update_status(s);
+    break;
+  case 'x': /* exit */
+    if (s->islog) break;
+    s->want =W_EXIT;
+    if (s->pid && s->state != S_FINISH)
+      stopservice(s);
+    break;
+  case 't': /* sig term */
+    if (s->pid && s->state != S_FINISH) stopservice(s);
+    break;
+  case 'k': /* sig kill */
+    if (s->pid) kill(s->pid, SIGKILL);
+    s->state =S_DOWN;
+    break;
+  case 'p': /* sig pause */
+    kill(s->pid, SIGSTOP);
+    s->ctrl |=C_PAUSE;
+    update_status(s);
+    break;
+  case 'c': /* sig cont */
+    kill(s->pid, SIGCONT);
+    if (s->ctrl & C_PAUSE)
+      s->ctrl &=~C_PAUSE;
+    update_status(s);
+    break;
+  case 'o': /* once */
+    s->want =W_DOWN;
+    if (! s->pid) startservice(s);
+    else update_status(s);
+    break;
+  case 'a': /* sig alarm */
+    if (s->pid) kill(s->pid, SIGALRM);
+    break;
+  case 'h': /* sig hup */
+    if (s->pid) kill(s->pid, SIGHUP);
+    break;
+  case 'i': /* sig int */
+    if (s->pid) kill(s->pid, SIGINT);
+    break;
+  }
+  return(1);
+}
+
+int main(int argc, char **argv) {
+  struct stat s;
+  int fd;
+
+  progname =argv[0];
+  if (! argv[1] || argv[2]) usage();
+  dir =argv[1];
+
+  if (pipe(selfpipe) == -1)
+    fatal("unable to create selfpipe");
+  coe(selfpipe[0]);
+  coe(selfpipe[1]);
+  ndelay_on(selfpipe[0]);
+  ndelay_on(selfpipe[1]);
+  
+  sig_block(sig_child);
+  sig_catch(sig_child, s_child);
+  sig_block(sig_term);
+  sig_catch(sig_term, s_term);
+
+  if (chdir(dir) == -1)
+    fatal("unable to change to directory");
+  svd[0].pid =0;
+  svd[0].state =S_DOWN;
+  svd[0].ctrl =C_NOOP;
+  svd[0].want =W_UP;
+  svd[0].islog =0;
+  taia_now(&svd[0].start);
+  if (stat("down", &s) != -1)
+    svd[0].want =W_DOWN;
+
+  if (stat("log", &s) == -1) {
+    if (errno != error_noent)
+      warn("unable to stat() ./log: ");
+  }
+  else {
+    if (! S_ISDIR(s.st_mode))
+      warnx("./log: not a directory.");
+    else {
+      haslog =1;
+      svd[1].pid =0;
+      svd[1].state =S_DOWN;
+      svd[1].ctrl =C_NOOP;
+      svd[1].want =W_UP;
+      svd[1].islog =1;
+      taia_now(&svd[1].start);
+      if (stat("log/down", &s) != -1)
+	svd[1].want =W_DOWN;
+    }
+  }
+
+  if (haslog) {
+    if (pipe(logpipe) == -1)
+      fatal("unable to create log pipe");
+    coe(logpipe[0]);
+    coe(logpipe[1]);
+  }
+
+  mkdir("supervise", 0700);
+  if ((svd[0].fdlock =open_append("supervise/lock")) == -1)
+    fatal("unable to open lock.");
+  if (lock_exnb(svd[0].fdlock) == -1)
+    fatal("unable to lock.");
+  if (haslog) {
+    mkdir("log/supervise", 0700);
+    if ((svd[1].fdlock =open_append("log/supervise/lock")) == -1)
+      fatal("unable to open log/lock.");
+    if (lock_ex(svd[1].fdlock) == -1)
+      fatal("unable to log/lock.");
+  }
+
+  fifo_make("supervise/control", 0600);
+  if ((svd[0].fdcontrol =open_read("supervise/control")) == -1)
+    fatal("unable to open supervise/control");
+  coe(svd[0].fdcontrol);
+  if ((svd[0].fdcontrolwrite =open_write("supervise/control")) == -1)
+    fatal("unable to open supervise/control");
+  coe(svd[0].fdcontrolwrite);
+  update_status(&svd[0]);
+  if (haslog) {
+    fifo_make("log/supervise/control", 0600);
+    if ((svd[1].fdcontrol =open_read("log/supervise/control")) == -1)
+      fatal("unable to open log/supervise/control");
+    coe(svd[1].fdcontrol);
+    if ((svd[1].fdcontrolwrite =open_write("log/supervise/control")) == -1)
+      fatal("unable to open log/supervise/control");
+    coe(svd[1].fdcontrolwrite);
+    update_status(&svd[1]);
+  }
+  fifo_make("supervise/ok",0600);
+  if ((fd =open_read("supervise/ok")) == -1)
+    fatal("unable to read supervise/ok");
+  coe(fd);
+  if (haslog) {
+    fifo_make("log/supervise/ok",0600);
+    if ((fd =open_read("log/supervise/ok")) == -1)
+      fatal("unable to read log/supervise/ok");
+    coe(fd);
+  }
+  for (;;) {
+    iopause_fd x[3];
+    struct taia deadline;
+    struct taia now;
+    char ch;
+
+    if (haslog)
+      if (! svd[1].pid && svd[1].want == W_UP) startservice(&svd[1]);
+    if (! svd[0].pid && svd[0].want == W_UP) startservice(&svd[0]);
+
+    x[0].fd =selfpipe[0];
+    x[0].events =IOPAUSE_READ;
+    x[1].fd =svd[0].fdcontrol;
+    x[1].events =IOPAUSE_READ;
+    if (haslog) {
+      x[2].fd =svd[1].fdcontrol;
+      x[2].events =IOPAUSE_READ;
+    }
+    taia_now(&now);
+    taia_uint(&deadline, 3600);
+    taia_add(&deadline, &now, &deadline);
+
+    sig_unblock(sig_term);
+    sig_unblock(sig_child);
+    iopause(x, 2 +haslog, &deadline, &now);
+    sig_block(sig_term);
+    sig_block(sig_child);
+
+    while (read(selfpipe[0], &ch, 1) == 1)
+      ;
+    for (;;) {
+      int child;
+      int wstat;
+      
+      child = wait_nohang(&wstat);
+      if (!child) break;
+      if ((child == -1) && (errno != error_intr)) break;
+      if (child == svd[0].pid) {
+	svd[0].pid =0;
+	if (open_read("finish") != -1) {
+	  svd[0].state =S_FINISH;
+	  startservice(&svd[0]);
+	  update_status(&svd[0]);
+	  break;
+	}
+	svd[0].state =S_DOWN;
+	taia_now(&svd[0].start);
+	update_status(&svd[0]);
+	if (svd[0].want == W_UP) {
+	  startservice(&svd[0]);
+	  break;
+	}
+      }
+      if (haslog) {
+	if (child == svd[1].pid) {
+	  svd[1].pid =0;
+	  svd[1].state =S_DOWN;
+	  taia_now(&svd[1].start);
+	  update_status(&svd[1]);
+	  if (svd[1].want == W_UP) {
+	    startservice(&svd[1]);
+	    break;
+	  }
+	}
+      }
+    }
+    if (read(svd[0].fdcontrol, &ch, 1) == 1)
+      ctrl(&svd[0], ch);
+    if (haslog) {
+      if (read(svd[1].fdcontrol, &ch, 1) == 1)
+	ctrl(&svd[1], ch);
+    }
+
+    if (svd[0].want == W_EXIT && svd[0].pid == 0) {
+      if (svd[1].pid == 0)
+	exit(0);
+      if (svd[1].want != W_EXIT) {
+	svd[1].want =W_EXIT;
+	stopservice(&svd[1]);
+      }
+    }
+  }
+  exit(0);
+}
diff --git a/src/runsvdir.c b/src/runsvdir.c
new file mode 100644
index 0000000..1a8040f
--- /dev/null
+++ b/src/runsvdir.c
@@ -0,0 +1,242 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <signal.h>
+#include "direntry.h"
+#include "strerr.h"
+#include "error.h"
+#include "wait.h"
+#include "env.h"
+#include "pathexec.h"
+#include "fd.h"
+#include "str.h"
+#include "coe.h"
+#include "iopause.h"
+#include "sig.h"
+#include "ndelay.h"
+
+#define USAGE " dir"
+#define VERSION "$Id$"
+
+#define MAXSERVICES 1000
+
+char *progname;
+char *svdir;
+struct {
+  unsigned long dev;
+  unsigned long ino;
+  int pid;
+  int isgone;
+} sv[MAXSERVICES];
+int svnum =0;
+int check =1;
+char *svlog =0;
+int svloglen;
+int logpipe[2];
+iopause_fd io[1];
+struct taia stamplog;
+
+void usage () {
+  strerr_die4x(1, "usage: ", progname, USAGE, "\n");
+}
+void fatal(char *m1, char *m2) {
+  strerr_die6sys(100, "runsvdir ", svdir, ": fatal: ", m1, m2, ": ");
+}
+void warn(char *m1, char *m2) {
+  strerr_warn6("runsvdir ", svdir, ": warning: ", m1, m2, ": ", &strerr_sys);
+}
+void warn3x(char *m1, char *m2, char *m3) {
+  strerr_warn6("runsvdir ", svdir, ": warning: ", m1, m2, m3, 0);
+} 
+void runsv(int no, char *name) {
+  struct stat s;
+  int pid;
+
+  if (stat(name, &s) == -1) {
+    warn("unable to stat ", name);
+    return;
+  }
+  if (! S_ISDIR(s.st_mode))
+    return;
+  if ((pid =fork()) == -1) {
+    warn("unable to fork for ", name);
+    return;
+  }
+  if (pid == 0) {
+    /* child */
+    const char *prog[3];
+
+    prog[0] ="runsv";
+    prog[1] =name;
+    prog[2] =0;
+    if (svlog)
+      if (fd_move(2, logpipe[1]) == -1)
+	warn("unable to set filedescriptor for log service", 0);
+    pathexec_run(*prog, prog, (const char* const*)environ);
+    fatal("unable to start runsv ", name);
+  }
+  sv[no].pid =pid;
+}
+
+void runsvdir() {
+  DIR *dir;
+  direntry *d;
+  int i;
+  struct stat s;
+
+  if (! (dir =opendir("."))) {
+    warn("unable to open directory ", svdir);
+    return;
+  }
+  for (i =0; i < svnum; i++)
+    sv[i].isgone =1;
+  errno =0;
+  while ((d =readdir(dir))) {
+    if (d->d_name[0] == '.') continue;
+    if (stat(d->d_name, &s) == -1) {
+      warn("unable to stat ", d->d_name);
+      return;
+    }
+    for (i =0; i < svnum; i++) {
+      if ((sv[i].ino == s.st_ino) && (sv[i].dev == s.st_dev)) {
+	sv[i].isgone =0;
+	if (! sv[i].pid)
+	  runsv(i, d->d_name);
+	break;
+      }
+    }
+    if (i == svnum) {
+      /* new service */
+      if (svnum >= MAXSERVICES) {
+	warn3x("unable to start runsv ", d->d_name, ": too many services.");
+	continue;
+      }
+      sv[i].ino =s.st_ino;
+      sv[i].dev =s.st_dev;
+      sv[i].pid =0;
+      sv[i].isgone =0;
+      svnum++;
+      runsv(i, d->d_name);
+    }
+  }
+  if (errno) {
+    warn("unable to read directory ", svdir);
+    closedir(dir);
+    return;
+  }
+  closedir(dir);
+
+  /* SIGTERM removed runsv's */
+  for (i =0; i < svnum; i++) {
+    if (! sv[i].isgone)
+      continue;
+    if (sv[i].pid)
+      kill(sv[i].pid, SIGTERM);
+    sv[i] =sv[--svnum];
+    check =1;
+  }
+}
+
+int setup_svlog() {
+  if ((svloglen =str_len(svlog)) < 7) {
+    warn3x("log must have at least seven characters.", 0, 0);
+    return(0);
+  }
+  if (pipe(logpipe) == -1) {
+    warn3x("unable to create pipe for log.", 0, 0);
+    return(-1);
+  }
+  coe(logpipe[1]);
+  coe(logpipe[0]);
+  ndelay_on(logpipe[0]);
+  ndelay_on(logpipe[1]);
+  if (fd_copy(2, logpipe[1]) == -1) {
+    warn3x("unable to set filedescriptor for log.", 0, 0);
+    return(-1);
+  }
+  io[0].fd =logpipe[0];
+  io[0].events =IOPAUSE_READ;
+  taia_now(&stamplog);
+  return(1);
+}
+
+int main(int argc, char **argv) {
+  struct stat s;
+  time_t mtime =0;
+  int wstat;
+  int pid;
+  struct taia deadline;
+  struct taia now;
+  char ch;
+  int i;
+
+  progname =*argv++;
+  if (! argv || ! *argv) usage();
+
+  svdir =*argv++;
+  if (argv && *argv) {
+    svlog =*argv;
+    if (setup_svlog() != 1) {
+      svlog =0;
+      warn3x("log service disabled.", 0, 0);
+    }
+  }
+
+  if (chdir(svdir) == -1)
+    fatal("unable to change directory to ", svdir);
+
+  for (;;) {
+    /* collect children */
+    for (;;) {
+      if ((pid =wait_nohang(&wstat)) <= 0) break;
+      for (i =0; i < svnum; i++) {
+	if (pid == sv[i].pid) {
+	  /* runsv has gone */
+	  sv[i].pid =0;
+	  check =1;
+	  break;
+	}
+      }
+    }
+    if (stat(".", &s) != -1) {
+      if (check || s.st_mtime > mtime) {
+	/* svdir modified */
+	mtime =s.st_mtime;
+	check =0;
+	runsvdir();
+      }
+    }
+    else
+      warn("unable to stat ", svdir);
+
+    taia_now(&now);
+    if (svlog)
+      if (taia_less(&now, &stamplog) == 0) {
+	write(logpipe[1], ".", 1);
+	taia_uint(&deadline, 900);
+	taia_add(&stamplog, &stamplog, &deadline);
+      }
+    taia_uint(&deadline, 5);
+    taia_add(&deadline, &now, &deadline);
+
+    sig_block(sig_child);
+    if (svlog)
+      iopause(io, 1, &deadline, &now);
+    else
+      iopause(0, 0, &deadline, &now);
+    sig_unblock(sig_child);
+
+    if (svlog)
+      if (io[0].revents | IOPAUSE_READ) {
+	while (read(logpipe[0], &ch, 1) > 0) {
+	  if (ch) {
+	    for (i =6; i < svloglen; i++)
+	      svlog[i -1] =svlog[i];
+	    svlog[svloglen -1] =ch;
+	  }
+	}
+      }
+  }
+  /* not reached */
+  exit(0);
+}