summaryrefslogtreecommitdiffstats
path: root/src/lib-index/mailbox-log.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/lib-index/mailbox-log.c292
1 files changed, 292 insertions, 0 deletions
diff --git a/src/lib-index/mailbox-log.c b/src/lib-index/mailbox-log.c
new file mode 100644
index 0000000..433ba29
--- /dev/null
+++ b/src/lib-index/mailbox-log.c
@@ -0,0 +1,292 @@
+/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "eacces-error.h"
+#include "mailbox-log.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+/* How often to reopen the log file to make sure that the changes are written
+ to the latest file. The main problem here is if the value is too high the
+ changes could be written to a file that was already rotated and deleted.
+ That wouldn't happen in any real world situations though, since the file
+ rotation time is probably measured in months or years. Still, each session
+ rarely writes anything here, so the value can just as well be a pretty small
+ one without any performance problems. */
+#define MAILBOX_LOG_REOPEN_SECS (60)
+#define MAILBOX_LOG_ROTATE_SIZE (1024*4)
+
+struct mailbox_log {
+ char *filepath, *filepath2;
+ int fd;
+ struct event *event;
+ time_t open_timestamp;
+
+ mode_t mode;
+ gid_t gid;
+ char *gid_origin;
+};
+
+struct mailbox_log_iter {
+ struct mailbox_log *log;
+
+ int fd;
+ const char *filepath;
+
+ struct mailbox_log_record buf[128];
+ unsigned int idx, count;
+ uoff_t offset;
+ bool failed;
+};
+
+static void mailbox_log_close(struct mailbox_log *log);
+
+struct mailbox_log *
+mailbox_log_alloc(struct event *parent_event, const char *path)
+{
+ struct mailbox_log *log;
+
+ log = i_new(struct mailbox_log, 1);
+ log->event = event_create(parent_event);
+ log->filepath = i_strdup(path);
+ log->filepath2 = i_strconcat(path, ".2", NULL);
+ log->mode = 0644;
+ log->gid = (gid_t)-1;
+ log->fd = -1;
+ return log;
+}
+
+void mailbox_log_free(struct mailbox_log **_log)
+{
+ struct mailbox_log *log = *_log;
+
+ *_log = NULL;
+
+ mailbox_log_close(log);
+ event_unref(&log->event);
+ i_free(log->gid_origin);
+ i_free(log->filepath);
+ i_free(log->filepath2);
+ i_free(log);
+}
+
+static void mailbox_log_close(struct mailbox_log *log)
+{
+ i_close_fd_path(&log->fd, log->filepath);
+}
+
+void mailbox_log_set_permissions(struct mailbox_log *log, mode_t mode,
+ gid_t gid, const char *gid_origin)
+{
+ log->mode = mode;
+ log->gid = gid;
+ i_free(log->gid_origin);
+ log->gid_origin = i_strdup(gid_origin);
+}
+
+static int mailbox_log_open(struct mailbox_log *log)
+{
+ mode_t old_mode;
+
+ i_assert(log->fd == -1);
+
+ log->open_timestamp = ioloop_time;
+ log->fd = open(log->filepath, O_RDWR | O_APPEND);
+ if (log->fd != -1)
+ return 0;
+
+ /* try to create it */
+ old_mode = umask(0666 ^ log->mode);
+ log->fd = open(log->filepath, O_RDWR | O_APPEND | O_CREAT, 0666);
+ umask(old_mode);
+
+ if (log->fd == -1) {
+ if (errno != EACCES)
+ e_error(log->event, "creat(%s) failed: %m",
+ log->filepath);
+ else
+ e_error(log->event, "%s",
+ eacces_error_get("creat", log->filepath));
+ return -1;
+ }
+ if (fchown(log->fd, (uid_t)-1, log->gid) < 0) {
+ if (errno != EPERM)
+ e_error(log->event, "fchown(%s) failed: %m",
+ log->filepath);
+ else {
+ e_error(log->event, "%s",
+ eperm_error_get_chgrp("fchown",
+ log->filepath, log->gid,
+ log->gid_origin));
+ }
+ }
+ return 0;
+}
+
+static int mailbox_log_rotate_if_needed(struct mailbox_log *log)
+{
+ struct stat st;
+
+ if (fstat(log->fd, &st) < 0) {
+ e_error(log->event, "fstat(%s) failed: %m", log->filepath);
+ return -1;
+ }
+
+ if (st.st_size < MAILBOX_LOG_ROTATE_SIZE)
+ return 0;
+
+ if (rename(log->filepath, log->filepath2) < 0 && errno != ENOENT) {
+ e_error(log->event, "rename(%s, %s) failed: %m",
+ log->filepath, log->filepath2);
+ return -1;
+ }
+ return 0;
+}
+
+void mailbox_log_record_set_timestamp(struct mailbox_log_record *rec,
+ time_t stamp)
+{
+ cpu32_to_be_unaligned(stamp, rec->timestamp);
+}
+
+time_t mailbox_log_record_get_timestamp(const struct mailbox_log_record *rec)
+{
+ return (time_t) be32_to_cpu_unaligned(rec->timestamp);
+}
+
+int mailbox_log_append(struct mailbox_log *log,
+ const struct mailbox_log_record *rec)
+{
+ struct stat st;
+ ssize_t ret;
+
+ /* we don't have to be too strict about appending to the latest log
+ file. the records' ordering doesn't matter and iteration goes
+ through both logs anyway. still, if there's a long running session
+ it shouldn't keep writing to a rotated log forever. */
+ if (log->open_timestamp/MAILBOX_LOG_REOPEN_SECS !=
+ ioloop_time/MAILBOX_LOG_REOPEN_SECS)
+ mailbox_log_close(log);
+ if (log->fd == -1) {
+ if (mailbox_log_open(log) < 0)
+ return -1;
+ i_assert(log->fd != -1);
+ }
+
+ /* We don't bother with locking, atomic appends will protect us.
+ If they don't (NFS), the worst that can happen is that a few
+ records get overwritten (because they're all the same size).
+ This whole log isn't supposed to be super-reliable anyway. */
+ ret = write(log->fd, rec, sizeof(*rec));
+ if (ret < 0) {
+ e_error(log->event, "write(%s) failed: %m", log->filepath);
+ return -1;
+ } else if (ret != sizeof(*rec)) {
+ e_error(log->event, "write(%s) wrote %d/%u bytes", log->filepath,
+ (int)ret, (unsigned int)sizeof(*rec));
+ if (fstat(log->fd, &st) == 0) {
+ if (ftruncate(log->fd, st.st_size - ret) < 0) {
+ e_error(log->event, "ftruncate(%s) failed: %m",
+ log->filepath);
+ }
+ }
+ return -1;
+ }
+
+ (void)mailbox_log_rotate_if_needed(log);
+ return 0;
+}
+
+static bool mailbox_log_iter_open_next(struct mailbox_log_iter *iter)
+{
+ i_close_fd_path(&iter->fd, iter->filepath);
+ if (iter->filepath == NULL)
+ iter->filepath = iter->log->filepath2;
+ else if (iter->filepath == iter->log->filepath2)
+ iter->filepath = iter->log->filepath;
+ else
+ return FALSE;
+
+ iter->fd = open(iter->filepath, O_RDONLY | O_APPEND);
+ if (iter->fd != -1)
+ return TRUE;
+ else if (errno == ENOENT) {
+ if (iter->filepath == iter->log->filepath2)
+ return mailbox_log_iter_open_next(iter);
+ } else {
+ e_error(iter->log->event, "open(%s) failed: %m", iter->filepath);
+ iter->failed = TRUE;
+ }
+ return FALSE;
+}
+
+struct mailbox_log_iter *mailbox_log_iter_init(struct mailbox_log *log)
+{
+ struct mailbox_log_iter *iter;
+
+ iter = i_new(struct mailbox_log_iter, 1);
+ iter->log = log;
+ iter->fd = -1;
+ (void)mailbox_log_iter_open_next(iter);
+ return iter;
+}
+
+const struct mailbox_log_record *
+mailbox_log_iter_next(struct mailbox_log_iter *iter)
+{
+ const struct mailbox_log_record *rec;
+ uoff_t offset;
+ ssize_t ret;
+
+ if (iter->idx == iter->count) {
+ if (iter->fd == -1)
+ return NULL;
+
+ ret = pread(iter->fd, iter->buf, sizeof(iter->buf),
+ iter->offset);
+ if (ret < 0) {
+ e_error(iter->log->event, "pread(%s) failed: %m",
+ iter->filepath);
+ iter->failed = TRUE;
+ return NULL;
+ }
+ if (ret == 0) {
+ if (!mailbox_log_iter_open_next(iter))
+ return NULL;
+ iter->idx = iter->count = 0;
+ iter->offset = 0;
+ return mailbox_log_iter_next(iter);
+ }
+ iter->idx = 0;
+ iter->count = ret / sizeof(iter->buf[0]);
+ iter->offset += iter->count * sizeof(iter->buf[0]);
+ }
+ rec = &iter->buf[iter->idx++];
+ if (rec->type < MAILBOX_LOG_RECORD_DELETE_MAILBOX ||
+ rec->type > MAILBOX_LOG_RECORD_UNSUBSCRIBE) {
+ offset = iter->offset -
+ (iter->count - iter->idx) * sizeof(iter->buf[0]);
+ e_error(iter->log->event,
+ "Corrupted mailbox log %s at offset %"PRIuUOFF_T": "
+ "type=%d", iter->filepath, offset, rec->type);
+ i_unlink(iter->filepath);
+ return NULL;
+ }
+ return rec;
+}
+
+int mailbox_log_iter_deinit(struct mailbox_log_iter **_iter)
+{
+ struct mailbox_log_iter *iter = *_iter;
+ int ret = iter->failed ? -1 : 0;
+
+ *_iter = NULL;
+
+ i_close_fd_path(&iter->fd, iter->filepath);
+ i_free(iter);
+ return ret;
+}