chiark / gitweb /
Convert track names and input lines to NFC. This is a database format
[disorder] / server / server.c
index 6bfee6b67323b8332c34c0710a80307943831012..64502c0330d7588e4afcd27b242abefcfd9c23c7 100644 (file)
@@ -63,6 +63,7 @@
 #include "eventlog.h"
 #include "defs.h"
 #include "cache.h"
+#include "unicode.h"
 
 #ifndef NONCE_SIZE
 # define NONCE_SIZE 16
@@ -81,16 +82,30 @@ struct listener {
   int pf;
 };
 
+/** @brief One client connection */
 struct conn {
+  /** @brief Read commands from here */
   ev_reader *r;
+  /** @brief Send responses to here */
   ev_writer *w;
+  /** @brief Underlying file descriptor */
   int fd;
+  /** @brief Unique identifier for connection used in log messages */
   unsigned tag;
+  /** @brief Login name or NULL */
   char *who;
+  /** @brief Event loop */
   ev_source *ev;
+  /** @brief Nonce chosen for this connection */
   unsigned char nonce[NONCE_SIZE];
+  /** @brief Current reader callback
+   *
+   * We change this depending on whether we're servicing the @b log command
+   */
   ev_reader_callback *reader;
+  /** @brief Event log output sending to this connection */
   struct eventlog_output *lo;
+  /** @brief Parent listener */
   const struct listener *l;
 };
 
@@ -103,38 +118,44 @@ static int reader_callback(ev_source *ev,
 
 static const char *noyes[] = { "no", "yes" };
 
+/** @brief Called when a connection's writer fails or is shut down
+ *
+ * If the connection still has a raeder that is cancelled.
+ */
 static int writer_error(ev_source attribute((unused)) *ev,
                        int errno_value,
                        void *u) {
   struct conn *c = u;
 
-  D(("server writer_error %d", errno_value));
-  info("writer_error S%x %d", c->tag, errno_value);
+  D(("server writer_error S%x %d", c->tag, errno_value));
   if(errno_value == 0) {
     /* writer is done */
-    error(errno_value, "S%x writer completed", c->tag);        /* TODO */
+    D(("S%x writer completed", c->tag));
   } else {
     if(errno_value != EPIPE)
       error(errno_value, "S%x write error on socket", c->tag);
     if(c->r) {
-      info("cancel reader");
+      D(("cancel reader"));
       ev_reader_cancel(c->r);
       c->r = 0;
     }
-    info("done cancel reader");
+    D(("done cancel reader"));
   }
   c->w = 0;
   ev_report(ev);
   return 0;
 }
 
+/** @brief Called when a conncetion's reader fails or is shut down
+ *
+ * If connection still has a writer then it is closed.
+ */
 static int reader_error(ev_source attribute((unused)) *ev,
                        int errno_value,
                        void *u) {
   struct conn *c = u;
 
-  D(("server reader_error %d", errno_value));
-  info("reader_error S%x %d", c->tag, errno_value);
+  D(("server reader_error S%x %d", c->tag, errno_value));
   error(errno_value, "S%x read error on socket", c->tag);
   if(c->w)
     ev_writer_close(c->w);
@@ -144,7 +165,7 @@ static int reader_error(ev_source attribute((unused)) *ev,
   return 0;
 }
 
-/* return true if we are talking to a trusted user */
+/** @brief Return true if we are talking to a trusted user */
 static int trusted(struct conn *c) {
   int n;
   
@@ -715,26 +736,31 @@ static int c_volume(struct conn *c,
   return 1;
 }
 
-/* we are logging, and some data is available to read */
-static int logging_reader_callback(ev_source *ev,
+/** @brief Called when data arrives on a log connection
+ *
+ * We just discard all such data.  The client may occasionally send data as a
+ * keepalive.
+ */
+static int logging_reader_callback(ev_source attribute((unused)) *ev,
                                   ev_reader *reader,
-                                  void *ptr,
+                                  void attribute((unused)) *ptr,
                                   size_t bytes,
-                                  int eof,
-                                  void *u) {
+                                  int attribute((unused)) eof,
+                                  void attribute((unused)) *u) {
   struct conn *c = u;
 
-  /* don't log to this conn any more */
-  eventlog_remove(c->lo);
-  if(c->w) {
-    /* Terminate the log output, but only if the writer hasn't been killed off
-     * from a failure on some earlier write */
-    sink_writes(ev_writer_sink(c->w), ".\n");
+  ev_reader_consume(reader, bytes);
+  if(eof) {
+    /* Oops, that's all for now */
+    D(("logging reader eof"));
+    if(c->w) {
+      D(("close writer"));
+      ev_writer_close(c->w);
+      c->w = 0;
+    }
+    c->r = 0;
   }
-  /* restore the reader callback */
-  c->reader = reader_callback;
-  /* ...and exit via it */
-  return c->reader(ev, reader, ptr, bytes, eof, u);
+  return 0;
 }
 
 static void logclient(const char *msg, void *user) {
@@ -896,6 +922,10 @@ static int c_tags(struct conn *c,
 static int c_set_global(struct conn *c,
                        char **vec,
                        int attribute((unused)) nvec) {
+  if(vec[0][0] == '_') {
+    sink_writes(ev_writer_sink(c->w), "550 cannot set internal global preferences\n");
+    return 1;
+  }
   trackdb_set_global(vec[0], vec[1], c->who);
   sink_printf(ev_writer_sink(c->w), "250 OK\n");
   return 1;
@@ -1015,6 +1045,11 @@ static int command(struct conn *c, char *line) {
   int nvec, n;
 
   D(("server command %s", line));
+  /* We force everything into NFC as early as possible */
+  if(!(line = utf8_compose_canon(line, strlen(line), 0))) {
+    sink_writes(ev_writer_sink(c->w), "500 cannot normalize command\n");
+    return 1;
+  }
   if(!(vec = split(line, &nvec, SPLIT_QUOTES, command_error, c))) {
     sink_writes(ev_writer_sink(c->w), "500 cannot parse command\n");
     return 1;
@@ -1093,7 +1128,13 @@ static int reader_callback(ev_source attribute((unused)) *ev,
   if(eof) {
     if(bytes)
       error(0, "S%x unterminated line", c->tag);
-    return ev_writer_close(c->w);
+    D(("normal reader close"));
+    c->r = 0;
+    if(c->w) {
+      D(("close associated writer"));
+      ev_writer_close(c->w);
+      c->w = 0;
+    }
   }
   return 0;
 }