int family;
socket_privilege privilege;
+ /* state for BATCH processing */
+ int batch_mode;
+ int batch_cmd;
+
/* buffered IO */
char *rbuf;
off_t next_cmd;
* Variables
*/
static int stay_foreground = 0;
+static uid_t daemon_uid;
static listen_socket_t *listen_fds = NULL;
static size_t listen_fds_num = 0;
int len;
if (sock == NULL) return 0; /* journal replay mode */
+ if (sock->batch_mode) return 0; /* no extra info returned when in BATCH */
va_start(argp, fmt);
#ifdef HAVE_VSNPRINTF
if (sock == NULL) return rc; /* journal replay mode */
- if (rc == RESP_OK)
+ if (sock->batch_mode)
{
- lines = count_lines(sock->wbuf);
+ if (rc == RESP_OK)
+ return rc; /* no response on success during BATCH */
+ lines = sock->batch_cmd;
}
+ else if (rc == RESP_OK)
+ lines = count_lines(sock->wbuf);
else
lines = -1;
len += rclen;
+ /* append the result to the wbuf, don't write to the user */
+ if (sock->batch_mode)
+ return add_to_wbuf(sock, buffer, len);
+
/* first write must be complete */
if (len != write(sock->fd, buffer, len))
{
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;
"FLUSHALL\n"
"HELP [<command>]\n"
"UPDATE <filename> <values> [<values> ...]\n"
+ "BATCH\n"
"STATS\n"
};
"a description of the values.\n"
};
+ char *help_batch[2] =
+ {
+ "Help for BATCH\n"
+ ,
+ "The 'BATCH' command permits the client to initiate a bulk load\n"
+ " of commands to rrdcached.\n"
+ "\n"
+ "Usage:\n"
+ "\n"
+ " client: BATCH\n"
+ " server: 0 Go ahead. End with dot '.' on its own line.\n"
+ " client: command #1\n"
+ " client: command #2\n"
+ " client: ... and so on\n"
+ " client: .\n"
+ " server: 2 errors\n"
+ " server: 7 message for command #7\n"
+ " server: 9 message for command #9\n"
+ "\n"
+ "For more information, consult the rrdcached(1) documentation.\n"
+ };
+
status = buffer_get_field (&buffer, &buffer_size, &command);
if (status != 0)
help_text = help_help;
help_text = help_flushall;
else if (strcasecmp (command, "stats") == 0)
help_text = help_stats;
+ else if (strcasecmp (command, "batch") == 0)
+ help_text = help_batch;
else
help_text = help_help;
}
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");
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 (0);
} /* }}} int handle_request_wrote */
-/* returns 1 if we have the required privilege level */
-static int has_privilege (listen_socket_t *sock, /* {{{ */
- socket_privilege priv)
+/* start "BATCH" processing */
+static int batch_start (listen_socket_t *sock) /* {{{ */
{
- if (sock == NULL) /* journal replay */
- return 1;
+ int status;
+ if (sock->batch_mode)
+ return send_response(sock, RESP_ERR, "Already in BATCH\n");
- if (sock->privilege >= priv)
- return 1;
+ status = send_response(sock, RESP_OK,
+ "Go ahead. End with dot '.' on its own line.\n");
+ sock->batch_mode = 1;
+ sock->batch_cmd = 0;
- return send_response(sock, RESP_ERR, "%s\n", rrd_strerror(EACCES));
-} /* }}} static int has_privilege */
+ return status;
+} /* }}} static int batch_start */
+
+/* finish "BATCH" processing and return results to the client */
+static int batch_done (listen_socket_t *sock) /* {{{ */
+{
+ assert(sock->batch_mode);
+ sock->batch_mode = 0;
+ sock->batch_cmd = 0;
+ return send_response(sock, RESP_OK, "errors\n");
+} /* }}} static int batch_done */
/* if sock==NULL, we are in journal replay mode */
static int handle_request (listen_socket_t *sock, /* {{{ */
return (-1);
}
- 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);
+ if (sock != NULL && sock->batch_mode)
+ sock->batch_cmd++;
+ if (strcasecmp (command, "update") == 0)
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, "stats") == 0)
return (handle_request_stats (sock));
else if (strcasecmp (command, "help") == 0)
return (handle_request_help (sock, buffer_ptr, buffer_size));
+ else if (strcasecmp (command, "batch") == 0 && sock != NULL)
+ return batch_start(sock);
+ else if (strcasecmp (command, ".") == 0 && sock != NULL && sock->batch_mode)
+ return batch_done(sock);
else
return send_response(sock, RESP_ERR, "Unknown command: %s\n", command);
static void journal_rotate(void) /* {{{ */
{
FILE *old_fh = NULL;
+ int new_fd;
if (journal_cur == NULL || journal_old == NULL)
return;
if (journal_fh != NULL)
{
old_fh = journal_fh;
+ journal_fh = NULL;
rename(journal_cur, journal_old);
++stats_journal_rotate;
}
- journal_fh = fopen(journal_cur, "a");
+ new_fd = open(journal_cur, O_WRONLY|O_CREAT|O_APPEND,
+ S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
+ if (new_fd >= 0)
+ {
+ journal_fh = fdopen(new_fd, "a");
+ if (journal_fh == NULL)
+ close(new_fd);
+ }
+
pthread_mutex_unlock(&journal_lock);
if (old_fh != NULL)
if (file == NULL) return 0;
+ {
+ char *reason;
+ int status = 0;
+ struct stat statbuf;
+
+ memset(&statbuf, 0, sizeof(statbuf));
+ if (stat(file, &statbuf) != 0)
+ {
+ if (errno == ENOENT)
+ return 0;
+
+ reason = "stat error";
+ status = errno;
+ }
+ else if (!S_ISREG(statbuf.st_mode))
+ {
+ reason = "not a regular file";
+ status = EPERM;
+ }
+ if (statbuf.st_uid != daemon_uid)
+ {
+ reason = "not owned by daemon user";
+ status = EACCES;
+ }
+ if (statbuf.st_mode & (S_IWGRP|S_IWOTH))
+ {
+ reason = "must not be user/group writable";
+ status = EACCES;
+ }
+
+ if (status != 0)
+ {
+ RRDD_LOG(LOG_ERR, "journal_replay: %s : %s (%s)",
+ file, rrd_strerror(status), reason);
+ return 0;
+ }
+ }
+
fh = fopen(file, "r");
if (fh == NULL)
{
fclose(fh);
- if (entry_cnt > 0)
- {
- RRDD_LOG(LOG_INFO, "Replayed %d entries (%d failures)",
- entry_cnt, fail_cnt);
- return 1;
- }
- else
- return 0;
+ RRDD_LOG(LOG_INFO, "Replayed %d entries (%d failures)",
+ entry_cnt, fail_cnt);
+ return entry_cnt > 0 ? 1 : 0;
} /* }}} static int journal_replay */
+static void journal_init(void) /* {{{ */
+{
+ int had_journal = 0;
+
+ if (journal_cur == NULL) return;
+
+ pthread_mutex_lock(&journal_lock);
+
+ RRDD_LOG(LOG_INFO, "checking for journal files");
+
+ had_journal += journal_replay(journal_old);
+ had_journal += journal_replay(journal_cur);
+
+ /* it must have been a crash. start a flush */
+ if (had_journal && config_flush_at_shutdown)
+ flush_old_values(-1);
+
+ pthread_mutex_unlock(&journal_lock);
+ journal_rotate();
+
+ RRDD_LOG(LOG_INFO, "journal processing complete");
+
+} /* }}} static void journal_init */
+
static void close_connection(listen_socket_t *sock)
{
close(sock->fd) ; sock->fd = -1;
int fd;
char *base_dir;
+ daemon_uid = geteuid();
+
fd = open_pidfile();
if (fd < 0) return fd;
return (1);
}
- if (journal_cur != NULL)
- {
- int had_journal = 0;
-
- pthread_mutex_lock(&journal_lock);
-
- RRDD_LOG(LOG_INFO, "checking for journal files");
-
- had_journal += journal_replay(journal_old);
- had_journal += journal_replay(journal_cur);
-
- if (had_journal)
- flush_old_values(-1);
-
- pthread_mutex_unlock(&journal_lock);
- journal_rotate();
-
- RRDD_LOG(LOG_INFO, "journal processing complete");
- }
+ journal_init();
/* start the queue thread */
memset (&queue_thread, 0, sizeof (queue_thread));