+ if (sock != NULL && sock->batch_start)
+ sock->batch_cmd++;
+
+ cmd = find_command(cmd_str);
+ if (!cmd)
+ return send_response(sock, RESP_ERR, "Unknown command: %s\n", cmd_str);
+
+ if (!socket_permission_check (sock, cmd->cmd))
+ return send_response(sock, RESP_ERR, "Permission denied.\n");
+
+ if (!command_check_context(sock, cmd))
+ return send_response(sock, RESP_ERR, "Can't use '%s' here.\n", cmd_str);
+
+ return cmd->handler(cmd, sock, now, buffer_ptr, buffer_size);
+} /* }}} int handle_request */
+
+static void journal_set_free (journal_set *js) /* {{{ */
+{
+ if (js == NULL)
+ return;
+
+ rrd_free_ptrs((void ***) &js->files, &js->files_num);
+
+ free(js);
+} /* }}} journal_set_free */
+
+static void journal_set_remove (journal_set *js) /* {{{ */
+{
+ if (js == NULL)
+ return;
+
+ for (uint i=0; i < js->files_num; i++)
+ {
+ RRDD_LOG(LOG_DEBUG, "removing old journal %s", js->files[i]);
+ unlink(js->files[i]);
+ }
+} /* }}} journal_set_remove */
+
+/* close current journal file handle.
+ * MUST hold journal_lock before calling */
+static void journal_close(void) /* {{{ */
+{
+ if (journal_fh != NULL)
+ {
+ if (fclose(journal_fh) != 0)
+ RRDD_LOG(LOG_ERR, "cannot close journal: %s", rrd_strerror(errno));
+ }
+
+ journal_fh = NULL;
+ journal_size = 0;
+} /* }}} journal_close */
+
+/* MUST hold journal_lock before calling */
+static void journal_new_file(void) /* {{{ */
+{
+ struct timeval now;
+ int new_fd;
+ char new_file[PATH_MAX + 1];
+
+ assert(journal_dir != NULL);
+ assert(journal_cur != NULL);
+
+ journal_close();
+
+ gettimeofday(&now, NULL);
+ /* this format assures that the files sort in strcmp() order */
+ snprintf(new_file, PATH_MAX, "%s/%s.%010d.%06d",
+ journal_dir, JOURNAL_BASE, (int)now.tv_sec, (int)now.tv_usec);
+
+ new_fd = open(new_file, O_WRONLY|O_CREAT|O_APPEND,
+ S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
+ if (new_fd < 0)
+ goto error;
+
+ journal_fh = fdopen(new_fd, "a");
+ if (journal_fh == NULL)
+ goto error;
+
+ journal_size = ftell(journal_fh);
+ RRDD_LOG(LOG_DEBUG, "started new journal %s", new_file);
+
+ /* record the file in the journal set */
+ rrd_add_strdup(&journal_cur->files, &journal_cur->files_num, new_file);
+
+ return;
+
+error:
+ RRDD_LOG(LOG_CRIT,
+ "JOURNALING DISABLED: Error while trying to create %s : %s",
+ new_file, rrd_strerror(errno));
+ RRDD_LOG(LOG_CRIT,
+ "JOURNALING DISABLED: All values will be flushed at shutdown");
+
+ close(new_fd);
+ config_flush_at_shutdown = 1;
+
+} /* }}} journal_new_file */
+
+/* MUST NOT hold journal_lock before calling this */
+static void journal_rotate(void) /* {{{ */
+{
+ journal_set *old_js = NULL;
+
+ if (journal_dir == NULL)
+ return;
+
+ RRDD_LOG(LOG_DEBUG, "rotating journals");
+
+ pthread_mutex_lock(&stats_lock);
+ ++stats_journal_rotate;
+ pthread_mutex_unlock(&stats_lock);
+
+ pthread_mutex_lock(&journal_lock);
+
+ journal_close();
+
+ /* rotate the journal sets */
+ old_js = journal_old;
+ journal_old = journal_cur;
+ journal_cur = calloc(1, sizeof(journal_set));
+
+ if (journal_cur != NULL)
+ journal_new_file();
+ else
+ RRDD_LOG(LOG_CRIT, "journal_rotate: malloc(journal_set) failed\n");
+
+ pthread_mutex_unlock(&journal_lock);
+
+ journal_set_remove(old_js);
+ journal_set_free (old_js);
+
+} /* }}} static void journal_rotate */
+
+/* MUST hold journal_lock when calling */
+static void journal_done(void) /* {{{ */
+{
+ if (journal_cur == NULL)
+ return;
+
+ journal_close();
+
+ if (config_flush_at_shutdown)
+ {
+ RRDD_LOG(LOG_INFO, "removing journals");
+ journal_set_remove(journal_old);
+ journal_set_remove(journal_cur);
+ }
+ else
+ {
+ RRDD_LOG(LOG_INFO, "expedited shutdown; "
+ "journals will be used at next startup");
+ }
+
+ journal_set_free(journal_cur);
+ journal_set_free(journal_old);
+ free(journal_dir);
+
+} /* }}} static void journal_done */
+
+static int journal_write(char *cmd, char *args) /* {{{ */
+{
+ int chars;
+
+ if (journal_fh == NULL)
+ return 0;
+
+ pthread_mutex_lock(&journal_lock);
+ chars = fprintf(journal_fh, "%s %s\n", cmd, args);
+ journal_size += chars;
+
+ if (journal_size > JOURNAL_MAX)
+ journal_new_file();
+
+ pthread_mutex_unlock(&journal_lock);
+
+ if (chars > 0)
+ {
+ pthread_mutex_lock(&stats_lock);
+ stats_journal_bytes += chars;
+ pthread_mutex_unlock(&stats_lock);
+ }
+
+ return chars;
+} /* }}} static int journal_write */
+
+static int journal_replay (const char *file) /* {{{ */
+{
+ FILE *fh;
+ int entry_cnt = 0;
+ int fail_cnt = 0;
+ uint64_t line = 0;
+ char entry[CMD_MAX];
+ time_t now;
+
+ if (file == NULL) return 0;
+
+ {
+ char *reason = "unknown error";
+ int status = 0;
+ struct stat statbuf;
+
+ memset(&statbuf, 0, sizeof(statbuf));
+ if (stat(file, &statbuf) != 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)
+ {
+ if (errno != ENOENT)
+ RRDD_LOG(LOG_ERR, "journal_replay: cannot open journal file: '%s' (%s)",
+ file, rrd_strerror(errno));
+ return 0;
+ }
+ else
+ RRDD_LOG(LOG_NOTICE, "replaying from journal: %s", file);
+
+ now = time(NULL);
+
+ while(!feof(fh))
+ {
+ size_t entry_len;
+
+ ++line;
+ if (fgets(entry, sizeof(entry), fh) == NULL)
+ break;
+ entry_len = strlen(entry);
+
+ /* check \n termination in case journal writing crashed mid-line */
+ if (entry_len == 0)
+ continue;
+ else if (entry[entry_len - 1] != '\n')
+ {
+ RRDD_LOG(LOG_NOTICE, "Malformed journal entry at line %"PRIu64, line);
+ ++fail_cnt;
+ continue;
+ }
+
+ entry[entry_len - 1] = '\0';
+
+ if (handle_request(NULL, now, entry, entry_len) == 0)
+ ++entry_cnt;
+ else
+ ++fail_cnt;
+ }
+
+ fclose(fh);
+
+ RRDD_LOG(LOG_INFO, "Replayed %d entries (%d failures)",
+ entry_cnt, fail_cnt);
+
+ return entry_cnt > 0 ? 1 : 0;
+} /* }}} static int journal_replay */
+
+static int journal_sort(const void *v1, const void *v2)
+{
+ char **jn1 = (char **) v1;
+ char **jn2 = (char **) v2;
+
+ return strcmp(*jn1,*jn2);
+}
+
+static void journal_init(void) /* {{{ */
+{
+ int had_journal = 0;
+ DIR *dir;
+ struct dirent *dent;
+ char path[PATH_MAX+1];
+
+ if (journal_dir == NULL) return;
+
+ pthread_mutex_lock(&journal_lock);
+
+ journal_cur = calloc(1, sizeof(journal_set));
+ if (journal_cur == NULL)
+ {
+ RRDD_LOG(LOG_CRIT, "journal_rotate: malloc(journal_set) failed\n");
+ return;
+ }
+
+ RRDD_LOG(LOG_INFO, "checking for journal files");
+
+ /* Handle old journal files during transition. This gives them the
+ * correct sort order. TODO: remove after first release
+ */
+ {
+ char old_path[PATH_MAX+1];
+ snprintf(old_path, PATH_MAX, "%s/%s", journal_dir, JOURNAL_BASE ".old" );
+ snprintf(path, PATH_MAX, "%s/%s", journal_dir, JOURNAL_BASE ".0000");
+ rename(old_path, path);
+
+ snprintf(old_path, PATH_MAX, "%s/%s", journal_dir, JOURNAL_BASE );
+ snprintf(path, PATH_MAX, "%s/%s", journal_dir, JOURNAL_BASE ".0001");
+ rename(old_path, path);
+ }
+
+ dir = opendir(journal_dir);
+ while ((dent = readdir(dir)) != NULL)
+ {
+ /* looks like a journal file? */
+ if (strncmp(dent->d_name, JOURNAL_BASE, strlen(JOURNAL_BASE)))
+ continue;
+
+ snprintf(path, PATH_MAX, "%s/%s", journal_dir, dent->d_name);
+
+ if (!rrd_add_strdup(&journal_cur->files, &journal_cur->files_num, path))
+ {
+ RRDD_LOG(LOG_CRIT, "journal_init: cannot add journal file %s!",
+ dent->d_name);
+ break;
+ }
+ }
+ closedir(dir);
+
+ qsort(journal_cur->files, journal_cur->files_num,
+ sizeof(journal_cur->files[0]), journal_sort);
+
+ for (uint i=0; i < journal_cur->files_num; i++)
+ had_journal += journal_replay(journal_cur->files[i]);
+
+ journal_new_file();
+
+ /* 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);
+
+ RRDD_LOG(LOG_INFO, "journal processing complete");
+
+} /* }}} static void journal_init */
+
+static void free_listen_socket(listen_socket_t *sock) /* {{{ */
+{
+ assert(sock != NULL);
+
+ free(sock->rbuf); sock->rbuf = NULL;
+ free(sock->wbuf); sock->wbuf = NULL;
+ free(sock);
+} /* }}} void free_listen_socket */
+
+static void close_connection(listen_socket_t *sock) /* {{{ */
+{
+ if (sock->fd >= 0)