chiark / gitweb /
uslip/uslip.c: Shut the server down on `SIGTERM'.
[tripe] / uslip / uslip.c
index a552ba9e952d2ce34489e9a1b7ea8f625609a978..0ed9e70e59b8da707b681dcc203d810758ce0ef7 100644 (file)
 #include <netdb.h>
 
 #include <mLib/alloc.h>
+#include <mLib/bits.h>
+#include <mLib/conn.h>
 #include <mLib/dstr.h>
 #include <mLib/fdflags.h>
 #include <mLib/mdwopt.h>
 #include <mLib/quis.h>
 #include <mLib/report.h>
 #include <mLib/sel.h>
+#include <mLib/sig.h>
 #include <mLib/sub.h>
+#include <mLib/tv.h>
 
 #include "slip.h"
 
@@ -81,6 +85,8 @@ typedef struct client {
 
 typedef struct gobbler {
   sel_file f;
+  void (*done)(struct gobbler *, int, void *);
+  void *p;
 } gobbler;
 
 typedef struct dribbler {
@@ -130,16 +136,15 @@ static void socketaddr(struct sockaddr_un *sun, size_t *sz)
 
 static void initqueue(pkq *q) { q->head = 0; q->tail = &q->head; }
 
-static pkq_node *make_pkqnode(void *p, size_t n)
+static pkq_node *make_pkqnode(const void *p, size_t n)
 {
   pkq_node *pn;
 
-  if (!n)
-    return (0);
+  if (!n) return (0);
   pn = CREATE(pkq_node);
   pn->next = 0;
   pn->buf = xmalloc(n);
-  memcpy(pn->buf, p, n);
+  if (p) memcpy(pn->buf, p, n);
   pn->sz = n;
   pn->n = 0;
   return (pn);
@@ -149,8 +154,7 @@ static int enqueue(pkq *q, pkq_node *pn)
 {
   int rc = 0;
 
-  if (!pn)
-    return (0);
+  if (!pn) return (0);
   rc = !q->head;
   pn->next = 0;
   *q->tail = pn;
@@ -188,14 +192,14 @@ static void destroy_pkq(pkq *q)
 /*----- Gobblers ----------------------------------------------------------*
  *
  * A gobbler just eats everything it sees on its input descriptor.
- * Eventually, when it sees end-of-file, it closes the input descriptor and
- * quits.
+ * Eventually, when it sees end-of-file, it closes the input descriptor,
+ * calls a user-supplied calback function, and quits.
  */
 
-static void gobbler_close(gobbler *g)
-  { if (g->f.fd != -1) { sel_rmfile(&g->f); g->f.fd = -1; } }
+static void close_gobbler(gobbler *g)
+  { if (g->f.fd != -1) { sel_rmfile(&g->f); close(g->f.fd); g->f.fd = -1; } }
 
-static void gobbler_destroy(gobbler *g) { gobbler_close(g); DESTROY(g); }
+static void destroy_gobbler(gobbler *g) { close_gobbler(g); DESTROY(g); }
 
 static void do_gobble_in(int fd, unsigned mode, void *p)
 {
@@ -209,21 +213,27 @@ static void do_gobble_in(int fd, unsigned mode, void *p)
        break;
       else {
        moan("read (gobble): %s", strerror(errno));
-       gobbler_close(g);
+       if (g->done) g->done(g, errno, g->p);
+       close_gobbler(g);
        break;
       }
     } else if (n == 0) {
-      gobbler_close(g);
+      if (g->done) g->done(g, 0, g->p);
+      close_gobbler(g);
       break;
     }
   }
 }
 
-static gobbler *make_gobbler(int fd)
+static gobbler *make_gobbler(int fd,
+                            void (*done)(gobbler *, int, void *),
+                            void *p)
 {
   gobbler *g;
 
   g = CREATE(gobbler);
+  g->done = done;
+  g->p = p;
   sel_initfile(&sel, &g->f, fd, SEL_READ, do_gobble_in, g);
   sel_addfile(&g->f);
   do_gobble_in(fd, SEL_READ, g);
@@ -311,7 +321,7 @@ static void done_client_dribble(dribbler *d, int err, void *p)
 {
   if (err)
     moan("write (client): %s", strerror(err));
-  gobbler_destroy(p);
+  destroy_gobbler(p);
   destroy_dribbler(d);
   reasons--;
 }
@@ -336,7 +346,7 @@ static void dequeue_to_waiter(void)
   }
 }
 
-static void client_destroy(client *c)
+static void destroy_client(client *c)
 {
   sel_rmfile(&c->f);
   close(c->f.fd);
@@ -365,7 +375,7 @@ static void do_client_in(int fd, unsigned mode, void *p)
        break;
       else {
        moan("read (client): %s", strerror(errno));
-       client_destroy(c);
+       destroy_client(c);
        return;
       }
     } else if (n == 0) {
@@ -374,7 +384,7 @@ static void do_client_in(int fd, unsigned mode, void *p)
        if (enqueue_dribble(dribble_out, make_pkqnode(c->d.buf, c->d.len)))
          reasons++;
       }
-      client_destroy(c);
+      destroy_client(c);
       return;
     }
     if (c->mode == '?') {
@@ -385,7 +395,7 @@ static void do_client_in(int fd, unsigned mode, void *p)
          break;
        case '<':
          w = CREATE(waiter);
-         w->g = make_gobbler(fd);
+         w->g = make_gobbler(fd, 0, 0);
          w->next = 0;
          w->fd = fd;
          *wait_tail = w;
@@ -396,7 +406,7 @@ static void do_client_in(int fd, unsigned mode, void *p)
          return;
        default:
          moan("bad client mode `%c'", buf[0]);
-         client_destroy(c);
+         destroy_client(c);
          return;
       }
     }
@@ -442,7 +452,7 @@ static void do_accept(int fd, unsigned mode, void *hunoz)
 
 /*----- Main daemon -------------------------------------------------------*/
 
-static void done_slip_dribble(dribbler *d, int err, void *p)
+static void done_slip_dribble(dribbler *d, int err, void *hunoz)
 {
   if (!err)
     reasons--;
@@ -452,6 +462,16 @@ static void done_slip_dribble(dribbler *d, int err, void *p)
     cripple_dribbler(d);
 }
 
+static void close_slip(int fd)
+{
+  switch (slipstate) {
+    case SYNC1: case SYNC2: case START: case BAD: break;
+    default: moan("eof found while processing packet (discarding)"); break;
+  }
+  close(fd); sel_rmfile(&slip_in);
+  reasons--;
+}
+
 static void do_slip_in(int fd, unsigned mode, void *hunoz)
 {
   ssize_t i, n;
@@ -468,19 +488,7 @@ static void do_slip_in(int fd, unsigned mode, void *hunoz)
   for (;;) {
     n = read(fd, buf, sizeof(buf));
     if (n == 0) {
-      switch (slipstate) {
-       case SYNC1:
-       case SYNC2:
-       case START:
-       case BAD:
-         break;
-       default:
-         moan("eof found while processing packet (discarding)");
-         break;
-      }
-      close(fd);
-      sel_rmfile(&slip_in);
-      reasons--;
+      close_slip(fd);
       return;
     } else if (n < 0) {
       if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
@@ -521,9 +529,11 @@ static void do_slip_in(int fd, unsigned mode, void *hunoz)
          switch (buf[i]) {
            case SL_ESCEND:
              DPUTC(&slipbuf, SL_END);
+             slipstate = OK;
              break;
            case SL_ESCESC:
              DPUTC(&slipbuf, SL_ESC);
+             slipstate = OK;
              break;
            case SL_END:
              moan("found escaped end byte (discard packet and resync");
@@ -531,7 +541,7 @@ static void do_slip_in(int fd, unsigned mode, void *hunoz)
              slipstate = OK;
              break;
            default:
-             moan("unspected escape char 0x%02x", buf[i]);
+             moan("unexpected escape char 0x%02x", buf[i]);
              slipstate = BAD;
              break;
          }
@@ -561,11 +571,15 @@ static void do_slip_in(int fd, unsigned mode, void *hunoz)
   }
 }
 
+static void slip_term(int n, void *fdp)
+  { close_slip(*(int *)fdp); }
+
 static void slipif(void)
 {
   int fd;
   dstr d = DSTR_INIT;
   struct sockaddr_un sun;
+  sig term;
   size_t sz;
 
   /* --- Make the socket --- */
@@ -590,6 +604,10 @@ static void slipif(void)
   dribble_out = make_dribbler(STDOUT_FILENO, done_slip_dribble, 0);
   sel_addfile(&slip_in);
 
+  sig_init(&sel);
+  sig_add(&term, SIGTERM, slip_term, &fd);
+  sig_add(&term, SIGINT, slip_term, &fd);
+
   initqueue(&q_in);
   reasons++;
 
@@ -669,9 +687,163 @@ static void shovel(int from, int to)
 static void put(void) { shovel(STDIN_FILENO, make_sock('>')); }
 static void get(void) { shovel(make_sock('<'), STDOUT_FILENO); }
 
+/*----- Flooding and sinking ----------------------------------------------*/
+
+/* --- Connection jobs --- */
+
+typedef struct conninfo {
+  struct conninfo *next;
+  conn c;
+} conninfo;
+
+#define MAXCONN 32
+static conninfo conns[MAXCONN], *freeconns;
+
+static void (*connhook)(int, conninfo *);
+
+static void connrecycle(conninfo *c) { c->next = freeconns; freeconns = c; }
+
+static void connerr(conninfo *c)
+{
+  if (errno == EAGAIN) connrecycle(c);
+  else die(EXIT_FAILURE, "connect: %s", strerror(errno));
+}
+
+static void connected(int fd, void *p)
+  { if (fd == -1) connerr(p); else connhook(fd, p); }
+
+static void conndoconnect(conninfo *c)
+{
+  int fd;
+  struct sockaddr_un sun;
+  size_t sz;
+
+  if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
+    die(EXIT_FAILURE, "socket: %s", strerror(errno));
+  socketaddr(&sun, &sz);
+  if (conn_init(&c->c, &sel, fd, (struct sockaddr *)&sun, sz, connected, c))
+    connerr(c);
+}
+
+static int timerflag;
+
+static void conndrip(struct timeval *tv, void *hunoz)
+{
+  conninfo *c, *cc;
+
+  timerflag = 0;
+  while (freeconns) {
+    c = freeconns; freeconns = 0;
+    while (c) { cc = c->next; conndoconnect(c); c = cc; }
+  }
+}
+
+static void connloop(void (*connected)(int, conninfo *))
+{
+  int i;
+  conninfo **cc;
+  sel_timer t;
+  struct timeval tv;
+
+  connhook = connected;
+  for (i = 0, cc = &freeconns; i < MAXCONN; i++) {
+    *cc = &conns[i];
+    cc = &conns[i].next;
+  }
+  *cc = 0;
+
+  for (;;) {
+    if (freeconns && !timerflag) {
+      gettimeofday(&tv, 0);
+      TV_ADDL(&tv, &tv, 0, 10000);
+      sel_addtimer(&sel, &t, &tv, conndrip, 0);
+      timerflag = 1;
+    }
+    if (sel_select(&sel))
+      die(EXIT_FAILURE, "select: %s", strerror(errno));
+  }
+}
+
+/* --- Sinking (glug glug) --- */
+
+static void close_sink(gobbler *g, int err, void *p)
+{
+  static char baton[4] = "/-\\|";
+  static int pos = 0;
+  static int count = 0;
+  static int state = '?';
+
+  conninfo *c = p;
+
+  if (!err) {
+    if (state == '?')
+      state = isatty(STDOUT_FILENO) ? 'y' : 'n';
+    if (state == 'y') {
+      if (count) count--;
+      else {
+       putchar(baton[pos]);
+       putchar('\b');
+       fflush(stdout);
+       pos++;
+       if (pos >= sizeof(baton)) pos = 0;
+       count = 128;
+      }
+    }
+  }
+  connrecycle(c);
+}
+
+static void sink_connected(int fd, conninfo *c)
+{
+  char dir = '<';
+
+  if (write(fd, &dir, 1) != 1) {
+    moan("write: %s (continuing)", strerror(errno));
+    close(fd);
+    connrecycle(c);
+    return;
+  }
+  make_gobbler(fd, close_sink, c);
+}
+
+static void sink(void) { connloop(sink_connected); }
+
+/* --- Flooding --- */
+
+static void close_flood(dribbler *d, int err, void *p)
+{
+  conninfo *c = p;
+
+  if (err) moan("write: %s (continuing)\n", strerror(errno));
+  destroy_dribbler(d);
+  connrecycle(c);
+}
+
+static void flood_connected(int fd, conninfo *c)
+{
+  static uint32 seq;
+
+  dribbler *d;
+  pkq_node *pn;
+  int i;
+
+#define FLOOD_PKSZ 1024
+
+  pn = make_pkqnode(0, 1 + FLOOD_PKSZ);
+  pn->buf[0] = '>';
+  STORE32(pn->buf + 1, seq);
+  for (i = 4; i < FLOOD_PKSZ; i++)
+    pn->buf[i + 1] = i & 0xff;
+  seq++;
+  d = make_dribbler(fd, close_flood, c);
+  enqueue_dribble(d, pn);
+}
+
+static void flood(void) { connloop(flood_connected); }
+
 /*----- Main code ---------------------------------------------------------*/
 
-static void usage(FILE *fp) { pquis(fp, "Usage: $ [-pg] SOCKET\n"); }
+static void usage(FILE *fp) { pquis(fp, "Usage: $ [-fgps] SOCKET\n"); }
 
 static void version(void)
   { pquis(stdout, "$ (" PACKAGE " version " VERSION")\n"); }
@@ -685,8 +857,10 @@ static void help(void)
 With no options, provides a SLIP interface for TrIPE.\n\
 \n\
 Options:\n\
+  -f, --flood  Send packets to TrIPE as fast as possible.\n\
+  -g, --get    Receive packet from TrIPE and write to stdout.\n\
   -p, --put    Send packet on stdin to TrIPE.\n\
-  -g, --get    Receive packet from TrIPE and write to stdout.");
+  -s, --sink   Slurp packets out of TrIPE and display progress.");
 }
 
 int main(int argc, char *argv[])
@@ -701,15 +875,17 @@ int main(int argc, char *argv[])
       { "version",             0,              0,              'v' },
       { "put",                 0,              0,              'p' },
       { "get",                 0,              0,              'g' },
+      { "flood",               0,              0,              'f' },
+      { "sink",                        0,              0,              's' },
       { 0,                     0,              0,              0 }
     };
-    i = mdwopt(argc, argv, "hvpg", opt, 0, 0, 0);
+    i = mdwopt(argc, argv, "hvpgfs", opt, 0, 0, 0);
     if (i < 0)
       break;
     switch (i) {
       case 'h': help(); return (0);
       case 'v': version(); return (0);
-      case 'p': case 'g': mode = i; break;
+      case 'p': case 'g': case 's': case 'f': mode = i; break;
       default: usage(stderr); exit(EXIT_FAILURE); break;
     }
   }
@@ -720,6 +896,8 @@ int main(int argc, char *argv[])
     case 'd': slipif(); break;
     case 'p': put(); break;
     case 'g': get(); break;
+    case 'f': flood(); break;
+    case 's': sink(); break;
   }
   return (0);
 }