void mail_transaction_log_sync_unlock(struct mail_transaction_log *log) { i_assert(log->index->log_sync_locked); log->index->log_sync_locked = FALSE; mail_transaction_log_file_unlock(log->head); }
void mail_transaction_log_sync_unlock(struct mail_transaction_log *log, const char *lock_reason) { i_assert(log->index->log_sync_locked); log->index->log_sync_locked = FALSE; mail_transaction_log_file_unlock(log->head, lock_reason); }
int mail_transaction_log_rotate(struct mail_transaction_log *log, bool reset) { struct mail_transaction_log_file *file; const char *path = log->head->filepath; struct stat st; int ret; i_assert(log->head->locked); if (MAIL_INDEX_IS_IN_MEMORY(log->index)) { file = mail_transaction_log_file_alloc_in_memory(log); if (reset) { file->hdr.prev_file_seq = 0; file->hdr.prev_file_offset = 0; } } else { /* we're locked, we shouldn't need to worry about ESTALE problems in here. */ if (fstat(log->head->fd, &st) < 0) { mail_index_file_set_syscall_error(log->index, log->head->filepath, "fstat()"); return -1; } file = mail_transaction_log_file_alloc(log, path); file->st_dev = st.st_dev; file->st_ino = st.st_ino; file->last_mtime = st.st_mtime; file->last_size = st.st_size; if ((ret = mail_transaction_log_file_create(file, reset)) < 0) { mail_transaction_log_file_free(&file); return -1; } if (ret == 0) { mail_index_set_error(log->index, "Transaction log %s was recreated while we had it locked - " "locking is broken (lock_method=%s)", path, file_lock_method_to_str(log->index->lock_method)); mail_transaction_log_file_free(&file); return -1; } i_assert(file->locked); } if (--log->head->refcount == 0) mail_transaction_logs_clean(log); else { /* the newly created log file is already locked */ mail_transaction_log_file_unlock(log->head, !log->index->log_sync_locked ? "rotating" : "rotating while syncing"); } mail_transaction_log_set_head(log, file); return 0; }
int mail_transaction_log_lock_head(struct mail_transaction_log *log) { struct mail_transaction_log_file *file; int ret = 0; if (!log->log_2_unlink_checked) { /* we need to check once in a while if .log.2 should be deleted to avoid wasting space on such old files. but we also don't want to waste time on checking it when the same mailbox gets opened over and over again rapidly (e.g. pop3). so do this only when there have actually been some changes to mailbox (i.e. when it's being locked here) */ log->log_2_unlink_checked = TRUE; mail_transaction_log_2_unlink_old(log); } /* we want to get the head file locked. this is a bit racy, since by the time we have it locked a new log file may have been created. creating new log file requires locking the head file, so if we can lock it and don't see another file, we can be sure no-one is creating a new log at the moment */ for (;;) { file = log->head; if (mail_transaction_log_file_lock(file) < 0) return -1; file->refcount++; ret = mail_transaction_log_refresh(log, TRUE); if (--file->refcount == 0) { mail_transaction_logs_clean(log); file = NULL; } if (ret == 0 && log->head == file) { /* success */ break; } if (file != NULL) mail_transaction_log_file_unlock(file); if (ret < 0) break; /* try again */ } return ret; }
int mail_transaction_log_rotate(struct mail_transaction_log *log, bool reset) { struct mail_transaction_log_file *file; const char *path = log->head->filepath; struct stat st; i_assert(log->head->locked); if (MAIL_INDEX_IS_IN_MEMORY(log->index)) { file = mail_transaction_log_file_alloc_in_memory(log); if (reset) { file->hdr.prev_file_seq = 0; file->hdr.prev_file_offset = 0; } } else { /* we're locked, we shouldn't need to worry about ESTALE problems in here. */ if (fstat(log->head->fd, &st) < 0) { mail_index_file_set_syscall_error(log->index, log->head->filepath, "fstat()"); return -1; } file = mail_transaction_log_file_alloc(log, path); file->st_dev = st.st_dev; file->st_ino = st.st_ino; file->last_mtime = st.st_mtime; file->last_size = st.st_size; if (mail_transaction_log_file_create(file, reset) < 0) { mail_transaction_log_file_free(&file); return -1; } } if (--log->head->refcount == 0) mail_transaction_logs_clean(log); else mail_transaction_log_file_unlock(log->head); mail_transaction_log_set_head(log, file); return 0; }
int mail_transaction_log_sync_lock(struct mail_transaction_log *log, uint32_t *file_seq_r, uoff_t *file_offset_r) { i_assert(!log->index->log_sync_locked); if (mail_transaction_log_lock_head(log) < 0) return -1; /* update sync_offset */ if (mail_transaction_log_file_map(log->head, log->head->sync_offset, (uoff_t)-1) <= 0) { mail_transaction_log_file_unlock(log->head); return -1; } log->index->log_sync_locked = TRUE; *file_seq_r = log->head->hdr.file_seq; *file_offset_r = log->head->sync_offset; return 0; }
int mail_transaction_log_sync_lock(struct mail_transaction_log *log, const char *lock_reason, uint32_t *file_seq_r, uoff_t *file_offset_r) { i_assert(!log->index->log_sync_locked); if (mail_transaction_log_lock_head(log, lock_reason) < 0) return -1; /* update sync_offset */ if (mail_transaction_log_file_map(log->head, log->head->sync_offset, (uoff_t)-1) <= 0) { mail_transaction_log_file_unlock(log->head, t_strdup_printf( "%s - map failed", lock_reason)); return -1; } log->index->log_sync_locked = TRUE; *file_seq_r = log->head->hdr.file_seq; *file_offset_r = log->head->sync_offset; return 0; }
void mail_transaction_logs_clean(struct mail_transaction_log *log) { struct mail_transaction_log_file *file, *next; /* remove only files from the beginning. this way if a view has referenced an old file, it can still find the new files even if there aren't any references to it currently. */ for (file = log->files; file != NULL; file = next) { next = file->next; i_assert(file->refcount >= 0); if (file->refcount > 0) break; mail_transaction_log_file_free(&file); } /* if we still have locked files with refcount=0, unlock them */ for (; file != NULL; file = file->next) { if (file->locked && file->refcount == 0) mail_transaction_log_file_unlock(file); } i_assert(log->head == NULL || log->files != NULL); }
int mail_transaction_log_lock_head(struct mail_transaction_log *log, const char *lock_reason) { struct mail_transaction_log_file *file; time_t lock_wait_started, lock_secs = 0; const char *reason; int ret = 0; if (!log->log_2_unlink_checked) { /* we need to check once in a while if .log.2 should be deleted to avoid wasting space on such old files. but we also don't want to waste time on checking it when the same mailbox gets opened over and over again rapidly (e.g. pop3). so do this only when there have actually been some changes to mailbox (i.e. when it's being locked here) */ log->log_2_unlink_checked = TRUE; mail_transaction_log_2_unlink_old(log); } /* we want to get the head file locked. this is a bit racy, since by the time we have it locked a new log file may have been created. creating new log file requires locking the head file, so if we can lock it and don't see another file, we can be sure no-one is creating a new log at the moment */ lock_wait_started = time(NULL); for (;;) { file = log->head; if (mail_transaction_log_file_lock(file) < 0) return -1; file->refcount++; ret = mail_transaction_log_refresh(log, TRUE, &reason); if (--file->refcount == 0) { mail_transaction_log_file_unlock(file, t_strdup_printf( "trying to lock head for %s", lock_reason)); mail_transaction_logs_clean(log); file = NULL; } if (ret == 0 && log->head == file) { /* success */ i_assert(file != NULL); lock_secs = file->lock_created - lock_wait_started; break; } if (file != NULL) { mail_transaction_log_file_unlock(file, t_strdup_printf( "trying to lock head for %s", lock_reason)); } if (ret < 0) break; /* try again */ } if (lock_secs > MAIL_TRANSACTION_LOG_LOCK_WARN_SECS) { i_warning("Locking transaction log file %s took %ld seconds (%s)", log->head->filepath, (long)lock_secs, lock_reason); } i_assert(ret < 0 || log->head != NULL); return ret; }