]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-index: Added mailbox transaction log.
authorTimo Sirainen <tss@iki.fi>
Thu, 6 Aug 2009 00:28:50 +0000 (20:28 -0400)
committerTimo Sirainen <tss@iki.fi>
Thu, 6 Aug 2009 00:28:50 +0000 (20:28 -0400)
It's a much simplified version of mail transaction log.

--HG--
branch : HEAD

src/lib-index/Makefile.am
src/lib-index/mailbox-log.c [new file with mode: 0644]
src/lib-index/mailbox-log.h [new file with mode: 0644]

index 33e1a591b35bb2768eee6263883cf1c7ee387278..bc2fd4b95a19ebb49b0def2e5671da5db966f51c 100644 (file)
@@ -41,7 +41,8 @@ libindex_la_SOURCES = \
         mail-transaction-log-file.c \
         mail-transaction-log-view.c \
         mailbox-list-index.c \
-        mailbox-list-index-sync.c
+        mailbox-list-index-sync.c \
+        mailbox-log.c
 
 headers = \
        mail-cache.h \
@@ -58,7 +59,8 @@ headers = \
        mail-transaction-log-private.h \
        mail-transaction-log-view-private.h \
         mailbox-list-index.h \
-        mailbox-list-index-private.h
+        mailbox-list-index-private.h \
+        mailbox-log.h
 
 test_programs = \
        test-mail-index-transaction-finish \
diff --git a/src/lib-index/mailbox-log.c b/src/lib-index/mailbox-log.c
new file mode 100644 (file)
index 0000000..b88aa81
--- /dev/null
@@ -0,0 +1,279 @@
+/* Copyright (c) 2009 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "eacces-error.h"
+#include "mailbox-log.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#define MAILBOX_LOG_ROTATE_SIZE (1024*4)
+
+struct mailbox_log {
+       char *filepath, *filepath2;
+       int fd;
+
+       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(const char *path)
+{
+       struct mailbox_log *log;
+
+       log = i_new(struct mailbox_log, 1);
+       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);
+       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)
+{
+       if (log->fd != -1) {
+               if (close(log->fd) < 0)
+                       i_error("close(%s) failed: %m", 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;
+
+       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)
+                       i_error("creat(%s) failed: %m", log->filepath);
+               else
+                       i_error("%s", eacces_error_get("creat", log->filepath));
+               return -1;
+       }
+       if (fchown(log->fd, (uid_t)-1, log->gid) < 0) {
+               if (errno != EPERM)
+                       i_error("fchown(%s) failed: %m", log->filepath);
+               else {
+                       i_error("%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) {
+               i_error("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) {
+               i_error("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)
+{
+       rec->timestamp[0] = (stamp & 0xff000000) >> 24;
+       rec->timestamp[1] = (stamp & 0x00ff0000) >> 16;
+       rec->timestamp[2] = (stamp & 0x0000ff00) >> 8;
+       rec->timestamp[3] = (stamp & 0x000000ff);
+}
+
+time_t mailbox_log_record_get_timestamp(const struct mailbox_log_record *rec)
+{
+       return ((time_t)rec->timestamp[0] << 24) |
+               ((time_t)rec->timestamp[1] << 16) |
+               ((time_t)rec->timestamp[2] << 8) |
+               (time_t)rec->timestamp[3];
+}
+
+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. */
+       if (log->fd == -1) {
+               if (mailbox_log_open(log) < 0)
+                       return -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) {
+               i_error("write(%s) failed: %m", log->filepath);
+               return -1;
+       } else if (ret != sizeof(*rec)) {
+               i_error("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) {
+                               i_error("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)
+{
+       if (iter->fd != -1) {
+               if (close(iter->fd) < 0)
+                       i_error("close(%s) failed: %m", iter->filepath);
+               iter->fd = -1;
+       }
+       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 {
+               i_error("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) {
+                       i_error("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;
+                       return mailbox_log_iter_next(iter);
+               }
+               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]);
+               i_error("Corrupted mailbox log at offset %"PRIuUOFF_T": %s",
+                       offset, iter->filepath);
+               if (unlink(iter->filepath) < 0)
+                       i_error("unlink(%s) failed: %m", 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;
+
+       if (iter->fd != -1) {
+               if (close(iter->fd) < 0)
+                       i_error("close(%s) failed: %m", iter->filepath);
+       }
+       i_free(iter);
+       return ret;
+}
diff --git a/src/lib-index/mailbox-log.h b/src/lib-index/mailbox-log.h
new file mode 100644 (file)
index 0000000..2f10b5c
--- /dev/null
@@ -0,0 +1,42 @@
+#ifndef MAILBOX_LOG_H
+#define MAILBOX_LOG_H
+
+#include "mail-types.h"
+
+enum mailbox_log_record_type {
+       MAILBOX_LOG_RECORD_DELETE_MAILBOX = 1,
+       MAILBOX_LOG_RECORD_DELETE_DIR,
+       MAILBOX_LOG_RECORD_RENAME,
+       MAILBOX_LOG_RECORD_SUBSCRIBE,
+       MAILBOX_LOG_RECORD_UNSUBSCRIBE
+};
+
+struct mailbox_log_record {
+       uint8_t type;
+       uint8_t padding[3];
+       uint8_t mailbox_guid[MAIL_GUID_128_SIZE];
+       uint8_t timestamp[4];
+};
+
+struct mailbox_log *mailbox_log_alloc(const char *path);
+void mailbox_log_free(struct mailbox_log **log);
+
+void mailbox_log_set_permissions(struct mailbox_log *log, mode_t mode,
+                                gid_t gid, const char *gid_origin);
+
+void mailbox_log_record_set_timestamp(struct mailbox_log_record *rec,
+                                     time_t stamp);
+time_t mailbox_log_record_get_timestamp(const struct mailbox_log_record *rec);
+
+/* Append a new record to mailbox log. Returns 0 if ok, -1 if error. */
+int mailbox_log_append(struct mailbox_log *log,
+                      const struct mailbox_log_record *rec);
+
+/* Iterate through all records in mailbox log. */
+struct mailbox_log_iter *mailbox_log_iter_init(struct mailbox_log *log);
+const struct mailbox_log_record *
+mailbox_log_iter_next(struct mailbox_log_iter *iter);
+/* Returns 0 if ok, -1 if I/O error. */
+int mailbox_log_iter_deinit(struct mailbox_log_iter **iter);
+
+#endif