mail-transaction-log.c revision 4e441ff49b1897d34a72e9287c206d7e9cfaeb34
/* Copyright (C) 2003-2004 Timo Sirainen */
#include "lib.h"
#include "ioloop.h"
#include "buffer.h"
#include "file-dotlock.h"
#include "nfs-workarounds.h"
#include "close-keep-errno.h"
#include "read-full.h"
#include "write-full.h"
#include "mmap-util.h"
#include "mail-index-private.h"
#include "mail-index-view-private.h"
#include "mail-transaction-log-private.h"
#include "mail-transaction-util.h"
#include "mail-index-transaction-private.h"
#include <stddef.h>
#include <stdio.h>
#define LOG_PREFETCH 1024
/* this lock should never exist for a long time.. */
#define LOG_DOTLOCK_TIMEOUT 60
#define LOG_DOTLOCK_STALE_TIMEOUT 60
#define MAIL_TRANSACTION_LOG_SUFFIX ".log"
#define LOG_NEW_DOTLOCK_SUFFIX ".newlock"
static struct mail_transaction_log_file *
const char *path);
static int
void
const char *fmt, ...)
{
if (!MAIL_TRANSACTION_LOG_FILE_IN_MEMORY(file)) {
}
}
t_push();
"Corrupted transaction log file %s: %s",
t_pop();
/* this may have happened because of broken index.
make sure it's ok. */
}
}
static int
{
int ret;
ret = 1;
else {
}
if (ret > 0) {
return 0;
}
if (ret < 0) {
"file_dotlock_create()");
return -1;
}
"Timeout while waiting for release of "
"dotlock for transaction log file %s",
return -1;
}
static int
{
int ret;
return 0;
if (ret < 0) {
return -1;
}
if (ret == 0) {
"Dotlock was lost for transaction log file %s",
return -1;
}
return 0;
}
static int
{
int ret;
return 0;
return 0;
}
return mail_transaction_log_file_dotlock(file);
if (ret > 0) {
return 0;
}
if (ret < 0) {
"mail_index_wait_lock_fd()");
return -1;
}
"Timeout while waiting for lock for transaction log file %s",
return -1;
}
{
return;
return;
return;
}
}
{
struct mail_transaction_log_file *file;
unsigned int lock_id;
int ret;
if (mail_transaction_log_lock_head(log) < 0)
return -1;
if (ret == 0) {
if (ret <= 0)
ret = -1;
/* broken - fix it by creating a new log file */
}
}
else
return ret;
}
struct mail_transaction_log *
{
struct mail_transaction_log *log;
const char *path;
/* fallback to in-memory indexes */
if (mail_index_move_to_memory(index) < 0) {
return NULL;
}
}
/* head log file isn't same as head index file -
shouldn't happen except in race conditions.
lock them and check again */
if (mail_transaction_log_check_file_seq(log) < 0) {
return NULL;
}
}
return log;
}
{
}
static void
{
struct mail_transaction_log_file **p;
if (*p == file) {
break;
}
}
"munmap()");
}
}
"close()");
}
}
}
{
return 0;
/* read the whole file to memory. we might currently be appending
data into it, so we want to read it up to end of file */
file->buffer_offset = 0;
}
"munmap()");
}
}
if (mail_transaction_log_file_read(file, 0) <= 0)
return -1;
/* after we've read the file into memory, make it into in-memory
log file */
}
return 0;
}
static int
int head, bool ignore_estale)
{
struct mail_transaction_log_file *f;
int ret;
if (ret < 0) {
"pread_full()");
}
return -1;
}
if (ret == 0) {
"unexpected end of file while reading header");
return 0;
}
/* incompatible version - fix silently */
return 0;
}
"Header size too small");
return 0;
}
/* @UNSAFE: smaller than we expected - zero out the fields we
shouldn't have filled */
}
/* corrupted */
"Transaction log file %s: marked corrupted",
return 0;
}
/* index file was probably just rebuilt and we don't
know about it yet */
"invalid indexid (%u != %u)",
return 0;
}
/* creating index file. since transaction log is created
first, use the indexid in it to create the main index
to avoid races. */
}
/* 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.
If we're opening head log file, make sure the sequence is larger
than any existing one. */
if (head) {
"invalid new transaction log sequence "
"(%u >= %u)",
return 0;
}
}
} else {
"old transaction log already opened "
"(%u == %u)",
return 0;
}
}
}
return 1;
}
static int
struct mail_transaction_log_header *hdr)
{
unsigned int lock_id;
/* not creating index - make sure we have latest header */
return -1;
return -1;
}
}
/* make sure the sequence grows */
}
return 0;
}
static int
{
struct mail_transaction_log_header hdr;
const char *path2;
bool rename_existing;
/* log creation is locked now - see if someone already created it */
if (lock) {
/* don't even bother checking the existing file, but rename it
if it exists */
"fstat()");
/* same file, still broken */
} else {
/* file changed, use the new file */
(void)file_dotlock_delete(dotlock);
return 0;
}
old_fd = -1;
if (ret < 0) {
/* fstat() failure, return after closing fd.. */
return -1;
}
"open()");
return -1;
} else {
}
return -1;
"write_full()");
return -1;
}
if (lock) {
if (ret < 0)
return -1;
}
/* keep two log files */
if (rename_existing) {
/* rename() would be nice and easy way to do this, except then
there's a race condition between the rename and
file_dotlock_replace(). during that time the log file
doesn't exist, which could cause problems. */
path2);
/* try to link() anyway */
}
/* ignore the error. we don't care that much about the
second log file and we're going to overwrite this
first one. */
}
}
return -1;
/* success */
return 0;
}
static int
{
int fd;
/* the whole index directory was deleted, which means
the mailbox was deleted by another process.
fail silently. */
return -1;
}
return -1;
}
/* With dotlocking we might already have path.lock created, so this
filename has to be different. */
if (fd == -1) {
"file_dotlock_open()");
return -1;
}
"fchown()");
(void)file_dotlock_delete(&dotlock);
return -1;
}
/* either fd gets used or the dotlock gets deleted and returned fd
is for the existing file */
(void)file_dotlock_delete(&dotlock);
return -1;
}
return 0;
}
static void
{
struct mail_transaction_log_file **p;
/* we can get a valid log offset from index file. initialize
sync_offset from it so we don't have to read the whole log
file from beginning. */
} else {
}
/* append to end of list. */
*p = file;
}
static void
{
struct mail_transaction_log_file **p;
/* insert it to correct position */
break;
}
*p = file;
}
static struct mail_transaction_log_file *
const char *path)
{
struct mail_transaction_log_file *file;
return file;
}
static int
bool head, bool ignore_estale)
{
"fstat()");
}
return -1;
}
}
static int
{
int ret;
if (ret == 0) {
/* corrupted header, recreate the file */
ret = -1;
else {
FALSE);
if (ret == 0) {
/* newly created transaction log corrupted */
return -1;
}
}
}
if (ret < 0)
return 1;
}
static struct mail_transaction_log_file *
{
struct mail_transaction_log_file *file;
return NULL;
}
return file;
}
static struct mail_transaction_log_file *
const char *path)
{
struct mail_transaction_log_file *file;
unsigned int i;
int ret;
for (i = 0; ; i++) {
path,
"open()");
break;
}
/* doesn't exist, try creating it */
0, 0, 0) < 0)
break;
}
if (ret > 0)
return file;
if (ret < 0)
break;
/* ESTALE - retry */
}
return NULL;
}
static struct mail_transaction_log_file *
const char *path)
{
struct mail_transaction_log_file *file;
unsigned int i;
int ret;
for (i = 0;; i++) {
"open()");
break;
}
if (ret > 0) {
/* success */
return file;
}
if (ret == 0) {
/* corrupted */
break;
}
i == MAIL_INDEX_ESTALE_RETRY_COUNT) {
/* syscall error */
break;
}
/* ESTALE - try again */
}
return NULL;
}
{
}
}
{
struct mail_transaction_log_file *file;
int ret;
else {
/* we're locked, we shouldn't need to worry about ESTALE
problems in here. */
"fstat()");
return -1;
}
return -1;
}
if (ret <= 0) {
return -1;
}
}
else
return 0;
}
bool create_if_needed)
{
struct mail_transaction_log_file *file;
const char *path;
return 0;
"stat()");
return -1;
}
} else {
/* same file */
return 0;
}
}
file = create_if_needed ?
return -1;
return 0;
}
struct mail_transaction_log_file **file_r)
{
struct mail_transaction_log_file *file;
const char *path;
/* don't try to recreate log file if it gets lost. we're
already in trouble and with mmap_disable the creation
could cause a recursive mail_index_map() call */
return -1;
}
return 1;
}
}
return 0;
/* see if we have it in log.2 file */
if (fd == -1) {
return 0;
return -1;
}
/* treat as "doesn't exist" */
return 0;
}
return -1;
}
/* see if we have it already opened */
i_error("close() failed: %m");
return 0;
}
}
if (ret <= 0) {
if (ret == 0) {
/* corrupted, delete it */
i_error("unlink(%s) failed: %m",
}
return 0;
}
if (stale) {
/* treat as "doesn't exist" */
return 0;
}
return -1;
}
/* got it */
/* but is it what we expected? */
return 0;
return 1;
}
static int
{
const struct mail_transaction_header *hdr;
const void *data;
if (hdr_size == 0) {
/* unfinished */
return 0;
}
return -1;
}
break;
}
/* record goes outside the file we've seen. or if
we're accessing the log file via unlocked mmaped
memory, it may be just that the memory was updated
after we checked the file size. */
return -1;
}
}
return 0;
}
static int
{
void *data;
int ret;
/* we have to insert missing data to beginning of buffer */
if (ret == 0) {
"Unexpected end of file");
return 0;
}
if (ret < 0) {
/* log file was deleted in NFS server,
fail silently */
return 0;
}
"pread()");
return -1;
}
}
}
/* read all records */
do {
if (ret > 0)
read_offset += ret;
if (mail_transaction_log_file_sync(file) < 0)
return -1;
if (ret == 0) {
/* EOF */
return 1;
}
/* log file was deleted in NFS server, fail silently */
return 0;
}
"pread()");
return -1;
}
{
/* corrupted */
return 0;
}
return -1;
}
return 1;
/* with mmap_no_write we could alternatively just write to log with
msync() rather than pwrite(). but since there aren't many such OSes
left, it's easier to just use mmap_disable behavior with it */
/* see if we already have it */
return 1;
}
if (use_mmap) {
"fstat()");
return -1;
}
return -1;
}
/* it's all mmaped already */
if (mail_transaction_log_file_sync(file) < 0)
return -1;
return 1;
}
}
}
"munmap()");
}
}
if (!use_mmap) {
if (ret <= 0) {
/* make sure we don't leave ourself in
inconsistent state */
}
return ret;
}
} else {
"mmap()");
return -1;
}
MADV_SEQUENTIAL) < 0) {
}
}
file->buffer_offset = 0;
if (mail_transaction_log_file_sync(file) < 0)
return -1;
}
return -1;
}
return -1;
}
return 1;
}
{
struct mail_transaction_log_file *file;
int ret = 0;
/* 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 (;;) {
if (mail_transaction_log_file_lock(file) < 0)
return -1;
}
/* success */
break;
}
if (ret < 0)
break;
/* try again */
}
return ret;
}
{
if (mail_transaction_log_lock_head(log) < 0)
return -1;
/* update sync_offset */
(uoff_t)-1) < 0) {
return -1;
}
return 0;
}
{
}
{
}
{
}