int mail_transaction_log_open(struct mail_transaction_log *log) { struct mail_transaction_log_file *file; const char *reason; int ret; i_free(log->filepath); i_free(log->filepath2); log->filepath = i_strconcat(log->index->filepath, MAIL_TRANSACTION_LOG_SUFFIX, NULL); log->filepath2 = i_strconcat(log->filepath, ".2", NULL); /* these settings aren't available at alloc() time, so we need to set them here: */ log->nfs_flush = (log->index->flags & MAIL_INDEX_OPEN_FLAG_NFS_FLUSH) != 0; if (log->open_file != NULL) mail_transaction_log_file_free(&log->open_file); if (MAIL_INDEX_IS_IN_MEMORY(log->index)) return 0; file = mail_transaction_log_file_alloc(log, log->filepath); if ((ret = mail_transaction_log_file_open(file, &reason)) <= 0) { /* leave the file for _create() */ log->open_file = file; return ret; } mail_transaction_log_set_head(log, file); return 1; }
void mail_index_write(struct mail_index *index, bool want_rotate) { struct mail_index_map *map = index->map; const struct mail_index_header *hdr = &map->hdr; i_assert(index->log_sync_locked); if (index->readonly) return; if (!MAIL_INDEX_IS_IN_MEMORY(index)) { if (mail_index_recreate(index) < 0) { (void)mail_index_move_to_memory(index); return; } } index->last_read_log_file_seq = hdr->log_file_seq; index->last_read_log_file_head_offset = hdr->log_file_head_offset; index->last_read_log_file_tail_offset = hdr->log_file_tail_offset; if (want_rotate && hdr->log_file_seq == index->log->head->hdr.file_seq && hdr->log_file_tail_offset == hdr->log_file_head_offset) (void)mail_transaction_log_rotate(index->log, FALSE); }
int mail_transaction_log_create(struct mail_transaction_log *log, bool reset) { struct mail_transaction_log_file *file; if (MAIL_INDEX_IS_IN_MEMORY(log->index)) { file = mail_transaction_log_file_alloc_in_memory(log); mail_transaction_log_set_head(log, file); return 0; } file = mail_transaction_log_file_alloc(log, log->filepath); if (log->open_file != NULL) { /* remember what file we tried to open. if someone else created a new file, use it instead of recreating it */ file->st_ino = log->open_file->st_ino; file->st_dev = log->open_file->st_dev; file->last_size = log->open_file->last_size; file->last_mtime = log->open_file->last_mtime; mail_transaction_log_file_free(&log->open_file); } if (mail_transaction_log_file_create(file, reset) < 0) { mail_transaction_log_file_free(&file); return -1; } mail_transaction_log_set_head(log, file); return 1; }
static int mail_index_recreate(struct mail_index *index) { struct mail_index_map *map = index->map; struct ostream *output; unsigned int base_size; const char *path; int ret = 0, fd; i_assert(!MAIL_INDEX_IS_IN_MEMORY(index)); i_assert(map->hdr.indexid == index->indexid); i_assert((map->hdr.flags & MAIL_INDEX_HDR_FLAG_CORRUPTED) == 0); i_assert(index->indexid != 0); fd = mail_index_create_tmp_file(index, index->filepath, &path); if (fd == -1) return -1; output = o_stream_create_fd_file(fd, 0, FALSE); o_stream_cork(output); base_size = I_MIN(map->hdr.base_header_size, sizeof(map->hdr)); o_stream_nsend(output, &map->hdr, base_size); o_stream_nsend(output, CONST_PTR_OFFSET(map->hdr_base, base_size), map->hdr.header_size - base_size); o_stream_nsend(output, map->rec_map->records, map->rec_map->records_count * map->hdr.record_size); o_stream_nflush(output); if (o_stream_nfinish(output) < 0) { mail_index_file_set_syscall_error(index, path, "write()"); ret = -1; } o_stream_destroy(&output); if (ret == 0 && index->fsync_mode != FSYNC_MODE_NEVER) { if (fdatasync(fd) < 0) { mail_index_file_set_syscall_error(index, path, "fdatasync()"); ret = -1; } } if (close(fd) < 0) { mail_index_file_set_syscall_error(index, path, "close()"); ret = -1; } if ((index->flags & MAIL_INDEX_OPEN_FLAG_KEEP_BACKUPS) != 0) (void)mail_index_create_backup(index); if (ret == 0 && rename(path, index->filepath) < 0) { mail_index_set_error(index, "rename(%s, %s) failed: %m", path, index->filepath); ret = -1; } if (ret < 0) i_unlink(path); return ret; }
int mail_transaction_log_find_file(struct mail_transaction_log *log, uint32_t file_seq, bool nfs_flush, struct mail_transaction_log_file **file_r) { struct mail_transaction_log_file *file; int ret; if (file_seq > log->head->hdr.file_seq) { /* see if the .log file has been recreated */ if (log->head->locked) { /* transaction log is locked. there's no way a newer file exists. */ return 0; } if (log->index->open_count == 0) { /* we're opening the index and we just opened the log file. don't waste time checking if there's a newer one. */ return 0; } if (mail_transaction_log_refresh(log, FALSE) < 0) return -1; if (file_seq > log->head->hdr.file_seq) { if (!nfs_flush || !log->nfs_flush) return 0; /* try again, this time flush attribute cache */ if (mail_transaction_log_refresh(log, TRUE) < 0) return -1; if (file_seq > log->head->hdr.file_seq) return 0; } } for (file = log->files; file != NULL; file = file->next) { if (file->hdr.file_seq == file_seq) { *file_r = file; return 1; } } if (MAIL_INDEX_IS_IN_MEMORY(log->index)) return 0; /* see if we have it in log.2 file */ file = mail_transaction_log_file_alloc(log, log->filepath2); if ((ret = mail_transaction_log_file_open(file, TRUE)) <= 0) { mail_transaction_log_file_free(&file); return ret; } /* but is it what we expected? */ if (file->hdr.file_seq != file_seq) return 0; *file_r = file; return 1; }
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_index_lock_fd(struct mail_index *index, const char *path, int fd, int lock_type, unsigned int timeout_secs, struct file_lock **lock_r) { if (fd == -1) { i_assert(MAIL_INDEX_IS_IN_MEMORY(index)); return 1; } return file_wait_lock(fd, path, lock_type, index->lock_method, timeout_secs, lock_r); }
static void mail_transaction_log_2_unlink_old(struct mail_transaction_log *log) { struct stat st; if (MAIL_INDEX_IS_IN_MEMORY(log->index)) return; if (stat(log->filepath2, &st) < 0) { if (errno != ENOENT && errno != ESTALE) { mail_index_set_error(log->index, "stat(%s) failed: %m", log->filepath2); } return; } if (ioloop_time - st.st_mtime >= (time_t)log->index->log_rotate_log2_stale_secs && !log->index->readonly) i_unlink_if_exists(log->filepath2); }
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_file_create(struct mail_transaction_log_file *file, bool reset) { struct mail_index *index = file->log->index; struct dotlock_settings new_dotlock_set; struct dotlock *dotlock; mode_t old_mask; int fd, ret; i_assert(!MAIL_INDEX_IS_IN_MEMORY(index)); if (file->log->index->readonly) { mail_index_set_error(index, "Can't create log file %s: Index is read-only", file->filepath); return -1; } mail_transaction_log_get_dotlock_set(file->log, &new_dotlock_set); new_dotlock_set.lock_suffix = LOG_NEW_DOTLOCK_SUFFIX; /* With dotlocking we might already have path.lock created, so this filename has to be different. */ old_mask = umask(index->mode ^ 0666); fd = file_dotlock_open(&new_dotlock_set, file->filepath, 0, &dotlock); umask(old_mask); if (fd == -1) { log_file_set_syscall_error(file, "file_dotlock_open()"); return -1; } mail_index_fchown(index, fd, file_dotlock_get_lock_path(dotlock)); /* either fd gets used or the dotlock gets deleted and returned fd is for the existing file */ ret = mail_transaction_log_file_create2(file, fd, reset, &dotlock); if (ret < 0) { if (dotlock != NULL) file_dotlock_delete(&dotlock); return -1; } return ret; }
static void mail_transaction_log_2_unlink_old(struct mail_transaction_log *log) { struct stat st; if (MAIL_INDEX_IS_IN_MEMORY(log->index)) return; if (stat(log->filepath2, &st) < 0) { if (errno != ENOENT && errno != ESTALE) { mail_index_set_error(log->index, "stat(%s) failed: %m", log->filepath2); } return; } if (st.st_mtime + MAIL_TRANSACTION_LOG2_STALE_SECS <= ioloop_time && !log->index->readonly) { if (unlink(log->filepath2) < 0 && errno != ENOENT) { mail_index_set_error(log->index, "unlink(%s) failed: %m", log->filepath2); } } }
void mail_index_write(struct mail_index *index, bool want_rotate) { struct mail_index_map *map = index->map; struct mail_index_header *hdr = &map->hdr; i_assert(index->log_sync_locked); if (index->readonly) return; /* rotate the .log before writing index, so the index will point to the latest log. */ if (want_rotate && hdr->log_file_seq == index->log->head->hdr.file_seq && hdr->log_file_tail_offset == hdr->log_file_head_offset) { if (mail_transaction_log_rotate(index->log, FALSE) == 0) { struct mail_transaction_log_file *file = index->log->head; i_assert(file->hdr.prev_file_seq == hdr->log_file_seq); i_assert(file->hdr.prev_file_offset == hdr->log_file_head_offset); hdr->log_file_seq = file->hdr.file_seq; hdr->log_file_head_offset = hdr->log_file_tail_offset = file->hdr.hdr_size; } } if (!MAIL_INDEX_IS_IN_MEMORY(index)) { if (mail_index_recreate(index) < 0) { (void)mail_index_move_to_memory(index); return; } } index->last_read_log_file_seq = hdr->log_file_seq; index->last_read_log_file_tail_offset = hdr->log_file_tail_offset; }
int mail_transaction_log_find_file(struct mail_transaction_log *log, uint32_t file_seq, bool nfs_flush, struct mail_transaction_log_file **file_r, const char **reason_r) { struct mail_transaction_log_file *file; const char *reason; int ret; if (file_seq > log->head->hdr.file_seq) { /* see if the .log file has been recreated */ if (log->head->locked) { /* transaction log is locked. there's no way a newer file exists. */ *reason_r = "Log is locked - newer log can't exist"; return 0; } if (mail_transaction_log_refresh(log, FALSE, &reason) < 0) { *reason_r = reason; return -1; } if (file_seq > log->head->hdr.file_seq) { if (!nfs_flush || !log->nfs_flush) { *reason_r = t_strdup_printf( "Requested newer log than exists: %s", reason); return 0; } /* try again, this time flush attribute cache */ if (mail_transaction_log_refresh(log, TRUE, &reason) < 0) { *reason_r = t_strdup_printf( "Log refresh with NFS flush failed: %s", reason); return -1; } if (file_seq > log->head->hdr.file_seq) { *reason_r = t_strdup_printf( "Requested newer log than exists - " "still after NFS flush: %s", reason); return 0; } } } for (file = log->files; file != NULL; file = file->next) { if (file->hdr.file_seq == file_seq) { *file_r = file; return 1; } } if (MAIL_INDEX_IS_IN_MEMORY(log->index)) { *reason_r = "Logs are only in memory"; return 0; } /* see if we have it in log.2 file */ file = mail_transaction_log_file_alloc(log, log->filepath2); if ((ret = mail_transaction_log_file_open(file, reason_r)) <= 0) { mail_transaction_log_file_free(&file); return ret; } /* but is it what we expected? */ if (file->hdr.file_seq != file_seq) { *reason_r = t_strdup_printf(".log.2 contains file_seq=%u", file->hdr.file_seq); return 0; } *file_r = file; return 1; }