summaryrefslogtreecommitdiffstats
path: root/src/lib-index/mail-index-write.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-index/mail-index-write.c')
-rw-r--r--src/lib-index/mail-index-write.c215
1 files changed, 215 insertions, 0 deletions
diff --git a/src/lib-index/mail-index-write.c b/src/lib-index/mail-index-write.c
new file mode 100644
index 0000000..689cb9c
--- /dev/null
+++ b/src/lib-index/mail-index-write.c
@@ -0,0 +1,215 @@
+/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "nfs-workarounds.h"
+#include "read-full.h"
+#include "write-full.h"
+#include "ostream.h"
+#include "mail-index-private.h"
+#include "mail-transaction-log-private.h"
+
+#include <stdio.h>
+
+#define MAIL_INDEX_MIN_UPDATE_SIZE 1024
+/* if we're updating >= count-n messages, recreate the index */
+#define MAIL_INDEX_MAX_OVERWRITE_NEG_SEQ_COUNT 10
+
+static int mail_index_create_backup(struct mail_index *index)
+{
+ const char *backup_path, *tmp_backup_path;
+ int ret;
+
+ if (index->fd != -1) {
+ /* we very much want to avoid creating a backup file that
+ hasn't been written to disk yet */
+ if (fdatasync(index->fd) < 0) {
+ mail_index_set_error(index, "fdatasync(%s) failed: %m",
+ index->filepath);
+ return -1;
+ }
+ }
+
+ backup_path = t_strconcat(index->filepath, ".backup", NULL);
+ tmp_backup_path = t_strconcat(backup_path, ".tmp", NULL);
+ ret = link(index->filepath, tmp_backup_path);
+ if (ret < 0 && errno == EEXIST) {
+ if (unlink(tmp_backup_path) < 0 && errno != ENOENT) {
+ mail_index_set_error(index, "unlink(%s) failed: %m",
+ tmp_backup_path);
+ return -1;
+ }
+ ret = link(index->filepath, tmp_backup_path);
+ }
+ if (ret < 0) {
+ if (errno == ENOENT) {
+ /* no dovecot.index file, ignore */
+ return 0;
+ }
+ mail_index_set_error(index, "link(%s, %s) failed: %m",
+ index->filepath, tmp_backup_path);
+ return -1;
+ }
+
+ if (rename(tmp_backup_path, backup_path) < 0) {
+ mail_index_set_error(index, "rename(%s, %s) failed: %m",
+ tmp_backup_path, backup_path);
+ return -1;
+ }
+ return 0;
+}
+
+static int mail_index_recreate(struct mail_index *index)
+{
+ struct mail_index_map *map = index->map;
+ struct ostream *output;
+ unsigned int base_size;
+ const char *path;
+ int ret = 0, fd;
+
+ i_assert(!MAIL_INDEX_IS_IN_MEMORY(index));
+ i_assert(map->hdr.indexid == index->indexid);
+ i_assert((map->hdr.flags & MAIL_INDEX_HDR_FLAG_CORRUPTED) == 0);
+ i_assert(index->indexid != 0);
+
+ fd = mail_index_create_tmp_file(index, index->filepath, &path);
+ if (fd == -1)
+ return -1;
+
+ output = o_stream_create_fd_file(fd, 0, FALSE);
+ o_stream_cork(output);
+
+ struct mail_index_header hdr = map->hdr;
+ /* Write tail_offset the same as head_offset. This function must not
+ be called unless it's safe to do this. See the explanations in
+ mail_index_sync_commit(). */
+ hdr.log_file_tail_offset = hdr.log_file_head_offset;
+
+ base_size = I_MIN(hdr.base_header_size, sizeof(hdr));
+ o_stream_nsend(output, &hdr, base_size);
+ o_stream_nsend(output, MAIL_INDEX_MAP_HDR_OFFSET(map, base_size),
+ hdr.header_size - base_size);
+ o_stream_nsend(output, map->rec_map->records,
+ map->rec_map->records_count * hdr.record_size);
+ if (o_stream_finish(output) < 0) {
+ mail_index_file_set_syscall_error(index, path, "write()");
+ ret = -1;
+ }
+ o_stream_destroy(&output);
+
+ if (ret == 0 && index->set.fsync_mode != FSYNC_MODE_NEVER) {
+ if (fdatasync(fd) < 0) {
+ mail_index_file_set_syscall_error(index, path,
+ "fdatasync()");
+ ret = -1;
+ }
+ }
+
+ if (close(fd) < 0) {
+ mail_index_file_set_syscall_error(index, path, "close()");
+ ret = -1;
+ }
+
+ if ((index->flags & MAIL_INDEX_OPEN_FLAG_KEEP_BACKUPS) != 0)
+ (void)mail_index_create_backup(index);
+
+ if (ret == 0 && rename(path, index->filepath) < 0) {
+ mail_index_set_error(index, "rename(%s, %s) failed: %m",
+ path, index->filepath);
+ ret = -1;
+ }
+
+ if (ret < 0)
+ i_unlink(path);
+ return ret;
+}
+
+static bool mail_index_should_recreate(struct mail_index *index)
+{
+ struct stat st1, st2;
+
+ if (nfs_safe_stat(index->filepath, &st1) < 0) {
+ if (errno != ENOENT) {
+ mail_index_set_syscall_error(index, "stat()");
+ return FALSE;
+ } else if (index->fd == -1) {
+ /* main index hasn't been created yet */
+ return TRUE;
+ } else {
+ /* mailbox was just deleted? don't log an error */
+ return FALSE;
+ }
+ }
+ if (index->fd == -1) {
+ /* main index was just created by another process */
+ return FALSE;
+ }
+ if (fstat(index->fd, &st2) < 0) {
+ if (!ESTALE_FSTAT(errno))
+ mail_index_set_syscall_error(index, "fstat()");
+ return FALSE;
+ }
+ if (st1.st_ino != st2.st_ino ||
+ !CMP_DEV_T(st1.st_dev, st2.st_dev)) {
+ /* Index has already been recreated since we last read it.
+ We can't trust our decisions about whether to recreate it. */
+ return FALSE;
+ }
+ return TRUE;
+}
+
+void mail_index_write(struct mail_index *index, bool want_rotate,
+ const char *reason)
+{
+ struct mail_index_header *hdr = &index->map->hdr;
+ bool rotated = FALSE;
+
+ i_assert(index->log_sync_locked);
+
+ if (index->readonly)
+ return;
+
+ /* rotate the .log before writing index, so the index will point to
+ the latest log. Note that it's the caller's responsibility to make
+ sure that the .log can be safely rotated (i.e. everything has been
+ synced). */
+ if (want_rotate) {
+ if (mail_transaction_log_rotate(index->log, FALSE) == 0) {
+ struct mail_transaction_log_file *file =
+ index->log->head;
+ /* Log rotation refreshes the index, which may cause the
+ map to change. Because we're locked, it's not
+ supposed to happen and will likely lead to an
+ assert-crash below, but we still need to make sure
+ we're using the latest map to do the checks. */
+ hdr = &index->map->hdr;
+ i_assert(file->hdr.prev_file_seq == hdr->log_file_seq);
+ i_assert(file->hdr.prev_file_offset == hdr->log_file_head_offset);
+ hdr->log_file_seq = file->hdr.file_seq;
+ hdr->log_file_head_offset =
+ hdr->log_file_tail_offset = file->hdr.hdr_size;
+ /* Assume .log.2 was created successfully. If it
+ wasn't, it just causes an extra stat() and gets
+ fixed later on. */
+ hdr->log2_rotate_time = ioloop_time;
+ rotated = TRUE;
+ }
+ }
+
+ if (MAIL_INDEX_IS_IN_MEMORY(index))
+ ;
+ else if (!rotated && !mail_index_should_recreate(index)) {
+ /* make sure we don't keep getting back in here */
+ index->reopen_main_index = TRUE;
+ } else {
+ if (mail_index_recreate(index) < 0) {
+ (void)mail_index_move_to_memory(index);
+ return;
+ }
+ event_set_name(index->event, "mail_index_recreated");
+ e_debug(index->event, "Recreated %s (file_seq=%u) because: %s",
+ index->filepath, hdr->log_file_seq, reason);
+ }
+
+ index->main_index_hdr_log_file_seq = hdr->log_file_seq;
+ index->main_index_hdr_log_file_tail_offset = hdr->log_file_tail_offset;
+}