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!
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)
{
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;
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_mutex_unlock(&cache_lock);
return (0);
{
"Command overview\n"
,
+ "HELP [<command>]\n"
"FLUSH <filename>\n"
"FLUSHALL\n"
- "HELP [<command>]\n"
+ "PENDING <filename>\n"
+ "FORGET <filename>\n"
"UPDATE <filename> <values> [<values> ...]\n"
"BATCH\n"
"STATS\n"
"Triggers writing of all pending updates. Returns immediately.\n"
};
+ 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] =
+ {
+ "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"
help_text = help_flush;
else if (strcasecmp (command, "flushall") == 0)
help_text = help_flushall;
+ 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;
else if (strcasecmp (command, "batch") == 0)
static int handle_request_flushall(listen_socket_t *sock) /* {{{ */
{
+ int status;
+
+ status = has_privilege(sock, PRIV_HIGH);
+ if (status <= 0)
+ return status;
RRDD_LOG(LOG_DEBUG, "Received FLUSHALL");
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)
+ {
+ pthread_mutex_unlock(&cache_lock);
+ return send_response(sock, RESP_ERR, "%s\n", rrd_strerror(ENOENT));
+ }
+
+ for (int i=0; i < ci->values_num; i++)
+ add_response_info(sock, "%s\n", ci->values[i]);
+
+ 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, /* {{{ */
char *buffer, size_t buffer_size)
{
char *file;
int values_num = 0;
int status;
+ char orig_buf[CMD_MAX];
time_t now;
cache_item_t *ci;
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)
return send_response(sock, RESP_ERR,
} /* }}} */
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;
return send_response(sock, RESP_OK, "errors\n");
} /* }}} static int batch_done */
-/* returns 1 if we have the required privilege level */
-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 */
-
/* if sock==NULL, we are in journal replay mode */
static int handle_request (listen_socket_t *sock, /* {{{ */
char *buffer, size_t buffer_size)
sock->batch_cmd++;
if (strcasecmp (command, "update") == 0)
- {
- status = has_privilege(sock, PRIV_HIGH);
- if (status <= 0)
- return status;
-
- /* don't re-write updates in replay mode */
- if (sock != NULL)
- journal_write(command, buffer_ptr);
-
return (handle_request_update (sock, buffer_ptr, buffer_size));
- }
else if (strcasecmp (command, "wrote") == 0 && sock == NULL)
{
/* this is only valid in replay mode */
else if (strcasecmp (command, "flush") == 0)
return (handle_request_flush (sock, buffer_ptr, buffer_size));
else if (strcasecmp (command, "flushall") == 0)
- {
- status = has_privilege(sock, PRIV_HIGH);
- if (status <= 0)
- return status;
-
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 (sock));
else if (strcasecmp (command, "help") == 0)
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_connection(sock);
break;
- }
else if ((pollfd.revents & (POLLIN | POLLPRI)) == 0)
{
RRDD_LOG (LOG_WARNING, "connection_thread_main: "
"poll(2) returned something unexpected: %#04hx",
pollfd.revents);
- close_connection(sock);
break;
}