fix for the memory leak in info fixes the memory leak in "INFO".
[rrdtool.git] / src / rrd_daemon.c
index 2480958..0dc8e0b 100644 (file)
@@ -71,8 +71,9 @@
 #endif
 #endif
 
-#include "rrd.h"
+#include "rrd_tool.h"
 #include "rrd_client.h"
+#include "unused.h"
 
 #include <stdlib.h>
 
 
 #define RRDD_LOG(severity, ...) \
   do { \
-    if (stay_foreground) \
+    if (stay_foreground) \
       fprintf(stderr, __VA_ARGS__); \
+      fprintf(stderr, "\n"); } \
     syslog ((severity), __VA_ARGS__); \
   } while (0)
 
-#ifndef __GNUC__
-# define __attribute__(x) /**/
-#endif
-
 /*
  * Types
  */
@@ -155,12 +153,12 @@ typedef struct listen_socket_s listen_socket_t;
 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 DISPATCH_PROTO listen_socket_t UNUSED(*sock),\
+                       time_t UNUSED(now),\
+                       char  UNUSED(*buffer),\
+                       size_t UNUSED(buffer_size)
 
-#define HANDLER_PROTO  command_t *cmd          __attribute__((unused)),\
+#define HANDLER_PROTO  command_t UNUSED(*cmd),\
                        DISPATCH_PROTO
 
 struct command_s {
@@ -230,6 +228,8 @@ static uid_t daemon_uid;
 static listen_socket_t *listen_fds = NULL;
 static size_t listen_fds_num = 0;
 
+static listen_socket_t default_socket;
+
 enum {
   RUNNING,             /* normal operation */
   FLUSHING,            /* flushing remaining values */
@@ -275,6 +275,8 @@ static uint64_t stats_journal_bytes = 0;
 static uint64_t stats_journal_rotate = 0;
 static pthread_mutex_t stats_lock = PTHREAD_MUTEX_INITIALIZER;
 
+static int opt_no_overwrite = 0; /* default for the daemon */
+
 /* Journaled updates */
 #define JOURNAL_REPLAY(s) ((s) == NULL)
 #define JOURNAL_BASE "rrd.journal"
@@ -303,23 +305,23 @@ static void sig_common (const char *sig) /* {{{ */
   pthread_cond_broadcast(&queue_cond);
 } /* }}} void sig_common */
 
-static void sig_int_handler (int s __attribute__((unused))) /* {{{ */
+static void sig_int_handler (int UNUSED(s)) /* {{{ */
 {
   sig_common("INT");
 } /* }}} void sig_int_handler */
 
-static void sig_term_handler (int s __attribute__((unused))) /* {{{ */
+static void sig_term_handler (int UNUSED(s)) /* {{{ */
 {
   sig_common("TERM");
 } /* }}} void sig_term_handler */
 
-static void sig_usr1_handler (int s __attribute__((unused))) /* {{{ */
+static void sig_usr1_handler (int UNUSED(s)) /* {{{ */
 {
   config_flush_at_shutdown = 1;
   sig_common("USR1");
 } /* }}} void sig_usr1_handler */
 
-static void sig_usr2_handler (int s __attribute__((unused))) /* {{{ */
+static void sig_usr2_handler (int UNUSED(s)) /* {{{ */
 {
   config_flush_at_shutdown = 0;
   sig_common("USR2");
@@ -844,7 +846,7 @@ static int flush_old_values (int max_age)
   return (0);
 } /* int flush_old_values */
 
-static void *flush_thread_main (void *args __attribute__((unused))) /* {{{ */
+static void *flush_thread_main (void UNUSED(*args)) /* {{{ */
 {
   struct timeval now;
   struct timespec next_flush;
@@ -897,7 +899,7 @@ static void *flush_thread_main (void *args __attribute__((unused))) /* {{{ */
   return NULL;
 } /* void *flush_thread_main */
 
-static void *queue_thread_main (void *args __attribute__((unused))) /* {{{ */
+static void *queue_thread_main (void UNUSED(*args)) /* {{{ */
 {
   pthread_mutex_lock (&cache_lock);
 
@@ -1479,13 +1481,11 @@ static int handle_request_update (HANDLER_PROTO) /* {{{ */
 
 static int handle_request_fetch (HANDLER_PROTO) /* {{{ */
 {
-  char *file;
+  char *file, file_tmp[PATH_MAX];
   char *cf;
 
   char *start_str;
   char *end_str;
-  rrd_time_value_t start_tv;
-  rrd_time_value_t end_tv;
   time_t start_tm;
   time_t end_tm;
 
@@ -1535,43 +1535,63 @@ static int handle_request_fetch (HANDLER_PROTO) /* {{{ */
   if (status != 0)
     return (syntax_error(sock,cmd));
 
+  get_abs_path(&file, file_tmp);
+  if (!check_file_access(file, sock)) return 0;
+
   status = flush_file (file);
   if ((status != 0) && (status != ENOENT))
     return (send_response (sock, RESP_ERR,
           "flush_file (%s) failed with status %i.\n", file, status));
 
+  t = time (NULL); /* "now" */
+
   /* Parse start time */
   if (start_str != NULL)
   {
-    const char *errmsg;
+    char *endptr;
+    long value;
 
-    errmsg = rrd_parsetime (start_str, &start_tv);
-    if (errmsg != NULL)
+    endptr = NULL;
+    errno = 0;
+    value = strtol (start_str, &endptr, /* base = */ 0);
+    if ((endptr == start_str) || (errno != 0))
       return (send_response(sock, RESP_ERR,
-            "Cannot parse start time `%s': %s\n", start_str, errmsg));
+            "Cannot parse start time `%s': Only simple integers are allowed.\n",
+            start_str));
+
+    if (value > 0)
+      start_tm = (time_t) value;
+    else
+      start_tm = (time_t) (t + value);
   }
   else
-    rrd_parsetime ("-86400", &start_tv);
+  {
+    start_tm = t - 86400;
+  }
 
   /* Parse end time */
   if (end_str != NULL)
   {
-    const char *errmsg;
+    char *endptr;
+    long value;
 
-    errmsg = rrd_parsetime (end_str, &end_tv);
-    if (errmsg != NULL)
+    endptr = NULL;
+    errno = 0;
+    value = strtol (end_str, &endptr, /* base = */ 0);
+    if ((endptr == end_str) || (errno != 0))
       return (send_response(sock, RESP_ERR,
-            "Cannot parse end time `%s': %s\n", end_str, errmsg));
+            "Cannot parse end time `%s': Only simple integers are allowed.\n",
+            end_str));
+
+    if (value > 0)
+      end_tm = (time_t) value;
+    else
+      end_tm = (time_t) (t + value);
   }
   else
-    rrd_parsetime ("now", &end_tv);
-
-  start_tm = 0;
-  end_tm = 0;
-  status = rrd_proc_start_end (&start_tv, &end_tv, &start_tm, &end_tm);
-  if (status != 0)
-    return (send_response(sock, RESP_ERR,
-          "rrd_proc_start_end failed: %s\n", rrd_get_error ()));
+  {
+    end_tm = t;
+  }
 
   step = -1;
   ds_cnt = 0;
@@ -1616,7 +1636,9 @@ static int handle_request_fetch (HANDLER_PROTO) /* {{{ */
       if (i > 0)
         SSTRCAT (linebuf, " ", linebuf_fill);
       SSTRCAT (linebuf, ds_namv[i], linebuf_fill);
+      rrd_freemem(ds_namv[i]);
     }
+    rrd_freemem(ds_namv);
     add_response_info (sock, "DSName: %s\n", linebuf);
   }
 
@@ -1642,6 +1664,7 @@ static int handle_request_fetch (HANDLER_PROTO) /* {{{ */
 
     add_response_info (sock, "%10lu:%s\n", (unsigned long) t, linebuf);
   } /* for (t) */
+  rrd_freemem(data);
 
   return (send_response (sock, RESP_OK, "Success\n"));
 #undef SSTRCAT
@@ -1674,6 +1697,192 @@ static int handle_request_wrote (HANDLER_PROTO) /* {{{ */
   return (0);
 } /* }}} int handle_request_wrote */
 
+static int handle_request_info (HANDLER_PROTO) /* {{{ */
+{
+  char *file, file_tmp[PATH_MAX];
+  int status;
+  rrd_info_t *info;
+
+  /* obtain filename */
+  status = buffer_get_field(&buffer, &buffer_size, &file);
+  if (status != 0)
+    return syntax_error(sock,cmd);
+  /* get full pathname */
+  get_abs_path(&file, file_tmp);
+  if (!check_file_access(file, sock)) {
+    return send_response(sock, RESP_ERR, "Cannot read: %s\n", file);
+  }
+  /* get data */
+  rrd_clear_error ();
+  info = rrd_info_r(file);
+  if(!info) {
+    return send_response(sock, RESP_ERR, "RRD Error: %s\n", rrd_get_error());
+  }
+  for (rrd_info_t *data = info; data != NULL; data = data->next) {
+      switch (data->type) {
+      case RD_I_VAL:
+          if (isnan(data->value.u_val))
+              add_response_info(sock,"%s %d NaN\n",data->key, data->type);
+          else
+              add_response_info(sock,"%s %d %0.10e\n", data->key, data->type, data->value.u_val);
+          break;
+      case RD_I_CNT:
+          add_response_info(sock,"%s %d %lu\n", data->key, data->type, data->value.u_cnt);
+          break;
+      case RD_I_INT:
+          add_response_info(sock,"%s %d %d\n", data->key, data->type, data->value.u_int);
+          break;
+      case RD_I_STR:
+          add_response_info(sock,"%s %d %s\n", data->key, data->type, data->value.u_str);
+          break;
+      case RD_I_BLO:
+          add_response_info(sock,"%s %d %lu\n", data->key, data->type, data->value.u_blo.size);
+          break;
+      }
+  }
+
+  rrd_info_free(info);
+
+  return send_response(sock, RESP_OK, "Info for %s follows\n",file);
+} /* }}} static int handle_request_info  */
+
+static int handle_request_first (HANDLER_PROTO) /* {{{ */
+{
+  char *i, *file, file_tmp[PATH_MAX];
+  int status;
+  int idx;
+  time_t t;
+
+  /* obtain filename */
+  status = buffer_get_field(&buffer, &buffer_size, &file);
+  if (status != 0)
+    return syntax_error(sock,cmd);
+  /* get full pathname */
+  get_abs_path(&file, file_tmp);
+  if (!check_file_access(file, sock)) {
+    return send_response(sock, RESP_ERR, "Cannot read: %s\n", file);
+  }
+
+  status = buffer_get_field(&buffer, &buffer_size, &i);
+  if (status != 0)
+    return syntax_error(sock,cmd);
+  idx = atoi(i);
+  if(idx<0) { 
+    return send_response(sock, RESP_ERR, "Invalid index specified (%d)\n", idx);
+  }
+
+  /* get data */
+  rrd_clear_error ();
+  t = rrd_first_r(file,idx);
+  if(t<1) {
+    return send_response(sock, RESP_ERR, "RRD Error: %s\n", rrd_get_error());
+  }
+  return send_response(sock, RESP_OK, "%lu\n",(unsigned)t);
+} /* }}} static int handle_request_first  */
+
+
+static int handle_request_last (HANDLER_PROTO) /* {{{ */
+{
+  char *file, file_tmp[PATH_MAX];
+  int status;
+  time_t t, from_file, step;
+  rrd_file_t * rrd_file;
+  cache_item_t * ci;
+  rrd_t rrd; 
+
+  /* obtain filename */
+  status = buffer_get_field(&buffer, &buffer_size, &file);
+  if (status != 0)
+    return syntax_error(sock,cmd);
+  /* get full pathname */
+  get_abs_path(&file, file_tmp);
+  if (!check_file_access(file, sock)) {
+    return send_response(sock, RESP_ERR, "Cannot read: %s\n", file);
+  }
+  rrd_clear_error();
+  rrd_init(&rrd);
+  rrd_file = rrd_open(file,&rrd,RRD_READONLY);
+  if(!rrd_file) {
+    return send_response(sock, RESP_ERR, "RRD Error: %s\n", rrd_get_error());
+  }
+  from_file = rrd.live_head->last_up;
+  step = rrd.stat_head->pdp_step;
+  rrd_close(rrd_file);
+  pthread_mutex_lock(&cache_lock);
+  ci = g_tree_lookup(cache_tree, file);
+  if (ci)
+    t = ci->last_update_stamp;
+  else
+    t = from_file;
+  pthread_mutex_unlock(&cache_lock);
+  t -= t % step;
+  rrd_free(&rrd);
+  if(t<1) {
+    return send_response(sock, RESP_ERR, "Error: rrdcached: Invalid timestamp returned\n");
+  }
+  return send_response(sock, RESP_OK, "%lu\n",(unsigned)t);
+} /* }}} static int handle_request_last  */
+
+static int handle_request_create (HANDLER_PROTO) /* {{{ */
+{
+  char *file, file_tmp[PATH_MAX];
+  char *tok;
+  int ac = 0;
+  char *av[128];
+  int status;
+  unsigned long step = 300;
+  time_t last_up = time(NULL)-10;
+  int no_overwrite = opt_no_overwrite;
+
+
+  /* obtain filename */
+  status = buffer_get_field(&buffer, &buffer_size, &file);
+  if (status != 0)
+    return syntax_error(sock,cmd);
+  /* get full pathname */
+  get_abs_path(&file, file_tmp);
+  if (!check_file_access(file, sock)) {
+    return send_response(sock, RESP_ERR, "Cannot read: %s\n", file);
+  }
+  RRDD_LOG(LOG_INFO, "rrdcreate request for %s",file);
+
+  while ((status = buffer_get_field(&buffer, &buffer_size, &tok)) == 0 && tok) {
+    if( ! strncmp(tok,"-b",2) ) {
+      status = buffer_get_field(&buffer, &buffer_size, &tok );
+      if (status != 0) return syntax_error(sock,cmd);
+      last_up = (time_t) atol(tok);
+      continue;
+    }
+    if( ! strncmp(tok,"-s",2) ) {
+      status = buffer_get_field(&buffer, &buffer_size, &tok );
+      if (status != 0) return syntax_error(sock,cmd);
+      step = atol(tok);
+      continue;
+    }
+    if( ! strncmp(tok,"-O",2) ) {
+      no_overwrite = 1;
+      continue;
+    }
+    if( ! strncmp(tok,"DS:",3) ) { av[ac++]=tok; continue; }
+    if( ! strncmp(tok,"RRA:",4) ) { av[ac++]=tok; continue; }
+    return syntax_error(sock,cmd);
+  }
+  if(step<1) {
+    return send_response(sock, RESP_ERR, "The step size cannot be less than 1 second.\n");
+  }
+  if (last_up < 3600 * 24 * 365 * 10) {
+    return send_response(sock, RESP_ERR, "The first entry must be after 1980.\n");
+  }
+
+  rrd_clear_error ();
+  status = rrd_create_r2(file,step,last_up,no_overwrite,ac,(const char **)av);
+
+  if(!status) {
+    return send_response(sock, RESP_OK, "RRD created OK\n");
+  }
+  return send_response(sock, RESP_ERR, "RRD Error: %s\n", rrd_get_error());
+} /* }}} static int handle_request_create  */
+
 /* start "BATCH" processing */
 static int batch_start (HANDLER_PROTO) /* {{{ */
 {
@@ -1827,6 +2036,47 @@ static command_t list_of_commands[] = { /* {{{ */
     "The 'FETCH' can be used by the client to retrieve values from an RRD file.\n"
   },
   {
+    "INFO",
+    handle_request_info,
+    CMD_CONTEXT_CLIENT,
+    "INFO <filename>\n",
+    "The INFO command retrieves information about a specified RRD file.\n"
+    "This is returned in standard rrdinfo format, a sequence of lines\n"
+    "with the format <keyname> = <value>\n"
+    "Note that this is the data as of the last update of the RRD file itself,\n"
+    "not the last time data was received via rrdcached, so there may be pending\n"
+    "updates in the queue.  If this bothers you, then first run a FLUSH.\n"
+  },
+  {
+    "FIRST",
+    handle_request_first,
+    CMD_CONTEXT_CLIENT,
+    "FIRST <filename> <rra index>\n",
+    "The FIRST command retrieves the first data time for a specified RRA in\n"
+    "an RRD file.\n"
+  },
+  {
+    "LAST",
+    handle_request_last,
+    CMD_CONTEXT_CLIENT,
+    "LAST <filename>\n",
+    "The LAST command retrieves the last update time for a specified RRD file.\n"
+    "Note that this is the time of the last update of the RRD file itself, not\n"
+    "the last time data was received via rrdcached, so there may be pending\n"
+    "updates in the queue.  If this bothers you, then first run a FLUSH.\n"
+  },
+  {
+    "CREATE",
+    handle_request_create,
+    CMD_CONTEXT_CLIENT | CMD_CONTEXT_BATCH,
+    "CREATE <filename> [-b start] [-s step] [-O] <DS definitions> <RRA definitions>\n",
+    "The CREATE command will create an RRD file, overwriting any existing file\n"
+    "unless the -O option is given or rrdcached was started with the -O option.\n"
+    "The start parameter needs to be in seconds since 1/1/70 (AT-style syntax is\n"
+    "not acceptable) and the step is in seconds (default is 300).\n"
+    "The DS and RRA definitions are as for the 'rrdtool create' command.\n"
+  },
+  {
     "QUIT",
     handle_request_quit,
     CMD_CONTEXT_CLIENT | CMD_CONTEXT_BATCH,
@@ -1903,6 +2153,17 @@ static int socket_permission_add (listen_socket_t *sock, /* {{{ */
   return (0);
 } /* }}} int socket_permission_add */
 
+static void socket_permission_clear (listen_socket_t *sock) /* {{{ */
+{
+  sock->permissions = 0;
+} /* }}} socket_permission_clear */
+
+static void socket_permission_copy (listen_socket_t *dest, /* {{{ */
+    listen_socket_t *src)
+{
+  dest->permissions = src->permissions;
+} /* }}} socket_permission_copy */
+
 /* check whether commands are received in the expected context */
 static int command_check_context(listen_socket_t *sock, command_t *cmd)
 {
@@ -2296,6 +2557,10 @@ static void journal_init(void) /* {{{ */
   }
 
   dir = opendir(journal_dir);
+  if (!dir) {
+    RRDD_LOG(LOG_CRIT, "journal_init: opendir(%s) failed\n", journal_dir);
+    return;
+  }
   while ((dent = readdir(dir)) != NULL)
   {
     /* looks like a journal file? */
@@ -2707,7 +2972,7 @@ static int close_listen_sockets (void) /* {{{ */
   return (0);
 } /* }}} int close_listen_sockets */
 
-static void *listen_thread_main (void *args __attribute__((unused))) /* {{{ */
+static void *listen_thread_main (void UNUSED(*args)) /* {{{ */
 {
   struct pollfd *pollfds;
   int pollfds_num;
@@ -2844,10 +3109,10 @@ static int daemonize (void) /* {{{ */
   }
   else
   {
-    listen_socket_t sock;
-    memset(&sock, 0, sizeof(sock));
-    strncpy(sock.addr, RRDCACHED_DEFAULT_ADDRESS, sizeof(sock.addr)-1);
-    open_listen_socket (&sock);
+    strncpy(default_socket.addr, RRDCACHED_DEFAULT_ADDRESS,
+        sizeof(default_socket.addr) - 1);
+    default_socket.addr[sizeof(default_socket.addr) - 1] = '\0';
+    open_listen_socket (&default_socket);
   }
 
   if (listen_fds_num < 1)
@@ -2952,16 +3217,19 @@ static int read_options (int argc, char **argv) /* {{{ */
   int option;
   int status = 0;
 
-  char **permissions = NULL;
-  size_t permissions_len = 0;
+  socket_permission_clear (&default_socket);
 
-  gid_t  socket_group = (gid_t)-1;
-  mode_t socket_permissions = (mode_t)-1;
+  default_socket.socket_group = (gid_t)-1;
+  default_socket.socket_permissions = (mode_t)-1;
 
-  while ((option = getopt(argc, argv, "gl:s:m:P:f:w:z:t:Bb:p:Fj:a:h?")) != -1)
+  while ((option = getopt(argc, argv, "Ogl:s:m:P:f:w:z:t:Bb:p:Fj:a:h?")) != -1)
   {
     switch (option)
     {
+      case 'O':
+        opt_no_overwrite = 1;
+        break;
+
       case 'g':
         stay_foreground=1;
         break;
@@ -2981,22 +3249,11 @@ static int read_options (int argc, char **argv) /* {{{ */
         strncpy(new->addr, optarg, sizeof(new->addr)-1);
 
         /* Add permissions to the socket {{{ */
-        if (permissions_len != 0)
+        if (default_socket.permissions != 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;
-            }
-          }
+          socket_permission_copy (new, &default_socket);
         }
-        else /* if (permissions_len == 0) */
+        else /* if (default_socket.permissions == 0) */
         {
           /* Add permission for ALL commands to the socket. */
           size_t i;
@@ -3007,15 +3264,15 @@ static int read_options (int argc, char **argv) /* {{{ */
             {
               fprintf (stderr, "read_options: Adding permission \"%s\" to "
                   "socket failed. This should never happen, ever! Sorry.\n",
-                  permissions[i]);
+                  list_of_commands[i].cmd);
               status = 4;
             }
           }
         }
         /* }}} Done adding permissions. */
 
-        new->socket_group = socket_group;
-        new->socket_permissions = socket_permissions;
+        new->socket_group = default_socket.socket_group;
+        new->socket_permissions = default_socket.socket_permissions;
 
         if (!rrd_add_ptr((void ***)&config_listen_address_list,
                          &config_listen_address_list_len, new))
@@ -3045,7 +3302,7 @@ static int read_options (int argc, char **argv) /* {{{ */
 
        if (grp)
        {
-         socket_group = grp->gr_gid;
+         default_socket.socket_group = grp->gr_gid;
        }
        else
        {
@@ -3070,7 +3327,7 @@ static int read_options (int argc, char **argv) /* {{{ */
           return (5);
         }
 
-        socket_permissions = (mode_t)tmp;
+        default_socket.socket_permissions = (mode_t)tmp;
       }
       break;
 
@@ -3081,7 +3338,7 @@ static int read_options (int argc, char **argv) /* {{{ */
         char *dummy;
         char *ptr;
 
-        rrd_free_ptrs ((void *) &permissions, &permissions_len);
+        socket_permission_clear (&default_socket);
 
         optcopy = strdup (optarg);
         dummy = optcopy;
@@ -3089,7 +3346,14 @@ static int read_options (int argc, char **argv) /* {{{ */
         while ((ptr = strtok_r (dummy, ", ", &saveptr)) != NULL)
         {
           dummy = NULL;
-          rrd_add_strdup ((void *) &permissions, &permissions_len, ptr);
+          status = socket_permission_add (&default_socket, ptr);
+          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", ptr);
+            status = 4;
+          }
         }
 
         free (optcopy);
@@ -3247,7 +3511,9 @@ static int read_options (int argc, char **argv) /* {{{ */
 
       case 'j':
       {
-        const char *dir = journal_dir = strdup(optarg);
+        char journal_dir_actual[PATH_MAX];
+        const char *dir;
+        dir = journal_dir = strdup(realpath((const char *)optarg, journal_dir_actual));
 
         status = rrd_mkdir_p(dir, 0777);
         if (status != 0)
@@ -3305,7 +3571,9 @@ static int read_options (int argc, char **argv) /* {{{ */
                             "for that group)\n"
             "  -m <mode>     File permissions (octal) of all following UNIX "
                             "sockets\n"
-            "  -a <size>     Memory allocation chunk size. Default is 1."
+            "  -a <size>     Memory allocation chunk size. Default is 1.\n"
+            "  -O            Do not allow CREATE commands to overwrite existing\n"
+            "                files, even if asked to.\n"
             "\n"
             "For more information and a detailed description of all options "
             "please refer\n"
@@ -3334,8 +3602,6 @@ 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 */