X-Git-Url: https://git.octo.it/?p=rrdtool.git;a=blobdiff_plain;f=src%2Frrd_daemon.c;h=9fbbc1133af5a79869a53e84657086b91d4f0b60;hp=03dd181f76189a60945c89c7aaf59de8f6c222ec;hb=607eea59c2ba85c63e2de1b665b37a6093e1575c;hpb=1164122dace49986850172ef2cb198dc301fe750 diff --git a/src/rrd_daemon.c b/src/rrd_daemon.c index 03dd181..9fbbc11 100644 --- a/src/rrd_daemon.c +++ b/src/rrd_daemon.c @@ -116,6 +116,10 @@ struct listen_socket_s int family; socket_privilege privilege; + /* state for BATCH processing */ + time_t batch_start; + int batch_cmd; + /* buffered IO */ char *rbuf; off_t next_cmd; @@ -134,6 +138,7 @@ struct cache_item_s char **values; int values_num; time_t last_flush_time; + time_t last_update_stamp; #define CI_FLAGS_IN_TREE (1<<0) #define CI_FLAGS_IN_QUEUE (1<<1) int flags; @@ -166,6 +171,7 @@ typedef enum queue_side_e queue_side_t; * 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; @@ -280,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; @@ -289,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; @@ -399,6 +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_start) return 0; /* no extra info returned when in BATCH */ va_start(argp, fmt); #ifdef HAVE_VSNPRINTF @@ -446,10 +491,14 @@ static int send_response (listen_socket_t *sock, response_code rc, if (sock == NULL) return rc; /* journal replay mode */ - if (rc == RESP_OK) + if (sock->batch_start) { - 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; @@ -466,6 +515,10 @@ 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_start) + return add_to_wbuf(sock, buffer, len); + /* first write must be complete */ if (len != write(sock->fd, buffer, len)) { @@ -473,7 +526,7 @@ static int send_response (listen_socket_t *sock, response_code rc, return -1; } - if (sock->wbuf != NULL) + if (sock->wbuf != NULL && rc == RESP_OK) { wrote = 0; while (wrote < sock->wbuf_len) @@ -526,6 +579,34 @@ static void remove_from_queue(cache_item_t *ci) /* {{{ */ 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! @@ -660,26 +741,10 @@ static int flush_old_values (int max_age) 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) { @@ -721,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 */ @@ -929,6 +994,40 @@ 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, /* {{{ */ + 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; @@ -949,6 +1048,9 @@ static int flush_file (const char *filename) /* {{{ */ 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); @@ -965,10 +1067,13 @@ static int handle_request_help (listen_socket_t *sock, /* {{{ */ { "Command overview\n" , + "HELP []\n" "FLUSH \n" "FLUSHALL\n" - "HELP []\n" + "PENDING \n" + "FORGET \n" "UPDATE [ ...]\n" + "BATCH\n" "STATS\n" }; @@ -991,6 +1096,26 @@ static int handle_request_help (listen_socket_t *sock, /* {{{ */ "Triggers writing of all pending updates. Returns immediately.\n" }; + char *help_pending[2] = + { + "Help for PENDING\n" + , + "Usage: PENDING \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 \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" @@ -1016,6 +1141,28 @@ static int handle_request_help (listen_socket_t *sock, /* {{{ */ "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; @@ -1027,8 +1174,14 @@ static int handle_request_help (listen_socket_t *sock, /* {{{ */ 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) + help_text = help_batch; else help_text = help_help; } @@ -1088,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); @@ -1102,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); @@ -1126,10 +1280,15 @@ static int handle_request_flush (listen_socket_t *sock, /* {{{ */ /* NOTREACHED */ assert(1==0); -} /* }}} int handle_request_slurp */ +} /* }}} int handle_request_flush */ 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"); @@ -1140,17 +1299,94 @@ static int handle_request_flushall(listen_socket_t *sock) /* {{{ */ 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, file_tmp[PATH_MAX]; + cache_item_t *ci; + + status = buffer_get_field(&buffer, &buffer_size, &file); + if (status != 0) + return send_response(sock, RESP_ERR, + "Usage: PENDING \n"); + + status = has_privilege(sock, PRIV_HIGH); + 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) + { + 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, file_tmp[PATH_MAX]; + + status = buffer_get_field(&buffer, &buffer_size, &file); + if (status != 0) + return send_response(sock, RESP_ERR, + "Usage: FORGET \n"); + + status = has_privilege(sock, PRIV_HIGH); + if (status <= 0) + return status; + + get_abs_path(&file, file_tmp); + 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) + 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; + + /* 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) @@ -1161,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); @@ -1219,10 +1456,16 @@ static int handle_request_update (listen_socket_t *sock, /* {{{ */ } /* }}} */ 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; char *value; + time_t stamp; + char *eostamp; status = buffer_get_field (&buffer, &buffer_size, &value); if (status != 0) @@ -1231,6 +1474,26 @@ static int handle_request_update (listen_socket_t *sock, /* {{{ */ break; } + /* make sure update time is always moving forward */ + stamp = strtol(value, &eostamp, 10); + if (eostamp == value || eostamp == NULL || *eostamp != ':') + { + ++bad_timestamps; + add_response_info(sock, "Cannot find timestamp in '%s'!\n", value); + continue; + } + else if (stamp <= ci->last_update_stamp) + { + ++bad_timestamps; + add_response_info(sock, + "illegal attempt to update using time %ld when" + " last update time is %ld (minimum one second step)\n", + stamp, ci->last_update_stamp); + continue; + } + else + ci->last_update_stamp = stamp; + temp = (char **) realloc (ci->values, sizeof (char *) * (ci->values_num + 1)); if (temp == NULL) @@ -1261,9 +1524,21 @@ static int handle_request_update (listen_socket_t *sock, /* {{{ */ pthread_mutex_unlock (&cache_lock); if (values_num < 1) - return send_response(sock, RESP_ERR, "No values updated.\n"); + { + /* 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 + */ + if (bad_timestamps == 1) + return send_response(sock, RESP_ERR, "%s", sock->wbuf); + else + return send_response(sock, RESP_ERR, + "No values updated (%d bad timestamps).\n", + bad_timestamps); + } else - return send_response(sock, RESP_OK, "Enqueued %i value(s).\n", values_num); + return send_response(sock, RESP_OK, + "errors, enqueued %i value(s).\n", values_num); /* NOTREACHED */ assert(1==0); @@ -1273,7 +1548,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; @@ -1296,28 +1571,40 @@ 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); 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_start) + 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_start = time(NULL); + 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_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; @@ -1335,37 +1622,32 @@ 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_start) + sock->batch_cmd++; - return (handle_request_update (sock, buffer_ptr, buffer_size)); - } + if (strcasecmp (command, "update") == 0) + 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)); 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) 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_start) + return batch_done(sock); else return send_response(sock, RESP_ERR, "Unknown command: %s\n", command); @@ -1377,6 +1659,7 @@ static int handle_request (listen_socket_t *sock, /* {{{ */ static void journal_rotate(void) /* {{{ */ { FILE *old_fh = NULL; + int new_fd; if (journal_cur == NULL || journal_old == NULL) return; @@ -1391,11 +1674,20 @@ static void journal_rotate(void) /* {{{ */ 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) @@ -1470,9 +1762,48 @@ 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; + { + 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) { @@ -1484,6 +1815,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; @@ -1505,7 +1838,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; @@ -1513,17 +1846,36 @@ static int journal_replay (const char *file) /* {{{ */ 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; @@ -1577,6 +1929,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; @@ -1593,23 +1946,18 @@ static void *connection_thread_main (void *args) /* {{{ */ 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; } @@ -1625,9 +1973,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; } @@ -2003,11 +2356,16 @@ static void *listen_thread_main (void *args __attribute__((unused))) /* {{{ */ static int daemonize (void) /* {{{ */ { int status; - int fd; + int pid_fd; char *base_dir; - fd = open_pidfile(); - if (fd < 0) return fd; + daemon_uid = geteuid(); + + 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; if (!stay_foreground) { @@ -2060,7 +2418,7 @@ static int daemonize (void) /* {{{ */ return (-1); } - status = write_pidfile (fd); + status = write_pidfile (pid_fd); return status; } /* }}} int daemonize */ @@ -2176,6 +2534,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); @@ -2186,6 +2545,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] == '/')) { @@ -2330,25 +2710,7 @@ int main (int argc, char **argv) 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));