chiark / gitweb /
prefork-interp: clean up old sockets
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Sat, 20 Aug 2022 12:03:40 +0000 (13:03 +0100)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Sun, 21 Aug 2022 20:21:10 +0000 (21:21 +0100)
Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
cprogs/prefork-interp.c
cprogs/prefork.c
cprogs/prefork.h

index 2a7fdc35c54dd8a24b56316abf94c64be1f1b722..305314a8c285e9c6669bff67b37656a7c279b3a9 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");
 }
@@ -470,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; }
index 12d60218c5adad56b45138d7329cc67172dbe907..e25aadf5af74e443585de21182a7fc66309e19a7 100644 (file)
@@ -5,9 +5,9 @@
 const char *interp, *ident, *script, *socket_path, *lock_path;
 bool logging;
 struct sha256_ctx identsc;
+const char *run_base;
 
 static uid_t us;
-static const char *run_base;
 static const char *run_base_mkdir_p;
 
 void common_diee(const char *m) { diee("%s", m); }
index 11329cef1f59dec4fcccadc8e36a68bcc4beb3af..66ed5fc8f223043e43b9f0fed6564df0e0c8e803 100644 (file)
@@ -37,6 +37,7 @@
 extern const char *interp, *ident, *script, *socket_path, *lock_path;
 extern bool logging;
 extern struct sha256_ctx identsc;
+extern const char *run_base;
 
 extern const char our_name[];