chiark / gitweb /
Internal review up to end of p11.
[userv.git] / overlord.c
diff --git a/overlord.c b/overlord.c
new file mode 100644 (file)
index 0000000..be2553a
--- /dev/null
@@ -0,0 +1,181 @@
+/*
+ * userv - overlord.c
+ * daemon main program, collects request and forks handlers
+ *
+ * Copyright (C)1996-1997 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
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with userv; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <wait.h>
+#include <unistd.h>
+#include <signal.h>
+#include <syslog.h>
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <assert.h>
+#include <fnmatch.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <dirent.h>
+#include <sys/un.h>
+
+#include "config.h"
+#include "common.h"
+#include "daemon.h"
+
+static void checkstalepipes(void) {
+  /* There is an unimportant race here.  If there is a stale pipe but
+   * another pair of processes with the same pids is about to create a
+   * new one we can check that the pipe is stale before they recreate
+   * it but then only remove it afterwards; then we remove the pipe
+   * they're actually using causing that invocation to fail with
+   * ENOENT on the pipe.  However, this can only happen if things are
+   * already shafted, because we check for stale pipes at startup
+   * before any children have been started, and then only when a child
+   * dies unhelpfully - and we actually have to have some stale pipes
+   * for the race to exist.
+   */
+  DIR *dir;
+  struct dirent *de;
+  struct stat stab;
+  time_t now;
+  unsigned long timediff;
+  int r;
+  
+  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))) {
+    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; }
+    timediff= (unsigned long)now - (unsigned long)stab.st_ctime;
+    if (timediff >= (~0UL>>1) || timediff < 3600) continue;
+    if (unlink(de->d_name) && errno!=ENOENT)
+      syslog(LOG_ERR,"could not remove stale pipe `%s': %m",de->d_name);
+  }
+  if (closedir(dir)) syslog(LOG_ERR,"close directory " VARDIR ": %m");
+}
+
+static void sighandler_chld(int x) {
+  pid_t r;
+  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 (WIFSIGNALED(status)) {
+      if (WCOREDUMP(status))
+       syslog(LOG_ERR,"call pid %ld dumped core due to signal %s",(long)r,
+              strsignal(WTERMSIG(status)));
+      else
+       syslog(LOG_ERR,"call pid %ld died due to signal %s",
+              (long)r,strsignal(WTERMSIG(status)));
+    } else if (!WIFEXITED(status)) {
+      syslog(LOG_ERR,"call pid %ld died due to unknown reason, code %ld",
+            (long)r,status);
+    } else if (WEXITSTATUS(status)>12) {
+      if (WEXITSTATUS(status)>24)
+       syslog(LOG_ERR,"call pid %ld exited with status %ld >24",
+              (long)r,WEXITSTATUS(status));
+      checkstalepipes();
+    }
+  }
+  errno= es;
+  return;
+}
+
+static void blocksignals(int how) {
+  int r;
+  sigset_t set;
+
+  sigemptyset(&set);
+  sigaddset(&set,SIGCHLD);
+  r= sigprocmask(how,&set,0); assert(!r);
+}
+
+int main(int argc, char *const *argv) {
+  int mfd, sfd, csocklen, e;
+  struct sigaction childact;
+  struct sockaddr_un ssockname, csockname;
+  pid_t child;
+
+#ifdef NDEBUG
+  abort(); /* Do not disable assertions in this security-critical code ! */
+#endif
+
+  if (argc>1) { fputs("usage: uservd\n",stderr); exit(3); }
+
+  openlog(USERVD_LOGIDENT,LOG_NDELAY,USERVD_LOGFACILITY);
+
+  if (chdir(VARDIR)) { syslog(LOG_CRIT,"cannot change to " VARDIR ": %m"); exit(4); }
+  checkstalepipes();
+
+  mfd= socket(AF_UNIX,SOCK_STREAM,0);
+  if (!mfd) { syslog(LOG_CRIT,"cannot create master socket: %m"); exit(4); }
+
+  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); }
+
+  childact.sa_handler= sighandler_chld;
+  sigemptyset(&childact.sa_mask);
+  childact.sa_flags= SA_NOCLDSTOP;
+  if (sigaction(SIGCHLD,&childact,0))
+    { syslog(LOG_CRIT,"cannot setup sigchld handler: %m"); exit(4); }
+  syslog(LOG_NOTICE,"started");
+  for (;;) {
+    csocklen= sizeof(csockname);
+    blocksignals(SIG_UNBLOCK);
+    sfd= accept(mfd,(struct sockaddr*)&csockname,&csocklen);
+    e= errno;
+    blocksignals(SIG_BLOCK);
+    if (sfd<0) {
+      errno= e;
+      if (errno == EINTR) continue;
+      if (errno == ENOMEM) {
+        syslog(LOG_ERR,"unable to accept connection: %m");
+       continue;
+      } else {
+       syslog(LOG_CRIT,"unable to accept new connections: %m");
+       exit(5);
+      }
+    }
+    child= nondebug_fork();
+    if (child == (pid_t)-1) {
+      syslog(LOG_ERR,"unable to fork server: %m");
+      close(sfd);
+      continue;
+    }
+    if (!child) {
+      close(mfd);
+      closelog();
+      blocksignals(SIG_UNBLOCK);
+      servicerequest(sfd);
+    }
+    close(sfd);
+  }
+}