chiark / gitweb /
Merge uaudio stragglers.
authorRichard Kettlewell <rjk@greenend.org.uk>
Sat, 14 Mar 2009 10:49:33 +0000 (10:49 +0000)
committerRichard Kettlewell <rjk@greenend.org.uk>
Sat, 14 Mar 2009 10:49:33 +0000 (10:49 +0000)
The command backend's pause mode is now configurable, both for server
and disorder-playrtp.

CHANGES.html
README.upgrades
debian/postrm.disorder-server
lib/basen.c
lib/basen.h
lib/random.c
libtests/t-basen.c
server/server.c
server/speaker.c

index 3722b613bfd004b602587cfabf07025f66e6cd24..fe3c687f138509bc6ed222cd3469c64b4a0b5d97 100644 (file)
@@ -62,15 +62,29 @@ span.command {
 
   <div class=section>
   
-    <h3>Mac OS X</h3>
-  
+    <h3>Server</h3>
     <div class=section>
 
-      <p>The <tt>device</tt> configuration option and <tt>--device</tt> option
-      to <tt>disorder-playrtp</tt> now work.  Devices may be specified either
-      by UID or name.  Fixes <a
+      <p>The <tt>device</tt> configuration option work under OS X.  Devices may
+      be specified either by UID or name.  Fixes <a
        href="http://code.google.com/p/disorder/issues/detail?id=27">Issue
       27</a>.</p>
+
+      <p>Confirmation URLs for online registrations are cleaner now.</p>
+
+    </div>
+      
+    <h3>RTP Player</h3>
+  
+    <div class=section>
+
+      <p>There is a new <tt>--command</tt> option which allows the RTP player
+      to send audio data to a user-chosen command instead of an audio API. See
+      the man page for details.</p>
+      
+      <p>The <tt>--device</tt> option to <tt>disorder-playrtp</tt> now works
+      under OS X (as above).</p>
         
     </div>
 
index c586dd87804865bb6f1340a446e2f19910fc3099..cbe86373ebff24bb87f2f4a981f5ac10807f9450 100644 (file)
@@ -17,6 +17,14 @@ all 1.1.x versions.
 
 If you install from .deb files then much of this work is automated.
 
+* 4.x -> 4.4
+
+The syntax of confirmation strings for online registrations has changed and old
+ones no longer work.  This only affects users who registered before the upgrade
+but have not yet confirmed their login.  You can delete such half-created users
+with 'disorder deluser USERNAME' (as an administrative user, for instance as
+root on the server) and they can start the registration process again.
+
 * 3.0 -> 4.x
 
 If you customized any of the templates, you will pretty much have to start from
index 7ba0933d4f4aef6efc8bfc54582b0240bc4159bb..4cd590a460baccd27a21a3a5e0511aa74a7dc69b 100755 (executable)
@@ -30,6 +30,7 @@ cleanup_remove() {
   rm -f $state/__db.*
   rm -f $state/noticed.db
   rm -f $state/search.db
+  rm -f $state/isearch.db
   rm -f $state/tags.db
   rm -f $state/tracks.db
 }
@@ -43,6 +44,7 @@ cleanup_purge() {
   rm -f $state/prefs.db
   rm -f $state/schedule.db
   rm -f $state/users.db
+  rm -f $state/playlists.db
 }
 
 case "$1" in
index 87d2795340d28657fd53616e86aff57d30009c99..7c1f9ec5c3ad72c4a0a81004b5a6e287b4ddf72a 100644 (file)
@@ -31,7 +31,7 @@
  * @param nwords Length of bignum
  * @return !v
  */
-static int zero(const unsigned long *v, int nwords) {
+static int zero(const uint32_t *v, int nwords) {
   int n;
 
   for(n = 0; n < nwords && !v[n]; ++n)
@@ -47,7 +47,7 @@ static int zero(const unsigned long *v, int nwords) {
  *
  * The quotient is stored in @p v.
  */
-static unsigned divide(unsigned long *v, int nwords, unsigned long m) {
+static unsigned divide(uint32_t *v, int nwords, unsigned long m) {
   unsigned long r = 0, a, b;
   int n;
 
@@ -66,6 +66,31 @@ static unsigned divide(unsigned long *v, int nwords, unsigned long m) {
   return r;
 }
 
+/** @brief Multiple v by m and add a
+ * @param v Pointer to bigendian bignum
+ * @param nwords Length of bignum
+ * @param m Value to multiply by
+ * @param a Value to add
+ * @return 0 on success, non-0 on overflow
+ *
+ * Does v = m * v + a.
+ */
+static int mla(uint32_t *v, int nwords, uint32_t m, uint32_t a) {
+  int n = nwords - 1;
+  uint32_t carry = a;
+
+  while(n >= 0) {
+    const uint64_t p = (uint64_t)v[n] * m + carry;
+    carry = (uint32_t)(p >> 32);
+    v[n] = (uint32_t)p;
+    --n;
+  }
+  /* If there is still a carry then we overflowed */
+  return !!carry;
+}
+
+static const char basen_chars[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
 /** @brief Convert v to a chosen base
  * @param v Pointer to bigendian bignum (modified!)
  * @param nwords Length of bignum
@@ -75,26 +100,53 @@ static unsigned divide(unsigned long *v, int nwords, unsigned long m) {
  * @return 0 on success, -1 if the buffer is too small
  *
  * Converts @p v to a string in the given base using decimal digits, lower case
- * letter sand upper case letters as digits.
+ * letters and upper case letters as digits.
+ *
+ * The inverse of nesab().
  */
-int basen(unsigned long *v,
+int basen(uint32_t *v,
          int nwords,
          char buffer[],
          size_t bufsize,
          unsigned base) {
   size_t i = bufsize;
-  static const char chars[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
   
   do {
     if(i <= 1)
       return -1;                       /* overflow */
-    buffer[--i] = chars[divide(v, nwords, base)];
+    buffer[--i] = basen_chars[divide(v, nwords, base)];
   } while(!zero(v, nwords));
   memmove(buffer, buffer + i, bufsize - i);
   buffer[bufsize - i] = 0;
   return 0;
 }
 
+/** @brief Convert a string back to a large integer in an arbitrary base
+ * @param v Where to store result as a bigendian bignum
+ * @param nwords Space available in @p v
+ * @param s Input string
+ * @param base Number base (2..62)
+ * @return 0 on success, non-0 on overflow or invalid input
+ *
+ * The inverse of basen().  If the number is much smaller than the buffer then
+ * the first words will be 0.
+ */
+int nesab(uint32_t *v,
+          int nwords,
+          const char *s,
+          unsigned base) {
+  /* Initialize to 0 */
+  memset(v, 0, nwords * sizeof *v);
+  while(*s) {
+    const char *dp = strchr(basen_chars, *s++);
+    if(!dp)
+      return -1;
+    if(mla(v, nwords, base, dp - basen_chars))
+      return -1;
+  }
+  return 0;
+}
+
 /*
 Local Variables:
 c-basic-offset:2
index 717f2eaeb48396fa887dc3b3883d9aeb9ceef2a1..c1a60f0c7835c728b649d0a95048e6186c0057e7 100644 (file)
@@ -19,7 +19,7 @@
 #ifndef BASEN_H
 #define BASEN_H
 
-int basen(unsigned long *v,
+int basen(uint32_t *v,
          int nwords,
          char buffer[],
          size_t bufsize,
@@ -29,6 +29,11 @@ int basen(unsigned long *v,
  * Returns 0 on success or -1 on overflow.
  */
 
+int nesab(uint32_t *v,
+          int nwords,
+          const char *s,
+          unsigned base);
+
 #endif /* BASEN_H */
 
 /*
index 3f8f714a8829f9ad969a00d51de3b4de457667d4..d6be3d9f47ec27614f8a536f8a5c88a104b9b63a 100644 (file)
@@ -75,7 +75,7 @@ void random_get(void *ptr, size_t bytes) {
 
 /** @brief Return a random ID string */
 char *random_id(void) {
-  unsigned long words[2];
+  uint32_t words[2];
   char id[128];
 
   random_get(words, sizeof words);
index 715490ca9e976d9d85b1797ccef925185ed20aea..3cbc044a27ea7120f9d3f5edcc8f8a363729eb05 100644 (file)
 #include "test.h"
 
 static void test_basen(void) {
-  unsigned long v[64];
+  uint32_t v[64];
   char buffer[1024];
 
   v[0] = 999;
   insist(basen(v, 1, buffer, sizeof buffer, 10) == 0);
   check_string(buffer, "999");
+  memset(v, 0xFF, sizeof v);
+  insist(nesab(v, 1, buffer, 10) == 0);
+  check_integer(v[0], 999);
+  check_integer(v[1], 0xFFFFFFFF);
+  insist(nesab(v, 4, buffer, 10) == 0);
+  check_integer(v[0], 0);
+  check_integer(v[1], 0);
+  check_integer(v[2], 0);
+  check_integer(v[3], 999);
+  check_integer(v[4], 0xFFFFFFFF);
 
   v[0] = 1+2*7+3*7*7+4*7*7*7;
   insist(basen(v, 1, buffer, sizeof buffer, 7) == 0);
@@ -35,6 +45,24 @@ static void test_basen(void) {
   v[3] = 0x0C0D0E0F;
   insist(basen(v, 4, buffer, sizeof buffer, 256) == 0);
   check_string(buffer, "123456789abcdef");
+  memset(v, 0xFF, sizeof v);
+  insist(nesab(v, 4, buffer, 256) == 0);
+  check_integer(v[0], 0x00010203);
+  check_integer(v[1], 0x04050607);
+  check_integer(v[2], 0x08090A0B);
+  check_integer(v[3], 0x0C0D0E0F);
+  check_integer(v[4], 0xFFFFFFFF);
+  memset(v, 0xFF, sizeof v);
+  insist(nesab(v, 8, buffer, 256) == 0);
+  check_integer(v[0], 0);
+  check_integer(v[1], 0);
+  check_integer(v[2], 0);
+  check_integer(v[3], 0);
+  check_integer(v[4], 0x00010203);
+  check_integer(v[5], 0x04050607);
+  check_integer(v[6], 0x08090A0B);
+  check_integer(v[7], 0x0C0D0E0F);
+  check_integer(v[8], 0xFFFFFFFF);
 
   v[0] = 0x00010203;
   v[1] = 0x04050607;
@@ -42,6 +70,24 @@ static void test_basen(void) {
   v[3] = 0x0C0D0E0F;
   insist(basen(v, 4, buffer, sizeof buffer, 16) == 0);
   check_string(buffer, "102030405060708090a0b0c0d0e0f");
+  memset(v, 0xFF, sizeof v);
+  insist(nesab(v, 4, buffer, 16) == 0);
+  check_integer(v[0], 0x00010203);
+  check_integer(v[1], 0x04050607);
+  check_integer(v[2], 0x08090A0B);
+  check_integer(v[3], 0x0C0D0E0F);
+  check_integer(v[4], 0xFFFFFFFF);
+  memset(v, 0xFF, sizeof v);
+  insist(nesab(v, 8, buffer, 16) == 0);
+  check_integer(v[0], 0);
+  check_integer(v[1], 0);
+  check_integer(v[2], 0);
+  check_integer(v[3], 0);
+  check_integer(v[4], 0x00010203);
+  check_integer(v[5], 0x04050607);
+  check_integer(v[6], 0x08090A0B);
+  check_integer(v[7], 0x0C0D0E0F);
+  check_integer(v[8], 0xFFFFFFFF);
 
   v[0] = 0x00010203;
   v[1] = 0x04050607;
index b00a70e69af29820c5531274f313bb8fbb3a4828..767fa31773ecb2bfeb39e3d5acd4e8ba7d10a1ac 100644 (file)
  */
 
 #include "disorder-server.h"
+#include "basen.h"
 
 #ifndef NONCE_SIZE
 # define NONCE_SIZE 16
 #endif
 
 #ifndef CONFIRM_SIZE
-# define CONFIRM_SIZE 10
+/** @brief Size of nonce in confirmation string in 32-bit words
+ *
+ * 64 bits gives 11 digits (in base 62).
+ */
+# define CONFIRM_SIZE 2
 #endif
 
 int volume_left, volume_right;         /* last known volume */
@@ -1319,30 +1324,23 @@ static int c_users(struct conn *c,
   return 1;                            /* completed */
 }
 
-/** @brief Base64 mapping table for confirmation strings
- *
- * This is used with generic_to_base64() and generic_base64().  We cannot use
- * the MIME table as that contains '+' and '=' which get quoted when
- * URL-encoding.  (The CGI still does the URL encoding but it is desirable to
- * avoid it being necessary.)
- */
-static const char confirm_base64_table[] =
-  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/.*";
-
 static int c_register(struct conn *c,
                      char **vec,
                      int attribute((unused)) nvec) {
-  char *buf, *cs;
-  size_t bufsize;
-  int offset;
-
-  /* The confirmation string is base64(username;nonce) */
-  bufsize = strlen(vec[0]) + CONFIRM_SIZE + 2;
-  buf = xmalloc_noptr(bufsize);
-  offset = byte_snprintf(buf, bufsize, "%s;", vec[0]);
-  gcry_randomize(buf + offset, CONFIRM_SIZE, GCRY_STRONG_RANDOM);
-  cs = generic_to_base64((uint8_t *)buf, offset + CONFIRM_SIZE,
-                        confirm_base64_table);
+  char *cs;
+  uint32_t nonce[CONFIRM_SIZE];
+  char nonce_str[(32 * CONFIRM_SIZE) / 5 + 1];
+
+  /* The confirmation string is username/base62(nonce).  The confirmation
+   * process will pick the username back out to identify them but the _whole_
+   * string is used as the confirmation string.  Base 62 means we used only
+   * letters and digits, minimizing the chance of the URL being mispasted. */
+  gcry_randomize(nonce, sizeof nonce, GCRY_STRONG_RANDOM);
+  if(basen(nonce, CONFIRM_SIZE, nonce_str, sizeof nonce_str, 62)) {
+    error(0, "buffer too small encoding confirmation string");
+    sink_writes(ev_writer_sink(c->w), "550 Cannot create user\n");
+  }
+  byte_xasprintf(&cs, "%s/%s", vec[0], nonce_str);
   if(trackdb_adduser(vec[0], vec[1], config->default_rights, vec[2], cs))
     sink_writes(ev_writer_sink(c->w), "550 Cannot create user\n");
   else
@@ -1353,7 +1351,6 @@ static int c_register(struct conn *c,
 static int c_confirm(struct conn *c,
                     char **vec,
                     int attribute((unused)) nvec) {
-  size_t nuser;
   char *user, *sep;
   rights_type rights;
   const char *host;
@@ -1363,12 +1360,12 @@ static int c_confirm(struct conn *c,
     sink_writes(ev_writer_sink(c->w), "530 Authentication failure\n");
     return 1;
   }
-  if(!(user = generic_base64(vec[0], &nuser, confirm_base64_table))
-     || !(sep = memchr(user, ';', nuser))) {
+  /* Picking the LAST / means we don't (here) rule out slashes in usernames. */
+  if(!(sep = strrchr(vec[0], '/'))) {
     sink_writes(ev_writer_sink(c->w), "550 Malformed confirmation string\n");
     return 1;
   }
-  *sep = 0;
+  user = xstrndup(vec[0], sep - vec[0]);
   if(trackdb_confirm(user, vec[0], &rights))
     sink_writes(ev_writer_sink(c->w), "550 Incorrect confirmation string\n");
   else {
index 835830441545e0f50738d75248759c3884e29b20..b41c94f844d8f9cee721c6d959c174ddc09ddda5 100644 (file)
@@ -163,9 +163,13 @@ struct track {
 /** @brief Lock protecting data structures
  *
  * This lock protects values shared between the main thread and the callback.
- * It is needed e.g. if changing @ref playing or if modifying buffer pointers.
- * It is not needed to add a new track, to read values only modified in the
- * same thread, etc.
+ *
+ * It is held 'all' the time by the main thread, the exceptions being when
+ * called activate/deactivate callbacks and when calling (potentially) slow
+ * system calls (in particular poll(), where in fact the main thread will spend
+ * most of its time blocked).
+ *
+ * The callback holds it when it's running.
  */
 static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
 
@@ -299,7 +303,6 @@ static int speaker_fill(struct track *t) {
      t->id, t->eof, t->used));
   if(t->eof)
     return -1;
-  pthread_mutex_lock(&lock);
   if(t->used < sizeof t->buffer) {
     /* there is room left in the buffer */
     where = (t->start + t->used) % sizeof t->buffer;
@@ -334,7 +337,6 @@ static int speaker_fill(struct track *t) {
       rc = 0;
     }
   }
-  pthread_mutex_unlock(&lock);
   return rc;
 }
 
@@ -366,9 +368,7 @@ static void report(void) {
     memset(&sm, 0, sizeof sm);
     sm.type = paused ? SM_PAUSED : SM_PLAYING;
     strcpy(sm.id, playing->id);
-    pthread_mutex_lock(&lock);
     sm.data = playing->played / (uaudio_rate * uaudio_channels);
-    pthread_mutex_unlock(&lock);
     speaker_send(1, &sm);
     time(&last_report);
   }
@@ -437,6 +437,10 @@ static size_t speaker_callback(void *buffer,
   if(!provided_samples) {
     memset(buffer, 0, max_bytes);
     provided_samples = max_samples;
+    if(playing)
+      info("%zu samples silence, playing->used=%zu", provided_samples, playing->used);
+    else
+      info("%zu samples silence, playing=NULL", provided_samples);
   }
   pthread_mutex_unlock(&lock);
   return provided_samples;
@@ -449,6 +453,7 @@ static void mainloop(void) {
   int n, fd, stdin_slot, timeout, listen_slot, sigpipe_slot;
 
   /* Keep going while our parent process is alive */
+  pthread_mutex_lock(&lock);
   while(getppid() != 1) {
     int force_report = 0;
 
@@ -481,9 +486,11 @@ static void mainloop(void) {
         } else
           t->slot = -1;
       }
-    sigpipe_slot = addfd(sigpipe[1], POLLIN);
+    sigpipe_slot = addfd(sigpipe[0], POLLIN);
     /* Wait for something interesting to happen */
+    pthread_mutex_unlock(&lock);
     n = poll(fds, fdno, timeout);
+    pthread_mutex_lock(&lock);
     if(n < 0) {
       if(errno == EINTR) continue;
       fatal(errno, "error calling poll");
@@ -570,7 +577,6 @@ static void mainloop(void) {
           D(("SM_CANCEL %s", sm.id));
          t = removetrack(sm.id);
          if(t) {
-            pthread_mutex_lock(&lock);
            if(t == playing || t == pending_playing) {
               /* Scratching the track that the server believes is playing,
                * which might either be the actual playing track or a pending
@@ -591,7 +597,6 @@ static void mainloop(void) {
             }
             strcpy(sm.id, t->id);
            destroy(t);
-            pthread_mutex_unlock(&lock);
          } else {
             /* Probably scratching the playing track well before it's got
              * going, but could indicate a bug, so we log this as an error. */
@@ -641,30 +646,30 @@ static void mainloop(void) {
     }
     /* When the track is actually finished, deconfigure it */
     if(playing && playing->eof && !playing->used) {
-      pthread_mutex_lock(&lock);
       removetrack(playing->id);
       destroy(playing);
       playing = 0;
-      pthread_mutex_unlock(&lock);
     }
     /* Act on the pending SM_PLAY */
     if(!playing && pending_playing) {
-      pthread_mutex_lock(&lock);
       playing = pending_playing;
       pending_playing = 0;
-      pthread_mutex_unlock(&lock);
       force_report = 1;
     }
     /* Impose any state change required by the above */
     if(playable()) {
       if(!activated) {
         activated = 1;
+        pthread_mutex_unlock(&lock);
         backend->activate();
+        pthread_mutex_lock(&lock);
       }
     } else {
       if(activated) {
         activated = 0;
+        pthread_mutex_unlock(&lock);
         backend->deactivate();
+        pthread_mutex_lock(&lock);
       }
     }
     /* If we've not reported our state for a second do so now. */