X-Git-Url: https://git.octo.it/?p=rrdtool.git;a=blobdiff_plain;f=src%2Frrd_daemon.c;h=d404e04b40fe2029051c605e11c22aa83c19f787;hp=289f1ee1c0e38ee55644cb3bb5fdf4e7229be490;hb=e8300a5a1b96f252de0315aa797a187ce2e01f43;hpb=27f157f9d71ed7f1e53e3f1758f22909c1b0a889 diff --git a/src/rrd_daemon.c b/src/rrd_daemon.c index 289f1ee..d404e04 100644 --- a/src/rrd_daemon.c +++ b/src/rrd_daemon.c @@ -117,7 +117,7 @@ struct listen_socket_s socket_privilege privilege; /* state for BATCH processing */ - int batch_mode; + time_t batch_start; int batch_cmd; /* buffered IO */ @@ -286,7 +286,7 @@ static void install_signal_handlers(void) /* {{{ */ } /* }}} void install_signal_handlers */ -static int open_pidfile(void) /* {{{ */ +static int open_pidfile(char *action, int oflag) /* {{{ */ { int fd; char *file; @@ -295,14 +295,52 @@ static int open_pidfile(void) /* {{{ */ ? 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; @@ -405,7 +443,7 @@ static int add_response_info(listen_socket_t *sock, char *fmt, ...) /* {{{ */ 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 @@ -453,7 +491,7 @@ static int send_response (listen_socket_t *sock, response_code rc, 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 */ @@ -478,7 +516,7 @@ static int send_response (listen_socket_t *sock, response_code rc, 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 */ @@ -748,8 +786,8 @@ static void *queue_thread_main (void *args __attribute__((unused))) /* {{{ */ 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 */ @@ -956,6 +994,26 @@ err: 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, /* {{{ */ @@ -1183,7 +1241,7 @@ static int handle_request_stats (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); @@ -1197,6 +1255,7 @@ static int handle_request_flush (listen_socket_t *sock, /* {{{ */ 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); @@ -1244,7 +1303,7 @@ static int handle_request_pending(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); @@ -1256,6 +1315,8 @@ static int handle_request_pending(listen_socket_t *sock, /* {{{ */ 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) @@ -1275,7 +1336,7 @@ static int handle_request_forget(listen_socket_t *sock, /* {{{ */ 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) @@ -1286,6 +1347,7 @@ static int handle_request_forget(listen_socket_t *sock, /* {{{ */ if (status <= 0) return status; + get_abs_path(&file, file_tmp); if (!check_file_access(file, sock)) return 0; pthread_mutex_lock(&cache_lock); @@ -1308,19 +1370,17 @@ static int handle_request_forget(listen_socket_t *sock, /* {{{ */ } /* }}} 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; @@ -1337,6 +1397,7 @@ static int handle_request_update (listen_socket_t *sock, /* {{{ */ 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); @@ -1464,6 +1525,9 @@ static int handle_request_update (listen_socket_t *sock, /* {{{ */ 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 @@ -1487,7 +1551,7 @@ static int handle_request_update (listen_socket_t *sock, /* {{{ */ /* 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; @@ -1510,7 +1574,7 @@ static int handle_request_wrote (const char *buffer) /* {{{ */ free(ci->values); } - wipe_ci_values(ci, time(NULL)); + wipe_ci_values(ci, now); remove_from_queue(ci); pthread_mutex_unlock(&cache_lock); @@ -1521,12 +1585,12 @@ static int handle_request_wrote (const char *buffer) /* {{{ */ 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; @@ -1535,14 +1599,15 @@ static int batch_start (listen_socket_t *sock) /* {{{ */ /* 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; @@ -1560,15 +1625,15 @@ static int handle_request (listen_socket_t *sock, /* {{{ */ 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)); @@ -1584,7 +1649,7 @@ static int handle_request (listen_socket_t *sock, /* {{{ */ 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); @@ -1700,6 +1765,7 @@ static int journal_replay (const char *file) /* {{{ */ int fail_cnt = 0; uint64_t line = 0; char entry[CMD_MAX]; + time_t now; if (file == NULL) return 0; @@ -1752,6 +1818,8 @@ static int journal_replay (const char *file) /* {{{ */ else RRDD_LOG(LOG_NOTICE, "replaying from journal: %s", file); + now = time(NULL); + while(!feof(fh)) { size_t entry_len; @@ -1773,7 +1841,7 @@ static int journal_replay (const char *file) /* {{{ */ 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; @@ -1864,6 +1932,7 @@ static void *connection_thread_main (void *args) /* {{{ */ char *cmd; ssize_t cmd_len; ssize_t rbytes; + time_t now; struct pollfd pollfd; int status; @@ -1907,9 +1976,14 @@ static void *connection_thread_main (void *args) /* {{{ */ 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; } @@ -1957,7 +2031,7 @@ static int open_listen_socket_unix (const listen_socket_t *sock) /* {{{ */ 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; @@ -1966,7 +2040,8 @@ static int open_listen_socket_unix (const listen_socket_t *sock) /* {{{ */ 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); } @@ -1974,19 +2049,26 @@ static int open_listen_socket_unix (const listen_socket_t *sock) /* {{{ */ 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); @@ -2032,8 +2114,7 @@ static int open_listen_socket_network(const listen_socket_t *sock) /* {{{ */ 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; @@ -2045,8 +2126,7 @@ static int open_listen_socket_network(const listen_socket_t *sock) /* {{{ */ 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 = ']') */ @@ -2065,8 +2145,8 @@ static int open_listen_socket_network(const listen_socket_t *sock) /* {{{ */ &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); } @@ -2080,7 +2160,8 @@ static int open_listen_socket_network(const listen_socket_t *sock) /* {{{ */ 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; @@ -2089,7 +2170,8 @@ static int open_listen_socket_network(const listen_socket_t *sock) /* {{{ */ 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; } @@ -2098,7 +2180,8 @@ static int open_listen_socket_network(const listen_socket_t *sock) /* {{{ */ 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; } @@ -2106,7 +2189,8 @@ static int open_listen_socket_network(const listen_socket_t *sock) /* {{{ */ 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); } @@ -2157,21 +2241,9 @@ static void *listen_thread_main (void *args __attribute__((unused))) /* {{{ */ 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); } @@ -2284,14 +2356,36 @@ static void *listen_thread_main (void *args __attribute__((unused))) /* {{{ */ 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) { @@ -2301,12 +2395,10 @@ static int daemonize (void) /* {{{ */ 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 (); @@ -2325,11 +2417,11 @@ static int daemonize (void) /* {{{ */ 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(); @@ -2341,11 +2433,14 @@ static int daemonize (void) /* {{{ */ 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) /* {{{ */ @@ -2460,6 +2555,7 @@ static int read_options (int argc, char **argv) /* {{{ */ case 'b': { size_t len; + char base_realpath[PATH_MAX]; if (config_base_dir != NULL) free (config_base_dir); @@ -2470,6 +2566,27 @@ static int read_options (int argc, char **argv) /* {{{ */ 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] == '/')) { @@ -2598,19 +2715,9 @@ int main (int argc, char **argv) } 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); }