missing spaces in extconf.rb caused compile failure -- kevin
[rrdtool.git] / src / rrd_daemon.c
index a0e6bdb..604aee3 100644 (file)
@@ -128,6 +128,7 @@ struct cache_item_s
 #define CI_FLAGS_IN_QUEUE (1<<1)
   int flags;
   pthread_cond_t  flushed;
+  cache_item_t *prev;
   cache_item_t *next;
 };
 
@@ -179,6 +180,8 @@ 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 listen_socket_t **config_listen_address_list = NULL;
 static int config_listen_address_list_len = 0;
@@ -400,7 +403,7 @@ static ssize_t swrite (int fd, const void *buf, size_t count) /* {{{ */
   return (0);
 } /* }}} ssize_t swrite */
 
-static void _wipe_ci_values(cache_item_t *ci, time_t when)
+static void wipe_ci_values(cache_item_t *ci, time_t when)
 {
   ci->values = NULL;
   ci->values_num = 0;
@@ -408,10 +411,30 @@ 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 */
+
 /*
  * enqueue_cache_item:
  * `cache_lock' must be acquired before calling this function!
@@ -419,8 +442,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);
 
@@ -429,67 +450,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_head == ci)
+      return 0;
 
-      if (cache_queue_tail == NULL)
-        cache_queue_tail = cache_queue_head;
+    /* remove from the double linked list */
+    if (ci->flags & CI_FLAGS_IN_QUEUE)
+      remove_from_queue(ci);
 
-      did_insert = 1;
-    }
-    else if (cache_queue_head == ci)
-    {
-      /* do nothing */
-    }
-    else /* enqueued, but not first entry */
-    {
-      cache_item_t *prev;
-
-      /* find previous entry */
-      for (prev = cache_queue_head; prev != NULL; prev = prev->next)
-        if (prev->next == ci)
-          break;
-      assert (prev != NULL);
-
-      /* 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 */
@@ -682,12 +683,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);
@@ -809,6 +806,38 @@ 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, int fd) /* {{{ */
+{
+  char error[CMD_MAX];
+  assert(file != NULL);
+
+  if (!config_write_base_only
+      || fd < 0 /* 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:
+  snprintf(error, sizeof(error)-1, "-1 %s\n", rrd_strerror(EACCES));
+  swrite(fd, error, strlen(error));
+  return 0;
+} /* }}} static int check_file_access */
+
 static int flush_file (const char *filename) /* {{{ */
 {
   cache_item_t *ci;
@@ -1051,6 +1080,8 @@ static int handle_request_flush (int fd, /* {{{ */
     stats_flush_received++;
     pthread_mutex_unlock(&stats_lock);
 
+    if (!check_file_access(file, fd)) return 0;
+
     status = flush_file (file);
     if (status == 0)
       snprintf (result, sizeof (result), "0 Successfully flushed %s.\n", file);
@@ -1141,6 +1172,8 @@ static int handle_request_update (int fd, /* {{{ */
   stats_updates_received++;
   pthread_mutex_unlock(&stats_lock);
 
+  if (!check_file_access(file, fd)) return 0;
+
   pthread_mutex_lock (&cache_lock);
   ci = g_tree_lookup (cache_tree, file);
 
@@ -1202,7 +1235,7 @@ static int handle_request_update (int fd, /* {{{ */
       return (0);
     }
 
-    _wipe_ci_values(ci, now);
+    wipe_ci_values(ci, now);
     ci->flags = CI_FLAGS_IN_TREE;
 
     pthread_mutex_lock(&cache_lock);
@@ -1293,7 +1326,8 @@ static int handle_request_wrote (int fd __attribute__((unused)), /* {{{ */
     free(ci->values);
   }
 
-  _wipe_ci_values(ci, time(NULL));
+  wipe_ci_values(ci, time(NULL));
+  remove_from_queue(ci);
 
   pthread_mutex_unlock(&cache_lock);
   return (0);
@@ -1339,14 +1373,14 @@ static int handle_request (int fd, socket_privilege privilege, /* {{{ */
 
   if (strcasecmp (command, "update") == 0)
   {
-    /* don't re-write updates in replay mode */
-    if (fd >= 0)
-      journal_write(command, buffer_ptr);
-
     status = has_privilege(privilege, PRIV_HIGH, fd);
     if (status <= 0)
       return status;
 
+    /* don't re-write updates in replay mode */
+    if (fd >= 0)
+      journal_write(command, buffer_ptr);
+
     return (handle_request_update (fd, buffer_ptr, buffer_size));
   }
   else if (strcasecmp (command, "wrote") == 0 && fd < 0)
@@ -2079,7 +2113,7 @@ static int read_options (int argc, char **argv) /* {{{ */
   int option;
   int status = 0;
 
-  while ((option = getopt(argc, argv, "gl:L: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)
     {
@@ -2164,6 +2198,10 @@ static int read_options (int argc, char **argv) /* {{{ */
         break;
       }
 
+      case 'B':
+        config_write_base_only = 1;
+        break;
+
       case 'b':
       {
         size_t len;
@@ -2189,6 +2227,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;
 
@@ -2258,6 +2298,7 @@ static int read_options (int argc, char **argv) /* {{{ */
             "  -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"
@@ -2279,6 +2320,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;