void mail_transaction_log_file_unlock(struct mail_transaction_log_file *file, const char *lock_reason) { unsigned int lock_time; if (!file->locked) return; file->locked = FALSE; file->locked_sync_offset_updated = FALSE; if (MAIL_TRANSACTION_LOG_FILE_IN_MEMORY(file)) return; lock_time = time(NULL) - file->lock_created; if (lock_time >= MAIL_TRANSACTION_LOG_LOCK_WARN_SECS && lock_reason != NULL) { i_warning("Transaction log file %s was locked for %u seconds (%s)", file->filepath, lock_time, lock_reason); } if (file->log->index->lock_method == FILE_LOCK_METHOD_DOTLOCK) { (void)mail_transaction_log_file_undotlock(file); return; } file_unlock(&file->file_lock); }
static int mail_transaction_log_refresh(struct mail_transaction_log *log, bool nfs_flush) { struct mail_transaction_log_file *file; struct stat st; i_assert(log->head != NULL); if (MAIL_TRANSACTION_LOG_FILE_IN_MEMORY(log->head)) return 0; if (nfs_flush && log->nfs_flush) nfs_flush_file_handle_cache(log->filepath); if (nfs_safe_stat(log->filepath, &st) < 0) { if (errno != ENOENT) { mail_index_file_set_syscall_error(log->index, log->filepath, "stat()"); return -1; } /* see if the whole directory got deleted */ if (nfs_safe_stat(log->index->dir, &st) < 0 && errno == ENOENT) { log->index->index_deleted = TRUE; return -1; } /* the file should always exist at this point. if it doesn't, someone deleted it manually while the index was open. try to handle this nicely by creating a new log file. */ file = log->head; if (mail_transaction_log_create(log, FALSE) < 0) return -1; i_assert(file->refcount > 0); file->refcount--; log->index->need_recreate = TRUE; return 0; } else if (log->head->st_ino == st.st_ino && CMP_DEV_T(log->head->st_dev, st.st_dev)) { /* NFS: log files get rotated to .log.2 files instead of being unlinked, so we don't bother checking if the existing file has already been unlinked here (in which case inodes could match but point to different files) */ return 0; } file = mail_transaction_log_file_alloc(log, log->filepath); if (mail_transaction_log_file_open(file, FALSE) <= 0) { mail_transaction_log_file_free(&file); return -1; } i_assert(!file->locked); if (--log->head->refcount == 0) mail_transaction_logs_clean(log); mail_transaction_log_set_head(log, file); return 0; }
static void mail_transaction_log_mark_corrupted(struct mail_transaction_log_file *file) { unsigned int offset = offsetof(struct mail_transaction_log_header, indexid); int flags; if (MAIL_TRANSACTION_LOG_FILE_IN_MEMORY(file) || file->log->index->readonly) return; /* indexid=0 marks the log file as corrupted. we opened the file with O_APPEND, and now we need to drop it for pwrite() to work (at least in Linux) */ flags = fcntl(file->fd, F_GETFL, 0); if (flags < 0) { mail_index_file_set_syscall_error(file->log->index, file->filepath, "fcntl(F_GETFL)"); return; } if (fcntl(file->fd, F_SETFL, flags & ~O_APPEND) < 0) { mail_index_file_set_syscall_error(file->log->index, file->filepath, "fcntl(F_SETFL)"); return; } if (pwrite_full(file->fd, &file->hdr.indexid, sizeof(file->hdr.indexid), offset) < 0) { mail_index_file_set_syscall_error(file->log->index, file->filepath, "pwrite()"); } }
void mail_transaction_log_file_move_to_memory(struct mail_transaction_log_file *file) { buffer_t *buf; if (MAIL_TRANSACTION_LOG_FILE_IN_MEMORY(file)) return; if (file->mmap_base != NULL) { /* just copy to memory */ i_assert(file->buffer_offset == 0); buf = buffer_create_dynamic(default_pool, file->mmap_size); buffer_append(buf, file->mmap_base, file->mmap_size); buffer_free(&file->buffer); file->buffer = buf; /* and lose the mmap */ if (munmap(file->mmap_base, file->mmap_size) < 0) log_file_set_syscall_error(file, "munmap()"); file->mmap_base = NULL; } else if (file->buffer_offset != 0) { /* we don't have the full log in the memory. read it. */ (void)mail_transaction_log_file_read(file, 0, FALSE); } file->last_size = 0; if (close(file->fd) < 0) log_file_set_syscall_error(file, "close()"); file->fd = -1; i_free(file->filepath); file->filepath = i_strdup(file->log->filepath); }
int mail_transaction_log_file_lock(struct mail_transaction_log_file *file) { unsigned int lock_timeout_secs; int ret; if (file->locked) return 0; if (MAIL_TRANSACTION_LOG_FILE_IN_MEMORY(file)) { file->locked = TRUE; return 0; } if (file->log->index->lock_method == FILE_LOCK_METHOD_DOTLOCK) return mail_transaction_log_file_dotlock(file); if (file->log->index->readonly) { mail_index_set_error(file->log->index, "Index is read-only, can't write-lock %s", file->filepath); return -1; } i_assert(file->file_lock == NULL); lock_timeout_secs = I_MIN(MAIL_TRANSACTION_LOG_LOCK_TIMEOUT, file->log->index->max_lock_timeout_secs); ret = mail_index_lock_fd(file->log->index, file->filepath, file->fd, F_WRLCK, lock_timeout_secs, &file->file_lock); if (ret > 0) { file->locked = TRUE; file->lock_created = time(NULL); return 0; } if (ret < 0) { log_file_set_syscall_error(file, "mail_index_wait_lock_fd()"); return -1; } mail_index_set_error(file->log->index, "Timeout (%us) while waiting for lock for " "transaction log file %s%s", lock_timeout_secs, file->filepath, file_lock_find(file->fd, file->log->index->lock_method, F_WRLCK)); file->log->index->index_lock_timeout = TRUE; return -1; }
static int mail_transaction_log_file_read_hdr(struct mail_transaction_log_file *file, bool ignore_estale) { struct mail_transaction_log_file *f; int ret; i_assert(!MAIL_TRANSACTION_LOG_FILE_IN_MEMORY(file)); if (file->corrupted) return 0; ret = mail_transaction_log_file_read_header(file); if (ret < 0) { if (errno != ESTALE || !ignore_estale) log_file_set_syscall_error(file, "pread()"); return -1; } if (file->hdr.major_version != MAIL_TRANSACTION_LOG_MAJOR_VERSION) { /* incompatible version - fix silently */ return 0; } if (ret < MAIL_TRANSACTION_LOG_HEADER_MIN_SIZE) { mail_transaction_log_file_set_corrupted(file, "unexpected end of file while reading header"); return 0; } if (file->hdr.minor_version >= 2 || file->hdr.major_version > 1) { /* we have compatibility flags */ enum mail_index_header_compat_flags compat_flags = 0; #if !WORDS_BIGENDIAN compat_flags |= MAIL_INDEX_COMPAT_LITTLE_ENDIAN; #endif if (file->hdr.compat_flags != compat_flags) { /* architecture change */ mail_index_set_error(file->log->index, "Rebuilding index file %s: " "CPU architecture changed", file->log->index->filepath); return 0; } } if (file->hdr.hdr_size < MAIL_TRANSACTION_LOG_HEADER_MIN_SIZE) { mail_transaction_log_file_set_corrupted(file, "Header size too small"); return 0; } if (file->hdr.hdr_size < sizeof(file->hdr)) { /* @UNSAFE: smaller than we expected - zero out the fields we shouldn't have filled */ memset(PTR_OFFSET(&file->hdr, file->hdr.hdr_size), 0, sizeof(file->hdr) - file->hdr.hdr_size); } if (file->hdr.indexid == 0) { /* corrupted */ file->corrupted = TRUE; mail_index_set_error(file->log->index, "Transaction log file %s: marked corrupted", file->filepath); return 0; } if (file->hdr.indexid != file->log->index->indexid) { if (file->log->index->indexid != 0 && !file->log->index->initial_create) { /* index file was probably just rebuilt and we don't know about it yet */ mail_transaction_log_file_set_corrupted(file, "indexid changed %u -> %u", file->log->index->indexid, file->hdr.indexid); return 0; } /* creating index file. since transaction log is created first, use the indexid in it to create the main index to avoid races. */ file->log->index->indexid = file->hdr.indexid; } /* make sure we already don't have a file with the same sequence opened. it shouldn't happen unless the old log file was corrupted. */ for (f = file->log->files; f != NULL; f = f->next) { if (f->hdr.file_seq == file->hdr.file_seq) { if (strcmp(f->filepath, f->log->head->filepath) != 0) { /* old "f" is the .log.2 */ return mail_transaction_log_file_fail_dupe(f); } else { /* new "file" is probably the .log.2 */ return mail_transaction_log_file_fail_dupe(file); } } } file->sync_highest_modseq = file->hdr.initial_modseq; return 1; }
int mail_transaction_log_file_map(struct mail_transaction_log_file *file, uoff_t start_offset, uoff_t end_offset) { struct mail_index *index = file->log->index; uoff_t map_start_offset = start_offset; size_t size; int ret; if (file->hdr.indexid == 0) { /* corrupted */ return 0; } i_assert(start_offset >= file->hdr.hdr_size); i_assert(start_offset <= end_offset); i_assert(file->buffer == NULL || file->mmap_base != NULL || file->sync_offset >= file->buffer_offset + file->buffer->used); if (file->locked_sync_offset_updated && file == file->log->head && end_offset == (uoff_t)-1) { /* we're not interested of going further than sync_offset */ if (log_file_map_check_offsets(file, start_offset, end_offset) == 0) return 0; i_assert(start_offset <= file->sync_offset); end_offset = file->sync_offset; } if (file->buffer != NULL && file->buffer_offset <= start_offset) { /* see if we already have it */ size = file->buffer->used; if (file->buffer_offset + size >= end_offset) return 1; } if (file->locked) { /* set this only when we've synced to end of file while locked (either end_offset=(uoff_t)-1 or we had to read anyway) */ file->locked_sync_offset_updated = TRUE; } if (MAIL_TRANSACTION_LOG_FILE_IN_MEMORY(file)) { if (start_offset < file->buffer_offset || file->buffer == NULL) { /* we had moved the log to memory but failed to read the beginning of the log file */ mail_index_set_error(index, "%s: Beginning of the log isn't available", file->filepath); return 0; } return log_file_map_check_offsets(file, start_offset, end_offset); } if (start_offset > file->sync_offset) mail_transaction_log_file_skip_to_head(file); if (start_offset > file->sync_offset) { /* although we could just skip over the unwanted data, we have to sync everything so that modseqs are calculated correctly */ map_start_offset = file->sync_offset; } if ((file->log->index->flags & MAIL_INDEX_OPEN_FLAG_MMAP_DISABLE) == 0) ret = mail_transaction_log_file_map_mmap(file, map_start_offset); else { mail_transaction_log_file_munmap(file); ret = mail_transaction_log_file_read(file, map_start_offset, FALSE); } i_assert(file->buffer == NULL || file->mmap_base != NULL || file->sync_offset >= file->buffer_offset + file->buffer->used); if (ret <= 0) return ret; i_assert(file->buffer != NULL); return log_file_map_check_offsets(file, start_offset, end_offset); }