rrdcached: Log to stderr (in addition to syslog) when running in foreground. -- Sebas...
[rrdtool.git] / src / rrd_daemon.c
index 199ebdb..154f0de 100644 (file)
@@ -1,7 +1,7 @@
 /**
  * RRDTool - src/rrd_daemon.c
- * Copyright (C) 2008 Florian octo Forster
- * Copyright (C) 2008 Kevin Brintnall
+ * Copyright (C) 2008,2009 Florian octo Forster
+ * Copyright (C) 2008,2009 Kevin Brintnall
  *
  * This program is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License as published by the
 #include <stdlib.h>
 
 #ifndef WIN32
-#include <stdint.h>
+#ifdef HAVE_STDINT_H
+#  include <stdint.h>
+#endif
 #include <unistd.h>
 #include <strings.h>
 #include <inttypes.h>
-#      include <sys/socket.h>
+#include <sys/socket.h>
 
 #else
 
 #include <assert.h>
 #include <sys/time.h>
 #include <time.h>
+#include <libgen.h>
+#include <grp.h>
 
 #include <glib-2.0/glib.h>
 /* }}} */
 
-#define RRDD_LOG(severity, ...) syslog ((severity), __VA_ARGS__)
+#define RRDD_LOG(severity, ...) \
+  do { \
+    if (stay_foreground) \
+      fprintf(stderr, __VA_ARGS__); \
+    syslog ((severity), __VA_ARGS__); \
+  } while (0)
 
 #ifndef __GNUC__
 # define __attribute__(x) /**/
 /*
  * Types
  */
-typedef enum
-{
-  PRIV_LOW,
-  PRIV_HIGH
-} socket_privilege;
-
 typedef enum { RESP_ERR = -1, RESP_OK = 0 } response_code;
 
 struct listen_socket_s
@@ -129,7 +132,6 @@ struct listen_socket_s
   int fd;
   char addr[PATH_MAX + 1];
   int family;
-  socket_privilege privilege;
 
   /* state for BATCH processing */
   time_t batch_start;
@@ -142,23 +144,28 @@ struct listen_socket_s
 
   char *wbuf;
   ssize_t wbuf_len;
+
+  uint32_t permissions;
+
+  gid_t  socket_group;
+  mode_t socket_permissions;
 };
 typedef struct listen_socket_s listen_socket_t;
 
-struct command;
+struct command_s;
+typedef struct command_s command_t;
 /* note: guard against "unused" warnings in the handlers */
 #define DISPATCH_PROTO listen_socket_t *sock   __attribute__((unused)),\
                        time_t now              __attribute__((unused)),\
                        char  *buffer           __attribute__((unused)),\
                        size_t buffer_size      __attribute__((unused))
 
-#define HANDLER_PROTO  struct command *cmd     __attribute__((unused)),\
+#define HANDLER_PROTO  command_t *cmd          __attribute__((unused)),\
                        DISPATCH_PROTO
 
-struct command {
+struct command_s {
   char   *cmd;
   int (*handler)(HANDLER_PROTO);
-  socket_privilege min_priv;
 
   char  context;               /* where we expect to see it */
 #define CMD_CONTEXT_CLIENT     (1<<0)
@@ -267,6 +274,7 @@ static uint64_t stats_journal_rotate = 0;
 static pthread_mutex_t stats_lock = PTHREAD_MUTEX_INITIALIZER;
 
 /* Journaled updates */
+#define JOURNAL_REPLAY(s) ((s) == NULL)
 #define JOURNAL_BASE "rrd.journal"
 static journal_set *journal_cur = NULL;
 static journal_set *journal_old = NULL;
@@ -351,12 +359,32 @@ static void install_signal_handlers(void) /* {{{ */
 static int open_pidfile(char *action, int oflag) /* {{{ */
 {
   int fd;
-  char *file;
+  const char *file;
+  char *file_copy, *dir;
 
   file = (config_pid_file != NULL)
     ? config_pid_file
     : LOCALSTATEDIR "/run/rrdcached.pid";
 
+  /* dirname may modify its argument */
+  file_copy = strdup(file);
+  if (file_copy == NULL)
+  {
+    fprintf(stderr, "rrdcached: strdup(): %s\n",
+        rrd_strerror(errno));
+    return -1;
+  }
+
+  dir = dirname(file_copy);
+  if (rrd_mkdir_p(dir, 0777) != 0)
+  {
+    fprintf(stderr, "Failed to create pidfile directory '%s': %s\n",
+        dir, rrd_strerror(errno));
+    return -1;
+  }
+
+  free(file_copy);
+
   fd = open(file, oflag, S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH);
   if (fd < 0)
     fprintf(stderr, "rrdcached: can't %s pid file '%s' (%s)\n",
@@ -479,7 +507,7 @@ static char *next_cmd (listen_socket_t *sock, ssize_t *len) /* {{{ */
 
   /* NOTREACHED */
   assert(1==0);
-}
+} /* }}} char *next_cmd */
 
 /* add the characters directly to the write buffer */
 static int add_to_wbuf(listen_socket_t *sock, char *str, size_t len) /* {{{ */
@@ -510,7 +538,7 @@ static int add_response_info(listen_socket_t *sock, char *fmt, ...) /* {{{ */
   char buffer[CMD_MAX];
   int len;
 
-  if (sock == NULL) return 0; /* journal replay mode */
+  if (JOURNAL_REPLAY(sock)) return 0;
   if (sock->batch_start) return 0; /* no extra info returned when in BATCH */
 
   va_start(argp, fmt);
@@ -557,7 +585,7 @@ static int send_response (listen_socket_t *sock, response_code rc,
   ssize_t wrote;
   int rclen, len;
 
-  if (sock == NULL) return rc;  /* journal replay mode */
+  if (JOURNAL_REPLAY(sock)) return rc;
 
   if (sock->batch_start)
   {
@@ -798,9 +826,10 @@ static int flush_old_values (int max_age)
 
   for (k = 0; k < cfd.keys_num; k++)
   {
+    gboolean status = g_tree_remove(cache_tree, cfd.keys[k]);
     /* should never fail, since we have held the cache_lock
      * the entire time */
-    assert( g_tree_remove(cache_tree, cfd.keys[k]) == TRUE );
+    assert(status == TRUE);
   }
 
   if (cfd.keys != NULL)
@@ -1025,7 +1054,7 @@ static int check_file_access (const char *file, listen_socket_t *sock) /* {{{ */
   assert(file != NULL);
 
   if (!config_write_base_only
-      || sock == NULL /* journal replay */
+      || JOURNAL_REPLAY(sock)
       || config_base_dir == NULL)
     return 1;
 
@@ -1068,20 +1097,6 @@ static void get_abs_path(char **filename, char *tmp)
   *filename = tmp;
 } /* }}} static int get_abs_path */
 
-/* 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;
@@ -1110,7 +1125,7 @@ static int flush_file (const char *filename) /* {{{ */
   return (0);
 } /* }}} int flush_file */
 
-static int syntax_error(listen_socket_t *sock, struct command *cmd) /* {{{ */
+static int syntax_error(listen_socket_t *sock, command_t *cmd) /* {{{ */
 {
   char *err = "Syntax error.\n";
 
@@ -1268,7 +1283,7 @@ static int handle_request_forget(HANDLER_PROTO) /* {{{ */
 
   if (found == TRUE)
   {
-    if (sock != NULL)
+    if (!JOURNAL_REPLAY(sock))
       journal_write("forget", file);
 
     return send_response(sock, RESP_OK, "Gone!\n");
@@ -1308,7 +1323,8 @@ static int handle_request_update (HANDLER_PROTO) /* {{{ */
   cache_item_t *ci;
 
   /* save it for the journal later */
-  strncpy(orig_buf, buffer, sizeof(orig_buf)-1);
+  if (!JOURNAL_REPLAY(sock))
+    strncpy(orig_buf, buffer, buffer_size);
 
   status = buffer_get_field (&buffer, &buffer_size, &file);
   if (status != 0)
@@ -1393,7 +1409,7 @@ static int handle_request_update (HANDLER_PROTO) /* {{{ */
   assert (ci != NULL);
 
   /* don't re-write updates in replay mode */
-  if (sock != NULL)
+  if (!JOURNAL_REPLAY(sock))
     journal_write("update", orig_buf);
 
   while (buffer_size > 0)
@@ -1513,11 +1529,10 @@ static int handle_request_quit (HANDLER_PROTO) /* {{{ */
   return -1;
 } /* }}} static int handle_request_quit */
 
-struct command COMMANDS[] = {
+static command_t list_of_commands[] = { /* {{{ */
   {
     "UPDATE",
     handle_request_update,
-    PRIV_HIGH,
     CMD_CONTEXT_ANY,
     "UPDATE <filename> <values> [<values> ...]\n"
     ,
@@ -1532,7 +1547,6 @@ struct command COMMANDS[] = {
   {
     "WROTE",
     handle_request_wrote,
-    PRIV_HIGH,
     CMD_CONTEXT_JOURNAL,
     NULL,
     NULL
@@ -1540,7 +1554,6 @@ struct command COMMANDS[] = {
   {
     "FLUSH",
     handle_request_flush,
-    PRIV_LOW,
     CMD_CONTEXT_CLIENT | CMD_CONTEXT_BATCH,
     "FLUSH <filename>\n"
     ,
@@ -1550,7 +1563,6 @@ struct command COMMANDS[] = {
   {
     "FLUSHALL",
     handle_request_flushall,
-    PRIV_HIGH,
     CMD_CONTEXT_CLIENT,
     "FLUSHALL\n"
     ,
@@ -1559,7 +1571,6 @@ struct command COMMANDS[] = {
   {
     "PENDING",
     handle_request_pending,
-    PRIV_HIGH,
     CMD_CONTEXT_CLIENT,
     "PENDING <filename>\n"
     ,
@@ -1569,7 +1580,6 @@ struct command COMMANDS[] = {
   {
     "FORGET",
     handle_request_forget,
-    PRIV_HIGH,
     CMD_CONTEXT_ANY,
     "FORGET <filename>\n"
     ,
@@ -1579,7 +1589,6 @@ struct command COMMANDS[] = {
   {
     "QUEUE",
     handle_request_queue,
-    PRIV_LOW,
     CMD_CONTEXT_CLIENT,
     "QUEUE\n"
     ,
@@ -1592,7 +1601,6 @@ struct command COMMANDS[] = {
   {
     "STATS",
     handle_request_stats,
-    PRIV_LOW,
     CMD_CONTEXT_CLIENT,
     "STATS\n"
     ,
@@ -1602,7 +1610,6 @@ struct command COMMANDS[] = {
   {
     "HELP",
     handle_request_help,
-    PRIV_LOW,
     CMD_CONTEXT_CLIENT,
     "HELP [<command>]\n",
     NULL, /* special! */
@@ -1610,7 +1617,6 @@ struct command COMMANDS[] = {
   {
     "BATCH",
     batch_start,
-    PRIV_LOW,
     CMD_CONTEXT_CLIENT,
     "BATCH\n"
     ,
@@ -1634,7 +1640,6 @@ struct command COMMANDS[] = {
   {
     ".",   /* BATCH terminator */
     batch_done,
-    PRIV_LOW,
     CMD_CONTEXT_BATCH,
     NULL,
     NULL
@@ -1642,36 +1647,84 @@ struct command COMMANDS[] = {
   {
     "QUIT",
     handle_request_quit,
-    PRIV_LOW,
     CMD_CONTEXT_CLIENT | CMD_CONTEXT_BATCH,
     "QUIT\n"
     ,
     "Disconnect from rrdcached.\n"
-  },
-  {NULL,NULL,0,0,NULL,NULL}  /* LAST ENTRY */
-};
+  }
+}; /* }}} command_t list_of_commands[] */
+static size_t list_of_commands_len = sizeof (list_of_commands)
+  / sizeof (list_of_commands[0]);
 
-static struct command *find_command(char *cmd)
+static command_t *find_command(char *cmd)
 {
-  struct command *c = COMMANDS;
-
-  while (c->cmd != NULL)
-  {
-    if (strcasecmp(cmd, c->cmd) == 0)
-      break;
-    c++;
-  }
+  size_t i;
 
-  if (c->cmd == NULL)
-    return NULL;
-  else
-    return c;
+  for (i = 0; i < list_of_commands_len; i++)
+    if (strcasecmp(cmd, list_of_commands[i].cmd) == 0)
+      return (&list_of_commands[i]);
+  return NULL;
 }
 
+/* We currently use the index in the `list_of_commands' array as a bit position
+ * in `listen_socket_t.permissions'. This member schould NEVER be accessed from
+ * outside these functions so that switching to a more elegant storage method
+ * is easily possible. */
+static ssize_t find_command_index (const char *cmd) /* {{{ */
+{
+  size_t i;
+
+  for (i = 0; i < list_of_commands_len; i++)
+    if (strcasecmp(cmd, list_of_commands[i].cmd) == 0)
+      return ((ssize_t) i);
+  return (-1);
+} /* }}} ssize_t find_command_index */
+
+static int socket_permission_check (listen_socket_t *sock, /* {{{ */
+    const char *cmd)
+{
+  ssize_t i;
+
+  if (JOURNAL_REPLAY(sock))
+    return (1);
+
+  if (cmd == NULL)
+    return (-1);
+
+  if ((strcasecmp ("QUIT", cmd) == 0)
+      || (strcasecmp ("HELP", cmd) == 0))
+    return (1);
+  else if (strcmp (".", cmd) == 0)
+    cmd = "BATCH";
+
+  i = find_command_index (cmd);
+  if (i < 0)
+    return (-1);
+  assert (i < 32);
+
+  if ((sock->permissions & (1 << i)) != 0)
+    return (1);
+  return (0);
+} /* }}} int socket_permission_check */
+
+static int socket_permission_add (listen_socket_t *sock, /* {{{ */
+    const char *cmd)
+{
+  ssize_t i;
+
+  i = find_command_index (cmd);
+  if (i < 0)
+    return (-1);
+  assert (i < 32);
+
+  sock->permissions |= (1 << i);
+  return (0);
+} /* }}} int socket_permission_add */
+
 /* check whether commands are received in the expected context */
-static int command_check_context(listen_socket_t *sock, struct command *cmd)
+static int command_check_context(listen_socket_t *sock, command_t *cmd)
 {
-  if (sock == NULL)
+  if (JOURNAL_REPLAY(sock))
     return (cmd->context & CMD_CONTEXT_JOURNAL);
   else if (sock->batch_start)
     return (cmd->context & CMD_CONTEXT_BATCH);
@@ -1687,7 +1740,7 @@ static int handle_request_help (HANDLER_PROTO) /* {{{ */
   int status;
   char *cmd_str;
   char *resp_txt;
-  struct command *help = NULL;
+  command_t *help = NULL;
 
   status = buffer_get_field (&buffer, &buffer_size, &cmd_str);
   if (status == 0)
@@ -1708,26 +1761,26 @@ static int handle_request_help (HANDLER_PROTO) /* {{{ */
   }
   else
   {
-    help = COMMANDS;
+    size_t i;
+
     resp_txt = "Command overview\n";
 
-    while (help->cmd)
+    for (i = 0; i < list_of_commands_len; i++)
     {
-      if (help->syntax)
-        add_response_info(sock, "%s", help->syntax);
-      help++;
+      if (list_of_commands[i].syntax == NULL)
+        continue;
+      add_response_info (sock, "%s", list_of_commands[i].syntax);
     }
   }
 
   return send_response(sock, RESP_OK, resp_txt);
 } /* }}} int handle_request_help */
 
-/* if sock==NULL, we are in journal replay mode */
 static int handle_request (DISPATCH_PROTO) /* {{{ */
 {
   char *buffer_ptr = buffer;
   char *cmd_str = NULL;
-  struct command *cmd = NULL;
+  command_t *cmd = NULL;
   int status;
 
   assert (buffer[buffer_size - 1] == '\0');
@@ -1746,9 +1799,8 @@ static int handle_request (DISPATCH_PROTO) /* {{{ */
   if (!cmd)
     return send_response(sock, RESP_ERR, "Unknown command: %s\n", cmd_str);
 
-  status = has_privilege(sock, cmd->min_priv);
-  if (status <= 0)
-    return status;
+  if (!socket_permission_check (sock, cmd->cmd))
+    return send_response(sock, RESP_ERR, "Permission denied.\n");
 
   if (!command_check_context(sock, cmd))
     return send_response(sock, RESP_ERR, "Can't use '%s' here.\n", cmd_str);
@@ -2222,11 +2274,31 @@ static int open_listen_socket_unix (const listen_socket_t *sock) /* {{{ */
   listen_socket_t *temp;
   int status;
   const char *path;
+  char *path_copy, *dir;
 
   path = sock->addr;
   if (strncmp(path, "unix:", strlen("unix:")) == 0)
     path += strlen("unix:");
 
+  /* dirname may modify its argument */
+  path_copy = strdup(path);
+  if (path_copy == NULL)
+  {
+    fprintf(stderr, "rrdcached: strdup(): %s\n",
+        rrd_strerror(errno));
+    return (-1);
+  }
+
+  dir = dirname(path_copy);
+  if (rrd_mkdir_p(dir, 0777) != 0)
+  {
+    fprintf(stderr, "Failed to create socket directory '%s': %s\n",
+        dir, rrd_strerror(errno));
+    return (-1);
+  }
+
+  free(path_copy);
+
   temp = (listen_socket_t *) rrd_realloc (listen_fds,
       sizeof (listen_fds[0]) * (listen_fds_num + 1));
   if (temp == NULL)
@@ -2264,6 +2336,23 @@ static int open_listen_socket_unix (const listen_socket_t *sock) /* {{{ */
     return (-1);
   }
 
+  /* tweak the sockets group ownership */
+  if (sock->socket_group != (gid_t)-1)
+  {
+    if ( (chown(path, getuid(), sock->socket_group) != 0) ||
+        (chmod(path, (S_IRUSR|S_IWUSR|S_IXUSR | S_IRGRP|S_IWGRP)) != 0) )
+    {
+      fprintf(stderr, "rrdcached: failed to set socket group permissions (%s)\n", strerror(errno));
+    }
+  }
+
+  if (sock->socket_permissions != (mode_t)-1)
+  {
+    if (chmod(path, sock->socket_permissions) != 0)
+      fprintf(stderr, "rrdcached: failed to set socket file permissions (%o): %s\n",
+          (unsigned int)sock->socket_permissions, strerror(errno));
+  }
+
   status = listen (fd, /* backlog = */ 10);
   if (status != 0)
   {
@@ -2329,8 +2418,8 @@ static int open_listen_socket_network(const listen_socket_t *sock) /* {{{ */
       fprintf (stderr, "rrdcached: Garbage after address: %s\n", port);
       return (-1);
     }
-  } /* if (*addr = ']') */
-  else if (strchr (addr, '.') != NULL) /* Hostname or IPv4 */
+  } /* if (*addr == '[') */
+  else
   {
     port = rindex(addr, ':');
     if (port != NULL)
@@ -2660,7 +2749,6 @@ static int cleanup (void) /* {{{ */
 
   free(queue_threads);
   free(config_base_dir);
-  free(config_pid_file);
 
   pthread_mutex_lock(&cache_lock);
   g_tree_destroy(cache_tree);
@@ -2672,6 +2760,7 @@ static int cleanup (void) /* {{{ */
   closelog ();
 
   remove_pidfile ();
+  free(config_pid_file);
 
   return (0);
 } /* }}} int cleanup */
@@ -2681,7 +2770,13 @@ static int read_options (int argc, char **argv) /* {{{ */
   int option;
   int status = 0;
 
-  while ((option = getopt(argc, argv, "gl:L:f:w:z:t:Bb:p:Fj:h?")) != -1)
+  char **permissions = NULL;
+  size_t permissions_len = 0;
+
+  gid_t  socket_group = (gid_t)-1;
+  mode_t socket_permissions = (mode_t)-1;
+
+  while ((option = getopt(argc, argv, "gl:s:m:P:f:w:z:t:Bb:p:Fj:h?")) != -1)
   {
     switch (option)
     {
@@ -2689,7 +2784,6 @@ static int read_options (int argc, char **argv) /* {{{ */
         stay_foreground=1;
         break;
 
-      case 'L':
       case 'l':
       {
         listen_socket_t *new;
@@ -2703,7 +2797,43 @@ static int read_options (int argc, char **argv) /* {{{ */
         memset(new, 0, sizeof(listen_socket_t));
 
         strncpy(new->addr, optarg, sizeof(new->addr)-1);
-        new->privilege = (option == 'l') ? PRIV_HIGH : PRIV_LOW;
+
+        /* Add permissions to the socket {{{ */
+        if (permissions_len != 0)
+        {
+          size_t i;
+          for (i = 0; i < permissions_len; i++)
+          {
+            status = socket_permission_add (new, permissions[i]);
+            if (status != 0)
+            {
+              fprintf (stderr, "read_options: Adding permission \"%s\" to "
+                  "socket failed. Most likely, this permission doesn't "
+                  "exist. Check your command line.\n", permissions[i]);
+              status = 4;
+            }
+          }
+        }
+        else /* if (permissions_len == 0) */
+        {
+          /* Add permission for ALL commands to the socket. */
+          size_t i;
+          for (i = 0; i < list_of_commands_len; i++)
+          {
+            status = socket_permission_add (new, list_of_commands[i].cmd);
+            if (status != 0)
+            {
+              fprintf (stderr, "read_options: Adding permission \"%s\" to "
+                  "socket failed. This should never happen, ever! Sorry.\n",
+                  permissions[i]);
+              status = 4;
+            }
+          }
+        }
+        /* }}} Done adding permissions. */
+
+        new->socket_group = socket_group;
+        new->socket_permissions = socket_permissions;
 
         if (!rrd_add_ptr((void ***)&config_listen_address_list,
                          &config_listen_address_list_len, new))
@@ -2714,6 +2844,76 @@ static int read_options (int argc, char **argv) /* {{{ */
       }
       break;
 
+      /* set socket group permissions */
+      case 's':
+      {
+       gid_t group_gid;
+       struct group *grp;
+
+       group_gid = strtoul(optarg, NULL, 10);
+       if (errno != EINVAL && group_gid>0)
+       {
+         /* we were passed a number */
+         grp = getgrgid(group_gid);
+       }
+       else
+       {
+         grp = getgrnam(optarg);
+       }
+
+       if (grp)
+       {
+         socket_group = grp->gr_gid;
+       }
+       else
+       {
+         /* no idea what the user wanted... */
+         fprintf (stderr, "read_options: couldn't map \"%s\" to a group, Sorry\n", optarg);
+         return (5);
+       }
+      }
+      break;
+
+      /* set socket file permissions */
+      case 'm':
+      {
+        long  tmp;
+        char *endptr = NULL;
+
+        tmp = strtol (optarg, &endptr, 8);
+        if ((endptr == optarg) || (! endptr) || (*endptr != '\0')
+            || (tmp > 07777) || (tmp < 0)) {
+          fprintf (stderr, "read_options: Invalid file mode \"%s\".\n",
+              optarg);
+          return (5);
+        }
+
+        socket_permissions = (mode_t)tmp;
+      }
+      break;
+
+      case 'P':
+      {
+        char *optcopy;
+        char *saveptr;
+        char *dummy;
+        char *ptr;
+
+        rrd_free_ptrs ((void *) &permissions, &permissions_len);
+
+        optcopy = strdup (optarg);
+        dummy = optcopy;
+        saveptr = NULL;
+        while ((ptr = strtok_r (dummy, ", ", &saveptr)) != NULL)
+        {
+          dummy = NULL;
+          rrd_add_strdup ((void *) &permissions, &permissions_len, ptr);
+        }
+
+        free (optcopy);
+      }
+      break;
+
       case 'f':
       {
         int temp;
@@ -2792,6 +2992,13 @@ static int read_options (int argc, char **argv) /* {{{ */
           return (3);
         }
 
+        if (rrd_mkdir_p (config_base_dir, 0777) != 0)
+        {
+          fprintf (stderr, "Failed to create base directory '%s': %s\n",
+              config_base_dir, rrd_strerror (errno));
+          return (3);
+        }
+
         /* make sure that the base directory is not resolved via
          * symbolic links.  this makes some performance-enhancing
          * assumptions possible (we don't have to resolve paths
@@ -2799,17 +3006,8 @@ static int read_options (int argc, char **argv) /* {{{ */
          */
         if (realpath(config_base_dir, base_realpath) == NULL)
         {
-          fprintf (stderr, "Invalid base directory '%s'.\n", config_base_dir);
-          return 5;
-        }
-        else if (strncmp(config_base_dir,
-                         base_realpath, sizeof(base_realpath)) != 0)
-        {
-          fprintf(stderr,
-                  "Base directory (-b) resolved via file system links!\n"
-                  "Please consult rrdcached '-b' documentation!\n"
-                  "Consider specifying the real directory (%s)\n",
-                  base_realpath);
+          fprintf (stderr, "Failed to canonicalize the base directory '%s': "
+              "%s\n", config_base_dir, rrd_strerror(errno));
           return 5;
         }
 
@@ -2827,6 +3025,24 @@ static int read_options (int argc, char **argv) /* {{{ */
         }
 
         _config_base_dir_len = len;
+
+        len = strlen (base_realpath);
+        while ((len > 0) && (base_realpath[len - 1] == '/'))
+        {
+          base_realpath[len - 1] = '\0';
+          len--;
+        }
+
+        if (strncmp(config_base_dir,
+                         base_realpath, sizeof(base_realpath)) != 0)
+        {
+          fprintf(stderr,
+                  "Base directory (-b) resolved via file system links!\n"
+                  "Please consult rrdcached '-b' documentation!\n"
+                  "Consider specifying the real directory (%s)\n",
+                  base_realpath);
+          return 5;
+        }
       }
       break;
 
@@ -2849,18 +3065,17 @@ static int read_options (int argc, char **argv) /* {{{ */
 
       case 'j':
       {
-        struct stat statbuf;
         const char *dir = journal_dir = strdup(optarg);
 
-        status = stat(dir, &statbuf);
+        status = rrd_mkdir_p(dir, 0777);
         if (status != 0)
         {
-          fprintf(stderr, "Cannot stat '%s' : %s\n", dir, rrd_strerror(errno));
+          fprintf(stderr, "Failed to create journal directory '%s': %s\n",
+              dir, rrd_strerror(errno));
           return 6;
         }
 
-        if (!S_ISDIR(statbuf.st_mode)
-            || access(dir, R_OK|W_OK|X_OK) != 0)
+        if (access(dir, R_OK|W_OK|X_OK) != 0)
         {
           fprintf(stderr, "Must specify a writable directory with -j! (%s)\n",
                   errno ? rrd_strerror(errno) : "");
@@ -2871,13 +3086,15 @@ static int read_options (int argc, char **argv) /* {{{ */
 
       case 'h':
       case '?':
-        printf ("RRDCacheD %s  Copyright (C) 2008 Florian octo Forster\n"
+        printf ("RRDCacheD %s\n"
+            "Copyright (C) 2008,2009 Florian octo Forster and Kevin Brintnall\n"
             "\n"
             "Usage: rrdcached [options]\n"
             "\n"
             "Valid options are:\n"
             "  -l <address>  Socket address to listen to.\n"
-            "  -L <address>  Socket address to listen to ('FLUSH' only).\n"
+            "  -P <perms>    Sets the permissions to assign to all following "
+                            "sockets\n"
             "  -w <seconds>  Interval in which to write data.\n"
             "  -z <delay>    Delay writes up to <delay> seconds to spread load\n"
             "  -t <threads>  Number of write threads.\n"
@@ -2888,6 +3105,7 @@ static int read_options (int argc, char **argv) /* {{{ */
             "  -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"
+            "  -s <id|name>  Make socket g+rw to named group\n"
             "\n"
             "For more information and a detailed description of all options "
             "please refer\n"
@@ -2913,6 +3131,8 @@ static int read_options (int argc, char **argv) /* {{{ */
   if (journal_dir == NULL)
     config_flush_at_shutdown = 1;
 
+  rrd_free_ptrs ((void *) &permissions, &permissions_len);
+
   return (status);
 } /* }}} int read_options */