chiark / gitweb /
prefork-interp: clean up old sockets, fix
[chiark-utils.git] / cprogs / prefork-interp.c
index 56e848ad1adcceb8d9f64303d6abd43287a30821..b59eed8204cd367d52e6bd98d70f44404554fb3f 100644 (file)
@@ -118,6 +118,8 @@ void fusagemessage(FILE *f) {
 }
 
 static int laundering;
+static int max_sockets = 100; // maximum entries in the run dir is 2x this
+
 static struct stat initial_stab;
 
 const struct cmdinfo cmdinfos[]= {
@@ -174,6 +176,111 @@ static void propagate_exit_status(int status, const char *what) {
   die("%s failed with weird wait status %d 0x%x", what, status, status);
 }
 
+typedef struct {
+  char *name_hash;
+  time_t atime;
+} PrecleanEntry;
+
+static int preclean_entry_compar_name(const void *av, const void *bv) {
+  const PrecleanEntry *a = av;
+  const PrecleanEntry *b = bv;
+  return strcmp(a->name_hash, b->name_hash);
+}
+
+static int preclean_entry_compar_atime(const void *av, const void *bv) {
+  const PrecleanEntry *ae = av;  time_t a = ae->atime;
+  const PrecleanEntry *be = bv;  time_t b = be->atime;
+  return (a > b ? +1 :
+         a < b ? -1 : 0);
+}
+
+static time_t preclean_stat_atime(const char *s_path) {
+  struct stat stab;
+  int r= lstat(s_path, &stab);
+  if (r) {
+    if (errno!=ENOENT) diee("pre-cleanup: stat socket (%s)", s_path);
+    return 0;
+  }
+  return stab.st_atime;
+}
+
+static void preclean(void) {
+  DIR *dir = opendir(run_base);
+  if (!dir) {
+    if (errno == ENOENT) return;
+    diee("pre-cleanup: open run dir (%s)", run_base);
+  }
+
+  PrecleanEntry *entries=0;
+  size_t avail_entries=0;
+  size_t used_entries=0;
+
+  struct dirent *de;
+  while ((errno = 0, de = readdir(dir))) {
+    char c0 = de->d_name[0];
+    if (!(c0 == 'l' || c0 == 's')) continue;
+    char *name_hash = m_asprintf("%s", de->d_name+1);
+    char *s_path = m_asprintf("%s/s%s", run_base, name_hash);
+    time_t atime = preclean_stat_atime(s_path);
+
+    if (avail_entries == used_entries) {
+      assert(avail_entries < INT_MAX / 4 / sizeof(PrecleanEntry));
+      avail_entries <<= 1;
+      avail_entries += 10;
+      entries = realloc(entries, avail_entries * sizeof(PrecleanEntry));
+    }
+    entries[used_entries].name_hash = name_hash;
+    entries[used_entries].atime = atime;
+    used_entries++;
+  }
+  if (errno) diee("pre-cleanup: read run dir (%s)", run_base);
+
+  // First we dedupe (after sorting by path)
+  qsort(entries, used_entries, sizeof(PrecleanEntry),
+       preclean_entry_compar_name);
+  PrecleanEntry *p, *q;
+  for (p=entries, q=entries; p < entries + used_entries; p++) {
+    if (q > entries && !strcmp(p->name_hash, (q-1)->name_hash))
+      continue;
+    *q++ = *p;
+  }
+  used_entries = q - entries;
+
+  // Now maybe delete some things
+  //
+  // Actually this has an off-by-one error since we are about
+  // to create a socket, so the actual number of sockets is one more.
+  // But, *actually*, since there might be multiple of us running at once,
+  // we might have even more than that.  This doesn't really matter.
+  if (used_entries > max_sockets) {
+    qsort(entries, used_entries, sizeof(PrecleanEntry),
+         preclean_entry_compar_atime);
+    for (p=entries; p < entries + max_sockets; p++) {
+      char *l_path = m_asprintf("%s/l%s", run_base, p->name_hash);
+      char *s_path = m_asprintf("%s/s%s", run_base, p->name_hash);
+      int lock_fd = flock_file(l_path);
+      // Recheck atime - we might have raced!
+      time_t atime = preclean_stat_atime(s_path);
+      if (atime != p->atime) {
+       // Raced.  This will leave use deleting too few things.  Whatever.
+      } else {
+       int r= unlink(s_path);
+       if (r && errno!=ENOENT) diee("preclean: delete stale (%s)", s_path);
+       r= unlink(l_path);
+       if (r) diee("preclean: delete stale lock (%s)", s_path);
+       // NB we don't hold the lock any more now.
+      }
+      close(lock_fd);
+      free(l_path);
+      free(s_path);
+    }
+  }
+
+  for (p=entries; p < entries + used_entries; p++)
+    free(p->name_hash);
+  free(entries);
+}
+
 static __attribute((noreturn)) void die_data_overflow(void) {
   die("cannot handle data with length >2^32");
 }
@@ -271,13 +378,14 @@ static void send_request(void) {
   size_t len = 0;
   prepare_message(&len, 0);
 
-  char *m = xmalloc(len + 4);
+  size_t tlen = len + 4;
+  char *m = xmalloc(tlen);
   char *p = m;
   prepare_length(0, &p, len);
   prepare_message(0, &p);
-  assert(p == m + len + 4);
+  assert(p == m + tlen);
 
-  ssize_t sr = fwrite(p, len, 1, call_sock);
+  ssize_t sr = fwrite(m, tlen, 1, call_sock);
   if (sr != 1) diee("write request (buffer)");
 
   if (fflush(call_sock)) diee("write request");
@@ -390,8 +498,7 @@ static void watcher_cb_sockpath(uv_fs_event_t *handle, const char *filename,
       if (errno==EINTR) continue;
       diee("stat socket: %s", socket_path);
     }
-    if (!(now_stab.st_dev == initial_stab.st_dev &&
-         now_stab.st_ino == initial_stab.st_ino))
+    if (!stabs_same_inode(&now_stab, &initial_stab))
       _exit(0);
   }
 }
@@ -446,6 +553,7 @@ void become_setup(int sfd, int fake_pair[2],
 
   int null_0 = open("/dev/null", O_RDONLY);  if (null_0 < 0) diee("open null");
   if (dup2(null_0, 0)) diee("dup2 /dev/null onto stdin");
+  close(null_0);
   if (dup2(2, 1) != 1) die("dup2 stderr onto stdout");
 
   nonblock(sfd);
@@ -469,6 +577,9 @@ static void connect_or_spawn(void) {
   call_sock = connect_existing();
   if (call_sock) return;
 
+  // We're going to make a new one, so clean out old ones
+  preclean();
+
   int lockfd = acquire_lock();
   call_sock = connect_existing();
   if (call_sock) { close(lockfd); return; }