#define CI_FLAGS_IN_QUEUE (1<<1)
int flags;
pthread_cond_t flushed;
+ cache_item_t *prev;
cache_item_t *next;
};
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;
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;
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!
static int enqueue_cache_item (cache_item_t *ci, /* {{{ */
queue_side_t side)
{
- int did_insert = 0;
-
if (ci == NULL)
return (-1);
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 */
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);
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;
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);
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);
return (0);
}
- _wipe_ci_values(ci, now);
+ wipe_ci_values(ci, now);
ci->flags = CI_FLAGS_IN_TREE;
pthread_mutex_lock(&cache_lock);
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);
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)
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)
{
break;
}
+ case 'B':
+ config_write_base_only = 1;
+ break;
+
case 'b':
{
size_t len;
fprintf (stderr, "Invalid base directory: %s\n", optarg);
return (4);
}
+
+ _config_base_dir_len = len;
}
break;
" -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"
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;