mail-transaction-log-file.c revision 0a1a4e586ced13635fc1b8f2c78c94cb35ef645a
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher/* Copyright (c) 2003-2017 Dovecot authors, see the included COPYING file */
84ae5edab16ad6be5e3be956cb6fa031c1428eb5Stephen Gallagher#define MEMORY_LOG_NAME "(in-memory transaction log file)"
002f84aea86371aa079b867c0ec39396b97109d3Lukas Slebodnikmail_transaction_log_file_sync(struct mail_transaction_log_file *file,
87d3b47abba6a40fcf809c85a2b138bc1013d9c5Jakub Hrozeklog_file_set_syscall_error(struct mail_transaction_log_file *file,
ccf340e56364851f2e5b75e52d3d63701b662954Lukas Slebodnik mail_index_file_set_syscall_error(file->log->index,
cc98edd9479d4622634a1275c98058916c14059aStephen Gallaghermail_transaction_log_mark_corrupted(struct mail_transaction_log_file *file)
cc98edd9479d4622634a1275c98058916c14059aStephen Gallagher unsigned int offset =
d3da1c165cdb4c1ec126a8f4b6b544ca415b9d20Pavel Březina offsetof(struct mail_transaction_log_header, indexid);
c481179da5d5b53ce16d8784c0bd2857ffc2f061Lukas Slebodnik if (MAIL_TRANSACTION_LOG_FILE_IN_MEMORY(file) ||
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher /* indexid=0 marks the log file as corrupted. we opened the file with
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher O_APPEND, and now we need to drop it for pwrite() to work (at least
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher mail_index_file_set_syscall_error(file->log->index,
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher if (fcntl(file->fd, F_SETFL, flags & ~O_APPEND) < 0) {
c89589fa349f38214c9cb8d9389c0fd557e5dca2Simo Sorce mail_index_file_set_syscall_error(file->log->index,
f775337a7d4ca1c0be8eab683d0d753cbaee49e2Lukas Slebodnik if (pwrite_full(file->fd, &file->hdr.indexid,
86b61156743b7ebdc049450a6f88452890fd9a61Jakub Hrozek mail_index_file_set_syscall_error(file->log->index,
86b61156743b7ebdc049450a6f88452890fd9a61Jakub Hrozekmail_transaction_log_file_set_corrupted(struct mail_transaction_log_file *file,
6d11fdcd8ef05000dd20b3431f8491790f99a802Lukas Slebodnik const char *fmt, ...)
6d11fdcd8ef05000dd20b3431f8491790f99a802Lukas Slebodnik "Corrupted transaction log file %s seq %u: %s "
5e6622722e84d594298a8324f3685a1bda2b5868Sumit Bosemail_transaction_log_file_alloc(struct mail_transaction_log *log,
5e6622722e84d594298a8324f3685a1bda2b5868Sumit Bose const char *path)
5e6622722e84d594298a8324f3685a1bda2b5868Sumit Bose file = i_new(struct mail_transaction_log_file, 1);
1658c567191c35beaddffafdb079abe33248037bLukas Slebodnikvoid mail_transaction_log_file_free(struct mail_transaction_log_file **_file)
faa16fc9f0c9a02b26497e7cf148a92586144c08David Disseldorp struct mail_transaction_log_file *file = *_file;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher for (p = &file->log->files; *p != NULL; p = &(*p)->next) {
d921c1eba437662437847279f251a0a5d8f70127Maxim if (*p == file) {
b9c8ce2bdd4045782c243605a1b999098bedcffcNoam Meltzer if (munmap(file->mmap_base, file->mmap_size) < 0)
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher log_file_set_syscall_error(file, "close()");
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallaghermail_transaction_log_file_skip_to_head(struct mail_transaction_log_file *file)
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher struct mail_transaction_log *log = file->log;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher struct mail_index_map *map = log->index->map;
4b6a0d0b3d42e5fdb457f47d9adfa5e66b160256Stephen Gallagher const struct mail_index_modseq_header *modseq_hdr;
4a5a18f489f4d19aa0571528a7f0c7a8d35ac83fLukas Slebodnik if (map == NULL || file->hdr.file_seq != map->hdr.log_file_seq ||
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher /* we can get a valid log offset from index file. initialize
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher sync_offset from it so we don't have to read the whole log
64ea4127f463798410a2c20e0261c6b15f60257fJakub Hrozek file from beginning. */
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher head_offset = map->hdr.log_file_head_offset;
32381402a4a9afc003782c9e2301fc59c9bda2a9Yassir Elley modseq_hdr = mail_index_map_get_modseq_header(map);
98ce3c3e85a4bb2e1822bf8ab2a1c2ab9e3dd61dJakub Hrozek "%s: log_file_head_offset too small",
f36078af138f052cd9a30360867b0ebd0805af5eJakub Hrozek file->sync_highest_modseq = file->hdr.initial_modseq;
34c78b745eb349eef2b0f13ef2b722632aebe619Jan Cholasta } else if (modseq_hdr == NULL && file->hdr.initial_modseq == 0) {
cb4d5b588e704114b7090678752d33512baa718eJakub Hrozek /* modseqs not used yet */
b9c8ce2bdd4045782c243605a1b999098bedcffcNoam Meltzer /* highest_modseq not synced, start from beginning */
bc13c352ba9c2877f1e9bc62e55ad60fc000a55dJakub Hrozek file->sync_highest_modseq = file->hdr.initial_modseq;
b1ce544568eff89f2263ae180e323f263f1cff3aSimo Sorce } else if (modseq_hdr->log_offset > head_offset) {
b1ce544568eff89f2263ae180e323f263f1cff3aSimo Sorce "%s: modseq_hdr.log_offset too large",
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher file->sync_highest_modseq = file->hdr.initial_modseq;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher /* start from where we last stopped tracking modseqs */
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher file->sync_offset = modseq_hdr->log_offset;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher file->sync_highest_modseq = modseq_hdr->highest_modseq;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher if (file->hdr.file_seq == log->index->map->hdr.log_file_seq) {
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher log->index->map->hdr.log_file_tail_offset;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher file->saved_tail_sync_offset = file->saved_tail_offset;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher if (file->saved_tail_offset > file->max_tail_offset)
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher file->max_tail_offset = file->saved_tail_offset;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallaghermail_transaction_log_file_add_to_list(struct mail_transaction_log_file *file)
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher file->sync_highest_modseq = file->hdr.initial_modseq;
af4ffe1001adcc0a96897e426d26444f07af9aa1Benjamin Franzke /* insert it to correct position */
6b0a7c72bb841d6885a620c68bd51d55109b66c7Jakub Hrozek for (p = &file->log->files; *p != NULL; p = &(*p)->next) {
9917c138d9a270deb5820915384fbde751190c2aLukas Slebodnik if ((*p)->hdr.file_seq > file->hdr.file_seq)
c3889e5a101a075defe533d81f5296d5e680f639Lukas Slebodnik i_assert((*p)->hdr.file_seq < file->hdr.file_seq);
3fc158e59eebbc2f538fe0076a03928d0d4eab9fPavel Březina /* if we read any unfinished data, make sure the buffer gets
f0beb4e313970ffd075cd711ed6cfbac03ad5af6Christian Heimes truncated. */
f0beb4e313970ffd075cd711ed6cfbac03ad5af6Christian Heimes (void)mail_transaction_log_file_sync(file, &retry, &reason);
793f2573b2beaf8b48eab850429482acf68ec2b1Pavel Březinamail_transaction_log_init_hdr(struct mail_transaction_log *log,
b9c563c29243291f40489bb0dcbf3946fca72d58Jakub Hrozek hdr->major_version = MAIL_TRANSACTION_LOG_MAJOR_VERSION;
b32159300fea63222d8dd9200ed634087704ea74Stephen Gallagher hdr->minor_version = MAIL_TRANSACTION_LOG_MINOR_VERSION;
b32159300fea63222d8dd9200ed634087704ea74Stephen Gallagher hdr->hdr_size = sizeof(struct mail_transaction_log_header);
b32159300fea63222d8dd9200ed634087704ea74Stephen Gallagher hdr->compat_flags |= MAIL_INDEX_COMPAT_LITTLE_ENDIAN;
b32159300fea63222d8dd9200ed634087704ea74Stephen Gallagher /* not creating index - make sure we have latest header */
574a1c20f114851071ae74112b34488c3d1aeeb3Ondrej Kos /* if we got here from mapping, the .log file is
574a1c20f114851071ae74112b34488c3d1aeeb3Ondrej Kos corrupted. use whatever values we got from index
9542512d7be40f2000298c86d3d2b728f4f0f65aStephen Gallagher hdr->prev_file_seq = index->map->hdr.log_file_seq;
e157b9f6cb370e1b94bcac2044d26ad66d640fbaPavel Březina hdr->prev_file_offset = index->map->hdr.log_file_head_offset;
0c1b38d1a86460a638fa0d97099a6eba10cfccf0Jakub Hrozek hdr->file_seq = index->map->hdr.log_file_seq + 1;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher /* modseq tracking in log files is required for many reasons
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher nowadays, even if per-message modseqs aren't enabled in
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher dovecot.index. */
6b01dae732eedee808f32a9cdd4b5656a9f839c4Jakub Hrozek /* make sure the sequence always increases to avoid crashes
6b01dae732eedee808f32a9cdd4b5656a9f839c4Jakub Hrozek later. this catches the buggy case where two processes
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher happen to replace the same log file. */
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher for (file = log->head->next; file != NULL; file = file->next) {
6b01dae732eedee808f32a9cdd4b5656a9f839c4Jakub Hrozek if (hdr->file_seq <= log->head->hdr.file_seq) {
6b01dae732eedee808f32a9cdd4b5656a9f839c4Jakub Hrozek /* make sure the sequence grows */
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher hdr->file_seq = log->head->hdr.file_seq+1;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher if (hdr->initial_modseq < log->head->sync_highest_modseq) {
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher /* this should be always up-to-date */
b50dffea929ee5cd0c59ba3c4822337cc162ff92Kamil Dudka hdr->initial_modseq = log->head->sync_highest_modseq;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallaghermail_transaction_log_file_alloc_in_memory(struct mail_transaction_log *log)
4d81fe27ced3d2e96866aeaf61661a925cb8edf1Jakub Hrozek file = mail_transaction_log_file_alloc(log, MEMORY_LOG_NAME);
4d81fe27ced3d2e96866aeaf61661a925cb8edf1Jakub Hrozek if (mail_transaction_log_init_hdr(log, &file->hdr) < 0) {
4d81fe27ced3d2e96866aeaf61661a925cb8edf1Jakub Hrozek file->buffer = buffer_create_dynamic(default_pool, 4096);
4d81fe27ced3d2e96866aeaf61661a925cb8edf1Jakub Hrozekmail_transaction_log_file_dotlock(struct mail_transaction_log_file *file)
827dd342494de18099dddd0272c1a85f10703556Lukas Slebodnik mail_transaction_log_get_dotlock_set(file->log, &dotlock_set);
827dd342494de18099dddd0272c1a85f10703556Lukas Slebodnik ret = file_dotlock_create(&dotlock_set, file->filepath, 0,
827dd342494de18099dddd0272c1a85f10703556Lukas Slebodnik log_file_set_syscall_error(file, "file_dotlock_create()");
4a5a18f489f4d19aa0571528a7f0c7a8d35ac83fLukas Slebodnik "Timeout (%us) while waiting for "
72e60fd4eabcfbcdbfe01e8c38b94052bc6c2067Jakub Hrozek "dotlock for transaction log file %s",
4a5a18f489f4d19aa0571528a7f0c7a8d35ac83fLukas Slebodnik file->log->index->index_lock_timeout = TRUE;
4a5a18f489f4d19aa0571528a7f0c7a8d35ac83fLukas Slebodnikmail_transaction_log_file_undotlock(struct mail_transaction_log_file *file)
4a5a18f489f4d19aa0571528a7f0c7a8d35ac83fLukas Slebodnik ret = file_dotlock_delete(&file->log->dotlock);
827dd342494de18099dddd0272c1a85f10703556Lukas Slebodnik log_file_set_syscall_error(file, "file_dotlock_delete()");
4a5a18f489f4d19aa0571528a7f0c7a8d35ac83fLukas Slebodnik "Dotlock was lost for transaction log file %s",
4a5a18f489f4d19aa0571528a7f0c7a8d35ac83fLukas Slebodnikint mail_transaction_log_file_lock(struct mail_transaction_log_file *file)
4a5a18f489f4d19aa0571528a7f0c7a8d35ac83fLukas Slebodnik if (MAIL_TRANSACTION_LOG_FILE_IN_MEMORY(file)) {
4a5a18f489f4d19aa0571528a7f0c7a8d35ac83fLukas Slebodnik if (file->log->index->lock_method == FILE_LOCK_METHOD_DOTLOCK)
4a5a18f489f4d19aa0571528a7f0c7a8d35ac83fLukas Slebodnik return mail_transaction_log_file_dotlock(file);
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher "Index is read-only, can't write-lock %s",
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher lock_timeout_secs = I_MIN(MAIL_TRANSACTION_LOG_LOCK_TIMEOUT,
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher ret = mail_index_lock_fd(file->log->index, file->filepath, file->fd,
bf01e8179cbb2be476805340636098deda7e1366Sumit Bose log_file_set_syscall_error(file, "mail_index_wait_lock_fd()");
9917c138d9a270deb5820915384fbde751190c2aLukas Slebodnik "Timeout (%us) while waiting for lock for "
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher "transaction log file %s%s",
336879aabae137f9a81304f147fb0d43001654b0Simo Sorce file_lock_find(file->fd, file->log->index->lock_method, F_WRLCK));
336879aabae137f9a81304f147fb0d43001654b0Simo Sorcevoid mail_transaction_log_file_unlock(struct mail_transaction_log_file *file,
336879aabae137f9a81304f147fb0d43001654b0Simo Sorce unsigned int lock_time;
aac071824f6c98003f30d49ab440c15b4b53692cLukas Slebodnik if (MAIL_TRANSACTION_LOG_FILE_IN_MEMORY(file))
aac071824f6c98003f30d49ab440c15b4b53692cLukas Slebodnik lock_time = time(NULL) - file->lock_created;
aac071824f6c98003f30d49ab440c15b4b53692cLukas Slebodnik if (lock_time >= MAIL_TRANSACTION_LOG_LOCK_WARN_SECS && lock_reason != NULL) {
aac071824f6c98003f30d49ab440c15b4b53692cLukas Slebodnik i_warning("Transaction log file %s was locked for %u seconds (%s)",
aac071824f6c98003f30d49ab440c15b4b53692cLukas Slebodnik if (file->log->index->lock_method == FILE_LOCK_METHOD_DOTLOCK) {
aac071824f6c98003f30d49ab440c15b4b53692cLukas Slebodnik (void)mail_transaction_log_file_undotlock(file);
356eef72675cde4dc5627c1e2f1a01846ec6eb1dLukas Slebodnikmail_transaction_log_file_read_header(struct mail_transaction_log_file *file)
356eef72675cde4dc5627c1e2f1a01846ec6eb1dLukas Slebodnik i_assert(file->buffer == NULL && file->mmap_base == NULL);
356eef72675cde4dc5627c1e2f1a01846ec6eb1dLukas Slebodnik if (file->last_size < mmap_get_page_size() && file->last_size > 0) {
356eef72675cde4dc5627c1e2f1a01846ec6eb1dLukas Slebodnik /* just read the entire transaction log to memory.
356eef72675cde4dc5627c1e2f1a01846ec6eb1dLukas Slebodnik note that if some of the data hasn't been fully committed
2e505786d6d9d537f5b6631099862f6b93e2e687Lukas Slebodnik yet (hdr.size=0), the buffer must be truncated later */
2e505786d6d9d537f5b6631099862f6b93e2e687Lukas Slebodnik file->buffer = buffer_create_dynamic(default_pool, 4096);
2e505786d6d9d537f5b6631099862f6b93e2e687Lukas Slebodnik dest = buffer_append_space_unsafe(file->buffer, dest_size);
2e505786d6d9d537f5b6631099862f6b93e2e687Lukas Slebodnik /* read only the header */
2e505786d6d9d537f5b6631099862f6b93e2e687Lukas Slebodnik /* it's not necessarily an error to read less than wanted header size,
2e505786d6d9d537f5b6631099862f6b93e2e687Lukas Slebodnik since older versions of the log format used smaller headers. */
2e505786d6d9d537f5b6631099862f6b93e2e687Lukas Slebodnik ret = pread(file->fd, PTR_OFFSET(dest, pos),
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallaghermail_transaction_log_file_fail_dupe(struct mail_transaction_log_file *file)
1c7f25390572025baa6783ede14523e22fc73043Lukas Slebodnik /* mark the old file corrupted. we can't safely remove
40b2be4f4312470044cdef460b02b66003f5c85fJakub Hrozek it from the list however, so return failure. */
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher if (strcmp(file->filepath, file->log->head->filepath) != 0) {
29c5542feb4c45865ea61be97e0e84a1d1f04918Jakub Hrozek /* only mark .2 corrupted, just to make sure we don't lose any
29c5542feb4c45865ea61be97e0e84a1d1f04918Jakub Hrozek changes from .log in case we're somehow wrong */
5484044ea7bb632b915f706685fce509f6eacc48Jakub Hrozek "Transaction log %s: "
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher "duplicate transaction log sequence (%u)",
96c73559adfbdac96720008fc022cb1d540b53c3Jakub Hrozekmail_transaction_log_file_read_hdr(struct mail_transaction_log_file *file,
a6098862048d4bb469130b9ff21be3020d6f2c54Sumit Bose i_assert(!MAIL_TRANSACTION_LOG_FILE_IN_MEMORY(file));
a7e27c11866a48742bb70564b88e15bf15e9367dPavel Březina ret = mail_transaction_log_file_read_header(file);
const unsigned int hdr_version =
#if !WORDS_BIGENDIAN
return mail_transaction_log_file_fail_dupe(f);
bool ignore_estale)
return TRUE;
return FALSE;
unsigned int hdr_offset;
const char *path2;
if (reset)
FALSE) > 0 &&
if (reset) {
if (ret < 0)
if (rename_existing) {
DOTLOCK_REPLACE_FLAG_DONT_CLOSE_FD) <= 0) {
bool reset)
if (ret < 0) {
return ret;
const char **reason_r)
bool ignore_estale;
int ret;
if (ret > 0) {
if (ret == 0) {
i == MAIL_INDEX_ESTALE_RETRY_COUNT) {
const char **error_r)
const unsigned int offset_pos =
sizeof(tail_offset));
return TRUE;
for (unsigned int i = 0; i < count; i++) {
return TRUE;
return FALSE;
unsigned int version)
if (*cur_modseq != 0) {
const unsigned int modseq_ext_len =
modseq_ext_len) == 0) {
case MAIL_TRANSACTION_APPEND:
case MAIL_TRANSACTION_FLAG_UPDATE: {
unsigned int count;
case MAIL_TRANSACTION_MODSEQ_UPDATE: {
static struct modseq_cache *
if (idx > 0) {
static struct modseq_cache *
return NULL;
best = i;
return NULL;
static struct modseq_cache *
return NULL;
best = i;
return NULL;
const char **error_r)
const char **error_r)
const char *reason;
int ret;
if (ret <= 0) {
const char *reason;
int ret;
if (ret <= 0) {
int ret;
if (ret != 0)
case MAIL_TRANSACTION_BOUNDARY: {
const void *data;
int ret;
if (trans_size == 0) {
if (ret < 0)
void *data;
if (ret > 0) {
if (ret == 0) {
const char **reason_r)
void *data;
if (ret > 0)
if (ret < 0) {
return TRUE;
return TRUE;
return FALSE;
const char **reason_r)
bool retry;
int ret;
if (ret <= 0)
return ret;
return ret;
const char **reason_r)
return FALSE;
return FALSE;
return TRUE;
const char **reason_r)
MADV_SEQUENTIAL) < 0)
bool retry;
int ret;
!retry)
return ret;
} while (retry);
return ret;
const char **reason_r)
int ret;
if (ret <= 0)
return ret;
*file)
const char *error;