1 /* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */
2 
3 #include "lib.h"
4 #include "nfs-workarounds.h"
5 #include "read-full.h"
6 #include "write-full.h"
7 #include "ostream.h"
8 #include "mail-index-private.h"
9 #include "mail-transaction-log-private.h"
10 
11 #include <stdio.h>
12 
13 #define MAIL_INDEX_MIN_UPDATE_SIZE 1024
14 /* if we're updating >= count-n messages, recreate the index */
15 #define MAIL_INDEX_MAX_OVERWRITE_NEG_SEQ_COUNT 10
16 
mail_index_create_backup(struct mail_index * index)17 static int mail_index_create_backup(struct mail_index *index)
18 {
19 	const char *backup_path, *tmp_backup_path;
20 	int ret;
21 
22 	if (index->fd != -1) {
23 		/* we very much want to avoid creating a backup file that
24 		   hasn't been written to disk yet */
25 		if (fdatasync(index->fd) < 0) {
26 			mail_index_set_error(index, "fdatasync(%s) failed: %m",
27 					     index->filepath);
28 			return -1;
29 		}
30 	}
31 
32 	backup_path = t_strconcat(index->filepath, ".backup", NULL);
33 	tmp_backup_path = t_strconcat(backup_path, ".tmp", NULL);
34 	ret = link(index->filepath, tmp_backup_path);
35 	if (ret < 0 && errno == EEXIST) {
36 		if (unlink(tmp_backup_path) < 0 && errno != ENOENT) {
37 			mail_index_set_error(index, "unlink(%s) failed: %m",
38 					     tmp_backup_path);
39 			return -1;
40 		}
41 		ret = link(index->filepath, tmp_backup_path);
42 	}
43 	if (ret < 0) {
44 		if (errno == ENOENT) {
45 			/* no dovecot.index file, ignore */
46 			return 0;
47 		}
48 		mail_index_set_error(index, "link(%s, %s) failed: %m",
49 				     index->filepath, tmp_backup_path);
50 		return -1;
51 	}
52 
53 	if (rename(tmp_backup_path, backup_path) < 0) {
54 		mail_index_set_error(index, "rename(%s, %s) failed: %m",
55 				     tmp_backup_path, backup_path);
56 		return -1;
57 	}
58 	return 0;
59 }
60 
mail_index_recreate(struct mail_index * index)61 static int mail_index_recreate(struct mail_index *index)
62 {
63 	struct mail_index_map *map = index->map;
64 	struct ostream *output;
65 	unsigned int base_size;
66 	const char *path;
67 	int ret = 0, fd;
68 
69 	i_assert(!MAIL_INDEX_IS_IN_MEMORY(index));
70 	i_assert(map->hdr.indexid == index->indexid);
71 	i_assert((map->hdr.flags & MAIL_INDEX_HDR_FLAG_CORRUPTED) == 0);
72 	i_assert(index->indexid != 0);
73 
74 	fd = mail_index_create_tmp_file(index, index->filepath, &path);
75 	if (fd == -1)
76 		return -1;
77 
78 	output = o_stream_create_fd_file(fd, 0, FALSE);
79 	o_stream_cork(output);
80 
81 	base_size = I_MIN(map->hdr.base_header_size, sizeof(map->hdr));
82 	o_stream_nsend(output, &map->hdr, base_size);
83 	o_stream_nsend(output, MAIL_INDEX_MAP_HDR_OFFSET(map, base_size),
84 		       map->hdr.header_size - base_size);
85 	o_stream_nsend(output, map->rec_map->records,
86 		       map->rec_map->records_count * map->hdr.record_size);
87 	if (o_stream_finish(output) < 0) {
88 		mail_index_file_set_syscall_error(index, path, "write()");
89 		ret = -1;
90 	}
91 	o_stream_destroy(&output);
92 
93 	if (ret == 0 && index->set.fsync_mode != FSYNC_MODE_NEVER) {
94 		if (fdatasync(fd) < 0) {
95 			mail_index_file_set_syscall_error(index, path,
96 							  "fdatasync()");
97 			ret = -1;
98 		}
99 	}
100 
101 	if (close(fd) < 0) {
102 		mail_index_file_set_syscall_error(index, path, "close()");
103 		ret = -1;
104 	}
105 
106 	if ((index->flags & MAIL_INDEX_OPEN_FLAG_KEEP_BACKUPS) != 0)
107 		(void)mail_index_create_backup(index);
108 
109 	if (ret == 0 && rename(path, index->filepath) < 0) {
110 		mail_index_set_error(index, "rename(%s, %s) failed: %m",
111 				     path, index->filepath);
112 		ret = -1;
113 	}
114 
115 	if (ret < 0)
116 		i_unlink(path);
117 	return ret;
118 }
119 
mail_index_should_recreate(struct mail_index * index)120 static bool mail_index_should_recreate(struct mail_index *index)
121 {
122 	struct stat st1, st2;
123 
124 	if (nfs_safe_stat(index->filepath, &st1) < 0) {
125 		if (errno != ENOENT) {
126 			mail_index_set_syscall_error(index, "stat()");
127 			return FALSE;
128 		} else if (index->fd == -1) {
129 			/* main index hasn't been created yet */
130 			return TRUE;
131 		} else {
132 			/* mailbox was just deleted? don't log an error */
133 			return FALSE;
134 		}
135 	}
136 	if (index->fd == -1) {
137 		/* main index was just created by another process */
138 		return FALSE;
139 	}
140 	if (fstat(index->fd, &st2) < 0) {
141 		if (!ESTALE_FSTAT(errno))
142 			mail_index_set_syscall_error(index, "fstat()");
143 		return FALSE;
144 	}
145 	if (st1.st_ino != st2.st_ino ||
146 	    !CMP_DEV_T(st1.st_dev, st2.st_dev)) {
147 		/* Index has already been recreated since we last read it.
148 		   We can't trust our decisions about whether to recreate it. */
149 		return FALSE;
150 	}
151 	return TRUE;
152 }
153 
mail_index_write(struct mail_index * index,bool want_rotate,const char * reason)154 void mail_index_write(struct mail_index *index, bool want_rotate,
155 		      const char *reason)
156 {
157 	struct mail_index_header *hdr = &index->map->hdr;
158 	bool rotated = FALSE;
159 
160 	i_assert(index->log_sync_locked);
161 
162 	if (index->readonly)
163 		return;
164 
165 	/* rotate the .log before writing index, so the index will point to
166 	   the latest log. Note that it's the caller's responsibility to make
167 	   sure that the .log can be safely rotated (i.e. everything has been
168 	   synced). */
169 	if (want_rotate) {
170 		if (mail_transaction_log_rotate(index->log, FALSE) == 0) {
171 			struct mail_transaction_log_file *file =
172 				index->log->head;
173 			/* Log rotation refreshes the index, which may cause the
174 			   map to change. Because we're locked, it's not
175 			   supposed to happen and will likely lead to an
176 			   assert-crash below, but we still need to make sure
177 			   we're using the latest map to do the checks. */
178 			hdr = &index->map->hdr;
179 			i_assert(file->hdr.prev_file_seq == hdr->log_file_seq);
180 			i_assert(file->hdr.prev_file_offset == hdr->log_file_head_offset);
181 			hdr->log_file_seq = file->hdr.file_seq;
182 			hdr->log_file_head_offset =
183 				hdr->log_file_tail_offset = file->hdr.hdr_size;
184 			/* Assume .log.2 was created successfully. If it
185 			   wasn't, it just causes an extra stat() and gets
186 			   fixed later on. */
187 			hdr->log2_rotate_time = ioloop_time;
188 			rotated = TRUE;
189 		}
190 	}
191 
192 	if (MAIL_INDEX_IS_IN_MEMORY(index))
193 		;
194 	else if (!rotated && !mail_index_should_recreate(index)) {
195 		/* make sure we don't keep getting back in here */
196 		index->reopen_main_index = TRUE;
197 	} else {
198 		if (mail_index_recreate(index) < 0) {
199 			(void)mail_index_move_to_memory(index);
200 			return;
201 		}
202 		event_set_name(index->event, "mail_index_recreated");
203 		e_debug(index->event, "Recreated %s (file_seq=%u) because: %s",
204 			index->filepath, hdr->log_file_seq, reason);
205 	}
206 
207 	index->main_index_hdr_log_file_seq = hdr->log_file_seq;
208 	index->main_index_hdr_log_file_tail_offset = hdr->log_file_tail_offset;
209 }
210