/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "test-common.h" #include "mail-index-private.h" #include "mail-transaction-log-view-private.h" static struct mail_transaction_log *log; static struct mail_transaction_log_view *view; static bool clean_refcount0_files = FALSE; static void test_transaction_log_file_add(uint32_t file_seq) { struct mail_transaction_log_file **p, *file; file = i_new(struct mail_transaction_log_file, 1); file->hdr.file_seq = file_seq; file->hdr.hdr_size = file->sync_offset = sizeof(file->hdr); file->hdr.prev_file_seq = file_seq - 1; file->hdr.prev_file_offset = (uint32_t)-1; file->log = log; file->fd = -1; file->buffer = buffer_create_dynamic(default_pool, 256); file->buffer_offset = file->hdr.hdr_size; /* files must be sorted by file_seq */ for (p = &log->files; *p != NULL; p = &(*p)->next) { if ((*p)->hdr.file_seq > file->hdr.file_seq) { file->next = *p; break; } } *p = file; log->head = file; } void mail_index_set_error(struct mail_index *index ATTR_UNUSED, const char *fmt ATTR_UNUSED, ...) { } void mail_transaction_log_file_set_corrupted(struct mail_transaction_log_file *file ATTR_UNUSED, const char *fmt ATTR_UNUSED, ...) { } void mail_transaction_logs_clean(struct mail_transaction_log *log ATTR_UNUSED) { } int mail_transaction_log_find_file(struct mail_transaction_log *log, uint32_t file_seq, bool nfs_flush ATTR_UNUSED, struct mail_transaction_log_file **file_r, const char **reason_r) { struct mail_transaction_log_file *file, *next; for (file = log->files; file != NULL; file = next) { next = file->next; if (file->hdr.file_seq == file_seq) { *file_r = file; return 1; } /* refcount=0 files at the beginning of the list may be freed */ if (file->refcount == 0 && file == log->files && clean_refcount0_files) log->files = next; } if (clean_refcount0_files && file_seq == 4) { /* "clean refcount=0 files" test autocreates this file */ test_transaction_log_file_add(4); *file_r = log->head; return 1; } *reason_r = "not found"; return 0; } int mail_transaction_log_file_map(struct mail_transaction_log_file *file ATTR_UNUSED, uoff_t start_offset ATTR_UNUSED, uoff_t end_offset ATTR_UNUSED, const char **reason_r ATTR_UNUSED) { return 1; } int mail_transaction_log_file_get_highest_modseq_at( struct mail_transaction_log_file *file ATTR_UNUSED, uoff_t offset ATTR_UNUSED, uint64_t *highest_modseq_r, const char **error_r ATTR_UNUSED) { *highest_modseq_r = 0; return 1; } void mail_transaction_update_modseq(const struct mail_transaction_header *hdr ATTR_UNUSED, const void *data ATTR_UNUSED, uint64_t *cur_modseq, unsigned int version ATTR_UNUSED) { *cur_modseq += 1; } static bool view_is_file_refed(uint32_t file_seq) { struct mail_transaction_log_file *const *files; unsigned int i, count; bool ret = FALSE; files = array_get(&view->file_refs, &count); for (i = 0; i < count; i++) { if (files[i]->hdr.file_seq == file_seq) { i_assert(!ret); /* could be a test too.. */ ret = TRUE; } } return ret; } static size_t add_append_record(struct mail_transaction_log_file *file, const struct mail_index_record *rec) { struct mail_transaction_header hdr; size_t size; i_zero(&hdr); hdr.type = MAIL_TRANSACTION_APPEND | MAIL_TRANSACTION_EXTERNAL; hdr.size = mail_index_uint32_to_offset(sizeof(hdr) + sizeof(*rec)); buffer_append(file->buffer, &hdr, sizeof(hdr)); buffer_append(file->buffer, rec, sizeof(*rec)); size = sizeof(hdr) + sizeof(*rec); file->sync_offset += size; return size; } static void test_mail_transaction_log_view(void) { const struct mail_transaction_header *hdr; const struct mail_index_record *rec; struct mail_index_record append_rec; const void *data; void *oldfile; uint32_t seq; uoff_t offset, last_log_size; const char *reason; bool reset; test_begin("init"); log = i_new(struct mail_transaction_log, 1); log->index = i_new(struct mail_index, 1); log->index->log = log; log->index->log_sync_locked = TRUE; test_transaction_log_file_add(1); test_transaction_log_file_add(2); test_transaction_log_file_add(3); /* add an append record to the 3rd log file */ i_zero(&append_rec); append_rec.uid = 1; last_log_size = sizeof(struct mail_transaction_log_header) + add_append_record(log->head, &append_rec); view = mail_transaction_log_view_open(log); i_assert(view != NULL); test_assert(log->views == view && !view_is_file_refed(1) && !view_is_file_refed(2) && view_is_file_refed(3)); test_end(); /* we have files 1-3 opened */ test_begin("set all"); test_assert(mail_transaction_log_view_set(view, 0, 0, (uint32_t)-1, UOFF_T_MAX, &reset, &reason) == 1 && reset && view_is_file_refed(1) && view_is_file_refed(2) && view_is_file_refed(3) && !mail_transaction_log_view_is_corrupted(view)); mail_transaction_log_view_get_prev_pos(view, &seq, &offset); test_assert(seq == 1 && offset == sizeof(struct mail_transaction_log_header)); test_assert(mail_transaction_log_view_next(view, &hdr, &data) == 1); test_assert(hdr->type == (MAIL_TRANSACTION_APPEND | MAIL_TRANSACTION_EXTERNAL)); rec = data; test_assert(memcmp(rec, &append_rec, sizeof(*rec)) == 0); test_assert(mail_transaction_log_view_next(view, &hdr, &data) == 0); test_assert(mail_transaction_log_view_is_last(view)); mail_transaction_log_view_get_prev_pos(view, &seq, &offset); test_assert(seq == 3 && offset == last_log_size); test_end(); test_begin("set first"); test_assert(mail_transaction_log_view_set(view, 0, 0, 0, 0, &reset, &reason) == 1); mail_transaction_log_view_get_prev_pos(view, &seq, &offset); test_assert(seq == 1 && offset == sizeof(struct mail_transaction_log_header)); test_assert(mail_transaction_log_view_next(view, &hdr, &data) == 0); mail_transaction_log_view_get_prev_pos(view, &seq, &offset); test_assert(seq == 1 && offset == sizeof(struct mail_transaction_log_header)); test_end(); test_begin("set end"); test_assert(mail_transaction_log_view_set(view, 3, last_log_size, (uint32_t)-1, UOFF_T_MAX, &reset, &reason) == 1); mail_transaction_log_view_get_prev_pos(view, &seq, &offset); test_assert(seq == 3 && offset == last_log_size); test_assert(mail_transaction_log_view_next(view, &hdr, &data) == 0); mail_transaction_log_view_get_prev_pos(view, &seq, &offset); test_assert(seq == 3 && offset == last_log_size); test_end(); test_begin("log clear"); mail_transaction_log_view_clear(view, 2); test_assert(!view_is_file_refed(1) && view_is_file_refed(2) && view_is_file_refed(3)); oldfile = log->files; buffer_free(&log->files->buffer); log->files = log->files->next; i_free(oldfile); test_assert(log->files->hdr.file_seq == 2); test_end(); /* --- first file has been removed --- */ test_begin("set 2-3"); test_assert(mail_transaction_log_view_set(view, 2, 0, (uint32_t)-1, UOFF_T_MAX, &reset, &reason) == 1); test_end(); test_begin("missing log handing"); test_assert(mail_transaction_log_view_set(view, 0, 0, (uint32_t)-1, UOFF_T_MAX, &reset, &reason) == 0); test_end(); test_begin("closed log handling"); view->log = NULL; test_assert(mail_transaction_log_view_set(view, 0, 0, (uint32_t)-1, UOFF_T_MAX, &reset, &reason) == 0); view->log = log; test_end(); test_begin("clean refcount=0 files"); oldfile = log->files; /* clear all references */ mail_transaction_log_view_clear(view, 0); clean_refcount0_files = TRUE; /* create a new file during mail_transaction_log_view_set(), which triggers freeing any unreferenced files. */ test_assert(mail_transaction_log_view_set(view, 2, 0, 4, UOFF_T_MAX, &reset, &reason) == 1); clean_refcount0_files = FALSE; log->files = oldfile; test_end(); mail_transaction_log_view_close(&view); i_free(log->index); while (log->files != NULL) { oldfile = log->files; buffer_free(&log->files->buffer); log->files = log->files->next; i_free(oldfile); } i_free(log); } int main(void) { static void (*const test_functions[])(void) = { test_mail_transaction_log_view, NULL }; return test_run(test_functions); }