chiark / gitweb /
spec.sgml: Update for update copyright notices
[userv.git] / overlord.c
index e32c9a10db0e2757cef1891af07a76dc1b21b81d..2ae825021c67f6e975ed0476688da2fe6cda6da8 100644 (file)
@@ -2,7 +2,7 @@
  * userv - overlord.c
  * daemon main program, collects request and forks handlers
  *
- * Copyright (C)1996-1997 Ian Jackson
+ * Copyright (C)1996-1997,1999 Ian Jackson
  *
  * This is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License as published by
 #include <errno.h>
 #include <string.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <assert.h>
 #include <fnmatch.h>
 #include <sys/wait.h>
 #include <sys/types.h>
-#include <sys/time.h>
 #include <sys/stat.h>
 #include <sys/socket.h>
+#include <fcntl.h>
+#include <time.h>
 #include <dirent.h>
 #include <sys/un.h>
 
@@ -41,8 +43,8 @@
 
 pid_t overlordpid;
 
-static pid_t checkpid= -1;
-static sig_atomic_t needcheck= 1;
+static pid_t checkpid= -1, detachpid= -1;
+static sig_atomic_t needcheck= 1; /* 2 means we half-expect the server to be down */
 
 static void checkstalepipes(void) {
   /* There is an unimportant race here.  If there is a stale pipe but
@@ -66,7 +68,7 @@ static void checkstalepipes(void) {
   if (time(&now) == -1) { syslog(LOG_ERR,"get current time: %m"); return; }
   dir= opendir(".");
   if (!dir) { syslog(LOG_ERR,"open directory " VARDIR ": %m"); return; }
-  while ((de= readdir(dir))) {
+  while ((errno=0, de= readdir(dir))) {
     if (fnmatch(PIPEPATTERN,de->d_name,FNM_PATHNAME|FNM_PERIOD)) continue;
     r= lstat(de->d_name,&stab); if (r && errno==ENOENT) continue;
     if (r) { syslog(LOG_ERR,"could not stat `" VARDIR "/%s': %m",de->d_name); continue; }
@@ -75,31 +77,37 @@ static void checkstalepipes(void) {
     if (unlink(de->d_name) && errno!=ENOENT)
       syslog(LOG_ERR,"could not remove stale pipe `%s': %m",de->d_name);
   }
+  if (errno) syslog(LOG_ERR,"read directory " VARDIR ": %m");
   if (closedir(dir)) syslog(LOG_ERR,"close directory " VARDIR ": %m");
 }
 
 static void sighandler_chld(int x) {
   pid_t r;
-  int status, es, ar;
+  int status, es;
 
   es= errno;
   for (;;) {
     r= waitpid((pid_t)-1,&status,WNOHANG);
     if (!r || (r==-1 && errno==ECHILD)) break;
     if (r==-1) { syslog(LOG_ERR,"wait in sigchild handler gave error: %m"); break; }
+    if (r==detachpid) {
+      if (WIFEXITED(status) && WEXITSTATUS(status)==4) _exit(4);
+      fprintf(stderr,"uservd: detaching child failed with unexpected code %d\n",status);
+      exit(6);
+    }
     if (r==checkpid) {
       if (WIFEXITED(status)) {
        if (!WEXITSTATUS(status)) {
-         syslog(LOG_NOTICE,"no longer the uservd - exiting");
-         _exit(0);
+         syslog(LOG_WARNING,"no longer the uservd - exiting");
+         _exit(2);
        } else if (WEXITSTATUS(status)!=1) {
          syslog(LOG_ERR,"check pid %ld exited with status %d",
                 (long)checkpid,WEXITSTATUS(status));
        }
       } else if (WIFSIGNALED(status)) {
        if (WTERMSIG(status) == SIGALRM && !WCOREDUMP(status)) {
-         syslog(LOG_NOTICE,"check timed out; no longer the uservd - exiting");
-         _exit(0);
+         syslog(LOG_WARNING,"check timed out; no longer the uservd - exiting");
+         _exit(2);
        } else {
          syslog(LOG_ERR,"check pid %ld %s due to signal %s",
                 (long)checkpid,
@@ -111,8 +119,7 @@ static void sighandler_chld(int x) {
               (long)checkpid,status);
       }
       checkpid= -1;
-      ar= alarm(USERVD_MYSELF_CHECK);
-      if (ar<0) { syslog(LOG_CRIT,"set alarm for next check: %m"); exit(5); }
+      alarm(USERVD_MYSELF_CHECK);
     } else {
       if (WIFSIGNALED(status)) {
        syslog(LOG_ERR,"call pid %ld %s due to signal %s",
@@ -122,11 +129,14 @@ static void sighandler_chld(int x) {
       } else if (!WIFEXITED(status)) {
        syslog(LOG_ERR,"call pid %ld died due to unknown reason, code %d",
               (long)r,status);
+      } else if (WEXITSTATUS(status)==10) {
+       needcheck= 2;
       } else if (WEXITSTATUS(status)>12) {
        if (WEXITSTATUS(status)>24)
          syslog(LOG_ERR,"call pid %ld exited with status %d >24",
                 (long)r,WEXITSTATUS(status));
        checkstalepipes();
+       needcheck= 1;
       }
     }
   }
@@ -134,10 +144,19 @@ static void sighandler_chld(int x) {
   return;
 }
 
+static void sighandler_usr1(int x) {
+  _exit(0);
+}
+
 static void sighandler_alrm(int x) {
   needcheck= 1;
 }
 
+static void sighandler_termint(int sig) {
+  syslog(LOG_NOTICE,"terminating due to signal %s",strsignal(sig));
+  _exit(1);
+}
+
 static void blocksignals(int how) {
   int r;
   sigset_t set;
@@ -145,10 +164,12 @@ static void blocksignals(int how) {
   sigemptyset(&set);
   sigaddset(&set,SIGCHLD);
   sigaddset(&set,SIGALRM);
+  sigaddset(&set,SIGTERM);
+  sigaddset(&set,SIGINT);
   r= sigprocmask(how,&set,0); assert(!r);
 }
 
-static void NONRETURNING docheck(void) {
+static void NONRETURNING docheck(int needwanted) {
 #ifndef DEBUG
   /* This subprocess exits with status 0 if the parent should die,
    * 1 if it should not, and something else if it fails horribly.
@@ -156,6 +177,8 @@ static void NONRETURNING docheck(void) {
   int sfd, r, remain;
   unsigned char *p;
   struct opening_msg opening_mbuf;
+  struct request_msg request_mbuf;
+  unsigned long endmagic;
   struct sigaction sig;
   struct sockaddr_un ssockname;
 
@@ -178,13 +201,15 @@ static void NONRETURNING docheck(void) {
 
   r= connect(sfd,(struct sockaddr*)&ssockname,sizeof(ssockname));
   if (r) {
-    if (errno == ECONNREFUSED || errno == ENOENT)
-      { syslog(LOG_NOTICE,"uservd daemon is not running: %m"); exit(0); }
+    if (errno == ECONNREFUSED || errno == ENOENT) {
+      if (needwanted != 2)
+       syslog(LOG_WARNING,"real uservd daemon is not running: %m");
+      exit(0);
+    }
     syslog(LOG_ERR,"unable to connect to uservd daemon: %m"); exit(1);
   }
 
-  r= alarm(USERVD_MYSELF_TIMEOUT);
-  if (r<0) { syslog(LOG_ERR,"set alarm for read: %m"); exit(1); }
+  alarm(USERVD_MYSELF_TIMEOUT);
   remain= sizeof(opening_mbuf); p= (unsigned char*)&opening_mbuf;
   while (remain) {
     r= read(sfd,p,remain);
@@ -193,53 +218,87 @@ static void NONRETURNING docheck(void) {
     remain-= r; p+= r;
   }
   if (opening_mbuf.magic != OPENING_MAGIC) {
-    syslog(LOG_NOTICE,"magic number mismatch");
+    syslog(LOG_WARNING,"magic number mismatch");
     exit(0);
   }
   if (memcmp(opening_mbuf.protocolchecksumversion,protocolchecksumversion,PCSUMSIZE)) {
-    syslog(LOG_NOTICE,"protocol checksum mismatch");
+    syslog(LOG_WARNING,"protocol checksum mismatch");
     exit(0);
   }
   if (opening_mbuf.overlordpid != overlordpid) {
-    syslog(LOG_NOTICE,"overlord pid mismatch");
+    syslog(LOG_WARNING,"overlord pid mismatch");
     exit(0);
   }
-  syslog(LOG_NOTICE,"check - same daemon still running");
+  memset(&request_mbuf,0,sizeof(request_mbuf));
+  request_mbuf.magic= REQUEST_MAGIC;
+  request_mbuf.clientpid= -1;
+  request_mbuf.serviceuserlen= 0;
+  request_mbuf.servicelen= 0;
+  request_mbuf.loginnamelen= 0;
+  request_mbuf.spoofed= 0;
+  request_mbuf.cwdlen= 0;
+  request_mbuf.overridelen= -1;
+  request_mbuf.callinguid= -1;
+  request_mbuf.ngids= 0;
+  request_mbuf.nreadfds= 0;
+  request_mbuf.nwritefds= 0;
+  request_mbuf.nargs= 0;
+  request_mbuf.nvars= 0;
+  r= write(sfd,&request_mbuf,sizeof(request_mbuf));
+  if (r==sizeof(request_mbuf)) {
+    endmagic= REQUEST_END_MAGIC;
+    write(sfd,&endmagic,sizeof(endmagic));
+  }
+  syslog(LOG_NOTICE,"uservd[%ld] is running",(long)overlordpid);
 #endif
   exit(1);
 }
 
+static void NONRETURNING startupsyscallerr(const char *what) {
+  fprintf(stderr,
+         "uservd: system call failed during startup:\n"
+         "uservd: %s: %s\n",
+         what,strerror(errno));
+  exit(4);
+}
+
 int main(int argc, char *const *argv) {
-  int mfd, sfd, csocklen, e;
+  int mfd, sfd, nfd, e, r, becomedaemon;
+  socklen_t csocklen;
   struct sigaction sigact;
   struct sockaddr_un ssockname, csockname;
-  pid_t child;
+  pid_t child, parentpid, sid;
 
 #ifdef NDEBUG
   abort(); /* Do not disable assertions in this security-critical code ! */
 #endif
 
-  if (argc>1) { fputs("usage: uservd\n",stderr); exit(3); }
+  becomedaemon= 0;
+  
+  if (argv[1] && !strcmp(argv[1],"-daemon")) {
+    becomedaemon= 1;
+    argv++; argc--;
+  }
+  if (argc>1) { fputs("usage: uservd [-daemon]\n",stderr); exit(3); }
 
   openlog(USERVD_LOGIDENT,LOG_NDELAY|LOG_PID,USERVD_LOGFACILITY);
 
-  if (chdir(VARDIR)) { syslog(LOG_CRIT,"cannot change to " VARDIR ": %m"); exit(4); }
+  if (chdir(VARDIR)) startupsyscallerr("cannot change to " VARDIR);
   checkstalepipes();
 
-  overlordpid= getpid();
-  if (overlordpid==-1) { syslog(LOG_CRIT,"cannot getpid: %m"); exit(4); }
+  overlordpid= parentpid= getpid();
+  if (parentpid==-1) startupsyscallerr("cannot getpid");
 
   mfd= socket(AF_UNIX,SOCK_STREAM,0);
-  if (mfd<0) { syslog(LOG_CRIT,"cannot create master socket: %m"); exit(4); }
+  if (mfd<0) startupsyscallerr("cannot create master socket");
 
   assert(sizeof(ssockname.sun_path) > sizeof(RENDEZVOUS));
   ssockname.sun_family= AF_UNIX;
   strcpy(ssockname.sun_path,RENDEZVOUS);
   unlink(RENDEZVOUS);
-  if (bind(mfd,(struct sockaddr*)&ssockname,sizeof(ssockname)))
-    { syslog(LOG_CRIT,"cannot bind master socket: %m"); exit(4); }
-  if (listen(mfd,5))
-    { syslog(LOG_CRIT,"cannot listen on master socket: %m"); exit(4); }
+  r= bind(mfd,(struct sockaddr*)&ssockname,sizeof(ssockname));
+  if (r) startupsyscallerr("cannot bind master socket");
+  if (listen(mfd,5)) startupsyscallerr("cannot listen on master socket");
 
   sigemptyset(&sigact.sa_mask);
   sigaddset(&sigact.sa_mask,SIGCHLD);
@@ -247,20 +306,60 @@ int main(int argc, char *const *argv) {
   sigact.sa_flags= SA_NOCLDSTOP;
 
   sigact.sa_handler= sighandler_chld;
-  if (sigaction(SIGCHLD,&sigact,0))
-    { syslog(LOG_CRIT,"cannot setup sigchld handler: %m"); exit(4); }
+  if (sigaction(SIGCHLD,&sigact,0)) startupsyscallerr("cannot setup sigchld handler");
 
   sigact.sa_handler= sighandler_alrm;
-  if (sigaction(SIGALRM,&sigact,0))
-    { syslog(LOG_CRIT,"cannot setup sigalrm handler: %m"); exit(4); }
+  if (sigaction(SIGALRM,&sigact,0)) startupsyscallerr("cannot setup sigalrm handler");
+
+  if (becomedaemon) {
+    sigact.sa_handler= sighandler_usr1;
+    if (sigaction(SIGUSR1,&sigact,0)) startupsyscallerr("cannot setup sigusr1 handler");
+    
+    detachpid= fork(); if (detachpid==-1) startupsyscallerr("cannot fork to detach");
+    if (detachpid) {
+      pause();
+      fputs("uservd: pause unexpectedly returned during detach\n",stderr);
+      exit(4);
+    }
+    sigact.sa_handler= SIG_DFL;
+    if (sigaction(SIGUSR1,&sigact,0)) startupsyscallerr("cannot restore sigusr1");
+  }
+
+  sigact.sa_handler= sighandler_termint;
+  if (sigaction(SIGTERM,&sigact,0)) startupsyscallerr("cannot setup sigterm handler");
+  if (sigaction(SIGINT,&sigact,0)) startupsyscallerr("cannot setup sigint handler");
+
+  if (becomedaemon) {
+    nfd= open("/dev/null",O_RDWR);
+    if (nfd<0) startupsyscallerr("cannot open /dev/null");
+    sid= setsid(); if (sid == -1) startupsyscallerr("cannot create new session");
+    overlordpid= getpid();
+    if (overlordpid == -1) startupsyscallerr("getpid after detach");
+    if (dup2(nfd,0)<0 || dup2(nfd,1)<0)
+      startupsyscallerr("cannot dup /dev/null for stdin/out");
+    r= kill(parentpid,SIGUSR1); if (r) startupsyscallerr("send SIGUSR1 to detach");
+    r= dup2(nfd,2);
+    if (r<0) { syslog(LOG_CRIT,"cannot dup /dev/null for stderr: %m"); exit(5); }
+    close(nfd);
+  }
 
   syslog(LOG_NOTICE,"started");
+
   for (;;) {
     if (needcheck) {
-      assert(checkpid==-1);
-      checkpid= fork();
-      if (checkpid==-1) { syslog(LOG_CRIT,"fork for check: %m"); exit(5); }
-      if (!checkpid) docheck();
+      while (checkpid==-1) {
+       checkpid= fork();
+       if (checkpid!=-1) {
+         if (!checkpid) docheck(needcheck);
+         break;
+       } else if (errno==EAGAIN) {
+         syslog(LOG_ERR,"fork for check - will wait and retry: %m");
+         alarm(USERVD_CHECKFORK_RETRY);
+         break;
+       } else if (errno!=EINTR) {
+         syslog(LOG_CRIT,"fork for check: %m"); exit(5);
+       }
+      }
       needcheck= 0;
     }
     csocklen= sizeof(csockname);