mail-duplicate.c (dovecot-2.3.16) | : | mail-duplicate.c (dovecot-2.3.17) | ||
---|---|---|---|---|
/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ | /* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ | |||
#include "lib.h" | #include "lib.h" | |||
#include "ioloop.h" | #include "ioloop.h" | |||
#include "hex-binary.h" | ||||
#include "mkdir-parents.h" | ||||
#include "istream.h" | #include "istream.h" | |||
#include "ostream.h" | #include "ostream.h" | |||
#include "time-util.h" | ||||
#include "home-expand.h" | #include "home-expand.h" | |||
#include "file-create-locked.h" | ||||
#include "file-dotlock.h" | #include "file-dotlock.h" | |||
#include "md5.h" | ||||
#include "hash.h" | #include "hash.h" | |||
#include "mail-user.h" | #include "mail-user.h" | |||
#include "mail-storage-settings.h" | #include "mail-storage-settings.h" | |||
#include "mail-duplicate.h" | #include "mail-duplicate.h" | |||
#include <fcntl.h> | #include <fcntl.h> | |||
#include <unistd.h> | #include <unistd.h> | |||
#define COMPRESS_PERCENTAGE 10 | #define COMPRESS_PERCENTAGE 10 | |||
#define DUPLICATE_BUFSIZE 4096 | #define DUPLICATE_BUFSIZE 4096 | |||
#define DUPLICATE_VERSION 2 | #define DUPLICATE_VERSION 2 | |||
#define DUPLICATE_LOCK_FNAME_PREFIX "duplicate.lock." | ||||
#define DUPLICATE_LOCK_TIMEOUT_SECS 65 | ||||
#define DUPLICATE_LOCK_WARN_SECS 4 | ||||
#define DUPLICATE_LOCK_MAX_LOCKS 100 | ||||
enum mail_duplicate_lock_result { | ||||
MAIL_DUPLICATE_LOCK_OK, | ||||
MAIL_DUPLICATE_LOCK_IO_ERROR, | ||||
MAIL_DUPLICATE_LOCK_TIMEOUT, | ||||
MAIL_DUPLICATE_LOCK_TOO_MANY, | ||||
MAIL_DUPLICATE_LOCK_DEADLOCK, | ||||
}; | ||||
struct mail_duplicate_lock { | ||||
int fd; | ||||
char *path; | ||||
struct file_lock *lock; | ||||
struct timeval start_time; | ||||
}; | ||||
struct mail_duplicate { | struct mail_duplicate { | |||
const void *id; | const void *id; | |||
unsigned int id_size; | unsigned int id_size; | |||
const char *user; | const char *user; | |||
time_t time; | time_t time; | |||
struct mail_duplicate_lock lock; | ||||
bool marked:1; | ||||
bool changed:1; | ||||
}; | }; | |||
struct mail_duplicate_file_header { | struct mail_duplicate_file_header { | |||
uint32_t version; | uint32_t version; | |||
}; | }; | |||
struct mail_duplicate_record_header { | struct mail_duplicate_record_header { | |||
uint32_t stamp; | uint32_t stamp; | |||
uint32_t id_size; | uint32_t id_size; | |||
uint32_t user_size; | uint32_t user_size; | |||
}; | }; | |||
struct mail_duplicate_file { | struct mail_duplicate_transaction { | |||
pool_t pool; | pool_t pool; | |||
struct mail_duplicate_db *db; | struct mail_duplicate_db *db; | |||
ino_t db_ino; | ||||
struct event *event; | ||||
HASH_TABLE(struct mail_duplicate *, struct mail_duplicate *) hash; | HASH_TABLE(struct mail_duplicate *, struct mail_duplicate *) hash; | |||
const char *path; | const char *path; | |||
unsigned int id_lock_count; | ||||
int new_fd; | ||||
struct dotlock *dotlock; | ||||
bool changed:1; | bool changed:1; | |||
}; | }; | |||
struct mail_duplicate_db { | struct mail_duplicate_db { | |||
struct mail_user *user; | struct mail_user *user; | |||
struct event *event; | ||||
char *path; | char *path; | |||
char *lock_dir; | ||||
struct dotlock_settings dotlock_set; | struct dotlock_settings dotlock_set; | |||
struct mail_duplicate_file *file; | ||||
unsigned int transaction_count; | ||||
}; | }; | |||
static const struct dotlock_settings default_mail_duplicate_dotlock_set = { | static const struct dotlock_settings default_mail_duplicate_dotlock_set = { | |||
.timeout = 20, | .timeout = 20, | |||
.stale_timeout = 10, | .stale_timeout = 10, | |||
}; | }; | |||
static int | static int | |||
mail_duplicate_cmp(const struct mail_duplicate *d1, | mail_duplicate_cmp(const struct mail_duplicate *d1, | |||
const struct mail_duplicate *d2) | const struct mail_duplicate *d2) | |||
skipping to change at line 90 | skipping to change at line 124 | |||
if ((g = h & 0xf0000000UL) != 0) { | if ((g = h & 0xf0000000UL) != 0) { | |||
h = h ^ (g >> 24); | h = h ^ (g >> 24); | |||
h = h ^ g; | h = h ^ g; | |||
} | } | |||
s++; | s++; | |||
} | } | |||
return h ^ strcase_hash(d->user); | return h ^ strcase_hash(d->user); | |||
} | } | |||
static enum mail_duplicate_lock_result | ||||
duplicate_lock_failed(struct mail_duplicate_transaction *trans, | ||||
struct mail_duplicate *dup, const char *error) | ||||
{ | ||||
struct mail_duplicate_lock *lock = &dup->lock; | ||||
enum mail_duplicate_lock_result result; | ||||
int diff; | ||||
i_assert(lock->fd == -1); | ||||
i_assert(lock->lock == NULL); | ||||
if (errno == EDEADLK) { | ||||
/* deadlock */ | ||||
result = MAIL_DUPLICATE_LOCK_DEADLOCK; | ||||
} else if (errno != EAGAIN) { | ||||
/* not a lock timeout */ | ||||
result = MAIL_DUPLICATE_LOCK_IO_ERROR; | ||||
} else { | ||||
diff = timeval_diff_msecs(&ioloop_timeval, | ||||
&lock->start_time); | ||||
error = t_strdup_printf("Lock timeout in %d.%03d secs", | ||||
diff/1000, diff%1000); | ||||
result = MAIL_DUPLICATE_LOCK_TIMEOUT; | ||||
} | ||||
e_error(trans->event, "Failed to lock %s: %s", lock->path, error); | ||||
i_free_and_null(lock->path); | ||||
i_zero(lock); | ||||
return result; | ||||
} | ||||
static bool mail_duplicate_is_locked(struct mail_duplicate *dup) | ||||
{ | ||||
struct mail_duplicate_lock *lock = &dup->lock; | ||||
return (lock->lock != NULL); | ||||
} | ||||
static enum mail_duplicate_lock_result | ||||
mail_duplicate_lock(struct mail_duplicate_transaction *trans, | ||||
struct mail_duplicate *dup) | ||||
{ | ||||
struct file_create_settings lock_set = { | ||||
.lock_timeout_secs = DUPLICATE_LOCK_TIMEOUT_SECS, | ||||
.lock_settings = { | ||||
.lock_method = FILE_LOCK_METHOD_FCNTL, | ||||
.allow_deadlock = TRUE, | ||||
}, | ||||
}; | ||||
struct mail_duplicate_db *db = trans->db; | ||||
struct mail_duplicate_lock *lock = &dup->lock; | ||||
const char *error; | ||||
unsigned char id_md5[MD5_RESULTLEN]; | ||||
bool created; | ||||
int diff; | ||||
if (mail_duplicate_is_locked(dup)) { | ||||
e_debug(trans->event, "Duplicate ID already locked"); | ||||
return MAIL_DUPLICATE_LOCK_OK; | ||||
} | ||||
if (trans->id_lock_count >= DUPLICATE_LOCK_MAX_LOCKS) { | ||||
e_debug(trans->event, "Too many duplicate IDs locked"); | ||||
return MAIL_DUPLICATE_LOCK_TOO_MANY; | ||||
} | ||||
i_assert(db->lock_dir != NULL); | ||||
lock->start_time = ioloop_timeval; | ||||
md5_get_digest(dup->id, dup->id_size, id_md5); | ||||
lock->path = i_strdup_printf("%s/"DUPLICATE_LOCK_FNAME_PREFIX"%s", | ||||
db->lock_dir, | ||||
binary_to_hex(id_md5, sizeof(id_md5))); | ||||
e_debug(trans->event, "Lock duplicate ID (path=%s)", lock->path); | ||||
lock->fd = file_create_locked(lock->path, &lock_set, &lock->lock, | ||||
&created, &error); | ||||
if (lock->fd == -1 && errno == ENOENT) { | ||||
/* parent directory missing - create it */ | ||||
if (mkdir_parents(db->lock_dir, 0700) < 0 && errno != EEXIST) { | ||||
error = t_strdup_printf( | ||||
"mkdir_parents(%s) failed: %m", db->lock_dir); | ||||
} else { | ||||
lock->fd = file_create_locked(lock->path, | ||||
&lock_set, &lock->lock, | ||||
&created, &error); | ||||
} | ||||
} | ||||
if (lock->fd == -1) | ||||
return duplicate_lock_failed(trans, dup, error); | ||||
diff = timeval_diff_msecs(&ioloop_timeval, &lock->start_time); | ||||
if (diff >= (DUPLICATE_LOCK_WARN_SECS * 1000)) { | ||||
e_warning(trans->event, "Locking %s took %d.%03d secs", | ||||
lock->path, diff/1000, diff%1000); | ||||
} | ||||
i_assert(mail_duplicate_is_locked(dup)); | ||||
trans->id_lock_count++; | ||||
return MAIL_DUPLICATE_LOCK_OK; | ||||
} | ||||
static void | ||||
mail_duplicate_unlock(struct mail_duplicate_transaction *trans, | ||||
struct mail_duplicate *dup) | ||||
{ | ||||
int orig_errno = errno; | ||||
if (dup->lock.path != NULL) { | ||||
struct mail_duplicate_lock *lock = &dup->lock; | ||||
e_debug(trans->event, "Unlock duplicate ID (path=%s)", | ||||
lock->path); | ||||
i_unlink(lock->path); | ||||
file_lock_free(&lock->lock); | ||||
i_close_fd(&lock->fd); | ||||
i_free_and_null(lock->path); | ||||
i_zero(lock); | ||||
i_assert(trans->id_lock_count > 0); | ||||
trans->id_lock_count--; | ||||
} | ||||
errno = orig_errno; | ||||
} | ||||
static int | static int | |||
mail_duplicate_read_records(struct mail_duplicate_file *file, | mail_duplicate_read_records(struct mail_duplicate_transaction *trans, | |||
struct istream *input, | struct istream *input, | |||
unsigned int record_size) | unsigned int record_size) | |||
{ | { | |||
const unsigned char *data; | const unsigned char *data; | |||
struct mail_duplicate_record_header hdr; | struct mail_duplicate_record_header hdr; | |||
size_t size; | size_t size; | |||
unsigned int change_count; | unsigned int change_count; | |||
change_count = 0; | change_count = 0; | |||
while (i_stream_read_bytes(input, &data, &size, record_size) > 0) { | while (i_stream_read_bytes(input, &data, &size, record_size) > 0) { | |||
skipping to change at line 123 | skipping to change at line 283 | |||
sizeof(hdr.id_size)); | sizeof(hdr.id_size)); | |||
memcpy(&hdr.user_size, | memcpy(&hdr.user_size, | |||
data + sizeof(time_t) + sizeof(uint32_t), | data + sizeof(time_t) + sizeof(uint32_t), | |||
sizeof(hdr.user_size)); | sizeof(hdr.user_size)); | |||
} | } | |||
i_stream_skip(input, record_size); | i_stream_skip(input, record_size); | |||
if (hdr.id_size == 0 || hdr.user_size == 0 || | if (hdr.id_size == 0 || hdr.user_size == 0 || | |||
hdr.id_size > DUPLICATE_BUFSIZE || | hdr.id_size > DUPLICATE_BUFSIZE || | |||
hdr.user_size > DUPLICATE_BUFSIZE) { | hdr.user_size > DUPLICATE_BUFSIZE) { | |||
e_error(file->db->user->event, | e_error(trans->event, | |||
"broken mail_duplicate file %s", file->path); | "broken mail_duplicate file %s", trans->path); | |||
return -1; | return -1; | |||
} | } | |||
if (i_stream_read_bytes(input, &data, &size, | if (i_stream_read_bytes(input, &data, &size, | |||
hdr.id_size + hdr.user_size) <= 0) { | hdr.id_size + hdr.user_size) <= 0) { | |||
e_error(file->db->user->event, | e_error(trans->event, | |||
"unexpected end of file in %s", file->path); | "unexpected end of file in %s", trans->path); | |||
return -1; | return -1; | |||
} | } | |||
if ((time_t)hdr.stamp >= ioloop_time) { | struct mail_duplicate dup_q, *dup; | |||
/* still valid, save it */ | ||||
struct mail_duplicate *d; | dup_q.id = data; | |||
void *new_id; | dup_q.id_size = hdr.id_size; | |||
dup_q.user = t_strndup(data + hdr.id_size, hdr.user_size); | ||||
new_id = p_malloc(file->pool, hdr.id_size); | ||||
memcpy(new_id, data, hdr.id_size); | dup = hash_table_lookup(trans->hash, &dup_q); | |||
if ((time_t)hdr.stamp < ioloop_time) { | ||||
d = p_new(file->pool, struct mail_duplicate, 1); | ||||
d->id = new_id; | ||||
d->id_size = hdr.id_size; | ||||
d->user = p_strndup(file->pool, | ||||
data + hdr.id_size, hdr.user_size); | ||||
d->time = hdr.stamp; | ||||
hash_table_update(file->hash, d, d); | ||||
} else { | ||||
change_count++; | change_count++; | |||
if (dup != NULL && !dup->changed) | ||||
dup->marked = FALSE; | ||||
} else { | ||||
if (dup == NULL) { | ||||
void *new_id; | ||||
new_id = p_malloc(trans->pool, hdr.id_size); | ||||
memcpy(new_id, data, hdr.id_size); | ||||
dup = p_new(trans->pool, | ||||
struct mail_duplicate, 1); | ||||
dup->id = new_id; | ||||
dup->id_size = hdr.id_size; | ||||
dup->user = p_strdup(trans->pool, dup_q.user); | ||||
hash_table_update(trans->hash, dup, dup); | ||||
} | ||||
if (!dup->changed) { | ||||
dup->marked = TRUE; | ||||
dup->time = hdr.stamp; | ||||
} | ||||
} | } | |||
i_stream_skip(input, hdr.id_size + hdr.user_size); | i_stream_skip(input, hdr.id_size + hdr.user_size); | |||
} | } | |||
if (hash_table_count(file->hash) * | if (hash_table_count(trans->hash) * | |||
COMPRESS_PERCENTAGE / 100 > change_count) | COMPRESS_PERCENTAGE / 100 > change_count) | |||
file->changed = TRUE; | trans->changed = TRUE; | |||
return 0; | return 0; | |||
} | } | |||
static int mail_duplicate_read(struct mail_duplicate_file *file) | static int | |||
mail_duplicate_read_db_from_fd(struct mail_duplicate_transaction *trans, int fd) | ||||
{ | { | |||
struct istream *input; | struct istream *input; | |||
struct mail_duplicate_file_header hdr; | struct mail_duplicate_file_header hdr; | |||
const unsigned char *data; | const unsigned char *data; | |||
size_t size; | size_t size; | |||
int fd; | struct stat st; | |||
unsigned int record_size = 0; | unsigned int record_size = 0; | |||
fd = open(file->path, O_RDONLY); | if (fstat(fd, &st) < 0) { | |||
if (fd == -1) { | e_error(trans->event, | |||
if (errno == ENOENT) | "stat(%s) failed: %m", trans->path); | |||
return 0; | ||||
e_error(file->db->user->event, | ||||
"open(%s) failed: %m", file->path); | ||||
return -1; | return -1; | |||
} | } | |||
trans->db_ino = st.st_ino; | ||||
/* <timestamp> <id_size> <user_size> <id> <user> */ | /* <timestamp> <id_size> <user_size> <id> <user> */ | |||
input = i_stream_create_fd(fd, DUPLICATE_BUFSIZE); | input = i_stream_create_fd(fd, DUPLICATE_BUFSIZE); | |||
if (i_stream_read_bytes(input, &data, &size, sizeof(hdr)) > 0) { | if (i_stream_read_bytes(input, &data, &size, sizeof(hdr)) > 0) { | |||
memcpy(&hdr, data, sizeof(hdr)); | memcpy(&hdr, data, sizeof(hdr)); | |||
if (hdr.version == 0 || hdr.version > DUPLICATE_VERSION + 10) { | if (hdr.version == 0 || hdr.version > DUPLICATE_VERSION + 10) { | |||
/* FIXME: backwards compatibility with v1.0 */ | /* FIXME: backwards compatibility with v1.0 */ | |||
record_size = sizeof(time_t) + sizeof(uint32_t)*2; | record_size = sizeof(time_t) + sizeof(uint32_t)*2; | |||
} else if (hdr.version == DUPLICATE_VERSION) { | } else if (hdr.version == DUPLICATE_VERSION) { | |||
record_size = sizeof(struct mail_duplicate_record_header) ; | record_size = sizeof(struct mail_duplicate_record_header) ; | |||
i_stream_skip(input, sizeof(hdr)); | i_stream_skip(input, sizeof(hdr)); | |||
} | } | |||
} | } | |||
if (record_size == 0 || | if (record_size == 0) | |||
mail_duplicate_read_records(file, input, record_size) < 0) | i_unlink_if_exists(trans->path); | |||
i_unlink_if_exists(file->path); | else T_BEGIN { | |||
if (mail_duplicate_read_records(trans, input, record_size) < 0) | ||||
i_unlink_if_exists(trans->path); | ||||
} T_END; | ||||
i_stream_unref(&input); | i_stream_unref(&input); | |||
if (close(fd) < 0) | ||||
e_error(file->db->user->event, | ||||
"close(%s) failed: %m", file->path); | ||||
return 0; | return 0; | |||
} | } | |||
static struct mail_duplicate_file * | static int mail_duplicate_read_db_file(struct mail_duplicate_transaction *trans) | |||
mail_duplicate_file_new(struct mail_duplicate_db *db) | ||||
{ | { | |||
struct mail_duplicate_file *file; | int fd, ret; | |||
pool_t pool; | ||||
i_assert(db->path != NULL); | e_debug(trans->event, "Reading %s", trans->path); | |||
pool = pool_alloconly_create("mail_duplicates", 10240); | fd = open(trans->path, O_RDONLY); | |||
if (fd == -1) { | ||||
if (errno == ENOENT) | ||||
return 0; | ||||
e_error(trans->event, | ||||
"open(%s) failed: %m", trans->path); | ||||
return -1; | ||||
} | ||||
ret = mail_duplicate_read_db_from_fd(trans, fd); | ||||
file = p_new(pool, struct mail_duplicate_file, 1); | if (close(fd) < 0) { | |||
file->pool = pool; | e_error(trans->event, | |||
file->db = db; | "close(%s) failed: %m", trans->path); | |||
file->path = p_strdup(pool, db->path); | } | |||
file->new_fd = file_dotlock_open(&db->dotlock_set, file->path, 0, | return ret; | |||
&file->dotlock); | } | |||
if (file->new_fd != -1) | ||||
static void mail_duplicate_read(struct mail_duplicate_transaction *trans) | ||||
{ | ||||
struct mail_duplicate_db *db = trans->db; | ||||
int new_fd; | ||||
struct dotlock *dotlock; | ||||
new_fd = file_dotlock_open(&db->dotlock_set, trans->path, 0, &dotlock); | ||||
if (new_fd != -1) | ||||
; | ; | |||
else if (errno != EAGAIN) { | else if (errno != EAGAIN) { | |||
e_error(db->user->event, | e_error(trans->event, | |||
"file_dotlock_open(%s) failed: %m", file->path); | "file_dotlock_open(%s) failed: %m", trans->path); | |||
} else { | } else { | |||
e_error(db->user->event, | e_error(trans->event, | |||
"Creating lock file for %s timed out in %u secs", | "Creating lock file for %s timed out in %u secs", | |||
file->path, db->dotlock_set.timeout); | trans->path, db->dotlock_set.timeout); | |||
} | } | |||
hash_table_create(&file->hash, pool, 0, mail_duplicate_hash, mail_duplica te_cmp); | ||||
(void)mail_duplicate_read(file); | (void)mail_duplicate_read_db_file(trans); | |||
return file; | ||||
if (dotlock != NULL) | ||||
file_dotlock_delete(&dotlock); | ||||
} | } | |||
static void mail_duplicate_file_free(struct mail_duplicate_file **_file) | static void mail_duplicate_update(struct mail_duplicate_transaction *trans) | |||
{ | { | |||
struct mail_duplicate_file *file = *_file; | struct stat st; | |||
*_file = NULL; | if (stat(trans->path, &st) < 0) { | |||
if (file->dotlock != NULL) | if (errno == ENOENT) { | |||
file_dotlock_delete(&file->dotlock); | e_debug(trans->event, "DB file not created yet"); | |||
} else { | ||||
e_error(trans->event, | ||||
"stat(%s) failed: %m", trans->path); | ||||
} | ||||
} else if (trans->db_ino == st.st_ino) { | ||||
e_debug(trans->event, "DB file not changed"); | ||||
} else { | ||||
e_debug(trans->event, "DB file changed: " | ||||
"Updating duplicate records from DB file"); | ||||
hash_table_destroy(&file->hash); | mail_duplicate_read(trans); | |||
pool_unref(&file->pool); | } | |||
} | } | |||
bool mail_duplicate_check(struct mail_duplicate_db *db, | struct mail_duplicate_transaction * | |||
const void *id, size_t id_size, const char *user) | mail_duplicate_transaction_begin(struct mail_duplicate_db *db) | |||
{ | { | |||
struct mail_duplicate d; | struct mail_duplicate_transaction *trans; | |||
pool_t pool; | ||||
if (db->file == NULL) { | db->transaction_count++; | |||
if (db->path == NULL) { | ||||
/* mail_duplicate database disabled */ | pool = pool_alloconly_create("mail_duplicates", 10240); | |||
return FALSE; | ||||
} | trans = p_new(pool, struct mail_duplicate_transaction, 1); | |||
db->file = mail_duplicate_file_new(db); | trans->pool = pool; | |||
trans->db = db; | ||||
trans->event = event_create(db->event); | ||||
event_set_append_log_prefix(trans->event, "transaction: "); | ||||
if (db->path == NULL) { | ||||
/* Duplicate database disabled; return dummy transaction */ | ||||
e_debug(trans->event, "Transaction begin (dummy)"); | ||||
return trans; | ||||
} | ||||
e_debug(trans->event, "Transaction begin; lock %s", db->path); | ||||
trans->path = p_strdup(pool, db->path); | ||||
hash_table_create(&trans->hash, pool, 0, | ||||
mail_duplicate_hash, mail_duplicate_cmp); | ||||
mail_duplicate_read(trans); | ||||
return trans; | ||||
} | ||||
static void | ||||
mail_duplicate_transaction_free(struct mail_duplicate_transaction **_trans) | ||||
{ | ||||
struct mail_duplicate_transaction *trans = *_trans; | ||||
struct hash_iterate_context *iter; | ||||
struct mail_duplicate *d; | ||||
if (trans == NULL) | ||||
return; | ||||
*_trans = NULL; | ||||
e_debug(trans->event, "Transaction free"); | ||||
i_assert(trans->db->transaction_count > 0); | ||||
trans->db->transaction_count--; | ||||
iter = hash_table_iterate_init(trans->hash); | ||||
while (hash_table_iterate(iter, trans->hash, &d, &d)) | ||||
mail_duplicate_unlock(trans, d); | ||||
hash_table_iterate_deinit(&iter); | ||||
i_assert(trans->id_lock_count == 0); | ||||
hash_table_destroy(&trans->hash); | ||||
event_unref(&trans->event); | ||||
pool_unref(&trans->pool); | ||||
} | ||||
static struct mail_duplicate * | ||||
mail_duplicate_get(struct mail_duplicate_transaction *trans, | ||||
const void *id, size_t id_size, const char *user) | ||||
{ | ||||
struct mail_duplicate dup_q, *dup; | ||||
dup_q.id = id; | ||||
dup_q.id_size = id_size; | ||||
dup_q.user = user; | ||||
dup = hash_table_lookup(trans->hash, &dup_q); | ||||
if (dup == NULL) { | ||||
dup = p_new(trans->pool, struct mail_duplicate, 1); | ||||
dup->id = p_memdup(trans->pool, id, id_size); | ||||
dup->id_size = id_size; | ||||
dup->user = p_strdup(trans->pool, user); | ||||
dup->time = (time_t)-1; | ||||
hash_table_insert(trans->hash, dup, dup); | ||||
} | } | |||
d.id = id; | return dup; | |||
d.id_size = id_size; | } | |||
d.user = user; | ||||
enum mail_duplicate_check_result | ||||
mail_duplicate_check(struct mail_duplicate_transaction *trans, | ||||
const void *id, size_t id_size, const char *user) | ||||
{ | ||||
struct mail_duplicate *dup; | ||||
if (trans->path == NULL) { | ||||
/* Duplicate database disabled */ | ||||
e_debug(trans->event, "Check ID (dummy)"); | ||||
return MAIL_DUPLICATE_CHECK_RESULT_NOT_FOUND; | ||||
} | ||||
dup = mail_duplicate_get(trans, id, id_size, user); | ||||
switch (mail_duplicate_lock(trans, dup)) { | ||||
case MAIL_DUPLICATE_LOCK_OK: | ||||
break; | ||||
case MAIL_DUPLICATE_LOCK_IO_ERROR: | ||||
e_debug(trans->event, | ||||
"Check ID: I/O error occurred while locking"); | ||||
return MAIL_DUPLICATE_CHECK_RESULT_IO_ERROR; | ||||
case MAIL_DUPLICATE_LOCK_TIMEOUT: | ||||
e_debug(trans->event, | ||||
"Check ID: lock timed out"); | ||||
return MAIL_DUPLICATE_CHECK_RESULT_LOCK_TIMEOUT; | ||||
case MAIL_DUPLICATE_LOCK_TOO_MANY: | ||||
e_debug(trans->event, | ||||
"Check ID: too many IDs locked"); | ||||
return MAIL_DUPLICATE_CHECK_RESULT_TOO_MANY_LOCKS; | ||||
case MAIL_DUPLICATE_LOCK_DEADLOCK: | ||||
e_debug(trans->event, | ||||
"Check ID: deadlock detected while locking"); | ||||
return MAIL_DUPLICATE_CHECK_RESULT_DEADLOCK; | ||||
} | ||||
mail_duplicate_update(trans); | ||||
if (dup->marked) { | ||||
e_debug(trans->event, "Check ID: found"); | ||||
return MAIL_DUPLICATE_CHECK_RESULT_EXISTS; | ||||
} | ||||
return hash_table_lookup(db->file->hash, &d) != NULL; | e_debug(trans->event, "Check ID: not found"); | |||
return MAIL_DUPLICATE_CHECK_RESULT_NOT_FOUND; | ||||
} | } | |||
void mail_duplicate_mark(struct mail_duplicate_db *db, | void mail_duplicate_mark(struct mail_duplicate_transaction *trans, | |||
const void *id, size_t id_size, | const void *id, size_t id_size, | |||
const char *user, time_t timestamp) | const char *user, time_t timestamp) | |||
{ | { | |||
struct mail_duplicate *d; | struct mail_duplicate *dup; | |||
void *new_id; | ||||
if (db->file == NULL) { | if (trans->path == NULL) { | |||
if (db->path == NULL) { | /* Duplicate database disabled */ | |||
/* mail_duplicate database disabled */ | e_debug(trans->event, "Mark ID (dummy)"); | |||
return; | return; | |||
} | ||||
db->file = mail_duplicate_file_new(db); | ||||
} | } | |||
new_id = p_malloc(db->file->pool, id_size); | e_debug(trans->event, "Mark ID"); | |||
memcpy(new_id, id, id_size); | ||||
dup = mail_duplicate_get(trans, id, id_size, user); | ||||
d = p_new(db->file->pool, struct mail_duplicate, 1); | /* Must already be checked and locked */ | |||
d->id = new_id; | i_assert(mail_duplicate_is_locked(dup)); | |||
d->id_size = id_size; | ||||
d->user = p_strdup(db->file->pool, user); | ||||
d->time = timestamp; | ||||
db->file->changed = TRUE; | dup->time = timestamp; | |||
hash_table_update(db->file->hash, d, d); | dup->marked = TRUE; | |||
dup->changed = TRUE; | ||||
trans->changed = TRUE; | ||||
} | } | |||
void mail_duplicate_db_flush(struct mail_duplicate_db *db) | void mail_duplicate_transaction_commit( | |||
struct mail_duplicate_transaction **_trans) | ||||
{ | { | |||
struct mail_duplicate_file *file = db->file; | struct mail_duplicate_transaction *trans = *_trans; | |||
struct mail_duplicate_file_header hdr; | struct mail_duplicate_file_header hdr; | |||
struct mail_duplicate_record_header rec; | struct mail_duplicate_record_header rec; | |||
struct ostream *output; | struct ostream *output; | |||
struct hash_iterate_context *iter; | struct hash_iterate_context *iter; | |||
struct mail_duplicate *d; | struct mail_duplicate *d; | |||
int new_fd; | ||||
struct dotlock *dotlock; | ||||
if (file == NULL) | if (trans == NULL) | |||
return; | return; | |||
if (!file->changed || file->new_fd == -1) { | *_trans = NULL; | |||
/* unlock the mail_duplicate database */ | ||||
mail_duplicate_file_free(&db->file); | if (trans->path == NULL) { | |||
e_debug(trans->event, "Commit (dummy)"); | ||||
mail_duplicate_transaction_free(&trans); | ||||
return; | ||||
} | ||||
if (!trans->changed) { | ||||
e_debug(trans->event, "Commit; no changes"); | ||||
mail_duplicate_transaction_free(&trans); | ||||
return; | ||||
} | ||||
struct mail_duplicate_db *db = trans->db; | ||||
i_assert(trans->path != NULL); | ||||
e_debug(trans->event, "Commit; overwrite %s", trans->path); | ||||
new_fd = file_dotlock_open(&db->dotlock_set, trans->path, 0, &dotlock); | ||||
if (new_fd != -1) | ||||
; | ||||
else if (errno != EAGAIN) { | ||||
e_error(trans->event, | ||||
"file_dotlock_open(%s) failed: %m", | ||||
trans->path); | ||||
mail_duplicate_transaction_free(&trans); | ||||
return; | ||||
} else { | ||||
e_error(trans->event, | ||||
"Creating lock file for %s timed out in %u secs", | ||||
trans->path, db->dotlock_set.timeout); | ||||
mail_duplicate_transaction_free(&trans); | ||||
return; | return; | |||
} | } | |||
i_zero(&hdr); | i_zero(&hdr); | |||
hdr.version = DUPLICATE_VERSION; | hdr.version = DUPLICATE_VERSION; | |||
output = o_stream_create_fd_file(file->new_fd, 0, FALSE); | output = o_stream_create_fd_file(new_fd, 0, FALSE); | |||
o_stream_cork(output); | o_stream_cork(output); | |||
o_stream_nsend(output, &hdr, sizeof(hdr)); | o_stream_nsend(output, &hdr, sizeof(hdr)); | |||
i_zero(&rec); | i_zero(&rec); | |||
iter = hash_table_iterate_init(file->hash); | iter = hash_table_iterate_init(trans->hash); | |||
while (hash_table_iterate(iter, file->hash, &d, &d)) { | while (hash_table_iterate(iter, trans->hash, &d, &d)) { | |||
rec.stamp = d->time; | if (d->marked) { | |||
rec.id_size = d->id_size; | rec.stamp = d->time; | |||
rec.user_size = strlen(d->user); | rec.id_size = d->id_size; | |||
rec.user_size = strlen(d->user); | ||||
o_stream_nsend(output, &rec, sizeof(rec)); | ||||
o_stream_nsend(output, d->id, rec.id_size); | o_stream_nsend(output, &rec, sizeof(rec)); | |||
o_stream_nsend(output, d->user, rec.user_size); | o_stream_nsend(output, d->id, rec.id_size); | |||
o_stream_nsend(output, d->user, rec.user_size); | ||||
} | ||||
} | } | |||
hash_table_iterate_deinit(&iter); | hash_table_iterate_deinit(&iter); | |||
if (o_stream_finish(output) < 0) { | if (o_stream_finish(output) < 0) { | |||
e_error(db->user->event, "write(%s) failed: %s", file->path, | e_error(trans->event, "write(%s) failed: %s", | |||
o_stream_get_error(output)); o_stream_unref(&o | trans->path, o_stream_get_error(output)); | |||
utput); | o_stream_unref(&output); | |||
mail_duplicate_file_free(&db->file); | mail_duplicate_transaction_free(&trans); | |||
return; | return; | |||
} | } | |||
o_stream_unref(&output); | o_stream_unref(&output); | |||
if (file_dotlock_replace(&file->dotlock, 0) < 0) | if (file_dotlock_replace(&dotlock, 0) < 0) { | |||
e_error(db->user->event, "file_dotlock_replace(%s) failed: %m", | e_error(trans->event, | |||
file->path); | "file_dotlock_replace(%s) failed: %m", trans->path); | |||
mail_duplicate_file_free(&db->file); | } | |||
iter = hash_table_iterate_init(trans->hash); | ||||
while (hash_table_iterate(iter, trans->hash, &d, &d)) | ||||
mail_duplicate_unlock(trans, d); | ||||
hash_table_iterate_deinit(&iter); | ||||
mail_duplicate_transaction_free(&trans); | ||||
} | ||||
void mail_duplicate_transaction_rollback( | ||||
struct mail_duplicate_transaction **_trans) | ||||
{ | ||||
struct mail_duplicate_transaction *trans = *_trans; | ||||
if (trans == NULL) | ||||
return; | ||||
*_trans = NULL; | ||||
if (trans->path == NULL) | ||||
e_debug(trans->event, "Rollback (dummy)"); | ||||
else | ||||
e_debug(trans->event, "Rollback"); | ||||
mail_duplicate_transaction_free(&trans); | ||||
} | } | |||
struct mail_duplicate_db * | struct mail_duplicate_db * | |||
mail_duplicate_db_init(struct mail_user *user, const char *name) | mail_duplicate_db_init(struct mail_user *user, const char *name) | |||
{ | { | |||
struct mail_duplicate_db *db; | struct mail_duplicate_db *db; | |||
const struct mail_storage_settings *mail_set; | const struct mail_storage_settings *mail_set; | |||
const char *home = NULL; | const char *home = NULL; | |||
const char *lock_dir; | ||||
db = i_new(struct mail_duplicate_db, 1); | ||||
db->event = event_create(user->event); | ||||
event_set_append_log_prefix(db->event, "duplicate db: "); | ||||
e_debug(db->event, "Initialize"); | ||||
if (mail_user_get_home(user, &home) <= 0) { | if (mail_user_get_home(user, &home) <= 0) { | |||
e_error(user->event, "User %s doesn't have home dir set, " | e_error(db->event, "User %s doesn't have home dir set, " | |||
"disabling duplicate database", user->username); | "disabling duplicate database", user->username); | |||
} | } | |||
db = i_new(struct mail_duplicate_db, 1); | ||||
db->user = user; | db->user = user; | |||
db->path = home == NULL ? NULL : | db->path = home == NULL ? NULL : | |||
i_strconcat(home, "/.dovecot.", name, NULL); | i_strconcat(home, "/.dovecot.", name, NULL); | |||
db->dotlock_set = default_mail_duplicate_dotlock_set; | db->dotlock_set = default_mail_duplicate_dotlock_set; | |||
lock_dir = mail_user_get_volatile_dir(user); | ||||
if (lock_dir == NULL) | ||||
lock_dir = home; | ||||
db->lock_dir = i_strconcat(lock_dir, "/.dovecot.", name, ".locks", | ||||
NULL); | ||||
mail_set = mail_user_set_get_storage_set(user); | mail_set = mail_user_set_get_storage_set(user); | |||
db->dotlock_set.use_excl_lock = mail_set->dotlock_use_excl; | db->dotlock_set.use_excl_lock = mail_set->dotlock_use_excl; | |||
db->dotlock_set.nfs_flush = mail_set->mail_nfs_storage; | db->dotlock_set.nfs_flush = mail_set->mail_nfs_storage; | |||
return db; | return db; | |||
} | } | |||
void mail_duplicate_db_deinit(struct mail_duplicate_db **_db) | void mail_duplicate_db_deinit(struct mail_duplicate_db **_db) | |||
{ | { | |||
struct mail_duplicate_db *db = *_db; | struct mail_duplicate_db *db = *_db; | |||
*_db = NULL; | *_db = NULL; | |||
if (db->file != NULL) { | ||||
mail_duplicate_db_flush(db); | e_debug(db->event, "Cleanup"); | |||
i_assert(db->file == NULL); | ||||
} | i_assert(db->transaction_count == 0); | |||
event_unref(&db->event); | ||||
i_free(db->path); | i_free(db->path); | |||
i_free(db->lock_dir); | ||||
i_free(db); | i_free(db); | |||
} | } | |||
End of changes. 68 change blocks. | ||||
129 lines changed or deleted | 508 lines changed or added |