1 /* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
2 
3 #include "lib.h"
4 #include "ioloop.h"
5 #include "hex-binary.h"
6 #include "mkdir-parents.h"
7 #include "istream.h"
8 #include "ostream.h"
9 #include "time-util.h"
10 #include "home-expand.h"
11 #include "file-create-locked.h"
12 #include "file-dotlock.h"
13 #include "md5.h"
14 #include "hash.h"
15 #include "mail-user.h"
16 #include "mail-storage-settings.h"
17 #include "mail-duplicate.h"
18 
19 #include <fcntl.h>
20 #include <unistd.h>
21 
22 #define COMPRESS_PERCENTAGE 10
23 #define DUPLICATE_BUFSIZE 4096
24 #define DUPLICATE_VERSION 2
25 
26 #define DUPLICATE_LOCK_FNAME_PREFIX "duplicate.lock."
27 
28 #define DUPLICATE_LOCK_TIMEOUT_SECS 65
29 #define DUPLICATE_LOCK_WARN_SECS 4
30 #define DUPLICATE_LOCK_MAX_LOCKS 100
31 
32 enum mail_duplicate_lock_result {
33 	MAIL_DUPLICATE_LOCK_OK,
34 	MAIL_DUPLICATE_LOCK_IO_ERROR,
35 	MAIL_DUPLICATE_LOCK_TIMEOUT,
36 	MAIL_DUPLICATE_LOCK_TOO_MANY,
37 	MAIL_DUPLICATE_LOCK_DEADLOCK,
38 };
39 
40 struct mail_duplicate_lock {
41 	int fd;
42 	char *path;
43 	struct file_lock *lock;
44 	struct timeval start_time;
45 };
46 
47 struct mail_duplicate {
48 	const void *id;
49 	unsigned int id_size;
50 
51 	const char *user;
52 	time_t time;
53 	struct mail_duplicate_lock lock;
54 
55 	bool marked:1;
56 	bool changed:1;
57 };
58 
59 struct mail_duplicate_file_header {
60 	uint32_t version;
61 };
62 
63 struct mail_duplicate_record_header {
64 	uint32_t stamp;
65 	uint32_t id_size;
66 	uint32_t user_size;
67 };
68 
69 struct mail_duplicate_transaction {
70 	pool_t pool;
71 	struct mail_duplicate_db *db;
72 	ino_t db_ino;
73 	struct event *event;
74 
75 	HASH_TABLE(struct mail_duplicate *, struct mail_duplicate *) hash;
76 	const char *path;
77 	unsigned int id_lock_count;
78 
79 	bool changed:1;
80 };
81 
82 struct mail_duplicate_db {
83 	struct mail_user *user;
84 	struct event *event;
85 	char *path;
86 	char *lock_dir;
87 	struct dotlock_settings dotlock_set;
88 
89 	unsigned int transaction_count;
90 };
91 
92 static const struct dotlock_settings default_mail_duplicate_dotlock_set = {
93 	.timeout = 20,
94 	.stale_timeout = 10,
95 };
96 
97 static int
mail_duplicate_cmp(const struct mail_duplicate * d1,const struct mail_duplicate * d2)98 mail_duplicate_cmp(const struct mail_duplicate *d1,
99 		   const struct mail_duplicate *d2)
100 {
101 	return (d1->id_size == d2->id_size &&
102 		memcmp(d1->id, d2->id, d1->id_size) == 0 &&
103 		strcasecmp(d1->user, d2->user) == 0) ? 0 : 1;
104 }
105 
mail_duplicate_hash(const struct mail_duplicate * d)106 static unsigned int mail_duplicate_hash(const struct mail_duplicate *d)
107 {
108 	/* a char* hash function from ASU -- from glib */
109         const unsigned char *s = d->id, *end = s + d->id_size;
110 	unsigned int g, h = 0;
111 
112 	while (s != end) {
113 		h = (h << 4) + *s;
114 		if ((g = h & 0xf0000000UL) != 0) {
115 			h = h ^ (g >> 24);
116 			h = h ^ g;
117 		}
118 		s++;
119 	}
120 
121 	return h ^ strcase_hash(d->user);
122 }
123 
124 static enum mail_duplicate_lock_result
duplicate_lock_failed(struct mail_duplicate_transaction * trans,struct mail_duplicate * dup,const char * error)125 duplicate_lock_failed(struct mail_duplicate_transaction *trans,
126 		      struct mail_duplicate *dup, const char *error)
127 {
128 	struct mail_duplicate_lock *lock = &dup->lock;
129 	enum mail_duplicate_lock_result result;
130 	int diff;
131 
132 	i_assert(lock->fd == -1);
133 	i_assert(lock->lock == NULL);
134 
135 	if (errno == EDEADLK) {
136 		/* deadlock */
137 		result = MAIL_DUPLICATE_LOCK_DEADLOCK;
138 	} else if (errno != EAGAIN) {
139 		/* not a lock timeout */
140 		result = MAIL_DUPLICATE_LOCK_IO_ERROR;
141 	} else {
142 		diff = timeval_diff_msecs(&ioloop_timeval,
143 					  &lock->start_time);
144 		error = t_strdup_printf("Lock timeout in %d.%03d secs",
145 					diff/1000, diff%1000);
146 		result = MAIL_DUPLICATE_LOCK_TIMEOUT;
147 	}
148 
149 	e_error(trans->event, "Failed to lock %s: %s", lock->path, error);
150 	i_free_and_null(lock->path);
151 	i_zero(lock);
152 	return result;
153 }
154 
mail_duplicate_is_locked(struct mail_duplicate * dup)155 static bool mail_duplicate_is_locked(struct mail_duplicate *dup)
156 {
157 	struct mail_duplicate_lock *lock = &dup->lock;
158 
159 	return (lock->lock != NULL);
160 }
161 
162 static enum mail_duplicate_lock_result
mail_duplicate_lock(struct mail_duplicate_transaction * trans,struct mail_duplicate * dup)163 mail_duplicate_lock(struct mail_duplicate_transaction *trans,
164 		    struct mail_duplicate *dup)
165 {
166 	struct file_create_settings lock_set = {
167 		.lock_timeout_secs = DUPLICATE_LOCK_TIMEOUT_SECS,
168 		.lock_settings = {
169 			.lock_method = FILE_LOCK_METHOD_FCNTL,
170 			.allow_deadlock = TRUE,
171 		},
172 	};
173 	struct mail_duplicate_db *db = trans->db;
174 	struct mail_duplicate_lock *lock = &dup->lock;
175 	const char *error;
176 	unsigned char id_md5[MD5_RESULTLEN];
177 	bool created;
178 	int diff;
179 
180 	if (mail_duplicate_is_locked(dup)) {
181 		e_debug(trans->event, "Duplicate ID already locked");
182 		return MAIL_DUPLICATE_LOCK_OK;
183 	}
184 	if (trans->id_lock_count >= DUPLICATE_LOCK_MAX_LOCKS) {
185 		e_debug(trans->event, "Too many duplicate IDs locked");
186 		return MAIL_DUPLICATE_LOCK_TOO_MANY;
187 	}
188 
189 	i_assert(db->lock_dir != NULL);
190 
191 	lock->start_time = ioloop_timeval;
192 	md5_get_digest(dup->id, dup->id_size, id_md5);
193 	lock->path = i_strdup_printf("%s/"DUPLICATE_LOCK_FNAME_PREFIX"%s",
194 				     db->lock_dir,
195 				     binary_to_hex(id_md5, sizeof(id_md5)));
196 
197 	e_debug(trans->event, "Lock duplicate ID (path=%s)", lock->path);
198 
199 	lock->fd = file_create_locked(lock->path, &lock_set, &lock->lock,
200 				      &created, &error);
201 	if (lock->fd == -1 && errno == ENOENT) {
202 		/* parent directory missing - create it */
203 		if (mkdir_parents(db->lock_dir, 0700) < 0 && errno != EEXIST) {
204 			error = t_strdup_printf(
205 				"mkdir_parents(%s) failed: %m", db->lock_dir);
206 		} else {
207 			lock->fd = file_create_locked(lock->path,
208 						      &lock_set, &lock->lock,
209 						      &created, &error);
210 		}
211 	}
212 	if (lock->fd == -1)
213 		return duplicate_lock_failed(trans, dup, error);
214 
215 	diff = timeval_diff_msecs(&ioloop_timeval, &lock->start_time);
216 	if (diff >= (DUPLICATE_LOCK_WARN_SECS * 1000)) {
217 		e_warning(trans->event, "Locking %s took %d.%03d secs",
218 			  lock->path, diff/1000, diff%1000);
219 	}
220 
221 	i_assert(mail_duplicate_is_locked(dup));
222 	trans->id_lock_count++;
223 	return MAIL_DUPLICATE_LOCK_OK;
224 }
225 
226 static void
mail_duplicate_unlock(struct mail_duplicate_transaction * trans,struct mail_duplicate * dup)227 mail_duplicate_unlock(struct mail_duplicate_transaction *trans,
228 		      struct mail_duplicate *dup)
229 {
230 	int orig_errno = errno;
231 
232 	if (dup->lock.path != NULL) {
233 		struct mail_duplicate_lock *lock = &dup->lock;
234 
235 		e_debug(trans->event, "Unlock duplicate ID (path=%s)",
236 			lock->path);
237 		i_unlink(lock->path);
238 		file_lock_free(&lock->lock);
239 		i_close_fd(&lock->fd);
240 		i_free_and_null(lock->path);
241 		i_zero(lock);
242 
243 		i_assert(trans->id_lock_count > 0);
244 		trans->id_lock_count--;
245 	}
246 
247 	errno = orig_errno;
248 }
249 
250 static int
mail_duplicate_read_records(struct mail_duplicate_transaction * trans,struct istream * input,unsigned int record_size)251 mail_duplicate_read_records(struct mail_duplicate_transaction *trans,
252 			    struct istream *input,
253 			    unsigned int record_size)
254 {
255 	const unsigned char *data;
256 	struct mail_duplicate_record_header hdr;
257 	size_t size;
258 	unsigned int change_count;
259 
260 	change_count = 0;
261 	while (i_stream_read_bytes(input, &data, &size, record_size) > 0) {
262 		if (record_size == sizeof(hdr))
263 			memcpy(&hdr, data, sizeof(hdr));
264 		else {
265 			/* FIXME: backwards compatibility with v1.0 */
266 			time_t stamp;
267 
268 			i_assert(record_size ==
269 				 sizeof(time_t) + sizeof(uint32_t)*2);
270 			memcpy(&stamp, data, sizeof(stamp));
271 			hdr.stamp = stamp;
272 			memcpy(&hdr.id_size, data + sizeof(time_t),
273 			       sizeof(hdr.id_size));
274 			memcpy(&hdr.user_size,
275 			       data + sizeof(time_t) + sizeof(uint32_t),
276 			       sizeof(hdr.user_size));
277 		}
278 		i_stream_skip(input, record_size);
279 
280 		if (hdr.id_size == 0 || hdr.user_size == 0 ||
281 		    hdr.id_size > DUPLICATE_BUFSIZE ||
282 		    hdr.user_size > DUPLICATE_BUFSIZE) {
283 			e_error(trans->event,
284 				"broken mail_duplicate file %s", trans->path);
285 			return -1;
286 		}
287 
288 		if (i_stream_read_bytes(input, &data, &size,
289 					hdr.id_size + hdr.user_size) <= 0) {
290 			e_error(trans->event,
291 				"unexpected end of file in %s", trans->path);
292 			return -1;
293 		}
294 
295 		struct mail_duplicate dup_q, *dup;
296 
297 		dup_q.id = data;
298 		dup_q.id_size = hdr.id_size;
299 		dup_q.user = t_strndup(data + hdr.id_size, hdr.user_size);
300 
301 		dup = hash_table_lookup(trans->hash, &dup_q);
302 		if ((time_t)hdr.stamp < ioloop_time) {
303                         change_count++;
304 			if (dup != NULL && !dup->changed)
305 				dup->marked = FALSE;
306 		} else {
307 			if (dup == NULL) {
308 				void *new_id;
309 
310 				new_id = p_malloc(trans->pool, hdr.id_size);
311 				memcpy(new_id, data, hdr.id_size);
312 
313 				dup = p_new(trans->pool,
314 					    struct mail_duplicate, 1);
315 				dup->id = new_id;
316 				dup->id_size = hdr.id_size;
317 				dup->user = p_strdup(trans->pool, dup_q.user);
318 				hash_table_update(trans->hash, dup, dup);
319 			}
320 			if (!dup->changed) {
321 				dup->marked = TRUE;
322 				dup->time = hdr.stamp;
323 			}
324 		}
325 		i_stream_skip(input, hdr.id_size + hdr.user_size);
326 	}
327 
328 	if (hash_table_count(trans->hash) *
329 	    COMPRESS_PERCENTAGE / 100 > change_count)
330 		trans->changed = TRUE;
331 	return 0;
332 }
333 
334 static int
mail_duplicate_read_db_from_fd(struct mail_duplicate_transaction * trans,int fd)335 mail_duplicate_read_db_from_fd(struct mail_duplicate_transaction *trans, int fd)
336 {
337 	struct istream *input;
338 	struct mail_duplicate_file_header hdr;
339 	const unsigned char *data;
340 	size_t size;
341 	struct stat st;
342 	unsigned int record_size = 0;
343 
344 	if (fstat(fd, &st) < 0) {
345 		e_error(trans->event,
346 			"stat(%s) failed: %m", trans->path);
347 		return -1;
348 	}
349 	trans->db_ino = st.st_ino;
350 
351 	/* <timestamp> <id_size> <user_size> <id> <user> */
352 	input = i_stream_create_fd(fd, DUPLICATE_BUFSIZE);
353 	if (i_stream_read_bytes(input, &data, &size, sizeof(hdr)) > 0) {
354 		memcpy(&hdr, data, sizeof(hdr));
355 		if (hdr.version == 0 || hdr.version > DUPLICATE_VERSION + 10) {
356 			/* FIXME: backwards compatibility with v1.0 */
357 			record_size = sizeof(time_t) + sizeof(uint32_t)*2;
358 		} else if (hdr.version == DUPLICATE_VERSION) {
359 			record_size = sizeof(struct mail_duplicate_record_header);
360 			i_stream_skip(input, sizeof(hdr));
361 		}
362 	}
363 
364 	if (record_size == 0)
365 		i_unlink_if_exists(trans->path);
366 	else T_BEGIN {
367 		if (mail_duplicate_read_records(trans, input, record_size) < 0)
368 			i_unlink_if_exists(trans->path);
369 	} T_END;
370 
371 	i_stream_unref(&input);
372 	return 0;
373 }
374 
mail_duplicate_read_db_file(struct mail_duplicate_transaction * trans)375 static int mail_duplicate_read_db_file(struct mail_duplicate_transaction *trans)
376 {
377 	int fd, ret;
378 
379 	e_debug(trans->event, "Reading %s", trans->path);
380 
381 	fd = open(trans->path, O_RDONLY);
382 	if (fd == -1) {
383 		if (errno == ENOENT)
384 			return 0;
385 		e_error(trans->event,
386 			"open(%s) failed: %m", trans->path);
387 		return -1;
388 	}
389 
390 	ret = mail_duplicate_read_db_from_fd(trans, fd);
391 
392 	if (close(fd) < 0) {
393 		e_error(trans->event,
394 			"close(%s) failed: %m", trans->path);
395 	}
396 	return ret;
397 }
398 
mail_duplicate_read(struct mail_duplicate_transaction * trans)399 static void mail_duplicate_read(struct mail_duplicate_transaction *trans)
400 {
401 	struct mail_duplicate_db *db = trans->db;
402 	int new_fd;
403 	struct dotlock *dotlock;
404 
405 	new_fd = file_dotlock_open(&db->dotlock_set, trans->path, 0, &dotlock);
406 	if (new_fd != -1)
407 		;
408 	else if (errno != EAGAIN) {
409 		e_error(trans->event,
410 			"file_dotlock_open(%s) failed: %m", trans->path);
411 	} else {
412 		e_error(trans->event,
413 			"Creating lock file for %s timed out in %u secs",
414 			trans->path, db->dotlock_set.timeout);
415 	}
416 
417 	(void)mail_duplicate_read_db_file(trans);
418 
419 	if (dotlock != NULL)
420 		file_dotlock_delete(&dotlock);
421 }
422 
mail_duplicate_update(struct mail_duplicate_transaction * trans)423 static void mail_duplicate_update(struct mail_duplicate_transaction *trans)
424 {
425 	struct stat st;
426 
427 	if (stat(trans->path, &st) < 0) {
428 		if (errno == ENOENT) {
429 			e_debug(trans->event, "DB file not created yet");
430 		} else {
431 			e_error(trans->event,
432 				"stat(%s) failed: %m", trans->path);
433 		}
434 	} else if (trans->db_ino == st.st_ino) {
435 		e_debug(trans->event, "DB file not changed");
436 	} else {
437 		e_debug(trans->event, "DB file changed: "
438 			"Updating duplicate records from DB file");
439 
440 		mail_duplicate_read(trans);
441 	}
442 }
443 
444 struct mail_duplicate_transaction *
mail_duplicate_transaction_begin(struct mail_duplicate_db * db)445 mail_duplicate_transaction_begin(struct mail_duplicate_db *db)
446 {
447 	struct mail_duplicate_transaction *trans;
448 	pool_t pool;
449 
450 	db->transaction_count++;
451 
452 	pool = pool_alloconly_create("mail_duplicates", 10240);
453 
454 	trans = p_new(pool, struct mail_duplicate_transaction, 1);
455 	trans->pool = pool;
456 	trans->db = db;
457 
458 	trans->event = event_create(db->event);
459 	event_set_append_log_prefix(trans->event, "transaction: ");
460 
461 	if (db->path == NULL) {
462 		/* Duplicate database disabled; return dummy transaction */
463 		e_debug(trans->event, "Transaction begin (dummy)");
464 		return trans;
465 	}
466 
467 	e_debug(trans->event, "Transaction begin; lock %s", db->path);
468 
469 	trans->path = p_strdup(pool, db->path);
470 	hash_table_create(&trans->hash, pool, 0,
471 			  mail_duplicate_hash, mail_duplicate_cmp);
472 
473 	mail_duplicate_read(trans);
474 
475 	return trans;
476 }
477 
478 static void
mail_duplicate_transaction_free(struct mail_duplicate_transaction ** _trans)479 mail_duplicate_transaction_free(struct mail_duplicate_transaction **_trans)
480 {
481 	struct mail_duplicate_transaction *trans = *_trans;
482 	struct hash_iterate_context *iter;
483 	struct mail_duplicate *d;
484 
485 	if (trans == NULL)
486 		return;
487 	*_trans = NULL;
488 
489 	e_debug(trans->event, "Transaction free");
490 
491 	i_assert(trans->db->transaction_count > 0);
492 	trans->db->transaction_count--;
493 
494 	iter = hash_table_iterate_init(trans->hash);
495 	while (hash_table_iterate(iter, trans->hash, &d, &d))
496 		mail_duplicate_unlock(trans, d);
497 	hash_table_iterate_deinit(&iter);
498 	i_assert(trans->id_lock_count == 0);
499 
500 	hash_table_destroy(&trans->hash);
501 	event_unref(&trans->event);
502 	pool_unref(&trans->pool);
503 }
504 
505 static struct mail_duplicate *
mail_duplicate_get(struct mail_duplicate_transaction * trans,const void * id,size_t id_size,const char * user)506 mail_duplicate_get(struct mail_duplicate_transaction *trans,
507 		   const void *id, size_t id_size, const char *user)
508 {
509 	struct mail_duplicate dup_q, *dup;
510 
511 	dup_q.id = id;
512 	dup_q.id_size = id_size;
513 	dup_q.user = user;
514 
515 	dup = hash_table_lookup(trans->hash, &dup_q);
516 	if (dup == NULL) {
517 		dup = p_new(trans->pool, struct mail_duplicate, 1);
518 		dup->id = p_memdup(trans->pool, id, id_size);
519 		dup->id_size = id_size;
520 		dup->user = p_strdup(trans->pool, user);
521 		dup->time = (time_t)-1;
522 
523 		hash_table_insert(trans->hash, dup, dup);
524 	}
525 
526 	return dup;
527 }
528 
529 enum mail_duplicate_check_result
mail_duplicate_check(struct mail_duplicate_transaction * trans,const void * id,size_t id_size,const char * user)530 mail_duplicate_check(struct mail_duplicate_transaction *trans,
531 		     const void *id, size_t id_size, const char *user)
532 {
533 	struct mail_duplicate *dup;
534 
535 	if (trans->path == NULL) {
536 		/* Duplicate database disabled */
537 		e_debug(trans->event, "Check ID (dummy)");
538 		return MAIL_DUPLICATE_CHECK_RESULT_NOT_FOUND;
539 	}
540 
541 	dup = mail_duplicate_get(trans, id, id_size, user);
542 
543 	switch (mail_duplicate_lock(trans, dup)) {
544 	case MAIL_DUPLICATE_LOCK_OK:
545 		break;
546 	case MAIL_DUPLICATE_LOCK_IO_ERROR:
547 		e_debug(trans->event,
548 			"Check ID: I/O error occurred while locking");
549 		return MAIL_DUPLICATE_CHECK_RESULT_IO_ERROR;
550 	case MAIL_DUPLICATE_LOCK_TIMEOUT:
551 		e_debug(trans->event,
552 			"Check ID: lock timed out");
553 		return MAIL_DUPLICATE_CHECK_RESULT_LOCK_TIMEOUT;
554 	case MAIL_DUPLICATE_LOCK_TOO_MANY:
555 		e_debug(trans->event,
556 			"Check ID: too many IDs locked");
557 		return MAIL_DUPLICATE_CHECK_RESULT_TOO_MANY_LOCKS;
558 	case MAIL_DUPLICATE_LOCK_DEADLOCK:
559 		e_debug(trans->event,
560 			"Check ID: deadlock detected while locking");
561 		return MAIL_DUPLICATE_CHECK_RESULT_DEADLOCK;
562 	}
563 
564 	mail_duplicate_update(trans);
565 	if (dup->marked) {
566 		e_debug(trans->event, "Check ID: found");
567 		return MAIL_DUPLICATE_CHECK_RESULT_EXISTS;
568 	}
569 
570 	e_debug(trans->event, "Check ID: not found");
571 	return MAIL_DUPLICATE_CHECK_RESULT_NOT_FOUND;
572 }
573 
mail_duplicate_mark(struct mail_duplicate_transaction * trans,const void * id,size_t id_size,const char * user,time_t timestamp)574 void mail_duplicate_mark(struct mail_duplicate_transaction *trans,
575 			 const void *id, size_t id_size,
576 			 const char *user, time_t timestamp)
577 {
578 	struct mail_duplicate *dup;
579 
580 	if (trans->path == NULL) {
581 		/* Duplicate database disabled */
582 		e_debug(trans->event, "Mark ID (dummy)");
583 		return;
584 	}
585 
586 	e_debug(trans->event, "Mark ID");
587 
588 	dup = mail_duplicate_get(trans, id, id_size, user);
589 
590 	/* Must already be checked and locked */
591 	i_assert(mail_duplicate_is_locked(dup));
592 
593 	dup->time = timestamp;
594 	dup->marked = TRUE;
595 	dup->changed = TRUE;
596 
597 	trans->changed = TRUE;
598 }
599 
mail_duplicate_transaction_commit(struct mail_duplicate_transaction ** _trans)600 void mail_duplicate_transaction_commit(
601 	struct mail_duplicate_transaction **_trans)
602 {
603 	struct mail_duplicate_transaction *trans = *_trans;
604 	struct mail_duplicate_file_header hdr;
605 	struct mail_duplicate_record_header rec;
606 	struct ostream *output;
607         struct hash_iterate_context *iter;
608 	struct mail_duplicate *d;
609 	int new_fd;
610 	struct dotlock *dotlock;
611 
612 	if (trans == NULL)
613 		return;
614 	*_trans = NULL;
615 
616 	if (trans->path == NULL) {
617 		e_debug(trans->event, "Commit (dummy)");
618 		mail_duplicate_transaction_free(&trans);
619 		return;
620 	}
621 	if (!trans->changed) {
622 		e_debug(trans->event, "Commit; no changes");
623 		mail_duplicate_transaction_free(&trans);
624 		return;
625 	}
626 
627 	struct mail_duplicate_db *db = trans->db;
628 
629 	i_assert(trans->path != NULL);
630 	e_debug(trans->event, "Commit; overwrite %s", trans->path);
631 
632 	new_fd = file_dotlock_open(&db->dotlock_set, trans->path, 0, &dotlock);
633 	if (new_fd != -1)
634 		;
635 	else if (errno != EAGAIN) {
636 		e_error(trans->event,
637 			"file_dotlock_open(%s) failed: %m",
638 			trans->path);
639 		mail_duplicate_transaction_free(&trans);
640 		return;
641 	} else {
642 		e_error(trans->event,
643 			"Creating lock file for %s timed out in %u secs",
644 			trans->path, db->dotlock_set.timeout);
645 		mail_duplicate_transaction_free(&trans);
646 		return;
647 	}
648 
649 	i_zero(&hdr);
650 	hdr.version = DUPLICATE_VERSION;
651 
652 	output = o_stream_create_fd_file(new_fd, 0, FALSE);
653 	o_stream_cork(output);
654 	o_stream_nsend(output, &hdr, sizeof(hdr));
655 
656 	i_zero(&rec);
657 	iter = hash_table_iterate_init(trans->hash);
658 	while (hash_table_iterate(iter, trans->hash, &d, &d)) {
659 		if (d->marked) {
660 			rec.stamp = d->time;
661 			rec.id_size = d->id_size;
662 			rec.user_size = strlen(d->user);
663 
664 			o_stream_nsend(output, &rec, sizeof(rec));
665 			o_stream_nsend(output, d->id, rec.id_size);
666 			o_stream_nsend(output, d->user, rec.user_size);
667 		}
668 	}
669 	hash_table_iterate_deinit(&iter);
670 
671 	if (o_stream_finish(output) < 0) {
672 		e_error(trans->event, "write(%s) failed: %s",
673 			trans->path, o_stream_get_error(output));
674 		o_stream_unref(&output);
675 		mail_duplicate_transaction_free(&trans);
676 		return;
677 	}
678 	o_stream_unref(&output);
679 
680 	if (file_dotlock_replace(&dotlock, 0) < 0) {
681 		e_error(trans->event,
682 			"file_dotlock_replace(%s) failed: %m", trans->path);
683 	}
684 
685 	iter = hash_table_iterate_init(trans->hash);
686 	while (hash_table_iterate(iter, trans->hash, &d, &d))
687 		mail_duplicate_unlock(trans, d);
688 	hash_table_iterate_deinit(&iter);
689 
690 	mail_duplicate_transaction_free(&trans);
691 }
692 
mail_duplicate_transaction_rollback(struct mail_duplicate_transaction ** _trans)693 void mail_duplicate_transaction_rollback(
694 	struct mail_duplicate_transaction **_trans)
695 {
696 	struct mail_duplicate_transaction *trans = *_trans;
697 
698 	if (trans == NULL)
699 		return;
700 	*_trans = NULL;
701 
702 	if (trans->path == NULL)
703 		e_debug(trans->event, "Rollback (dummy)");
704 	else
705 		e_debug(trans->event, "Rollback");
706 
707 	mail_duplicate_transaction_free(&trans);
708 }
709 
710 struct mail_duplicate_db *
mail_duplicate_db_init(struct mail_user * user,const char * name)711 mail_duplicate_db_init(struct mail_user *user, const char *name)
712 {
713 	struct mail_duplicate_db *db;
714 	const struct mail_storage_settings *mail_set;
715 	const char *home = NULL;
716 	const char *lock_dir;
717 
718 	db = i_new(struct mail_duplicate_db, 1);
719 
720 	db->event = event_create(user->event);
721 	event_set_append_log_prefix(db->event, "duplicate db: ");
722 
723 	e_debug(db->event, "Initialize");
724 
725 	if (mail_user_get_home(user, &home) <= 0) {
726 		e_error(db->event, "User %s doesn't have home dir set, "
727 			"disabling duplicate database", user->username);
728 	}
729 
730 	db->user = user;
731 	db->path = home == NULL ? NULL :
732 		i_strconcat(home, "/.dovecot.", name, NULL);
733 	db->dotlock_set = default_mail_duplicate_dotlock_set;
734 
735 	lock_dir = mail_user_get_volatile_dir(user);
736 	if (lock_dir == NULL)
737 		lock_dir = home;
738 	db->lock_dir = i_strconcat(lock_dir, "/.dovecot.", name, ".locks",
739 				   NULL);
740 
741 	mail_set = mail_user_set_get_storage_set(user);
742 	db->dotlock_set.use_excl_lock = mail_set->dotlock_use_excl;
743 	db->dotlock_set.nfs_flush = mail_set->mail_nfs_storage;
744 
745 	return db;
746 }
747 
mail_duplicate_db_deinit(struct mail_duplicate_db ** _db)748 void mail_duplicate_db_deinit(struct mail_duplicate_db **_db)
749 {
750 	struct mail_duplicate_db *db = *_db;
751 
752 	*_db = NULL;
753 
754 	e_debug(db->event, "Cleanup");
755 
756 	i_assert(db->transaction_count == 0);
757 
758 	event_unref(&db->event);
759 	i_free(db->path);
760 	i_free(db->lock_dir);
761 	i_free(db);
762 }
763