This patch reduces the number of time()/gettimeofday() system calls when
[rrdtool.git] / src / rrd_daemon.c
index e0e373a..28912c5 100644 (file)
 /*
  * Types
  */
+typedef enum
+{
+  PRIV_LOW,
+  PRIV_HIGH
+} socket_privilege;
+
+typedef enum { RESP_ERR = -1, RESP_OK = 0 } response_code;
+
 struct listen_socket_s
 {
   int fd;
-  char path[PATH_MAX + 1];
+  char addr[PATH_MAX + 1];
+  int family;
+  socket_privilege privilege;
+
+  /* state for BATCH processing */
+  time_t batch_start;
+  int batch_cmd;
+
+  /* buffered IO */
+  char *rbuf;
+  off_t next_cmd;
+  off_t next_read;
+
+  char *wbuf;
+  ssize_t wbuf_len;
 };
 typedef struct listen_socket_s listen_socket_t;
 
@@ -116,10 +138,12 @@ struct cache_item_s
   char **values;
   int values_num;
   time_t last_flush_time;
+  time_t last_update_stamp;
 #define CI_FLAGS_IN_TREE  (1<<0)
 #define CI_FLAGS_IN_QUEUE (1<<1)
   int flags;
   pthread_cond_t  flushed;
+  cache_item_t *prev;
   cache_item_t *next;
 };
 
@@ -141,11 +165,13 @@ typedef enum queue_side_e queue_side_t;
 
 /* max length of socket command or response */
 #define CMD_MAX 4096
+#define RBUF_SIZE (CMD_MAX*2)
 
 /*
  * Variables
  */
 static int stay_foreground = 0;
+static uid_t daemon_uid;
 
 static listen_socket_t *listen_fds = NULL;
 static size_t listen_fds_num = 0;
@@ -171,8 +197,10 @@ static int config_flush_interval = 3600;
 static int config_flush_at_shutdown = 0;
 static char *config_pid_file = NULL;
 static char *config_base_dir = NULL;
+static size_t _config_base_dir_len = 0;
+static int config_write_base_only = 0;
 
-static char **config_listen_address_list = NULL;
+static listen_socket_t **config_listen_address_list = NULL;
 static int config_listen_address_list_len = 0;
 
 static uint64_t stats_queue_length = 0;
@@ -273,7 +301,7 @@ static int open_pidfile(void) /* {{{ */
             file, rrd_strerror(errno));
 
   return(fd);
-}
+} /* }}} static int open_pidfile */
 
 static int write_pidfile (int fd) /* {{{ */
 {
@@ -311,88 +339,177 @@ static int remove_pidfile (void) /* {{{ */
   return (errno);
 } /* }}} int remove_pidfile */
 
-static ssize_t sread (int fd, void *buffer_void, size_t buffer_size) /* {{{ */
+static char *next_cmd (listen_socket_t *sock, ssize_t *len) /* {{{ */
 {
-  char    *buffer;
-  size_t   buffer_used;
-  size_t   buffer_free;
-  ssize_t  status;
+  char *eol;
 
-  buffer       = (char *) buffer_void;
-  buffer_used  = 0;
-  buffer_free  = buffer_size;
+  eol = memchr(sock->rbuf + sock->next_cmd, '\n',
+               sock->next_read - sock->next_cmd);
 
-  while (buffer_free > 0)
+  if (eol == NULL)
   {
-    status = read (fd, buffer + buffer_used, buffer_free);
-    if ((status < 0) && ((errno == EAGAIN) || (errno == EINTR)))
-      continue;
-
-    if (status < 0)
-      return (-1);
+    /* no commands left, move remainder back to front of rbuf */
+    memmove(sock->rbuf, sock->rbuf + sock->next_cmd,
+            sock->next_read - sock->next_cmd);
+    sock->next_read -= sock->next_cmd;
+    sock->next_cmd = 0;
+    *len = 0;
+    return NULL;
+  }
+  else
+  {
+    char *cmd = sock->rbuf + sock->next_cmd;
+    *eol = '\0';
 
-    if (status == 0)
-      return (0);
+    sock->next_cmd = eol - sock->rbuf + 1;
 
-    assert ((0 > status) || (buffer_free >= (size_t) status));
+    if (eol > sock->rbuf && *(eol-1) == '\r')
+      *(--eol) = '\0'; /* handle "\r\n" EOL */
 
-    buffer_free = buffer_free - status;
-    buffer_used = buffer_used + status;
+    *len = eol - cmd;
 
-    if (buffer[buffer_used - 1] == '\n')
-      break;
+    return cmd;
   }
 
-  assert (buffer_used > 0);
+  /* NOTREACHED */
+  assert(1==0);
+}
+
+/* add the characters directly to the write buffer */
+static int add_to_wbuf(listen_socket_t *sock, char *str, size_t len) /* {{{ */
+{
+  char *new_buf;
+
+  assert(sock != NULL);
 
-  if (buffer[buffer_used - 1] != '\n')
+  new_buf = realloc(sock->wbuf, sock->wbuf_len + len + 1);
+  if (new_buf == NULL)
   {
-    errno = ENOBUFS;
-    return (-1);
+    RRDD_LOG(LOG_ERR, "add_to_wbuf: realloc failed");
+    return -1;
   }
 
-  buffer[buffer_used - 1] = 0;
+  strncpy(new_buf + sock->wbuf_len, str, len + 1);
+
+  sock->wbuf = new_buf;
+  sock->wbuf_len += len;
+
+  return 0;
+} /* }}} static int add_to_wbuf */
 
-  /* Fix network line endings. */
-  if ((buffer_used > 1) && (buffer[buffer_used - 2] == '\r'))
+/* add the text to the "extra" info that's sent after the status line */
+static int add_response_info(listen_socket_t *sock, char *fmt, ...) /* {{{ */
+{
+  va_list argp;
+  char buffer[CMD_MAX];
+  int len;
+
+  if (sock == NULL) return 0; /* journal replay mode */
+  if (sock->batch_start) return 0; /* no extra info returned when in BATCH */
+
+  va_start(argp, fmt);
+#ifdef HAVE_VSNPRINTF
+  len = vsnprintf(buffer, sizeof(buffer)-1, fmt, argp);
+#else
+  len = vsprintf(buffer, fmt, argp);
+#endif
+  va_end(argp);
+  if (len < 0)
   {
-    buffer_used--;
-    buffer[buffer_used - 1] = 0;
+    RRDD_LOG(LOG_ERR, "add_response_info: vnsprintf failed");
+    return -1;
   }
 
-  return (buffer_used);
-} /* }}} ssize_t sread */
+  return add_to_wbuf(sock, buffer, len);
+} /* }}} static int add_response_info */
 
-static ssize_t swrite (int fd, const void *buf, size_t count) /* {{{ */
+static int count_lines(char *str) /* {{{ */
 {
-  const char *ptr;
-  size_t      nleft;
-  ssize_t     status;
+  int lines = 0;
+
+  if (str != NULL)
+  {
+    while ((str = strchr(str, '\n')) != NULL)
+    {
+      ++lines;
+      ++str;
+    }
+  }
+
+  return lines;
+} /* }}} static int count_lines */
 
-  /* special case for journal replay */
-  if (fd < 0) return 0;
+/* send the response back to the user.
+ * returns 0 on success, -1 on error
+ * write buffer is always zeroed after this call */
+static int send_response (listen_socket_t *sock, response_code rc,
+                          char *fmt, ...) /* {{{ */
+{
+  va_list argp;
+  char buffer[CMD_MAX];
+  int lines;
+  ssize_t wrote;
+  int rclen, len;
 
-  ptr   = (const char *) buf;
-  nleft = count;
+  if (sock == NULL) return rc;  /* journal replay mode */
 
-  while (nleft > 0)
+  if (sock->batch_start)
   {
-    status = write (fd, (const void *) ptr, nleft);
+    if (rc == RESP_OK)
+      return rc; /* no response on success during BATCH */
+    lines = sock->batch_cmd;
+  }
+  else if (rc == RESP_OK)
+    lines = count_lines(sock->wbuf);
+  else
+    lines = -1;
+
+  rclen = sprintf(buffer, "%d ", lines);
+  va_start(argp, fmt);
+#ifdef HAVE_VSNPRINTF
+  len = vsnprintf(buffer+rclen, sizeof(buffer)-rclen-1, fmt, argp);
+#else
+  len = vsprintf(buffer+rclen, fmt, argp);
+#endif
+  va_end(argp);
+  if (len < 0)
+    return -1;
 
-    if ((status < 0) && ((errno == EAGAIN) || (errno == EINTR)))
-      continue;
+  len += rclen;
 
-    if (status < 0)
-      return (status);
+  /* append the result to the wbuf, don't write to the user */
+  if (sock->batch_start)
+    return add_to_wbuf(sock, buffer, len);
 
-    nleft -= status;
-    ptr   += status;
+  /* first write must be complete */
+  if (len != write(sock->fd, buffer, len))
+  {
+    RRDD_LOG(LOG_INFO, "send_response: could not write status message");
+    return -1;
   }
 
-  return (0);
-} /* }}} ssize_t swrite */
+  if (sock->wbuf != NULL && rc == RESP_OK)
+  {
+    wrote = 0;
+    while (wrote < sock->wbuf_len)
+    {
+      ssize_t wb = write(sock->fd, sock->wbuf + wrote, sock->wbuf_len - wrote);
+      if (wb <= 0)
+      {
+        RRDD_LOG(LOG_INFO, "send_response: could not write results");
+        return -1;
+      }
+      wrote += wb;
+    }
+  }
+
+  free(sock->wbuf); sock->wbuf = NULL;
+  sock->wbuf_len = 0;
 
-static void _wipe_ci_values(cache_item_t *ci, time_t when)
+  return 0;
+} /* }}} */
+
+static void wipe_ci_values(cache_item_t *ci, time_t when)
 {
   ci->values = NULL;
   ci->values_num = 0;
@@ -400,10 +517,58 @@ static void _wipe_ci_values(cache_item_t *ci, time_t when)
   ci->last_flush_time = when;
   if (config_write_jitter > 0)
     ci->last_flush_time += (random() % config_write_jitter);
-
-  ci->flags &= ~(CI_FLAGS_IN_QUEUE);
 }
 
+/* remove_from_queue
+ * remove a "cache_item_t" item from the queue.
+ * must hold 'cache_lock' when calling this
+ */
+static void remove_from_queue(cache_item_t *ci) /* {{{ */
+{
+  if (ci == NULL) return;
+
+  if (ci->prev == NULL)
+    cache_queue_head = ci->next; /* reset head */
+  else
+    ci->prev->next = ci->next;
+
+  if (ci->next == NULL)
+    cache_queue_tail = ci->prev; /* reset the tail */
+  else
+    ci->next->prev = ci->prev;
+
+  ci->next = ci->prev = NULL;
+  ci->flags &= ~CI_FLAGS_IN_QUEUE;
+} /* }}} static void remove_from_queue */
+
+/* remove an entry from the tree and free all its resources.
+ * must hold 'cache lock' while calling this.
+ * returns 0 on success, otherwise errno */
+static int forget_file(const char *file)
+{
+  cache_item_t *ci;
+
+  ci = g_tree_lookup(cache_tree, file);
+  if (ci == NULL)
+    return ENOENT;
+
+  g_tree_remove (cache_tree, file);
+  remove_from_queue(ci);
+
+  for (int i=0; i < ci->values_num; i++)
+    free(ci->values[i]);
+
+  free (ci->values);
+  free (ci->file);
+
+  /* in case anyone is waiting */
+  pthread_cond_broadcast(&ci->flushed);
+
+  free (ci);
+
+  return 0;
+} /* }}} static int forget_file */
+
 /*
  * enqueue_cache_item:
  * `cache_lock' must be acquired before calling this function!
@@ -411,8 +576,6 @@ static void _wipe_ci_values(cache_item_t *ci, time_t when)
 static int enqueue_cache_item (cache_item_t *ci, /* {{{ */
     queue_side_t side)
 {
-  int did_insert = 0;
-
   if (ci == NULL)
     return (-1);
 
@@ -421,67 +584,47 @@ static int enqueue_cache_item (cache_item_t *ci, /* {{{ */
 
   if (side == HEAD)
   {
-    if ((ci->flags & CI_FLAGS_IN_QUEUE) == 0)
-    {
-      assert (ci->next == NULL);
-      ci->next = cache_queue_head;
-      cache_queue_head = ci;
-
-      if (cache_queue_tail == NULL)
-        cache_queue_tail = cache_queue_head;
-
-      did_insert = 1;
-    }
-    else if (cache_queue_head == ci)
-    {
-      /* do nothing */
-    }
-    else /* enqueued, but not first entry */
-    {
-      cache_item_t *prev;
+    if (cache_queue_head == ci)
+      return 0;
 
-      /* find previous entry */
-      for (prev = cache_queue_head; prev != NULL; prev = prev->next)
-        if (prev->next == ci)
-          break;
-      assert (prev != NULL);
+    /* remove from the double linked list */
+    if (ci->flags & CI_FLAGS_IN_QUEUE)
+      remove_from_queue(ci);
 
-      /* move to the front */
-      prev->next = ci->next;
-      ci->next = cache_queue_head;
-      cache_queue_head = ci;
+    ci->prev = NULL;
+    ci->next = cache_queue_head;
+    if (ci->next != NULL)
+      ci->next->prev = ci;
+    cache_queue_head = ci;
 
-      /* check if we need to adapt the tail */
-      if (cache_queue_tail == ci)
-        cache_queue_tail = prev;
-    }
+    if (cache_queue_tail == NULL)
+      cache_queue_tail = cache_queue_head;
   }
   else /* (side == TAIL) */
   {
     /* We don't move values back in the list.. */
-    if ((ci->flags & CI_FLAGS_IN_QUEUE) != 0)
+    if (ci->flags & CI_FLAGS_IN_QUEUE)
       return (0);
 
     assert (ci->next == NULL);
+    assert (ci->prev == NULL);
+
+    ci->prev = cache_queue_tail;
 
     if (cache_queue_tail == NULL)
       cache_queue_head = ci;
     else
       cache_queue_tail->next = ci;
-    cache_queue_tail = ci;
 
-    did_insert = 1;
+    cache_queue_tail = ci;
   }
 
   ci->flags |= CI_FLAGS_IN_QUEUE;
 
-  if (did_insert)
-  {
-    pthread_cond_broadcast(&cache_cond);
-    pthread_mutex_lock (&stats_lock);
-    stats_queue_length++;
-    pthread_mutex_unlock (&stats_lock);
-  }
+  pthread_cond_broadcast(&cache_cond);
+  pthread_mutex_lock (&stats_lock);
+  stats_queue_length++;
+  pthread_mutex_unlock (&stats_lock);
 
   return (0);
 } /* }}} int enqueue_cache_item */
@@ -560,26 +703,10 @@ static int flush_old_values (int max_age)
 
   for (k = 0; k < cfd.keys_num; k++)
   {
-    cache_item_t *ci;
-
-    /* This must not fail. */
-    ci = (cache_item_t *) g_tree_lookup (cache_tree, cfd.keys[k]);
-    assert (ci != NULL);
-
-    /* If we end up here with values available, something's seriously
-     * messed up. */
-    assert (ci->values_num == 0);
-
-    /* Remove the node from the tree */
-    g_tree_remove (cache_tree, cfd.keys[k]);
-    cfd.keys[k] = NULL;
-
-    /* Now free and clean up `ci'. */
-    free (ci->file);
-    ci->file = NULL;
-    free (ci);
-    ci = NULL;
-  } /* for (k = 0; k < cfd.keys_num; k++) */
+    /* should never fail, since we have held the cache_lock
+     * the entire time */
+    assert( forget_file(cfd.keys[k]) == 0 );
+  }
 
   if (cfd.keys != NULL)
   {
@@ -674,12 +801,8 @@ static void *queue_thread_main (void *args __attribute__((unused))) /* {{{ */
     values = ci->values;
     values_num = ci->values_num;
 
-    _wipe_ci_values(ci, time(NULL));
-
-    cache_queue_head = ci->next;
-    if (cache_queue_head == NULL)
-      cache_queue_tail = NULL;
-    ci->next = NULL;
+    wipe_ci_values(ci, time(NULL));
+    remove_from_queue(ci);
 
     pthread_mutex_lock (&stats_lock);
     assert (stats_queue_length > 0);
@@ -801,6 +924,52 @@ static int buffer_get_field (char **buffer_ret, /* {{{ */
   return (0);
 } /* }}} int buffer_get_field */
 
+/* if we're restricting writes to the base directory,
+ * check whether the file falls within the dir
+ * returns 1 if OK, otherwise 0
+ */
+static int check_file_access (const char *file, listen_socket_t *sock) /* {{{ */
+{
+  assert(file != NULL);
+
+  if (!config_write_base_only
+      || sock == NULL /* journal replay */
+      || config_base_dir == NULL)
+    return 1;
+
+  if (strstr(file, "../") != NULL) goto err;
+
+  /* relative paths without "../" are ok */
+  if (*file != '/') return 1;
+
+  /* file must be of the format base + "/" + <1+ char filename> */
+  if (strlen(file) < _config_base_dir_len + 2) goto err;
+  if (strncmp(file, config_base_dir, _config_base_dir_len) != 0) goto err;
+  if (*(file + _config_base_dir_len) != '/') goto err;
+
+  return 1;
+
+err:
+  if (sock != NULL && sock->fd >= 0)
+    send_response(sock, RESP_ERR, "%s\n", rrd_strerror(EACCES));
+
+  return 0;
+} /* }}} static int check_file_access */
+
+/* returns 1 if we have the required privilege level,
+ * otherwise issue an error to the user on sock */
+static int has_privilege (listen_socket_t *sock, /* {{{ */
+                          socket_privilege priv)
+{
+  if (sock == NULL) /* journal replay */
+    return 1;
+
+  if (sock->privilege >= priv)
+    return 1;
+
+  return send_response(sock, RESP_ERR, "%s\n", rrd_strerror(EACCES));
+} /* }}} static int has_privilege */
+
 static int flush_file (const char *filename) /* {{{ */
 {
   cache_item_t *ci;
@@ -814,135 +983,157 @@ static int flush_file (const char *filename) /* {{{ */
     return (ENOENT);
   }
 
-  /* Enqueue at head */
-  enqueue_cache_item (ci, HEAD);
+  if (ci->values_num > 0)
+  {
+    /* Enqueue at head */
+    enqueue_cache_item (ci, HEAD);
+    pthread_cond_wait(&ci->flushed, &cache_lock);
+  }
+
+  /* DO NOT DO ANYTHING WITH ci HERE!!  The entry
+   * may have been purged during our cond_wait() */
 
-  pthread_cond_wait(&ci->flushed, &cache_lock);
   pthread_mutex_unlock(&cache_lock);
 
   return (0);
 } /* }}} int flush_file */
 
-static int handle_request_help (int fd, /* {{{ */
+static int handle_request_help (listen_socket_t *sock, /* {{{ */
     char *buffer, size_t buffer_size)
 {
   int status;
   char **help_text;
-  size_t help_text_len;
   char *command;
-  size_t i;
 
-  char *help_help[] =
-  {
-    "5 Command overview\n",
-    "FLUSH <filename>\n",
-    "FLUSHALL\n",
-    "HELP [<command>]\n",
-    "UPDATE <filename> <values> [<values> ...]\n",
+  char *help_help[2] =
+  {
+    "Command overview\n"
+    ,
+    "HELP [<command>]\n"
+    "FLUSH <filename>\n"
+    "FLUSHALL\n"
+    "PENDING <filename>\n"
+    "FORGET <filename>\n"
+    "UPDATE <filename> <values> [<values> ...]\n"
+    "BATCH\n"
     "STATS\n"
   };
-  size_t help_help_len = sizeof (help_help) / sizeof (help_help[0]);
 
-  char *help_flush[] =
+  char *help_flush[2] =
   {
-    "4 Help for FLUSH\n",
-    "Usage: FLUSH <filename>\n",
-    "\n",
-    "Adds the given filename to the head of the update queue and returns\n",
+    "Help for FLUSH\n"
+    ,
+    "Usage: FLUSH <filename>\n"
+    "\n"
+    "Adds the given filename to the head of the update queue and returns\n"
     "after is has been dequeued.\n"
   };
-  size_t help_flush_len = sizeof (help_flush) / sizeof (help_flush[0]);
 
-  char *help_flushall[] =
+  char *help_flushall[2] =
   {
-    "3 Help for FLUSHALL\n",
-    "Usage: FLUSHALL\n",
-    "\n",
+    "Help for FLUSHALL\n"
+    ,
+    "Usage: FLUSHALL\n"
+    "\n"
     "Triggers writing of all pending updates.  Returns immediately.\n"
   };
-  size_t help_flushall_len = sizeof(help_flushall) / sizeof(help_flushall[0]);
 
-  char *help_update[] =
+  char *help_pending[2] =
+  {
+    "Help for PENDING\n"
+    ,
+    "Usage: PENDING <filename>\n"
+    "\n"
+    "Shows any 'pending' updates for a file, in order.\n"
+    "The updates shown have not yet been written to the underlying RRD file.\n"
+  };
+
+  char *help_forget[2] =
   {
-    "9 Help for UPDATE\n",
+    "Help for FORGET\n"
+    ,
+    "Usage: FORGET <filename>\n"
+    "\n"
+    "Removes the file completely from the cache.\n"
+    "Any pending updates for the file will be lost.\n"
+  };
+
+  char *help_update[2] =
+  {
+    "Help for UPDATE\n"
+    ,
     "Usage: UPDATE <filename> <values> [<values> ...]\n"
-    "\n",
-    "Adds the given file to the internal cache if it is not yet known and\n",
-    "appends the given value(s) to the entry. See the rrdcached(1) manpage\n",
-    "for details.\n",
-    "\n",
-    "Each <values> has the following form:\n",
-    "  <values> = <time>:<value>[:<value>[...]]\n",
+    "\n"
+    "Adds the given file to the internal cache if it is not yet known and\n"
+    "appends the given value(s) to the entry. See the rrdcached(1) manpage\n"
+    "for details.\n"
+    "\n"
+    "Each <values> has the following form:\n"
+    "  <values> = <time>:<value>[:<value>[...]]\n"
     "See the rrdupdate(1) manpage for details.\n"
   };
-  size_t help_update_len = sizeof (help_update) / sizeof (help_update[0]);
 
-  char *help_stats[] =
+  char *help_stats[2] =
   {
-    "4 Help for STATS\n",
-    "Usage: STATS\n",
-    "\n",
-    "Returns some performance counters, see the rrdcached(1) manpage for\n",
+    "Help for STATS\n"
+    ,
+    "Usage: STATS\n"
+    "\n"
+    "Returns some performance counters, see the rrdcached(1) manpage for\n"
     "a description of the values.\n"
   };
-  size_t help_stats_len = sizeof (help_stats) / sizeof (help_stats[0]);
+
+  char *help_batch[2] =
+  {
+    "Help for BATCH\n"
+    ,
+    "The 'BATCH' command permits the client to initiate a bulk load\n"
+    "   of commands to rrdcached.\n"
+    "\n"
+    "Usage:\n"
+    "\n"
+    "    client: BATCH\n"
+    "    server: 0 Go ahead.  End with dot '.' on its own line.\n"
+    "    client: command #1\n"
+    "    client: command #2\n"
+    "    client: ... and so on\n"
+    "    client: .\n"
+    "    server: 2 errors\n"
+    "    server: 7 message for command #7\n"
+    "    server: 9 message for command #9\n"
+    "\n"
+    "For more information, consult the rrdcached(1) documentation.\n"
+  };
 
   status = buffer_get_field (&buffer, &buffer_size, &command);
   if (status != 0)
-  {
     help_text = help_help;
-    help_text_len = help_help_len;
-  }
   else
   {
     if (strcasecmp (command, "update") == 0)
-    {
       help_text = help_update;
-      help_text_len = help_update_len;
-    }
     else if (strcasecmp (command, "flush") == 0)
-    {
       help_text = help_flush;
-      help_text_len = help_flush_len;
-    }
     else if (strcasecmp (command, "flushall") == 0)
-    {
       help_text = help_flushall;
-      help_text_len = help_flushall_len;
-    }
+    else if (strcasecmp (command, "pending") == 0)
+      help_text = help_pending;
+    else if (strcasecmp (command, "forget") == 0)
+      help_text = help_forget;
     else if (strcasecmp (command, "stats") == 0)
-    {
       help_text = help_stats;
-      help_text_len = help_stats_len;
-    }
+    else if (strcasecmp (command, "batch") == 0)
+      help_text = help_batch;
     else
-    {
       help_text = help_help;
-      help_text_len = help_help_len;
-    }
   }
 
-  for (i = 0; i < help_text_len; i++)
-  {
-    status = swrite (fd, help_text[i], strlen (help_text[i]));
-    if (status < 0)
-    {
-      status = errno;
-      RRDD_LOG (LOG_ERR, "handle_request_help: swrite returned an error.");
-      return (status);
-    }
-  }
-
-  return (0);
+  add_response_info(sock, help_text[1]);
+  return send_response(sock, RESP_OK, help_text[0]);
 } /* }}} int handle_request_help */
 
-static int handle_request_stats (int fd, /* {{{ */
-    char *buffer __attribute__((unused)),
-    size_t buffer_size __attribute__((unused)))
+static int handle_request_stats (listen_socket_t *sock) /* {{{ */
 {
-  int status;
-  char outbuf[CMD_MAX];
-
   uint64_t copy_queue_length;
   uint64_t copy_updates_received;
   uint64_t copy_flush_received;
@@ -969,70 +1160,36 @@ static int handle_request_stats (int fd, /* {{{ */
   tree_depth        = (uint64_t) g_tree_height (cache_tree);
   pthread_mutex_unlock (&cache_lock);
 
-#define RRDD_STATS_SEND \
-  outbuf[sizeof (outbuf) - 1] = 0; \
-  status = swrite (fd, outbuf, strlen (outbuf)); \
-  if (status < 0) \
-  { \
-    status = errno; \
-    RRDD_LOG (LOG_INFO, "handle_request_stats: swrite returned an error."); \
-    return (status); \
-  }
-
-  strncpy (outbuf, "9 Statistics follow\n", sizeof (outbuf));
-  RRDD_STATS_SEND;
-
-  snprintf (outbuf, sizeof (outbuf),
-      "QueueLength: %"PRIu64"\n", copy_queue_length);
-  RRDD_STATS_SEND;
-
-  snprintf (outbuf, sizeof (outbuf),
-      "UpdatesReceived: %"PRIu64"\n", copy_updates_received);
-  RRDD_STATS_SEND;
-
-  snprintf (outbuf, sizeof (outbuf),
-      "FlushesReceived: %"PRIu64"\n", copy_flush_received);
-  RRDD_STATS_SEND;
-
-  snprintf (outbuf, sizeof (outbuf),
-      "UpdatesWritten: %"PRIu64"\n", copy_updates_written);
-  RRDD_STATS_SEND;
-
-  snprintf (outbuf, sizeof (outbuf),
-      "DataSetsWritten: %"PRIu64"\n", copy_data_sets_written);
-  RRDD_STATS_SEND;
-
-  snprintf (outbuf, sizeof (outbuf),
-      "TreeNodesNumber: %"PRIu64"\n", tree_nodes_number);
-  RRDD_STATS_SEND;
-
-  snprintf (outbuf, sizeof (outbuf),
-      "TreeDepth: %"PRIu64"\n", tree_depth);
-  RRDD_STATS_SEND;
-
-  snprintf (outbuf, sizeof(outbuf),
-      "JournalBytes: %"PRIu64"\n", copy_journal_bytes);
-  RRDD_STATS_SEND;
-
-  snprintf (outbuf, sizeof(outbuf),
-      "JournalRotate: %"PRIu64"\n", copy_journal_rotate);
-  RRDD_STATS_SEND;
+  add_response_info(sock,
+                    "QueueLength: %"PRIu64"\n", copy_queue_length);
+  add_response_info(sock,
+                    "UpdatesReceived: %"PRIu64"\n", copy_updates_received);
+  add_response_info(sock,
+                    "FlushesReceived: %"PRIu64"\n", copy_flush_received);
+  add_response_info(sock,
+                    "UpdatesWritten: %"PRIu64"\n", copy_updates_written);
+  add_response_info(sock,
+                    "DataSetsWritten: %"PRIu64"\n", copy_data_sets_written);
+  add_response_info(sock, "TreeNodesNumber: %"PRIu64"\n", tree_nodes_number);
+  add_response_info(sock, "TreeDepth: %"PRIu64"\n", tree_depth);
+  add_response_info(sock, "JournalBytes: %"PRIu64"\n", copy_journal_bytes);
+  add_response_info(sock, "JournalRotate: %"PRIu64"\n", copy_journal_rotate);
+
+  send_response(sock, RESP_OK, "Statistics follow\n");
 
   return (0);
-#undef RRDD_STATS_SEND
 } /* }}} int handle_request_stats */
 
-static int handle_request_flush (int fd, /* {{{ */
+static int handle_request_flush (listen_socket_t *sock, /* {{{ */
     char *buffer, size_t buffer_size)
 {
   char *file;
   int status;
-  char result[CMD_MAX];
 
   status = buffer_get_field (&buffer, &buffer_size, &file);
   if (status != 0)
   {
-    strncpy (result, "-1 Usage: flush <filename>\n", sizeof (result));
+    return send_response(sock, RESP_ERR, "Usage: flush <filename>\n");
   }
   else
   {
@@ -1040,9 +1197,11 @@ static int handle_request_flush (int fd, /* {{{ */
     stats_flush_received++;
     pthread_mutex_unlock(&stats_lock);
 
+    if (!check_file_access(file, sock)) return 0;
+
     status = flush_file (file);
     if (status == 0)
-      snprintf (result, sizeof (result), "0 Successfully flushed %s.\n", file);
+      return send_response(sock, RESP_OK, "Successfully flushed %s.\n", file);
     else if (status == ENOENT)
     {
       /* no file in our tree; see whether it exists at all */
@@ -1050,32 +1209,27 @@ static int handle_request_flush (int fd, /* {{{ */
 
       memset(&statbuf, 0, sizeof(statbuf));
       if (stat(file, &statbuf) == 0 && S_ISREG(statbuf.st_mode))
-        snprintf (result, sizeof (result), "0 Nothing to flush: %s.\n", file);
+        return send_response(sock, RESP_OK, "Nothing to flush: %s.\n", file);
       else
-        snprintf (result, sizeof (result), "-1 No such file: %s.\n", file);
+        return send_response(sock, RESP_ERR, "No such file: %s.\n", file);
     }
     else if (status < 0)
-      strncpy (result, "-1 Internal error.\n", sizeof (result));
+      return send_response(sock, RESP_ERR, "Internal error.\n");
     else
-      snprintf (result, sizeof (result), "-1 Failed with status %i.\n", status);
+      return send_response(sock, RESP_ERR, "Failed with status %i.\n", status);
   }
-  result[sizeof (result) - 1] = 0;
 
-  status = swrite (fd, result, strlen (result));
-  if (status < 0)
-  {
-    status = errno;
-    RRDD_LOG (LOG_INFO, "handle_request_flush: swrite returned an error.");
-    return (status);
-  }
-
-  return (0);
+  /* NOTREACHED */
+  assert(1==0);
 } /* }}} int handle_request_flush */
 
-static int handle_request_flushall(int fd) /* {{{ */
+static int handle_request_flushall(listen_socket_t *sock) /* {{{ */
 {
   int status;
-  char answer[] ="0 Started flush.\n";
+
+  status = has_privilege(sock, PRIV_HIGH);
+  if (status <= 0)
+    return status;
 
   RRDD_LOG(LOG_DEBUG, "Received FLUSHALL");
 
@@ -1083,53 +1237,106 @@ static int handle_request_flushall(int fd) /* {{{ */
   flush_old_values(-1);
   pthread_mutex_unlock(&cache_lock);
 
-  status = swrite(fd, answer, strlen(answer));
-  if (status < 0)
+  return send_response(sock, RESP_OK, "Started flush.\n");
+} /* }}} static int handle_request_flushall */
+
+static int handle_request_pending(listen_socket_t *sock, /* {{{ */
+                                  char *buffer, size_t buffer_size)
+{
+  int status;
+  char *file;
+  cache_item_t *ci;
+
+  status = buffer_get_field(&buffer, &buffer_size, &file);
+  if (status != 0)
+    return send_response(sock, RESP_ERR,
+                         "Usage: PENDING <filename>\n");
+
+  status = has_privilege(sock, PRIV_HIGH);
+  if (status <= 0)
+    return status;
+
+  pthread_mutex_lock(&cache_lock);
+  ci = g_tree_lookup(cache_tree, file);
+  if (ci == NULL)
   {
-    status = errno;
-    RRDD_LOG(LOG_INFO, "handle_request_flushall: swrite returned an error.");
+    pthread_mutex_unlock(&cache_lock);
+    return send_response(sock, RESP_ERR, "%s\n", rrd_strerror(ENOENT));
   }
 
-  return (status);
-}
+  for (int i=0; i < ci->values_num; i++)
+    add_response_info(sock, "%s\n", ci->values[i]);
 
-static int handle_request_update (int fd, /* {{{ */
-    char *buffer, size_t buffer_size)
+  pthread_mutex_unlock(&cache_lock);
+  return send_response(sock, RESP_OK, "updates pending\n");
+} /* }}} static int handle_request_pending */
+
+static int handle_request_forget(listen_socket_t *sock, /* {{{ */
+                                 char *buffer, size_t buffer_size)
+{
+  int status;
+  char *file;
+
+  status = buffer_get_field(&buffer, &buffer_size, &file);
+  if (status != 0)
+    return send_response(sock, RESP_ERR,
+                         "Usage: FORGET <filename>\n");
+
+  status = has_privilege(sock, PRIV_HIGH);
+  if (status <= 0)
+    return status;
+
+  if (!check_file_access(file, sock)) return 0;
+
+  pthread_mutex_lock(&cache_lock);
+  status = forget_file(file);
+  pthread_mutex_unlock(&cache_lock);
+
+  if (status == 0)
+  {
+    if (sock != NULL)
+      journal_write("forget", file);
+
+    return send_response(sock, RESP_OK, "Gone!\n");
+  }
+  else
+    return send_response(sock, RESP_ERR, "cannot forget: %s\n",
+                         status < 0 ? "Internal error" : rrd_strerror(status));
+
+  /* NOTREACHED */
+  assert(1==0);
+} /* }}} static int handle_request_forget */
+
+static int handle_request_update (listen_socket_t *sock, /* {{{ */
+                                  time_t now,
+                                  char *buffer, size_t buffer_size)
 {
   char *file;
   int values_num = 0;
+  int bad_timestamps = 0;
   int status;
-
-  time_t now;
+  char orig_buf[CMD_MAX];
 
   cache_item_t *ci;
-  char answer[CMD_MAX];
-
-#define RRDD_UPDATE_SEND \
-  answer[sizeof (answer) - 1] = 0; \
-  status = swrite (fd, answer, strlen (answer)); \
-  if (status < 0) \
-  { \
-    status = errno; \
-    RRDD_LOG (LOG_INFO, "handle_request_update: swrite returned an error."); \
-    return (status); \
-  }
 
-  now = time (NULL);
+  status = has_privilege(sock, PRIV_HIGH);
+  if (status <= 0)
+    return status;
+
+  /* save it for the journal later */
+  strncpy(orig_buf, buffer, sizeof(orig_buf)-1);
 
   status = buffer_get_field (&buffer, &buffer_size, &file);
   if (status != 0)
-  {
-    strncpy (answer, "-1 Usage: UPDATE <filename> <values> [<values> ...]\n",
-        sizeof (answer));
-    RRDD_UPDATE_SEND;
-    return (0);
-  }
+    return send_response(sock, RESP_ERR,
+                         "Usage: UPDATE <filename> <values> [<values> ...]\n");
 
   pthread_mutex_lock(&stats_lock);
   stats_updates_received++;
   pthread_mutex_unlock(&stats_lock);
 
+  if (!check_file_access(file, sock)) return 0;
+
   pthread_mutex_lock (&cache_lock);
   ci = g_tree_lookup (cache_tree, file);
 
@@ -1148,35 +1355,24 @@ static int handle_request_update (int fd, /* {{{ */
 
       status = errno;
       if (status == ENOENT)
-        snprintf (answer, sizeof (answer), "-1 No such file: %s\n", file);
+        return send_response(sock, RESP_ERR, "No such file: %s\n", file);
       else
-        snprintf (answer, sizeof (answer), "-1 stat failed with error %i.\n",
-            status);
-      RRDD_UPDATE_SEND;
-      return (0);
+        return send_response(sock, RESP_ERR,
+                             "stat failed with error %i.\n", status);
     }
     if (!S_ISREG (statbuf.st_mode))
-    {
-      snprintf (answer, sizeof (answer), "-1 Not a regular file: %s\n", file);
-      RRDD_UPDATE_SEND;
-      return (0);
-    }
+      return send_response(sock, RESP_ERR, "Not a regular file: %s\n", file);
+
     if (access(file, R_OK|W_OK) != 0)
-    {
-      snprintf (answer, sizeof (answer), "-1 Cannot read/write %s: %s\n",
-                file, rrd_strerror(errno));
-      RRDD_UPDATE_SEND;
-      return (0);
-    }
+      return send_response(sock, RESP_ERR, "Cannot read/write %s: %s\n",
+                           file, rrd_strerror(errno));
 
     ci = (cache_item_t *) malloc (sizeof (cache_item_t));
     if (ci == NULL)
     {
       RRDD_LOG (LOG_ERR, "handle_request_update: malloc failed.");
 
-      strncpy (answer, "-1 malloc failed.\n", sizeof (answer));
-      RRDD_UPDATE_SEND;
-      return (0);
+      return send_response(sock, RESP_ERR, "malloc failed.\n");
     }
     memset (ci, 0, sizeof (cache_item_t));
 
@@ -1186,12 +1382,10 @@ static int handle_request_update (int fd, /* {{{ */
       free (ci);
       RRDD_LOG (LOG_ERR, "handle_request_update: strdup failed.");
 
-      strncpy (answer, "-1 strdup failed.\n", sizeof (answer));
-      RRDD_UPDATE_SEND;
-      return (0);
+      return send_response(sock, RESP_ERR, "strdup failed.\n");
     }
 
-    _wipe_ci_values(ci, now);
+    wipe_ci_values(ci, now);
     ci->flags = CI_FLAGS_IN_TREE;
 
     pthread_mutex_lock(&cache_lock);
@@ -1199,10 +1393,16 @@ static int handle_request_update (int fd, /* {{{ */
   } /* }}} */
   assert (ci != NULL);
 
+  /* don't re-write updates in replay mode */
+  if (sock != NULL)
+    journal_write("update", orig_buf);
+
   while (buffer_size > 0)
   {
     char **temp;
     char *value;
+    time_t stamp;
+    char *eostamp;
 
     status = buffer_get_field (&buffer, &buffer_size, &value);
     if (status != 0)
@@ -1211,6 +1411,26 @@ static int handle_request_update (int fd, /* {{{ */
       break;
     }
 
+    /* make sure update time is always moving forward */
+    stamp = strtol(value, &eostamp, 10);
+    if (eostamp == value || eostamp == NULL || *eostamp != ':')
+    {
+      ++bad_timestamps;
+      add_response_info(sock, "Cannot find timestamp in '%s'!\n", value);
+      continue;
+    }
+    else if (stamp <= ci->last_update_stamp)
+    {
+      ++bad_timestamps;
+      add_response_info(sock,
+                        "illegal attempt to update using time %ld when"
+                        " last update time is %ld (minimum one second step)\n",
+                        stamp, ci->last_update_stamp);
+      continue;
+    }
+    else
+      ci->last_update_stamp = stamp;
+
     temp = (char **) realloc (ci->values,
         sizeof (char *) * (ci->values_num + 1));
     if (temp == NULL)
@@ -1242,24 +1462,30 @@ static int handle_request_update (int fd, /* {{{ */
 
   if (values_num < 1)
   {
-    strncpy (answer, "-1 No values updated.\n", sizeof (answer));
+    /* if we had only one update attempt, then return the full
+       error message... try to get the most information out
+       of the limited error space allowed by the protocol
+    */
+    if (bad_timestamps == 1)
+      return send_response(sock, RESP_ERR, "%s", sock->wbuf);
+    else
+      return send_response(sock, RESP_ERR,
+                           "No values updated (%d bad timestamps).\n",
+                           bad_timestamps);
   }
   else
-  {
-    snprintf (answer, sizeof (answer), "0 Enqueued %i value%s\n", values_num,
-        (values_num == 1) ? "" : "s");
-  }
-  RRDD_UPDATE_SEND;
-  return (0);
-#undef RRDD_UPDATE_SEND
+    return send_response(sock, RESP_OK,
+                         "errors, enqueued %i value(s).\n", values_num);
+
+  /* NOTREACHED */
+  assert(1==0);
+
 } /* }}} int handle_request_update */
 
 /* we came across a "WROTE" entry during journal replay.
  * throw away any values that we have accumulated for this file
  */
-static int handle_request_wrote (int fd __attribute__((unused)), /* {{{ */
-                                 const char *buffer,
-                                 size_t buffer_size __attribute__((unused)))
+static int handle_request_wrote (const char *buffer, time_t now) /* {{{ */
 {
   int i;
   cache_item_t *ci;
@@ -1282,14 +1508,41 @@ static int handle_request_wrote (int fd __attribute__((unused)), /* {{{ */
     free(ci->values);
   }
 
-  _wipe_ci_values(ci, time(NULL));
+  wipe_ci_values(ci, now);
+  remove_from_queue(ci);
 
   pthread_mutex_unlock(&cache_lock);
   return (0);
 } /* }}} int handle_request_wrote */
 
-/* if fd < 0, we are in journal replay mode */
-static int handle_request (int fd, char *buffer, size_t buffer_size) /* {{{ */
+/* start "BATCH" processing */
+static int batch_start (listen_socket_t *sock) /* {{{ */
+{
+  int status;
+  if (sock->batch_start)
+    return send_response(sock, RESP_ERR, "Already in BATCH\n");
+
+  status = send_response(sock, RESP_OK,
+                         "Go ahead.  End with dot '.' on its own line.\n");
+  sock->batch_start = time(NULL);
+  sock->batch_cmd = 0;
+
+  return status;
+} /* }}} static int batch_start */
+
+/* finish "BATCH" processing and return results to the client */
+static int batch_done (listen_socket_t *sock) /* {{{ */
+{
+  assert(sock->batch_start);
+  sock->batch_start = 0;
+  sock->batch_cmd  = 0;
+  return send_response(sock, RESP_OK, "errors\n");
+} /* }}} static int batch_done */
+
+/* if sock==NULL, we are in journal replay mode */
+static int handle_request (listen_socket_t *sock, /* {{{ */
+                           time_t now,
+                           char *buffer, size_t buffer_size)
 {
   char *buffer_ptr;
   char *command;
@@ -1306,57 +1559,44 @@ static int handle_request (int fd, char *buffer, size_t buffer_size) /* {{{ */
     return (-1);
   }
 
-  if (strcasecmp (command, "update") == 0)
-  {
-    /* don't re-write updates in replay mode */
-    if (fd >= 0)
-      journal_write(command, buffer_ptr);
+  if (sock != NULL && sock->batch_start)
+    sock->batch_cmd++;
 
-    return (handle_request_update (fd, buffer_ptr, buffer_size));
-  }
-  else if (strcasecmp (command, "wrote") == 0 && fd < 0)
+  if (strcasecmp (command, "update") == 0)
+    return (handle_request_update (sock, now, buffer_ptr, buffer_size));
+  else if (strcasecmp (command, "wrote") == 0 && sock == NULL)
   {
     /* this is only valid in replay mode */
-    return (handle_request_wrote (fd, buffer_ptr, buffer_size));
+    return (handle_request_wrote (buffer_ptr, now));
   }
   else if (strcasecmp (command, "flush") == 0)
-  {
-    return (handle_request_flush (fd, buffer_ptr, buffer_size));
-  }
+    return (handle_request_flush (sock, buffer_ptr, buffer_size));
   else if (strcasecmp (command, "flushall") == 0)
-  {
-    return (handle_request_flushall(fd));
-  }
+    return (handle_request_flushall(sock));
+  else if (strcasecmp (command, "pending") == 0)
+    return (handle_request_pending(sock, buffer_ptr, buffer_size));
+  else if (strcasecmp (command, "forget") == 0)
+    return (handle_request_forget(sock, buffer_ptr, buffer_size));
   else if (strcasecmp (command, "stats") == 0)
-  {
-    return (handle_request_stats (fd, buffer_ptr, buffer_size));
-  }
+    return (handle_request_stats (sock));
   else if (strcasecmp (command, "help") == 0)
-  {
-    return (handle_request_help (fd, buffer_ptr, buffer_size));
-  }
+    return (handle_request_help (sock, buffer_ptr, buffer_size));
+  else if (strcasecmp (command, "batch") == 0 && sock != NULL)
+    return batch_start(sock);
+  else if (strcasecmp (command, ".") == 0 && sock != NULL && sock->batch_start)
+    return batch_done(sock);
   else
-  {
-    char result[CMD_MAX];
-
-    snprintf (result, sizeof (result), "-1 Unknown command: %s\n", command);
-    result[sizeof (result) - 1] = 0;
+    return send_response(sock, RESP_ERR, "Unknown command: %s\n", command);
 
-    status = swrite (fd, result, strlen (result));
-    if (status < 0)
-    {
-      RRDD_LOG (LOG_ERR, "handle_request: swrite failed.");
-      return (-1);
-    }
-  }
-
-  return (0);
+  /* NOTREACHED */
+  assert(1==0);
 } /* }}} int handle_request */
 
 /* MUST NOT hold journal_lock before calling this */
 static void journal_rotate(void) /* {{{ */
 {
   FILE *old_fh = NULL;
+  int new_fd;
 
   if (journal_cur == NULL || journal_old == NULL)
     return;
@@ -1371,11 +1611,20 @@ static void journal_rotate(void) /* {{{ */
   if (journal_fh != NULL)
   {
     old_fh = journal_fh;
+    journal_fh = NULL;
     rename(journal_cur, journal_old);
     ++stats_journal_rotate;
   }
 
-  journal_fh = fopen(journal_cur, "a");
+  new_fd = open(journal_cur, O_WRONLY|O_CREAT|O_APPEND,
+                S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
+  if (new_fd >= 0)
+  {
+    journal_fh = fdopen(new_fd, "a");
+    if (journal_fh == NULL)
+      close(new_fd);
+  }
+
   pthread_mutex_unlock(&journal_lock);
 
   if (old_fh != NULL)
@@ -1450,9 +1699,48 @@ static int journal_replay (const char *file) /* {{{ */
   int fail_cnt = 0;
   uint64_t line = 0;
   char entry[CMD_MAX];
+  time_t now;
 
   if (file == NULL) return 0;
 
+  {
+    char *reason;
+    int status = 0;
+    struct stat statbuf;
+
+    memset(&statbuf, 0, sizeof(statbuf));
+    if (stat(file, &statbuf) != 0)
+    {
+      if (errno == ENOENT)
+        return 0;
+
+      reason = "stat error";
+      status = errno;
+    }
+    else if (!S_ISREG(statbuf.st_mode))
+    {
+      reason = "not a regular file";
+      status = EPERM;
+    }
+    if (statbuf.st_uid != daemon_uid)
+    {
+      reason = "not owned by daemon user";
+      status = EACCES;
+    }
+    if (statbuf.st_mode & (S_IWGRP|S_IWOTH))
+    {
+      reason = "must not be user/group writable";
+      status = EACCES;
+    }
+
+    if (status != 0)
+    {
+      RRDD_LOG(LOG_ERR, "journal_replay: %s : %s (%s)",
+               file, rrd_strerror(status), reason);
+      return 0;
+    }
+  }
+
   fh = fopen(file, "r");
   if (fh == NULL)
   {
@@ -1464,12 +1752,15 @@ static int journal_replay (const char *file) /* {{{ */
   else
     RRDD_LOG(LOG_NOTICE, "replaying from journal: %s", file);
 
+  now = time(NULL);
+
   while(!feof(fh))
   {
     size_t entry_len;
 
     ++line;
-    fgets(entry, sizeof(entry), fh);
+    if (fgets(entry, sizeof(entry), fh) == NULL)
+      break;
     entry_len = strlen(entry);
 
     /* check \n termination in case journal writing crashed mid-line */
@@ -1484,7 +1775,7 @@ static int journal_replay (const char *file) /* {{{ */
 
     entry[entry_len - 1] = '\0';
 
-    if (handle_request(-1, entry, entry_len) == 0)
+    if (handle_request(NULL, now, entry, entry_len) == 0)
       ++entry_cnt;
     else
       ++fail_cnt;
@@ -1492,25 +1783,64 @@ static int journal_replay (const char *file) /* {{{ */
 
   fclose(fh);
 
-  if (entry_cnt > 0)
-  {
-    RRDD_LOG(LOG_INFO, "Replayed %d entries (%d failures)",
-             entry_cnt, fail_cnt);
-    return 1;
-  }
-  else
-    return 0;
+  RRDD_LOG(LOG_INFO, "Replayed %d entries (%d failures)",
+           entry_cnt, fail_cnt);
 
+  return entry_cnt > 0 ? 1 : 0;
 } /* }}} static int journal_replay */
 
+static void journal_init(void) /* {{{ */
+{
+  int had_journal = 0;
+
+  if (journal_cur == NULL) return;
+
+  pthread_mutex_lock(&journal_lock);
+
+  RRDD_LOG(LOG_INFO, "checking for journal files");
+
+  had_journal += journal_replay(journal_old);
+  had_journal += journal_replay(journal_cur);
+
+  /* it must have been a crash.  start a flush */
+  if (had_journal && config_flush_at_shutdown)
+    flush_old_values(-1);
+
+  pthread_mutex_unlock(&journal_lock);
+  journal_rotate();
+
+  RRDD_LOG(LOG_INFO, "journal processing complete");
+
+} /* }}} static void journal_init */
+
+static void close_connection(listen_socket_t *sock)
+{
+  close(sock->fd) ;  sock->fd   = -1;
+  free(sock->rbuf);  sock->rbuf = NULL;
+  free(sock->wbuf);  sock->wbuf = NULL;
+
+  free(sock);
+}
+
 static void *connection_thread_main (void *args) /* {{{ */
 {
   pthread_t self;
+  listen_socket_t *sock;
   int i;
   int fd;
-  
-  fd = *((int *) args);
-  free (args);
+
+  sock = (listen_socket_t *) args;
+  fd = sock->fd;
+
+  /* init read buffers */
+  sock->next_read = sock->next_cmd = 0;
+  sock->rbuf = malloc(RBUF_SIZE);
+  if (sock->rbuf == NULL)
+  {
+    RRDD_LOG(LOG_ERR, "connection_thread_main: cannot malloc read buffer");
+    close_connection(sock);
+    return NULL;
+  }
 
   pthread_mutex_lock (&connection_threads_lock);
   {
@@ -1533,7 +1863,10 @@ static void *connection_thread_main (void *args) /* {{{ */
 
   while (do_shutdown == 0)
   {
-    char buffer[CMD_MAX];
+    char *cmd;
+    ssize_t cmd_len;
+    ssize_t rbytes;
+    time_t now;
 
     struct pollfd pollfd;
     int status;
@@ -1550,43 +1883,48 @@ static void *connection_thread_main (void *args) /* {{{ */
     else if (status < 0) /* error */
     {
       status = errno;
-      if (status == EINTR)
-        continue;
-      RRDD_LOG (LOG_ERR, "connection_thread_main: poll(2) failed.");
+      if (status != EINTR)
+        RRDD_LOG (LOG_ERR, "connection_thread_main: poll(2) failed.");
       continue;
     }
 
     if ((pollfd.revents & POLLHUP) != 0) /* normal shutdown */
-    {
-      close (fd);
       break;
-    }
     else if ((pollfd.revents & (POLLIN | POLLPRI)) == 0)
     {
       RRDD_LOG (LOG_WARNING, "connection_thread_main: "
           "poll(2) returned something unexpected: %#04hx",
           pollfd.revents);
-      close (fd);
       break;
     }
 
-    status = (int) sread (fd, buffer, sizeof (buffer));
-    if (status <= 0)
+    rbytes = read(fd, sock->rbuf + sock->next_read,
+                  RBUF_SIZE - sock->next_read);
+    if (rbytes < 0)
     {
-      close (fd);
-
-      if (status < 0)
-        RRDD_LOG(LOG_ERR, "connection_thread_main: sread failed.");
-
+      RRDD_LOG(LOG_ERR, "connection_thread_main: read() failed.");
       break;
     }
+    else if (rbytes == 0)
+      break; /* eof */
 
-    status = handle_request (fd, buffer, /*buffer_size=*/ status);
-    if (status != 0)
-      break;
+    sock->next_read += rbytes;
+
+    if (sock->batch_start)
+      now = sock->batch_start;
+    else
+      now = time(NULL);
+
+    while ((cmd = next_cmd(sock, &cmd_len)) != NULL)
+    {
+      status = handle_request (sock, now, cmd, cmd_len+1);
+      if (status != 0)
+        goto out_close;
+    }
   }
 
-  close(fd);
+out_close:
+  close_connection(sock);
 
   self = pthread_self ();
   /* Remove this thread from the connection threads list */
@@ -1611,12 +1949,17 @@ static void *connection_thread_main (void *args) /* {{{ */
   return (NULL);
 } /* }}} void *connection_thread_main */
 
-static int open_listen_socket_unix (const char *path) /* {{{ */
+static int open_listen_socket_unix (const listen_socket_t *sock) /* {{{ */
 {
   int fd;
   struct sockaddr_un sa;
   listen_socket_t *temp;
   int status;
+  const char *path;
+
+  path = sock->addr;
+  if (strncmp(path, "unix:", strlen("unix:")) == 0)
+    path += strlen("unix:");
 
   temp = (listen_socket_t *) realloc (listen_fds,
       sizeof (listen_fds[0]) * (listen_fds_num + 1));
@@ -1626,7 +1969,7 @@ static int open_listen_socket_unix (const char *path) /* {{{ */
     return (-1);
   }
   listen_fds = temp;
-  memset (listen_fds + listen_fds_num, 0, sizeof (listen_fds[0]));
+  memcpy (listen_fds + listen_fds_num, sock, sizeof (listen_fds[0]));
 
   fd = socket (PF_UNIX, SOCK_STREAM, /* protocol = */ 0);
   if (fd < 0)
@@ -1656,17 +1999,17 @@ static int open_listen_socket_unix (const char *path) /* {{{ */
     unlink (path);
     return (-1);
   }
-  
+
   listen_fds[listen_fds_num].fd = fd;
-  snprintf (listen_fds[listen_fds_num].path,
-      sizeof (listen_fds[listen_fds_num].path) - 1,
-      "unix:%s", path);
+  listen_fds[listen_fds_num].family = PF_UNIX;
+  strncpy(listen_fds[listen_fds_num].addr, path,
+          sizeof (listen_fds[listen_fds_num].addr) - 1);
   listen_fds_num++;
 
   return (0);
 } /* }}} int open_listen_socket_unix */
 
-static int open_listen_socket (const char *addr_orig) /* {{{ */
+static int open_listen_socket_network(const listen_socket_t *sock) /* {{{ */
 {
   struct addrinfo ai_hints;
   struct addrinfo *ai_res;
@@ -1676,17 +2019,10 @@ static int open_listen_socket (const char *addr_orig) /* {{{ */
   char *port;
   int status;
 
-  assert (addr_orig != NULL);
-
-  strncpy (addr_copy, addr_orig, sizeof (addr_copy));
+  strncpy (addr_copy, sock->addr, sizeof (addr_copy));
   addr_copy[sizeof (addr_copy) - 1] = 0;
   addr = addr_copy;
 
-  if (strncmp ("unix:", addr, strlen ("unix:")) == 0)
-    return (open_listen_socket_unix (addr + strlen ("unix:")));
-  else if (addr[0] == '/')
-    return (open_listen_socket_unix (addr));
-
   memset (&ai_hints, 0, sizeof (ai_hints));
   ai_hints.ai_flags = 0;
 #ifdef AI_ADDRCONFIG
@@ -1696,7 +2032,7 @@ static int open_listen_socket (const char *addr_orig) /* {{{ */
   ai_hints.ai_socktype = SOCK_STREAM;
 
   port = NULL;
- if (*addr == '[') /* IPv6+port format */
 if (*addr == '[') /* IPv6+port format */
   {
     /* `addr' is something like "[2001:780:104:2:211:24ff:feab:26f8]:12345" */
     addr++;
@@ -1704,8 +2040,8 @@ static int open_listen_socket (const char *addr_orig) /* {{{ */
     port = strchr (addr, ']');
     if (port == NULL)
     {
-      RRDD_LOG (LOG_ERR, "open_listen_socket: Malformed address: %s",
-          addr_orig);
+      RRDD_LOG (LOG_ERR, "open_listen_socket_network: Malformed address: %s",
+          sock->addr);
       return (-1);
     }
     *port = 0;
@@ -1717,7 +2053,7 @@ static int open_listen_socket (const char *addr_orig) /* {{{ */
       port = NULL;
     else
     {
-      RRDD_LOG (LOG_ERR, "open_listen_socket: Garbage after address: %s",
+      RRDD_LOG (LOG_ERR, "open_listen_socket_network: Garbage after address: %s",
           port);
       return (-1);
     }
@@ -1737,7 +2073,7 @@ static int open_listen_socket (const char *addr_orig) /* {{{ */
                         &ai_hints, &ai_res);
   if (status != 0)
   {
-    RRDD_LOG (LOG_ERR, "open_listen_socket: getaddrinfo(%s) failed: "
+    RRDD_LOG (LOG_ERR, "open_listen_socket_network: getaddrinfo(%s) failed: "
         "%s", addr, gai_strerror (status));
     return (-1);
   }
@@ -1752,16 +2088,16 @@ static int open_listen_socket (const char *addr_orig) /* {{{ */
         sizeof (listen_fds[0]) * (listen_fds_num + 1));
     if (temp == NULL)
     {
-      RRDD_LOG (LOG_ERR, "open_listen_socket: realloc failed.");
+      RRDD_LOG (LOG_ERR, "open_listen_socket_network: realloc failed.");
       continue;
     }
     listen_fds = temp;
-    memset (listen_fds + listen_fds_num, 0, sizeof (listen_fds[0]));
+    memcpy (listen_fds + listen_fds_num, sock, sizeof (listen_fds[0]));
 
     fd = socket (ai_ptr->ai_family, ai_ptr->ai_socktype, ai_ptr->ai_protocol);
     if (fd < 0)
     {
-      RRDD_LOG (LOG_ERR, "open_listen_socket: socket(2) failed.");
+      RRDD_LOG (LOG_ERR, "open_listen_socket_network: socket(2) failed.");
       continue;
     }
 
@@ -1770,7 +2106,7 @@ static int open_listen_socket (const char *addr_orig) /* {{{ */
     status = bind (fd, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
     if (status != 0)
     {
-      RRDD_LOG (LOG_ERR, "open_listen_socket: bind(2) failed.");
+      RRDD_LOG (LOG_ERR, "open_listen_socket_network: bind(2) failed.");
       close (fd);
       continue;
     }
@@ -1778,18 +2114,29 @@ static int open_listen_socket (const char *addr_orig) /* {{{ */
     status = listen (fd, /* backlog = */ 10);
     if (status != 0)
     {
-      RRDD_LOG (LOG_ERR, "open_listen_socket: listen(2) failed.");
+      RRDD_LOG (LOG_ERR, "open_listen_socket_network: listen(2) failed.");
       close (fd);
       return (-1);
     }
 
     listen_fds[listen_fds_num].fd = fd;
-    strncpy (listen_fds[listen_fds_num].path, addr,
-        sizeof (listen_fds[listen_fds_num].path) - 1);
+    listen_fds[listen_fds_num].family = ai_ptr->ai_family;
     listen_fds_num++;
   } /* for (ai_ptr) */
 
   return (0);
+} /* }}} static int open_listen_socket_network */
+
+static int open_listen_socket (const listen_socket_t *sock) /* {{{ */
+{
+  assert(sock != NULL);
+  assert(sock->addr != NULL);
+
+  if (strncmp ("unix:", sock->addr, strlen ("unix:")) == 0
+      || sock->addr[0] == '/')
+    return (open_listen_socket_unix(sock));
+  else
+    return (open_listen_socket_network(sock));
 } /* }}} int open_listen_socket */
 
 static int close_listen_sockets (void) /* {{{ */
@@ -1799,8 +2146,9 @@ static int close_listen_sockets (void) /* {{{ */
   for (i = 0; i < listen_fds_num; i++)
   {
     close (listen_fds[i].fd);
-    if (strncmp ("unix:", listen_fds[i].path, strlen ("unix:")) == 0)
-      unlink (listen_fds[i].path + strlen ("unix:"));
+
+    if (listen_fds[i].family == PF_UNIX)
+      unlink(listen_fds[i].addr);
   }
 
   free (listen_fds);
@@ -1821,7 +2169,12 @@ static void *listen_thread_main (void *args __attribute__((unused))) /* {{{ */
     open_listen_socket (config_listen_address_list[i]);
 
   if (config_listen_address_list_len < 1)
-    open_listen_socket (RRDCACHED_DEFAULT_ADDRESS);
+  {
+    listen_socket_t sock;
+    memset(&sock, 0, sizeof(sock));
+    strncpy(sock.addr, RRDCACHED_DEFAULT_ADDRESS, sizeof(sock.addr));
+    open_listen_socket (&sock);
+  }
 
   if (listen_fds_num < 1)
   {
@@ -1868,7 +2221,7 @@ static void *listen_thread_main (void *args __attribute__((unused))) /* {{{ */
 
     for (i = 0; i < pollfds_num; i++)
     {
-      int *client_sd;
+      listen_socket_t *client_sock;
       struct sockaddr_storage client_sa;
       socklen_t client_sa_size;
       pthread_t tid;
@@ -1885,19 +2238,21 @@ static void *listen_thread_main (void *args __attribute__((unused))) /* {{{ */
         continue;
       }
 
-      client_sd = (int *) malloc (sizeof (int));
-      if (client_sd == NULL)
+      client_sock = (listen_socket_t *) malloc (sizeof (listen_socket_t));
+      if (client_sock == NULL)
       {
         RRDD_LOG (LOG_ERR, "listen_thread_main: malloc failed.");
         continue;
       }
+      memcpy(client_sock, &listen_fds[i], sizeof(listen_fds[0]));
 
       client_sa_size = sizeof (client_sa);
-      *client_sd = accept (pollfds[i].fd,
+      client_sock->fd = accept (pollfds[i].fd,
           (struct sockaddr *) &client_sa, &client_sa_size);
-      if (*client_sd < 0)
+      if (client_sock->fd < 0)
       {
         RRDD_LOG (LOG_ERR, "listen_thread_main: accept(2) failed.");
+        free(client_sock);
         continue;
       }
 
@@ -1905,12 +2260,11 @@ static void *listen_thread_main (void *args __attribute__((unused))) /* {{{ */
       pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
 
       status = pthread_create (&tid, &attr, connection_thread_main,
-          /* args = */ (void *) client_sd);
+                               client_sock);
       if (status != 0)
       {
         RRDD_LOG (LOG_ERR, "listen_thread_main: pthread_create failed.");
-        close (*client_sd);
-        free (client_sd);
+        close_connection(client_sock);
         continue;
       }
     } /* for (pollfds_num) */
@@ -1940,6 +2294,9 @@ static int daemonize (void) /* {{{ */
 {
   int status;
   int fd;
+  char *base_dir;
+
+  daemon_uid = geteuid();
 
   fd = open_pidfile();
   if (fd < 0) return fd;
@@ -1947,7 +2304,6 @@ static int daemonize (void) /* {{{ */
   if (!stay_foreground)
   {
     pid_t child;
-    char *base_dir;
 
     child = fork ();
     if (child < 0)
@@ -1960,17 +2316,6 @@ static int daemonize (void) /* {{{ */
       return (1);
     }
 
-    /* Change into the /tmp directory. */
-    base_dir = (config_base_dir != NULL)
-      ? config_base_dir
-      : "/tmp";
-    status = chdir (base_dir);
-    if (status != 0)
-    {
-      fprintf (stderr, "daemonize: chdir (%s) failed.\n", base_dir);
-      return (-1);
-    }
-
     /* Become session leader */
     setsid ();
 
@@ -1984,6 +2329,17 @@ static int daemonize (void) /* {{{ */
     dup (0);
   } /* if (!stay_foreground) */
 
+  /* Change into the /tmp directory. */
+  base_dir = (config_base_dir != NULL)
+    ? config_base_dir
+    : "/tmp";
+  status = chdir (base_dir);
+  if (status != 0)
+  {
+    fprintf (stderr, "daemonize: chdir (%s) failed.\n", base_dir);
+    return (-1);
+  }
+
   install_signal_handlers();
 
   openlog ("rrdcached", LOG_PID, LOG_DAEMON);
@@ -2020,7 +2376,7 @@ static int read_options (int argc, char **argv) /* {{{ */
   int option;
   int status = 0;
 
-  while ((option = getopt(argc, argv, "gl:f:w:b:z:p:j:h?F")) != -1)
+  while ((option = getopt(argc, argv, "gl:L:f:w:b:Bz:p:j:h?F")) != -1)
   {
     switch (option)
     {
@@ -2028,12 +2384,22 @@ static int read_options (int argc, char **argv) /* {{{ */
         stay_foreground=1;
         break;
 
+      case 'L':
       case 'l':
       {
-        char **temp;
+        listen_socket_t **temp;
+        listen_socket_t *new;
+
+        new = malloc(sizeof(listen_socket_t));
+        if (new == NULL)
+        {
+          fprintf(stderr, "read_options: malloc failed.\n");
+          return(2);
+        }
+        memset(new, 0, sizeof(listen_socket_t));
 
-        temp = (char **) realloc (config_listen_address_list,
-            sizeof (char *) * (config_listen_address_list_len + 1));
+        temp = (listen_socket_t **) realloc (config_listen_address_list,
+            sizeof (listen_socket_t *) * (config_listen_address_list_len + 1));
         if (temp == NULL)
         {
           fprintf (stderr, "read_options: realloc failed.\n");
@@ -2041,12 +2407,10 @@ static int read_options (int argc, char **argv) /* {{{ */
         }
         config_listen_address_list = temp;
 
-        temp[config_listen_address_list_len] = strdup (optarg);
-        if (temp[config_listen_address_list_len] == NULL)
-        {
-          fprintf (stderr, "read_options: strdup failed.\n");
-          return (2);
-        }
+        strncpy(new->addr, optarg, sizeof(new->addr)-1);
+        new->privilege = (option == 'l') ? PRIV_HIGH : PRIV_LOW;
+
+        temp[config_listen_address_list_len] = new;
         config_listen_address_list_len++;
       }
       break;
@@ -2097,6 +2461,10 @@ static int read_options (int argc, char **argv) /* {{{ */
         break;
       }
 
+      case 'B':
+        config_write_base_only = 1;
+        break;
+
       case 'b':
       {
         size_t len;
@@ -2122,6 +2490,8 @@ static int read_options (int argc, char **argv) /* {{{ */
           fprintf (stderr, "Invalid base directory: %s\n", optarg);
           return (4);
         }
+
+        _config_base_dir_len = len;
       }
       break;
 
@@ -2185,11 +2555,13 @@ static int read_options (int argc, char **argv) /* {{{ */
             "\n"
             "Valid options are:\n"
             "  -l <address>  Socket address to listen to.\n"
+            "  -L <address>  Socket address to listen to ('FLUSH' only).\n"
             "  -w <seconds>  Interval in which to write data.\n"
             "  -z <delay>    Delay writes up to <delay> seconds to spread load\n"
             "  -f <seconds>  Interval in which to flush dead data.\n"
             "  -p <file>     Location of the PID-file.\n"
             "  -b <dir>      Base directory to change to.\n"
+            "  -B            Restrict file access to paths within -b <dir>\n"
             "  -g            Do not fork and run in the foreground.\n"
             "  -j <dir>      Directory in which to create the journal files.\n"
             "  -F            Always flush all updates at shutdown\n"
@@ -2211,6 +2583,10 @@ static int read_options (int argc, char **argv) /* {{{ */
     fprintf(stderr, "WARNING: write delay (-z) should NOT be larger than"
             " write interval (-w) !\n");
 
+  if (config_write_base_only && config_base_dir == NULL)
+    fprintf(stderr, "WARNING: -B does not make sense without -b!\n"
+            "  Consult the rrdcached documentation\n");
+
   if (journal_cur == NULL)
     config_flush_at_shutdown = 1;
 
@@ -2246,25 +2622,7 @@ int main (int argc, char **argv)
     return (1);
   }
 
-  if (journal_cur != NULL)
-  {
-    int had_journal = 0;
-
-    pthread_mutex_lock(&journal_lock);
-
-    RRDD_LOG(LOG_INFO, "checking for journal files");
-
-    had_journal += journal_replay(journal_old);
-    had_journal += journal_replay(journal_cur);
-
-    if (had_journal)
-      flush_old_values(-1);
-
-    pthread_mutex_unlock(&journal_lock);
-    journal_rotate();
-
-    RRDD_LOG(LOG_INFO, "journal processing complete");
-  }
+  journal_init();
 
   /* start the queue thread */
   memset (&queue_thread, 0, sizeof (queue_thread));