summaryrefslogtreecommitdiffstats
path: root/src/lib-index/mail-transaction-log-view.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-index/mail-transaction-log-view.c')
-rw-r--r--src/lib-index/mail-transaction-log-view.c909
1 files changed, 909 insertions, 0 deletions
diff --git a/src/lib-index/mail-transaction-log-view.c b/src/lib-index/mail-transaction-log-view.c
new file mode 100644
index 0000000..0113bfb
--- /dev/null
+++ b/src/lib-index/mail-transaction-log-view.c
@@ -0,0 +1,909 @@
+/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "mail-index-private.h"
+#include "mail-transaction-log-view-private.h"
+
+struct mail_transaction_log_view *
+mail_transaction_log_view_open(struct mail_transaction_log *log)
+{
+ struct mail_transaction_log_view *view;
+
+ view = i_new(struct mail_transaction_log_view, 1);
+ view->log = log;
+ view->broken = TRUE;
+
+ i_assert(view->log->head != NULL);
+
+ view->head = view->tail = view->log->head;
+ view->head->refcount++;
+ i_array_init(&view->file_refs, 8);
+ array_push_back(&view->file_refs, &view->head);
+
+ view->next = log->views;
+ log->views = view;
+ return view;
+}
+
+static void
+mail_transaction_log_view_unref_all(struct mail_transaction_log_view *view)
+{
+ struct mail_transaction_log_file *const *files;
+ unsigned int i, count;
+
+ files = array_get(&view->file_refs, &count);
+ for (i = 0; i < count; i++)
+ files[i]->refcount--;
+
+ array_clear(&view->file_refs);
+}
+
+void mail_transaction_log_view_close(struct mail_transaction_log_view **_view)
+{
+ struct mail_transaction_log_view *view = *_view;
+ struct mail_transaction_log_view **p;
+
+ *_view = NULL;
+
+ for (p = &view->log->views; *p != NULL; p = &(*p)->next) {
+ if (*p == view) {
+ *p = view->next;
+ break;
+ }
+ }
+
+ mail_transaction_log_view_unref_all(view);
+ mail_transaction_logs_clean(view->log);
+
+ array_free(&view->file_refs);
+ i_free(view);
+}
+
+static const char *
+mail_transaction_log_get_file_seqs(struct mail_transaction_log *log)
+{
+ struct mail_transaction_log_file *file;
+ string_t *str = t_str_new(32);
+
+ if (log->files == NULL)
+ return "";
+
+ for (file = log->files; file != NULL; file = file->next)
+ str_printfa(str, ",%u", file->hdr.file_seq);
+ return str_c(str) + 1;
+}
+
+static void
+view_set_failed_unref(struct mail_transaction_log_file *head,
+ struct mail_transaction_log_file *tail)
+{
+ struct mail_transaction_log_file *file;
+
+ if (tail == NULL) {
+ i_assert(head == NULL);
+ return;
+ }
+
+ for (file = tail; file != head; file = file->next) {
+ i_assert(file != NULL);
+ i_assert(file->refcount > 0);
+ file->refcount--;
+ }
+ i_assert(file != NULL);
+ i_assert(file->refcount > 0);
+ file->refcount--;
+}
+
+int mail_transaction_log_view_set(struct mail_transaction_log_view *view,
+ uint32_t min_file_seq, uoff_t min_file_offset,
+ uint32_t max_file_seq, uoff_t max_file_offset,
+ bool *reset_r, const char **reason_r)
+{
+ struct mail_transaction_log_file *tail, *head, *file, *const *files;
+ uoff_t start_offset, end_offset;
+ unsigned int i;
+ uint32_t seq;
+ int ret;
+
+ *reset_r = FALSE;
+ *reason_r = NULL;
+
+ if (view->log == NULL) {
+ /* transaction log is closed already. this log view shouldn't
+ be used anymore. */
+ *reason_r = "Log already closed";
+ return 0;
+ }
+
+ if (min_file_seq == 0) {
+ /* index file doesn't exist yet. this transaction log should
+ start from the beginning */
+ if (view->log->files->hdr.prev_file_seq != 0) {
+ /* but it doesn't */
+ *reason_r = t_strdup_printf(
+ "Wanted log beginning, but found prev_file_seq=%u",
+ view->log->files->hdr.prev_file_seq);
+ return 0;
+ }
+
+ min_file_seq = view->log->files->hdr.file_seq;
+ min_file_offset = 0;
+
+ if (max_file_seq == 0) {
+ max_file_seq = min_file_seq;
+ max_file_offset = min_file_offset;
+ }
+ }
+
+ for (file = view->log->files; file != NULL; file = file->next) {
+ if (file->hdr.prev_file_seq == min_file_seq)
+ break;
+ }
+
+ if (file != NULL && min_file_offset == file->hdr.prev_file_offset) {
+ /* we can (and sometimes must) skip to the next file */
+ min_file_seq = file->hdr.file_seq;
+ min_file_offset = file->hdr.hdr_size;
+ }
+
+ for (file = view->log->files; file != NULL; file = file->next) {
+ if (file->hdr.prev_file_seq == max_file_seq)
+ break;
+ }
+ if (file != NULL && max_file_offset == file->hdr.prev_file_offset) {
+ /* we can skip to the next file. we've delayed checking for
+ min_file_seq <= max_file_seq until now, because it's not
+ really an error to specify the same position twice (even if
+ in "wrong" order) */
+ i_assert(min_file_seq <= max_file_seq ||
+ min_file_seq <= file->hdr.file_seq);
+ max_file_seq = file->hdr.file_seq;
+ max_file_offset = file->hdr.hdr_size;
+ } else {
+ i_assert(min_file_seq <= max_file_seq);
+ }
+
+ if (min_file_seq == max_file_seq && min_file_offset > max_file_offset) {
+ /* log file offset is probably corrupted in the index file. */
+ *reason_r = t_strdup_printf(
+ "Invalid offset: file_seq=%u, min_file_offset (%"PRIuUOFF_T
+ ") > max_file_offset (%"PRIuUOFF_T")",
+ min_file_seq, min_file_offset, max_file_offset);
+ mail_transaction_log_view_set_corrupted(view, "%s", *reason_r);
+ return 0;
+ }
+
+ tail = head = file = NULL;
+ for (seq = min_file_seq; seq <= max_file_seq; seq++) {
+ const char *reason = NULL;
+
+ if (file == NULL || file->hdr.file_seq != seq) {
+ /* see if we could find the missing file. if we know
+ the max. file sequence or we don't have the the min.
+ file, make sure NFS attribute cache gets flushed if
+ necessary. */
+ bool nfs_flush = seq == min_file_seq ||
+ max_file_seq != (uint32_t)-1;
+
+ ret = mail_transaction_log_find_file(view->log, seq,
+ nfs_flush, &file, &reason);
+ if (ret <= 0) {
+ if (ret < 0) {
+ *reason_r = t_strdup_printf(
+ "Failed to find file seq=%u: %s",
+ seq, reason);
+ view_set_failed_unref(head, tail);
+ return -1;
+ }
+
+ /* not found / corrupted */
+ file = NULL;
+ }
+ }
+
+ if (file == NULL || file->hdr.file_seq != seq) {
+ i_assert(reason != NULL);
+ if (file == NULL && max_file_seq == (uint32_t)-1 &&
+ head == view->log->head) {
+ /* we just wanted to sync everything */
+ i_assert(max_file_offset == UOFF_T_MAX);
+ max_file_seq = seq-1;
+ break;
+ }
+ /* if any of the found files reset the index,
+ ignore any missing files up to it */
+ file = tail != NULL ? tail : view->log->files;
+ for (;; file = file->next) {
+ if (file == NULL ||
+ file->hdr.file_seq > max_file_seq) {
+ /* missing files in the middle */
+ *reason_r = t_strdup_printf(
+ "Missing middle file seq=%u (between %u..%u, we have seqs %s): %s",
+ seq, min_file_seq, max_file_seq,
+ mail_transaction_log_get_file_seqs(view->log), reason);
+ view_set_failed_unref(head, tail);
+ return 0;
+ }
+
+ if (file->hdr.file_seq >= seq &&
+ file->hdr.prev_file_seq == 0) {
+ /* we can ignore the missing file */
+ break;
+ }
+ }
+ /* we're going to rebuild the head/tail. remove the old
+ references first. */
+ view_set_failed_unref(head, tail);
+ seq = file->hdr.file_seq;
+ tail = NULL;
+ }
+
+ if (tail == NULL)
+ tail = file;
+ head = file;
+ /* NOTE: we need to reference immediately or it could become
+ freed by mail_transaction_log_find_file() */
+ file->refcount++;
+ file = file->next;
+ }
+ i_assert(tail != NULL);
+
+ if (min_file_offset == 0) {
+ /* beginning of the file */
+ min_file_offset = tail->hdr.hdr_size;
+ if (min_file_offset > max_file_offset &&
+ min_file_seq == max_file_seq) {
+ /* we don't actually want to show anything */
+ max_file_offset = min_file_offset;
+ }
+ }
+
+ if (min_file_offset < tail->hdr.hdr_size) {
+ /* log file offset is probably corrupted in the index file. */
+ *reason_r = t_strdup_printf(
+ "Invalid min_file_offset: file_seq=%u, min_file_offset (%"PRIuUOFF_T
+ ") < hdr_size (%u)",
+ min_file_seq, min_file_offset, tail->hdr.hdr_size);
+ mail_transaction_log_view_set_corrupted(view, "%s", *reason_r);
+ view_set_failed_unref(head, tail);
+ return 0;
+ }
+ if (max_file_offset < head->hdr.hdr_size) {
+ /* log file offset is probably corrupted in the index file. */
+ *reason_r = t_strdup_printf(
+ "Invalid max_file_offset: file_seq=%u, min_file_offset (%"PRIuUOFF_T
+ ") < hdr_size (%u)",
+ max_file_seq, max_file_offset, head->hdr.hdr_size);
+ mail_transaction_log_view_set_corrupted(view, "%s", *reason_r);
+ view_set_failed_unref(head, tail);
+ return 0;
+ }
+
+ /* we have all of them. update refcounts. */
+ mail_transaction_log_view_unref_all(view);
+
+ /* Reference all used files. */
+ view->tail = tail;
+ view->head = head;
+ for (file = view->tail; ; file = file->next) {
+ array_push_back(&view->file_refs, &file);
+
+ if (file == head)
+ break;
+ }
+
+ view->cur = view->tail;
+ view->cur_offset = view->cur->hdr.file_seq == min_file_seq ?
+ min_file_offset : view->cur->hdr.hdr_size;
+
+ /* Map the files only after we've found them all. Otherwise if we map
+ one file and then another file just happens to get rotated, we could
+ include both files in the view but skip the last transactions from
+ the first file.
+
+ We're mapping the files in reverse order so that _log_file_map()
+ can verify that prev_file_offset matches how far it actually managed
+ to sync the file. */
+ files = array_front(&view->file_refs);
+ for (i = array_count(&view->file_refs); i > 0; i--) {
+ file = files[i-1];
+ start_offset = file->hdr.file_seq == min_file_seq ?
+ min_file_offset : file->hdr.hdr_size;
+ end_offset = file->hdr.file_seq == max_file_seq ?
+ max_file_offset : UOFF_T_MAX;
+ ret = mail_transaction_log_file_map(file, start_offset,
+ end_offset, reason_r);
+ if (ret <= 0) {
+ *reason_r = t_strdup_printf(
+ "Failed to map file seq=%u "
+ "offset=%"PRIuUOFF_T"..%"PRIuUOFF_T" (ret=%d): %s",
+ file->hdr.file_seq, start_offset, end_offset, ret, *reason_r);
+ return ret;
+ }
+
+ if (file->hdr.prev_file_seq == 0) {
+ /* this file resets the index.
+ don't bother reading the others. */
+ if (view->cur != file ||
+ view->cur_offset == file->hdr.hdr_size) {
+ view->cur = file;
+ view->cur_offset = file->hdr.hdr_size;
+ *reset_r = TRUE;
+ break;
+ }
+ i_assert(i == 1);
+ }
+ }
+
+ if (min_file_seq == view->head->hdr.file_seq &&
+ min_file_offset > view->head->sync_offset) {
+ /* log file offset is probably corrupted in the index file. */
+ *reason_r = t_strdup_printf(
+ "Invalid offset: file_seq=%u, min_file_offset (%"PRIuUOFF_T
+ ") > sync_offset (%"PRIuUOFF_T")", min_file_seq,
+ min_file_offset, view->head->sync_offset);
+ mail_transaction_log_view_set_corrupted(view, "%s", *reason_r);
+ return 0;
+ }
+
+ i_assert(max_file_seq == (uint32_t)-1 ||
+ max_file_seq == view->head->hdr.file_seq);
+ i_assert(max_file_offset == UOFF_T_MAX ||
+ max_file_offset <= view->head->sync_offset);
+ i_assert(min_file_seq != max_file_seq ||
+ max_file_seq != view->head->hdr.file_seq ||
+ max_file_offset != UOFF_T_MAX ||
+ min_file_offset <= view->head->sync_offset);
+
+ view->prev_file_seq = view->cur->hdr.file_seq;
+ view->prev_file_offset = view->cur_offset;
+
+ view->min_file_seq = min_file_seq;
+ view->min_file_offset = min_file_offset;
+ view->max_file_seq = max_file_seq;
+ view->max_file_offset = I_MIN(max_file_offset, view->head->sync_offset);
+ view->broken = FALSE;
+
+ ret = mail_transaction_log_file_get_highest_modseq_at(view->cur,
+ view->cur_offset, &view->prev_modseq, reason_r);
+ if (ret <= 0)
+ return ret;
+
+ i_assert(view->cur_offset <= view->cur->sync_offset);
+ return 1;
+}
+
+int mail_transaction_log_view_set_all(struct mail_transaction_log_view *view)
+{
+ struct mail_transaction_log_file *file, *first;
+ const char *reason = NULL;
+ int ret;
+
+ /* make sure .log.2 file is opened */
+ (void)mail_transaction_log_find_file(view->log, 1, FALSE, &file, &reason);
+
+ first = view->log->files;
+ i_assert(first != NULL);
+
+ for (file = view->log->files; file != NULL; file = file->next) {
+ ret = mail_transaction_log_file_map(file, file->hdr.hdr_size,
+ UOFF_T_MAX, &reason);
+ if (ret < 0) {
+ first = NULL;
+ break;
+ }
+ if (ret == 0) {
+ /* corrupted */
+ first = NULL;
+ } else if (file->hdr.prev_file_seq == 0) {
+ /* this file resets the index. skip the old ones. */
+ first = file;
+ }
+ }
+ if (first == NULL) {
+ /* index wasn't reset after corruption was found */
+ i_assert(reason != NULL);
+ mail_index_set_error(view->log->index,
+ "Failed to map transaction log %s for all-view: %s",
+ view->log->filepath, reason);
+ return -1;
+ }
+
+ mail_transaction_log_view_unref_all(view);
+ for (file = first; file != NULL; file = file->next) {
+ array_push_back(&view->file_refs, &file);
+ file->refcount++;
+ }
+
+ view->tail = first;
+ view->cur = view->tail;
+ view->cur_offset = view->tail->hdr.hdr_size;
+
+ view->prev_file_seq = view->cur->hdr.file_seq;
+ view->prev_file_offset = view->cur_offset;
+
+ view->min_file_seq = view->cur->hdr.file_seq;
+ view->min_file_offset = view->cur_offset;
+ view->max_file_seq = view->head->hdr.file_seq;
+ view->max_file_offset = view->head->sync_offset;
+ view->broken = FALSE;
+
+ if (mail_transaction_log_file_get_highest_modseq_at(view->cur,
+ view->cur_offset, &view->prev_modseq, &reason) <= 0) {
+ mail_index_set_error(view->log->index,
+ "Failed to get modseq in %s for all-view: %s",
+ view->log->filepath, reason);
+ return -1;
+ }
+ return 0;
+}
+
+void mail_transaction_log_view_clear(struct mail_transaction_log_view *view,
+ uint32_t oldest_file_seq)
+{
+ struct mail_transaction_log_file *file;
+ const char *reason;
+
+ mail_transaction_log_view_unref_all(view);
+ if (oldest_file_seq != 0 &&
+ mail_transaction_log_find_file(view->log, oldest_file_seq, FALSE,
+ &file, &reason) > 0) {
+ for (; file != NULL; file = file->next) {
+ array_push_back(&view->file_refs, &file);
+ file->refcount++;
+ }
+ }
+
+ view->cur = view->head = view->tail = NULL;
+
+ view->mark_file = NULL;
+ view->mark_offset = 0;
+ view->mark_modseq = 0;
+
+ view->min_file_seq = view->max_file_seq = 0;
+ view->min_file_offset = view->max_file_offset = 0;
+ view->cur_offset = 0;
+
+ view->prev_file_seq = 0;
+ view->prev_file_offset = 0;
+ view->prev_modseq = 0;
+}
+
+void
+mail_transaction_log_view_get_prev_pos(struct mail_transaction_log_view *view,
+ uint32_t *file_seq_r,
+ uoff_t *file_offset_r)
+{
+ *file_seq_r = view->prev_file_seq;
+ *file_offset_r = view->prev_file_offset;
+}
+
+uint64_t
+mail_transaction_log_view_get_prev_modseq(struct mail_transaction_log_view *view)
+{
+ return view->prev_modseq;
+}
+
+static bool
+mail_transaction_log_view_get_last(struct mail_transaction_log_view *view,
+ struct mail_transaction_log_file **last_r,
+ uoff_t *last_offset_r)
+{
+ struct mail_transaction_log_file *cur = view->cur;
+ uoff_t cur_offset = view->cur_offset;
+ bool last = FALSE;
+
+ if (cur == NULL) {
+ *last_r = NULL;
+ return TRUE;
+ }
+
+ for (;;) {
+ if (cur->hdr.file_seq == view->max_file_seq) {
+ /* last file */
+ if (cur_offset == view->max_file_offset ||
+ cur_offset == cur->sync_offset) {
+ /* we're all finished */
+ last = TRUE;
+ }
+ } else if (cur_offset == cur->sync_offset) {
+ /* end of file, go to next one */
+ if (cur->next == NULL) {
+ last = TRUE;
+ } else {
+ cur = cur->next;
+ cur_offset = cur->hdr.hdr_size;
+ continue;
+ }
+ }
+
+ /* not EOF */
+ break;
+ }
+
+ *last_r = cur;
+ *last_offset_r = cur_offset;
+ return last;
+}
+
+bool mail_transaction_log_view_is_last(struct mail_transaction_log_view *view)
+{
+ struct mail_transaction_log_file *cur;
+ uoff_t cur_offset;
+
+ return mail_transaction_log_view_get_last(view, &cur, &cur_offset);
+}
+
+void
+mail_transaction_log_view_set_corrupted(struct mail_transaction_log_view *view,
+ const char *fmt, ...)
+{
+ va_list va;
+
+ view->broken = TRUE;
+
+ va_start(va, fmt);
+ T_BEGIN {
+ mail_transaction_log_file_set_corrupted(view->log->head, "%s",
+ t_strdup_vprintf(fmt, va));
+ } T_END;
+ va_end(va);
+}
+
+bool
+mail_transaction_log_view_is_corrupted(struct mail_transaction_log_view *view)
+{
+ return view->broken;
+}
+
+static bool
+log_view_is_uid_range_valid(struct mail_transaction_log_file *file,
+ enum mail_transaction_type rec_type,
+ const ARRAY_TYPE(seq_range) *uids)
+{
+ const struct seq_range *rec, *prev = NULL;
+ unsigned int i, count = array_count(uids);
+
+ if ((uids->arr.buffer->used % uids->arr.element_size) != 0) {
+ mail_transaction_log_file_set_corrupted(file,
+ "Invalid record size (type=0x%x)", rec_type);
+ return FALSE;
+ } else if (count == 0) {
+ mail_transaction_log_file_set_corrupted(file,
+ "No UID ranges (type=0x%x)", rec_type);
+ return FALSE;
+ }
+
+ for (i = 0; i < count; i++, prev = rec) {
+ rec = array_idx(uids, i);
+ if (rec->seq1 > rec->seq2 || rec->seq1 == 0) {
+ mail_transaction_log_file_set_corrupted(file,
+ "Invalid UID range (%u .. %u, type=0x%x)",
+ rec->seq1, rec->seq2, rec_type);
+ return FALSE;
+ }
+ if (prev != NULL && rec->seq1 <= prev->seq2) {
+ mail_transaction_log_file_set_corrupted(file,
+ "Non-sorted UID ranges (type=0x%x)", rec_type);
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static bool
+log_view_is_record_valid(struct mail_transaction_log_file *file,
+ const struct mail_transaction_header *hdr,
+ const void *data)
+{
+ enum mail_transaction_type rec_type;
+ ARRAY_TYPE(seq_range) uids = ARRAY_INIT;
+ buffer_t uid_buf;
+ uint32_t rec_size;
+
+ rec_type = hdr->type & MAIL_TRANSACTION_TYPE_MASK;
+ rec_size = mail_index_offset_to_uint32(hdr->size) - sizeof(*hdr);
+
+ /* we want to be extra careful with expunges */
+ if ((hdr->type & MAIL_TRANSACTION_EXPUNGE) != 0) {
+ if (rec_type != (MAIL_TRANSACTION_EXPUNGE |
+ MAIL_TRANSACTION_EXPUNGE_PROT)) {
+ mail_transaction_log_file_set_corrupted(file,
+ "expunge record missing protection mask");
+ return FALSE;
+ }
+ rec_type &= ENUM_NEGATE(MAIL_TRANSACTION_EXPUNGE_PROT);
+ }
+ if ((hdr->type & MAIL_TRANSACTION_EXPUNGE_GUID) != 0) {
+ if (rec_type != (MAIL_TRANSACTION_EXPUNGE_GUID |
+ MAIL_TRANSACTION_EXPUNGE_PROT)) {
+ mail_transaction_log_file_set_corrupted(file,
+ "expunge guid record missing protection mask");
+ return FALSE;
+ }
+ rec_type &= ENUM_NEGATE(MAIL_TRANSACTION_EXPUNGE_PROT);
+ }
+
+ if (rec_size == 0) {
+ mail_transaction_log_file_set_corrupted(file,
+ "Empty record contents (type=0x%x)", rec_type);
+ return FALSE;
+ }
+
+ /* records that are exported by syncing and view syncing will be
+ checked here so that we don't have to implement the same validation
+ multiple times. other records are checked internally by
+ mail_index_sync_record(). */
+ switch (rec_type) {
+ case MAIL_TRANSACTION_APPEND:
+ if ((rec_size % sizeof(struct mail_index_record)) != 0) {
+ mail_transaction_log_file_set_corrupted(file,
+ "Invalid append record size");
+ return FALSE;
+ }
+ break;
+ case MAIL_TRANSACTION_EXPUNGE:
+ buffer_create_from_const_data(&uid_buf, data, rec_size);
+ array_create_from_buffer(&uids, &uid_buf,
+ sizeof(struct mail_transaction_expunge));
+ break;
+ case MAIL_TRANSACTION_EXPUNGE_GUID: {
+ const struct mail_transaction_expunge_guid *recs = data;
+ unsigned int i, count;
+
+ if ((rec_size % sizeof(*recs)) != 0) {
+ mail_transaction_log_file_set_corrupted(file,
+ "Invalid expunge guid record size");
+ return FALSE;
+ }
+ count = rec_size / sizeof(*recs);
+ for (i = 0; i < count; i++) {
+ if (recs[i].uid == 0) {
+ mail_transaction_log_file_set_corrupted(file,
+ "Expunge guid record with uid=0");
+ return FALSE;
+ }
+ }
+ break;
+ }
+ case MAIL_TRANSACTION_FLAG_UPDATE:
+ buffer_create_from_const_data(&uid_buf, data, rec_size);
+ array_create_from_buffer(&uids, &uid_buf,
+ sizeof(struct mail_transaction_flag_update));
+ break;
+ case MAIL_TRANSACTION_KEYWORD_UPDATE: {
+ const struct mail_transaction_keyword_update *rec = data;
+ unsigned int seqset_offset;
+
+ seqset_offset = sizeof(*rec) + rec->name_size;
+ if ((seqset_offset % 4) != 0)
+ seqset_offset += 4 - (seqset_offset % 4);
+
+ if (rec->name_size == 0) {
+ mail_transaction_log_file_set_corrupted(file,
+ "Trying to use empty keyword");
+ return FALSE;
+ }
+ if (seqset_offset > rec_size) {
+ mail_transaction_log_file_set_corrupted(file,
+ "Invalid keyword update record size");
+ return FALSE;
+ }
+
+ buffer_create_from_const_data(&uid_buf,
+ CONST_PTR_OFFSET(data, seqset_offset),
+ rec_size - seqset_offset);
+ array_create_from_buffer(&uids, &uid_buf,
+ sizeof(uint32_t)*2);
+ break;
+ }
+ case MAIL_TRANSACTION_KEYWORD_RESET:
+ buffer_create_from_const_data(&uid_buf, data, rec_size);
+ array_create_from_buffer(&uids, &uid_buf,
+ sizeof(struct mail_transaction_keyword_reset));
+ break;
+ case MAIL_TRANSACTION_EXT_INTRO: {
+ const struct mail_transaction_ext_intro *rec;
+ unsigned int i;
+
+ for (i = 0; i < rec_size; ) {
+ if (i + sizeof(*rec) > rec_size) {
+ /* should be just extra padding */
+ break;
+ }
+
+ rec = CONST_PTR_OFFSET(data, i);
+ if (i + sizeof(*rec) + rec->name_size > rec_size) {
+ mail_transaction_log_file_set_corrupted(file,
+ "ext intro: name_size too large");
+ return FALSE;
+ }
+ i += sizeof(*rec) + rec->name_size;
+ if ((i % 4) != 0)
+ i += 4 - (i % 4);
+ }
+ break;
+ }
+ case MAIL_TRANSACTION_ATTRIBUTE_UPDATE: {
+ const char *attr_changes = data;
+ unsigned int i;
+
+ for (i = 0; i+2 < rec_size && attr_changes[i] != '\0'; ) {
+ if (attr_changes[i] != '+' && attr_changes[i] != '-') {
+ mail_transaction_log_file_set_corrupted(file,
+ "attribute update: Invalid prefix 0x%02x",
+ attr_changes[i]);
+ return FALSE;
+ }
+ i++;
+ if (attr_changes[i] != 'p' && attr_changes[i] != 's') {
+ mail_transaction_log_file_set_corrupted(file,
+ "attribute update: Invalid type 0x%02x",
+ attr_changes[i]);
+ return FALSE;
+ }
+ i++;
+ if (attr_changes[i] == '\0') {
+ mail_transaction_log_file_set_corrupted(file,
+ "attribute update: Empty key");
+ return FALSE;
+ }
+ i += strlen(attr_changes+i) + 1;
+ }
+ if (i == 0 || (i < rec_size && attr_changes[i] != '\0')) {
+ mail_transaction_log_file_set_corrupted(file,
+ "attribute update doesn't end with NUL");
+ return FALSE;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ if (array_is_created(&uids)) {
+ if (!log_view_is_uid_range_valid(file, rec_type, &uids))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static int
+log_view_get_next(struct mail_transaction_log_view *view,
+ const struct mail_transaction_header **hdr_r,
+ const void **data_r)
+{
+ const struct mail_transaction_header *hdr;
+ struct mail_transaction_log_file *file;
+ const void *data;
+ enum mail_transaction_type rec_type;
+ uint32_t full_size;
+ size_t file_size;
+ int ret;
+
+ if (view->cur == NULL)
+ return 0;
+
+ /* prev_file_offset should point to beginning of previous log record.
+ when we reach EOF, it should be left there, not to beginning of the
+ next file that's not included inside the view. */
+ if (mail_transaction_log_view_get_last(view, &view->cur,
+ &view->cur_offset)) {
+ /* if the last file was the beginning of a file, we want to
+ move prev pointers there */
+ view->prev_file_seq = view->cur->hdr.file_seq;
+ view->prev_file_offset = view->cur_offset;
+ view->cur = NULL;
+ return 0;
+ }
+
+ view->prev_file_seq = view->cur->hdr.file_seq;
+ view->prev_file_offset = view->cur_offset;
+
+ file = view->cur;
+
+ data = file->buffer->data;
+ file_size = file->buffer->used + file->buffer_offset;
+
+ if (view->cur_offset + sizeof(*hdr) > file_size) {
+ mail_transaction_log_file_set_corrupted(file,
+ "offset points outside file "
+ "(%"PRIuUOFF_T" + %zu > %zu)",
+ view->cur_offset, sizeof(*hdr), file_size);
+ return -1;
+ }
+
+ i_assert(view->cur_offset >= file->buffer_offset);
+ hdr = CONST_PTR_OFFSET(data, view->cur_offset - file->buffer_offset);
+ data = CONST_PTR_OFFSET(hdr, sizeof(*hdr));
+
+ rec_type = hdr->type & MAIL_TRANSACTION_TYPE_MASK;
+ full_size = mail_index_offset_to_uint32(hdr->size);
+ if (full_size < sizeof(*hdr)) {
+ mail_transaction_log_file_set_corrupted(file,
+ "record size too small (type=0x%x, "
+ "offset=%"PRIuUOFF_T", size=%u)",
+ rec_type, view->cur_offset, full_size);
+ return -1;
+ }
+
+ if (file_size - view->cur_offset < full_size) {
+ mail_transaction_log_file_set_corrupted(file,
+ "record size too large (type=0x%x, "
+ "offset=%"PRIuUOFF_T", size=%u, end=%zu)",
+ rec_type, view->cur_offset, full_size, file_size);
+ return -1;
+ }
+
+ T_BEGIN {
+ ret = log_view_is_record_valid(file, hdr, data) ? 1 : -1;
+ } T_END;
+ if (ret > 0) {
+ mail_transaction_update_modseq(hdr, data, &view->prev_modseq,
+ MAIL_TRANSACTION_LOG_HDR_VERSION(&file->hdr));
+ *hdr_r = hdr;
+ *data_r = data;
+ view->cur_offset += full_size;
+ }
+ return ret;
+}
+
+int mail_transaction_log_view_next(struct mail_transaction_log_view *view,
+ const struct mail_transaction_header **hdr_r,
+ const void **data_r)
+{
+ const struct mail_transaction_header *hdr;
+ const void *data;
+ int ret = 0;
+
+ if (view->broken)
+ return -1;
+
+ ret = log_view_get_next(view, &hdr, &data);
+ if (ret <= 0) {
+ if (ret < 0)
+ view->cur_offset = view->cur->sync_offset;
+ return ret;
+ }
+
+ /* drop expunge protection */
+ if ((hdr->type & MAIL_TRANSACTION_TYPE_MASK) ==
+ (MAIL_TRANSACTION_EXPUNGE | MAIL_TRANSACTION_EXPUNGE_PROT) ||
+ (hdr->type & MAIL_TRANSACTION_TYPE_MASK) ==
+ (MAIL_TRANSACTION_EXPUNGE_GUID | MAIL_TRANSACTION_EXPUNGE_PROT))
+ view->tmp_hdr.type = hdr->type & ENUM_NEGATE(MAIL_TRANSACTION_EXPUNGE_PROT);
+ else
+ view->tmp_hdr.type = hdr->type;
+
+ /* return record's size */
+ view->tmp_hdr.size = mail_index_offset_to_uint32(hdr->size);
+ i_assert(view->tmp_hdr.size > sizeof(*hdr));
+ view->tmp_hdr.size -= sizeof(*hdr);
+
+ *hdr_r = &view->tmp_hdr;
+ *data_r = data;
+ return 1;
+}
+
+void mail_transaction_log_view_mark(struct mail_transaction_log_view *view)
+{
+ i_assert(view->cur->hdr.file_seq == view->prev_file_seq);
+
+ view->mark_file = view->cur;
+ view->mark_offset = view->prev_file_offset;
+ view->mark_next_offset = view->cur_offset;
+ view->mark_modseq = view->prev_modseq;
+}
+
+void mail_transaction_log_view_rewind(struct mail_transaction_log_view *view)
+{
+ i_assert(view->mark_file != NULL);
+
+ view->cur = view->mark_file;
+ view->cur_offset = view->mark_next_offset;
+ view->prev_file_seq = view->cur->hdr.file_seq;
+ view->prev_file_offset = view->mark_offset;
+ view->prev_modseq = view->mark_modseq;
+}