admin: Implement job table infrastructure.
[tripe] / server / admin.c
index 6ff8815..5030dc2 100644 (file)
@@ -65,6 +65,7 @@ static admin *admins;
 static admin *a_dead;
 static sel_file sock;
 static const char *sockname;
+static sym_table a_svcs;
 static unsigned flags = 0;
 static admin *a_stdin = 0;
 static sig s_term, s_int, s_hup;
@@ -731,6 +732,257 @@ static int a_bgadd(admin *a, admin_bgop *bg, const char *tag,
   return (0);
 }
 
+/*----- Job table manipulation --------------------------------------------*/
+
+#define JOB_SHIFT 16
+#define JOB_INDEXMASK ((1ul << JOB_SHIFT) - 1)
+#define JOB_SEQMASK ((1ul << (32 - JOB_SHIFT)) - 1)
+
+#define JOB_END 0xfffffffful
+
+static unsigned long a_joboffset;
+
+/* --- @a_jobidencode@ --- *
+ *
+ * Arguments:  @admin_svcop *svc@ = pointer to a service operation
+ *
+ * Returns:    A jobid for this job, in an internal static buffer.
+ *
+ * Use:                Constructs a jobid.  In order to dissuade people from
+ *             predicting jobids, we obfuscate them.
+ *
+ *             A `raw' jobid consists of two 16-bit fields.  The low 16 bits
+ *             are an index into a big array.  The high 16 bits are a
+ *             sequence number recording how many times that slot has been
+ *             reused.
+ *
+ *             This `raw' jobid is then obfuscated by adding a randomly-
+ *             generated offset, and multiplying (mod %$2^{32}$%) by a fixed
+ *             odd constant.
+ */
+
+static const char *a_jobidencode(admin_svcop *svc)
+{
+  admin_jobtable *j = &svc->prov->j;
+  static char buf[10];
+  unsigned long pre;
+  unsigned seq;
+
+  assert(svc->index <= JOB_INDEXMASK);
+  seq = j->v[svc->index].seq;
+  assert(seq <= JOB_SEQMASK);
+  pre = (unsigned long)svc->index | ((unsigned long)seq << JOB_SHIFT);
+  sprintf(buf, "J%08lx", ((pre + a_joboffset) * 0x0f87a7a3ul) & 0xffffffff);
+  return (buf);
+}
+
+/* --- @a_jobiddecode@ --- *
+ *
+ * Arguments:  @admin_jobtable *j@ = pointer to a job table
+ *             @const char *jid@ = pointer to a jobid string
+ *
+ * Returns:    A pointer to the job's @svcop@ structure.
+ */
+
+static admin_svcop *a_jobiddecode(admin_jobtable *j, const char *jid)
+{
+  unsigned i;
+  unsigned long pre;
+
+  if (jid[0] != 'J')
+    return (0);
+  for (i = 1; i < 9; i++) {
+    if (!isxdigit((unsigned char)jid[i]))
+      return (0);
+  }
+  if (jid[9] != 0)
+    return (0);
+  pre = strtoul(jid + 1, 0, 16);
+  pre = ((pre * 0xbd11c40bul) - a_joboffset) & 0xffffffff;
+  i = pre & JOB_INDEXMASK;
+  if (i >= j->n || j->v[i].seq != (pre >> JOB_SHIFT))
+    return (0);
+  return (j->v[i].u.op);
+}
+
+/* --- @a_jobcreate@ --- *
+ *
+ * Arguments:  @admin *a@ = pointer to administration client
+ *
+ * Returns:    A pointer to a freshly-allocated @svcop@, or null.
+ *
+ * Use:                Allocates a fresh @svcop@ and links it into a job table.
+ */
+
+static admin_svcop *a_jobcreate(admin *a)
+{
+  admin_svcop *svc;
+  unsigned i;
+  unsigned sz;
+  admin_jobtable *j = &a->j;
+
+  if (j->free != JOB_END) {
+    i = j->free;
+    j->free = j->v[i].u.next;
+  } else {
+    if (j->n == j->sz) {
+      if (j->sz > JOB_INDEXMASK)
+       return (0);
+      sz = j->sz;
+      if (!sz) {
+       j->sz = 16;
+       j->v = xmalloc(j->sz * sizeof(*j->v));
+      } else {
+       j->sz = sz << 1;
+       j->v = xrealloc(j->v, j->sz * sizeof(*j->v), sz * sizeof(*j->v));
+      }
+    }
+    i = j->n++;
+    j->v[i].seq = 0;
+  }
+  svc = xmalloc(sizeof(*svc));
+  svc->index = i;
+  svc->prov = a;
+  svc->next = j->active;
+  svc->prev = 0;
+  if (j->active) j->active->prev = svc;
+  j->active = svc;
+  j->v[i].u.op = svc;
+  IF_TRACING(T_ADMIN, {
+    trace(T_ADMIN, "admin: created job %s (%u)", a_jobidencode(svc), i);
+  })
+  return (svc);
+}
+
+/* --- @a_jobdestroy@ --- *
+ *
+ * Arguments:  @admin_svcop *svc@ = pointer to job block
+ *
+ * Returns:    ---
+ *
+ * Use:                Frees up a completed (or cancelled) job.
+ */
+
+static void a_jobdestroy(admin_svcop *svc)
+{
+  admin *a = svc->prov;
+  admin_jobtable *j = &a->j;
+  unsigned i = svc->index;
+
+  IF_TRACING(T_ADMIN, {
+    trace(T_ADMIN, "admin: destroying job %s (%u)", a_jobidencode(svc), i);
+  })
+  assert(j->v[i].u.op = svc);
+  j->v[i].u.next = j->free;
+  j->v[i].seq++;
+  j->free = i;
+  if (svc->next) svc->next->prev = svc->prev;
+  if (svc->prev) svc->prev->next = svc->next;
+  else j->active = svc->next;
+}
+
+/* --- @a_jobtableinit@ --- *
+ *
+ * Arguments:  @admin_jobtable *j@ = pointer to job table
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes a job table.
+ */
+
+static void a_jobtableinit(admin_jobtable *j)
+{
+  if (!a_joboffset)
+    a_joboffset = GR_RANGE(&rand_global, 0xffffffff) + 1;
+  j->n = j->sz = 0;
+  j->active = 0;
+  j->free = JOB_END;
+  j->v = 0;
+}
+
+/* --- @a_jobtablefinal@ --- *
+ *
+ * Arguments:  @admin_jobtable *j@ = pointer to job table
+ *
+ * Returns:    ---
+ *
+ * Use:                Closes down a job table.
+ */
+
+static void a_jobtablefinal(admin_jobtable *j)
+{
+  admin_svcop *svc, *ssvc;
+
+  for (svc = j->active; svc; svc = ssvc) {
+    ssvc = svc->next;
+    a_bgfail(&svc->bg, "provider-failed", A_END);
+    a_bgrelease(&svc->bg);
+  }
+  if (j->v) xfree(j->v);
+}
+
+/*----- Services infrastructure -------------------------------------------*/
+
+/* --- @a_svcfind@ --- *
+ *
+ * Arguments:  @admin *a@ = the requesting client
+ *             @const char *name@ = service name wanted
+ *
+ * Returns:    The service requested, or null.
+ *
+ * Use:                Finds a service; reports an error if the service couldn't be
+ *             found.
+ */
+
+static admin_service *a_svcfind(admin *a, const char *name)
+{
+  admin_service *svc;
+
+  if ((svc = sym_find(&a_svcs, name, -1, 0, 0)) == 0) {
+    a_fail(a, "unknown-service", "%s", name, A_END);
+    return (0);
+  }
+  return (svc);
+}
+
+/* --- @a_svcunlink@ --- *
+ *
+ * Arguments:  @admin_service *svc@ = pointer to service structure
+ *
+ * Returns:    ---
+ *
+ * Use:                Unlinks the service from its provider, without issuing a
+ *             message or freeing the structure.  The version string is
+ *             freed, however.
+ */
+
+static void a_svcunlink(admin_service *svc)
+{
+  if (svc->next)
+    svc->next->prev = svc->prev;
+  if (svc->prev)
+    svc->prev->next = svc->next;
+  else
+    svc->prov->svcs = svc->next;
+  xfree(svc->version);
+}
+
+/* --- @a_svcrelease@ --- *
+ *
+ * Arguments:  @admin_service *svc@ = pointer to service structure
+ *
+ * Returns:    ---
+ *
+ * Use:                Releases a service and frees its structure.
+ */
+
+static void a_svcrelease(admin_service *svc)
+{
+  a_notify("SVCRELEASE", "%s", SYM_NAME(svc), A_END);
+  a_svcunlink(svc);
+  sym_remove(&a_svcs, svc);
+}
+
 /*----- Name resolution operations ----------------------------------------*/
 
 /* --- @a_resolved@ --- *
@@ -843,7 +1095,7 @@ static void a_resolve(admin *a, admin_resop *r, const char *tag,
   if (*p) {
     struct servent *s = getservbyname(av[i + 1], "udp");
     if (!s) {
-      a_fail(a, "unknown-port", "%s", av[i + 1], A_END);
+      a_fail(a, "unknown-service", "%s", av[i + 1], A_END);
       goto fail;
     }
     pt = ntohs(s->s_port);
@@ -1135,6 +1387,86 @@ static void acmd_ping(admin *a, unsigned ac, char *av[])
 static void acmd_eping(admin *a, unsigned ac, char *av[])
   { a_ping(a, ac, av, "eping", MISC_EPING); }
 
+/*----- Service commands --------------------------------------------------*/
+
+static void acmd_svcclaim(admin *a, unsigned ac, char *av[])
+{
+  admin_service *svc;
+  unsigned f;
+
+  svc = sym_find(&a_svcs, av[0], -1, sizeof(*svc), &f);
+  if (f) {
+    if (versioncmp(av[1], svc->version) <= 0) {
+      a_fail(a,
+            "service-exists",
+            "%s", SYM_NAME(svc),
+            "%s", svc->version,
+            A_END);
+      return;
+    }
+    a_write(svc->prov, "SVCCLAIM", 0, "%s", av[0], "%s", av[1], A_END);
+    a_svcunlink(svc);
+  }
+  svc->prov = a;
+  svc->version = xstrdup(av[1]);
+  svc->next = a->svcs;
+  svc->prev = 0;
+  if (a->svcs) a->svcs->prev = svc;
+  a->svcs = svc;
+  a_notify("SVCCLAIM", "%s", SYM_NAME(svc), "%s", svc->version, A_END);
+  a_ok(a);
+}
+
+static void acmd_svcrelease(admin *a, unsigned ac, char *av[])
+{
+  admin_service *svc;
+
+  if ((svc = a_svcfind(a, av[0])) == 0)
+    return;
+  if (svc->prov != a) {
+    a_fail(a, "not-service-provider", "%s", SYM_NAME(svc), A_END);
+    return;
+  }
+  a_svcrelease(svc);
+  a_ok(a);
+}
+
+static void acmd_svcensure(admin *a, unsigned ac, char *av[])
+{
+  admin_service *svc;
+
+  if ((svc = a_svcfind(a, av[0])) == 0)
+    return;
+  if (av[1] && versioncmp(svc->version, av[1]) < 0) {
+    a_fail(a, "service-too-old",
+          "%s", SYM_NAME(svc),
+          "%s", svc->version,
+          A_END);
+    return;
+  }
+  a_ok(a);
+}
+
+static void acmd_svcquery(admin *a, unsigned ac, char *av[])
+{
+  admin_service *svc;
+
+  if ((svc = a_svcfind(a, av[0])) != 0) {
+    a_info(a, "name=%s", SYM_NAME(svc), "version=%s", svc->version, A_END);
+    a_ok(a);
+  }
+}
+
+static void acmd_svclist(admin *a, unsigned ac, char *av[])
+{
+  admin_service *svc;
+  sym_iter i;
+
+  for (sym_mkiter(&i, &a_svcs); (svc = sym_next(&i)) != 0; )
+    a_info(a, "%s", SYM_NAME(svc), "%s", svc->version, A_END);
+  a_ok(a);
+}
+
 /*----- Administration commands -------------------------------------------*/
 
 /* --- Miscellaneous commands --- */
@@ -1475,6 +1807,11 @@ static const acmd acmdtab[] = {
   { "reload",  0,                      0,      0,      acmd_reload },
   { "servinfo",        0,                      0,      0,      acmd_servinfo },
   { "setifname", "PEER NEW-NAME",      2,      2,      acmd_setifname },
+  { "svcclaim",        "SERVICE VERSION",      2,      2,      acmd_svcclaim },
+  { "svcensure", "SERVICE [VERSION]",  1,      2,      acmd_svcensure },
+  { "svclist", 0,                      0,      0,      acmd_svclist },
+  { "svcquery",        "SERVICE",              1,      1,      acmd_svcquery },
+  { "svcrelease", "SERVICE",           1,      1,      acmd_svcrelease },
   { "stats",   "PEER",                 1,      1,      acmd_stats },
 #ifndef NTRACE
   { "trace",   "[OPTIONS]",            0,      1,      acmd_trace },
@@ -1513,6 +1850,7 @@ static void a_destroypending(void)
 {
   admin *a, *aa;
   admin_bgop *bg, *bbg;
+  admin_service *svc, *ssvc;
 
   /* --- Destroy connections marked as pending --- */
 
@@ -1534,6 +1872,14 @@ static void a_destroypending(void)
       xfree(bg);
     }
 
+    /* --- Release services I hold, and abort pending jobs --- */
+
+    for (svc = a->svcs; svc; svc = ssvc) {
+      ssvc = svc->next;
+      a_svcrelease(svc);
+    }
+    a_jobtablefinal(&a->j);
+
     /* --- Close file descriptors and selectory --- */
 
     selbuf_destroy(&a->b);
@@ -1659,6 +2005,8 @@ void a_create(int fd_in, int fd_out, unsigned f)
   T( trace(T_ADMIN, "admin: accepted connection %u", a->seq); )
   a->bg = 0;
   a->ref = 0;
+  a->svcs = 0;
+  a_jobtableinit(&a->j);
   a->f = f;
   if (fd_in == STDIN_FILENO) a_stdin = a;
   fdflags(fd_in, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
@@ -1743,6 +2091,10 @@ void a_init(const char *name)
   struct sigaction sa;
   size_t sz;
 
+  /* --- Create services table --- */
+
+  sym_create(&a_svcs);
+
   /* --- Set up the socket address --- */
 
   sz = strlen(name) + 1;