/*
* Now for some includes..
*/
-#include "rrd.h" /* {{{ */
+/* {{{ */
+#if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__) && !defined(HAVE_CONFIG_H)
+#include "../win32/config.h"
+#else
+#ifdef HAVE_CONFIG_H
+#include "../rrd_config.h"
+#endif
+#endif
+
+#include "rrd.h"
#include "rrd_client.h"
#include <stdlib.h>
socket_privilege privilege;
/* state for BATCH processing */
- int batch_mode;
+ time_t batch_start;
int batch_cmd;
/* buffered IO */
} /* }}} void install_signal_handlers */
-static int open_pidfile(void) /* {{{ */
+static int open_pidfile(char *action, int oflag) /* {{{ */
{
int fd;
char *file;
? config_pid_file
: LOCALSTATEDIR "/run/rrdcached.pid";
- fd = open(file, O_CREAT|O_EXCL|O_WRONLY, S_IRUSR|S_IRGRP|S_IROTH);
+ fd = open(file, oflag, S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH);
if (fd < 0)
- fprintf(stderr, "FATAL: cannot create '%s' (%s)\n",
- file, rrd_strerror(errno));
+ fprintf(stderr, "rrdcached: can't %s pid file '%s' (%s)\n",
+ action, file, rrd_strerror(errno));
return(fd);
} /* }}} static int open_pidfile */
+/* check existing pid file to see whether a daemon is running */
+static int check_pidfile(void)
+{
+ int pid_fd;
+ pid_t pid;
+ char pid_str[16];
+
+ pid_fd = open_pidfile("open", O_RDWR);
+ if (pid_fd < 0)
+ return pid_fd;
+
+ if (read(pid_fd, pid_str, sizeof(pid_str)) <= 0)
+ return -1;
+
+ pid = atoi(pid_str);
+ if (pid <= 0)
+ return -1;
+
+ /* another running process that we can signal COULD be
+ * a competing rrdcached */
+ if (pid != getpid() && kill(pid, 0) == 0)
+ {
+ fprintf(stderr,
+ "FATAL: Another rrdcached daemon is running?? (pid %d)\n", pid);
+ close(pid_fd);
+ return -1;
+ }
+
+ lseek(pid_fd, 0, SEEK_SET);
+ ftruncate(pid_fd, 0);
+
+ fprintf(stderr,
+ "rrdcached: removed stale PID file (no rrdcached on pid %d)\n"
+ "rrdcached: starting normally.\n", pid);
+
+ return pid_fd;
+} /* }}} static int check_pidfile */
+
static int write_pidfile (int fd) /* {{{ */
{
pid_t pid;
int len;
if (sock == NULL) return 0; /* journal replay mode */
- if (sock->batch_mode) return 0; /* no extra info returned when in BATCH */
+ if (sock->batch_start) 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 (sock->batch_mode)
+ if (sock->batch_start)
{
if (rc == RESP_OK)
return rc; /* no response on success during BATCH */
len += rclen;
/* append the result to the wbuf, don't write to the user */
- if (sock->batch_mode)
+ if (sock->batch_start)
return add_to_wbuf(sock, buffer, len);
/* first write must be complete */
static void remove_from_queue(cache_item_t *ci) /* {{{ */
{
if (ci == NULL) return;
+ if ((ci->flags & CI_FLAGS_IN_QUEUE) == 0) return; /* not queued */
if (ci->prev == NULL)
cache_queue_head = ci->next; /* reset head */
if (cache_queue_head == ci)
return 0;
- /* remove from the double linked list */
- if (ci->flags & CI_FLAGS_IN_QUEUE)
- remove_from_queue(ci);
+ /* remove if further down in queue */
+ remove_from_queue(ci);
ci->prev = NULL;
ci->next = cache_queue_head;
ci = (cache_item_t *) value;
cfd = (callback_flush_data_t *) data;
+ if (ci->flags & CI_FLAGS_IN_QUEUE)
+ return FALSE;
+
if ((ci->last_flush_time <= cfd->abs_timeout)
- && ((ci->flags & CI_FLAGS_IN_QUEUE) == 0)
&& (ci->values_num > 0))
{
enqueue_cache_item (ci, TAIL);
}
else if ((do_shutdown != 0)
- && ((ci->flags & CI_FLAGS_IN_QUEUE) == 0)
&& (ci->values_num > 0))
{
enqueue_cache_item (ci, TAIL);
}
else if (((cfd->now - ci->last_flush_time) >= config_flush_interval)
- && ((ci->flags & CI_FLAGS_IN_QUEUE) == 0)
&& (ci->values_num <= 0))
{
char **temp;
flush_old_values (config_write_interval);
/* Determine the time of the next cache flush. */
- while (next_flush.tv_sec <= now.tv_sec)
- next_flush.tv_sec += config_flush_interval;
+ next_flush.tv_sec =
+ now.tv_sec + next_flush.tv_sec % config_flush_interval;
/* unlock the cache while we rotate so we don't block incoming
* updates if the fsync() blocks on disk I/O */
return 0;
} /* }}} static int check_file_access */
+/* when using a base dir, convert relative paths to absolute paths.
+ * if necessary, modifies the "filename" pointer to point
+ * to the new path created in "tmp". "tmp" is provided
+ * by the caller and sizeof(tmp) must be >= PATH_MAX.
+ *
+ * this allows us to optimize for the expected case (absolute path)
+ * with a no-op.
+ */
+static void get_abs_path(char **filename, char *tmp)
+{
+ assert(tmp != NULL);
+ assert(filename != NULL && *filename != NULL);
+
+ if (config_base_dir == NULL || **filename == '/')
+ return;
+
+ snprintf(tmp, PATH_MAX, "%s/%s", config_base_dir, *filename);
+ *filename = tmp;
+} /* }}} static int get_abs_path */
+
/* 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, /* {{{ */
static int handle_request_flush (listen_socket_t *sock, /* {{{ */
char *buffer, size_t buffer_size)
{
- char *file;
+ char *file, file_tmp[PATH_MAX];
int status;
status = buffer_get_field (&buffer, &buffer_size, &file);
stats_flush_received++;
pthread_mutex_unlock(&stats_lock);
+ get_abs_path(&file, file_tmp);
if (!check_file_access(file, sock)) return 0;
status = flush_file (file);
/* NOTREACHED */
assert(1==0);
-} /* }}} int handle_request_slurp */
+} /* }}} int handle_request_flush */
static int handle_request_flushall(listen_socket_t *sock) /* {{{ */
{
char *buffer, size_t buffer_size)
{
int status;
- char *file;
+ char *file, file_tmp[PATH_MAX];
cache_item_t *ci;
status = buffer_get_field(&buffer, &buffer_size, &file);
if (status <= 0)
return status;
+ get_abs_path(&file, file_tmp);
+
pthread_mutex_lock(&cache_lock);
ci = g_tree_lookup(cache_tree, file);
if (ci == NULL)
char *buffer, size_t buffer_size)
{
int status;
- char *file;
+ char *file, file_tmp[PATH_MAX];
status = buffer_get_field(&buffer, &buffer_size, &file);
if (status != 0)
if (status <= 0)
return status;
+ get_abs_path(&file, file_tmp);
if (!check_file_access(file, sock)) return 0;
pthread_mutex_lock(&cache_lock);
} /* }}} static int handle_request_forget */
static int handle_request_update (listen_socket_t *sock, /* {{{ */
- char *buffer, size_t buffer_size)
+ time_t now,
+ char *buffer, size_t buffer_size)
{
- char *file;
+ char *file, file_tmp[PATH_MAX];
int values_num = 0;
int bad_timestamps = 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;
stats_updates_received++;
pthread_mutex_unlock(&stats_lock);
+ get_abs_path(&file, file_tmp);
if (!check_file_access(file, sock)) return 0;
pthread_mutex_lock (&cache_lock);
wipe_ci_values(ci, now);
ci->flags = CI_FLAGS_IN_TREE;
+ pthread_cond_init(&ci->flushed, NULL);
pthread_mutex_lock(&cache_lock);
g_tree_insert (cache_tree, (void *) ci->file, (void *) ci);
if (values_num < 1)
{
+ /* journal replay mode */
+ if (sock == NULL) return RESP_ERR;
+
/* if we had only one update attempt, then return the full
error message... try to get the most information out
of the limited error space allowed by the protocol
/* we came across a "WROTE" entry during journal replay.
* throw away any values that we have accumulated for this file
*/
-static int handle_request_wrote (const char *buffer) /* {{{ */
+static int handle_request_wrote (const char *buffer, time_t now) /* {{{ */
{
int i;
cache_item_t *ci;
free(ci->values);
}
- wipe_ci_values(ci, time(NULL));
+ wipe_ci_values(ci, now);
remove_from_queue(ci);
pthread_mutex_unlock(&cache_lock);
static int batch_start (listen_socket_t *sock) /* {{{ */
{
int status;
- if (sock->batch_mode)
+ if (sock->batch_start)
return send_response(sock, RESP_ERR, "Already in BATCH\n");
status = send_response(sock, RESP_OK,
"Go ahead. End with dot '.' on its own line.\n");
- sock->batch_mode = 1;
+ sock->batch_start = time(NULL);
sock->batch_cmd = 0;
return status;
/* 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;
+ assert(sock->batch_start);
+ sock->batch_start = 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, /* {{{ */
+ time_t now,
char *buffer, size_t buffer_size)
{
char *buffer_ptr;
return (-1);
}
- if (sock != NULL && sock->batch_mode)
+ if (sock != NULL && sock->batch_start)
sock->batch_cmd++;
if (strcasecmp (command, "update") == 0)
- return (handle_request_update (sock, buffer_ptr, buffer_size));
+ return (handle_request_update (sock, now, buffer_ptr, buffer_size));
else if (strcasecmp (command, "wrote") == 0 && sock == NULL)
{
/* this is only valid in replay mode */
- return (handle_request_wrote (buffer_ptr));
+ return (handle_request_wrote (buffer_ptr, now));
}
else if (strcasecmp (command, "flush") == 0)
return (handle_request_flush (sock, buffer_ptr, buffer_size));
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)
+ else if (strcasecmp (command, ".") == 0 && sock != NULL && sock->batch_start)
return batch_done(sock);
else
return send_response(sock, RESP_ERR, "Unknown command: %s\n", command);
int fail_cnt = 0;
uint64_t line = 0;
char entry[CMD_MAX];
+ time_t now;
if (file == NULL) return 0;
else
RRDD_LOG(LOG_NOTICE, "replaying from journal: %s", file);
+ now = time(NULL);
+
while(!feof(fh))
{
size_t entry_len;
entry[entry_len - 1] = '\0';
- if (handle_request(NULL, entry, entry_len) == 0)
+ if (handle_request(NULL, now, entry, entry_len) == 0)
++entry_cnt;
else
++fail_cnt;
char *cmd;
ssize_t cmd_len;
ssize_t rbytes;
+ time_t now;
struct pollfd pollfd;
int status;
sock->next_read += rbytes;
+ if (sock->batch_start)
+ now = sock->batch_start;
+ else
+ now = time(NULL);
+
while ((cmd = next_cmd(sock, &cmd_len)) != NULL)
{
- status = handle_request (sock, cmd, cmd_len+1);
+ status = handle_request (sock, now, cmd, cmd_len+1);
if (status != 0)
goto out_close;
}
sizeof (listen_fds[0]) * (listen_fds_num + 1));
if (temp == NULL)
{
- RRDD_LOG (LOG_ERR, "open_listen_socket_unix: realloc failed.");
+ fprintf (stderr, "rrdcached: open_listen_socket_unix: realloc failed.\n");
return (-1);
}
listen_fds = temp;
fd = socket (PF_UNIX, SOCK_STREAM, /* protocol = */ 0);
if (fd < 0)
{
- RRDD_LOG (LOG_ERR, "open_listen_socket_unix: socket(2) failed.");
+ fprintf (stderr, "rrdcached: unix socket(2) failed: %s\n",
+ rrd_strerror(errno));
return (-1);
}
sa.sun_family = AF_UNIX;
strncpy (sa.sun_path, path, sizeof (sa.sun_path) - 1);
+ /* if we've gotten this far, we own the pid file. any daemon started
+ * with the same args must not be alive. therefore, ensure that we can
+ * create the socket...
+ */
+ unlink(path);
+
status = bind (fd, (struct sockaddr *) &sa, sizeof (sa));
if (status != 0)
{
- RRDD_LOG (LOG_ERR, "open_listen_socket_unix: bind(2) failed.");
+ fprintf (stderr, "rrdcached: bind(%s) failed: %s.\n",
+ path, rrd_strerror(errno));
close (fd);
- unlink (path);
return (-1);
}
status = listen (fd, /* backlog = */ 10);
if (status != 0)
{
- RRDD_LOG (LOG_ERR, "open_listen_socket_unix: listen(2) failed.");
+ fprintf (stderr, "rrdcached: listen(%s) failed: %s.\n",
+ path, rrd_strerror(errno));
close (fd);
unlink (path);
return (-1);
port = strchr (addr, ']');
if (port == NULL)
{
- RRDD_LOG (LOG_ERR, "open_listen_socket_network: Malformed address: %s",
- sock->addr);
+ fprintf (stderr, "rrdcached: Malformed address: %s\n", sock->addr);
return (-1);
}
*port = 0;
port = NULL;
else
{
- RRDD_LOG (LOG_ERR, "open_listen_socket_network: Garbage after address: %s",
- port);
+ fprintf (stderr, "rrdcached: Garbage after address: %s\n", port);
return (-1);
}
} /* if (*addr = ']') */
&ai_hints, &ai_res);
if (status != 0)
{
- RRDD_LOG (LOG_ERR, "open_listen_socket_network: getaddrinfo(%s) failed: "
- "%s", addr, gai_strerror (status));
+ fprintf (stderr, "rrdcached: getaddrinfo(%s) failed: %s\n",
+ addr, gai_strerror (status));
return (-1);
}
sizeof (listen_fds[0]) * (listen_fds_num + 1));
if (temp == NULL)
{
- RRDD_LOG (LOG_ERR, "open_listen_socket_network: realloc failed.");
+ fprintf (stderr,
+ "rrdcached: open_listen_socket_network: realloc failed.\n");
continue;
}
listen_fds = temp;
fd = socket (ai_ptr->ai_family, ai_ptr->ai_socktype, ai_ptr->ai_protocol);
if (fd < 0)
{
- RRDD_LOG (LOG_ERR, "open_listen_socket_network: socket(2) failed.");
+ fprintf (stderr, "rrdcached: network socket(2) failed: %s.\n",
+ rrd_strerror(errno));
continue;
}
status = bind (fd, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
if (status != 0)
{
- RRDD_LOG (LOG_ERR, "open_listen_socket_network: bind(2) failed.");
+ fprintf (stderr, "rrdcached: bind(%s) failed: %s.\n",
+ sock->addr, rrd_strerror(errno));
close (fd);
continue;
}
status = listen (fd, /* backlog = */ 10);
if (status != 0)
{
- RRDD_LOG (LOG_ERR, "open_listen_socket_network: listen(2) failed.");
+ fprintf (stderr, "rrdcached: listen(%s) failed: %s\n.",
+ sock->addr, rrd_strerror(errno));
close (fd);
return (-1);
}
int status;
int i;
- for (i = 0; i < config_listen_address_list_len; i++)
- open_listen_socket (config_listen_address_list[i]);
-
- if (config_listen_address_list_len < 1)
- {
- listen_socket_t sock;
- memset(&sock, 0, sizeof(sock));
- strncpy(sock.addr, RRDCACHED_DEFAULT_ADDRESS, sizeof(sock.addr));
- open_listen_socket (&sock);
- }
-
if (listen_fds_num < 1)
{
- RRDD_LOG (LOG_ERR, "listen_thread_main: No listen sockets "
- "could be opened. Sorry.");
+ RRDD_LOG(LOG_ERR, "listen_thread_main: no listen_fds !");
return (NULL);
}
static int daemonize (void) /* {{{ */
{
- int status;
- int fd;
+ int pid_fd;
char *base_dir;
daemon_uid = geteuid();
- fd = open_pidfile();
- if (fd < 0) return fd;
+ pid_fd = open_pidfile("create", O_CREAT|O_EXCL|O_WRONLY);
+ if (pid_fd < 0)
+ pid_fd = check_pidfile();
+ if (pid_fd < 0)
+ return pid_fd;
+
+ /* open all the listen sockets */
+ if (config_listen_address_list_len > 0)
+ {
+ for (int i = 0; i < config_listen_address_list_len; i++)
+ open_listen_socket (config_listen_address_list[i]);
+ }
+ else
+ {
+ listen_socket_t sock;
+ memset(&sock, 0, sizeof(sock));
+ strncpy(sock.addr, RRDCACHED_DEFAULT_ADDRESS, sizeof(sock.addr));
+ open_listen_socket (&sock);
+ }
+
+ if (listen_fds_num < 1)
+ {
+ fprintf (stderr, "rrdcached: FATAL: cannot open any listen sockets\n");
+ goto error;
+ }
if (!stay_foreground)
{
if (child < 0)
{
fprintf (stderr, "daemonize: fork(2) failed.\n");
- return (-1);
+ goto error;
}
else if (child > 0)
- {
- return (1);
- }
+ exit(0);
/* Become session leader */
setsid ();
base_dir = (config_base_dir != NULL)
? config_base_dir
: "/tmp";
- status = chdir (base_dir);
- if (status != 0)
+
+ if (chdir (base_dir) != 0)
{
fprintf (stderr, "daemonize: chdir (%s) failed.\n", base_dir);
- return (-1);
+ goto error;
}
install_signal_handlers();
if (cache_tree == NULL)
{
RRDD_LOG (LOG_ERR, "daemonize: g_tree_new failed.");
- return (-1);
+ goto error;
}
- status = write_pidfile (fd);
- return status;
+ return write_pidfile (pid_fd);
+
+error:
+ remove_pidfile();
+ return -1;
} /* }}} int daemonize */
static int cleanup (void) /* {{{ */
case 'b':
{
size_t len;
+ char base_realpath[PATH_MAX];
if (config_base_dir != NULL)
free (config_base_dir);
return (3);
}
+ /* make sure that the base directory is not resolved via
+ * symbolic links. this makes some performance-enhancing
+ * assumptions possible (we don't have to resolve paths
+ * that start with a "/")
+ */
+ if (realpath(config_base_dir, base_realpath) == NULL)
+ {
+ fprintf (stderr, "Invalid base directory '%s'.\n", config_base_dir);
+ return 5;
+ }
+ else if (strncmp(config_base_dir,
+ base_realpath, sizeof(base_realpath)) != 0)
+ {
+ fprintf(stderr,
+ "Base directory (-b) resolved via file system links!\n"
+ "Please consult rrdcached '-b' documentation!\n"
+ "Consider specifying the real directory (%s)\n",
+ base_realpath);
+ return 5;
+ }
+
len = strlen (config_base_dir);
while ((len > 0) && (config_base_dir[len - 1] == '/'))
{
}
status = daemonize ();
- if (status == 1)
- {
- struct sigaction sigchld;
-
- memset (&sigchld, 0, sizeof (sigchld));
- sigchld.sa_handler = SIG_IGN;
- sigaction (SIGCHLD, &sigchld, NULL);
-
- return (0);
- }
- else if (status != 0)
+ if (status != 0)
{
- fprintf (stderr, "daemonize failed, exiting.\n");
+ fprintf (stderr, "rrdcached: daemonize failed, exiting.\n");
return (1);
}