1 /* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */
2
3 #include "lib.h"
4 #include "array.h"
5 #include "nfs-workarounds.h"
6 #include "mmap-util.h"
7 #include "read-full.h"
8 #include "mail-index-private.h"
9 #include "mail-index-sync-private.h"
10 #include "mail-transaction-log-private.h"
11 #include "ioloop.h"
12
mail_index_map_copy_hdr(struct mail_index_map * map,const struct mail_index_header * hdr)13 static void mail_index_map_copy_hdr(struct mail_index_map *map,
14 const struct mail_index_header *hdr)
15 {
16 if (hdr->base_header_size < sizeof(map->hdr)) {
17 /* header smaller than ours, make a copy so our newer headers
18 won't have garbage in them */
19 i_zero(&map->hdr);
20 memcpy(&map->hdr, hdr, hdr->base_header_size);
21 } else {
22 map->hdr = *hdr;
23 }
24
25 /* FIXME: backwards compatibility, remove later. In case this index is
26 accessed with Dovecot v1.0, avoid recent message counter errors. */
27 map->hdr.unused_old_recent_messages_count = 0;
28 }
29
mail_index_mmap(struct mail_index_map * map,uoff_t file_size)30 static int mail_index_mmap(struct mail_index_map *map, uoff_t file_size)
31 {
32 struct mail_index *index = map->index;
33 struct mail_index_record_map *rec_map = map->rec_map;
34 const struct mail_index_header *hdr;
35 const char *error;
36
37 i_assert(rec_map->mmap_base == NULL);
38
39 buffer_free(&rec_map->buffer);
40 if (file_size > SSIZE_T_MAX) {
41 /* too large file to map into memory */
42 mail_index_set_error(index, "Index file too large: %s",
43 index->filepath);
44 return -1;
45 }
46
47 rec_map->mmap_base = mmap(NULL, file_size, PROT_READ | PROT_WRITE,
48 MAP_PRIVATE, index->fd, 0);
49 if (rec_map->mmap_base == MAP_FAILED) {
50 rec_map->mmap_base = NULL;
51 if (ioloop_time != index->last_mmap_error_time) {
52 index->last_mmap_error_time = ioloop_time;
53 mail_index_set_syscall_error(index, t_strdup_printf(
54 "mmap(size=%"PRIuUOFF_T")", file_size));
55 }
56 return -1;
57 }
58 rec_map->mmap_size = file_size;
59
60 hdr = rec_map->mmap_base;
61 if (rec_map->mmap_size >
62 offsetof(struct mail_index_header, major_version) &&
63 hdr->major_version != MAIL_INDEX_MAJOR_VERSION) {
64 /* major version change - handle silently */
65 return 0;
66 }
67
68 if (rec_map->mmap_size < MAIL_INDEX_HEADER_MIN_SIZE) {
69 mail_index_set_error(index, "Corrupted index file %s: "
70 "File too small (%zu)",
71 index->filepath, rec_map->mmap_size);
72 return 0;
73 }
74
75 if (!mail_index_check_header_compat(index, hdr, rec_map->mmap_size, &error)) {
76 /* Can't use this file */
77 mail_index_set_error(index, "Corrupted index file %s: %s",
78 index->filepath, error);
79 return 0;
80 }
81
82 rec_map->mmap_used_size = hdr->header_size +
83 hdr->messages_count * hdr->record_size;
84
85 if (rec_map->mmap_used_size <= rec_map->mmap_size)
86 rec_map->records_count = hdr->messages_count;
87 else {
88 rec_map->records_count =
89 (rec_map->mmap_size - hdr->header_size) /
90 hdr->record_size;
91 rec_map->mmap_used_size = hdr->header_size +
92 rec_map->records_count * hdr->record_size;
93 mail_index_set_error(index, "Corrupted index file %s: "
94 "messages_count too large (%u > %u)",
95 index->filepath, hdr->messages_count,
96 rec_map->records_count);
97 }
98
99 mail_index_map_copy_hdr(map, hdr);
100 buffer_set_used_size(map->hdr_copy_buf, 0);
101 buffer_append(map->hdr_copy_buf, rec_map->mmap_base, hdr->header_size);
102
103 rec_map->records = PTR_OFFSET(rec_map->mmap_base, map->hdr.header_size);
104 return 1;
105 }
106
mail_index_read_header(struct mail_index * index,void * buf,size_t buf_size,size_t * pos_r)107 static int mail_index_read_header(struct mail_index *index,
108 void *buf, size_t buf_size, size_t *pos_r)
109 {
110 size_t pos;
111 int ret;
112
113 memset(buf, 0, sizeof(struct mail_index_header));
114
115 /* try to read the whole header, but it's not necessarily an error to
116 read less since the older versions of the index format could be
117 smaller. Request reading up to buf_size, but accept if we only got
118 the header. */
119 pos = 0;
120 do {
121 ret = pread(index->fd, PTR_OFFSET(buf, pos),
122 buf_size - pos, pos);
123 if (ret > 0)
124 pos += ret;
125 } while (ret > 0 && pos < sizeof(struct mail_index_header));
126
127 *pos_r = pos;
128 return ret;
129 }
130
131 static int
mail_index_try_read_map(struct mail_index_map * map,uoff_t file_size,bool * retry_r,bool try_retry)132 mail_index_try_read_map(struct mail_index_map *map,
133 uoff_t file_size, bool *retry_r, bool try_retry)
134 {
135 struct mail_index *index = map->index;
136 const struct mail_index_header *hdr;
137 unsigned char read_buf[IO_BLOCK_SIZE];
138 const char *error;
139 const void *buf;
140 void *data = NULL;
141 ssize_t ret;
142 size_t pos, records_size, initial_buf_pos = 0;
143 unsigned int records_count = 0, extra;
144
145 i_assert(map->rec_map->mmap_base == NULL);
146
147 *retry_r = FALSE;
148 ret = mail_index_read_header(index, read_buf, sizeof(read_buf), &pos);
149 buf = read_buf; hdr = buf;
150
151 if (pos > (ssize_t)offsetof(struct mail_index_header, major_version) &&
152 hdr->major_version != MAIL_INDEX_MAJOR_VERSION) {
153 /* major version change - handle silently */
154 return 0;
155 }
156
157 if (ret >= 0 && pos >= MAIL_INDEX_HEADER_MIN_SIZE &&
158 (ret > 0 || pos >= hdr->base_header_size)) {
159 if (!mail_index_check_header_compat(index, hdr, file_size, &error)) {
160 /* Can't use this file */
161 mail_index_set_error(index, "Corrupted index file %s: %s",
162 index->filepath, error);
163 return 0;
164 }
165
166 initial_buf_pos = pos;
167 if (pos > hdr->header_size)
168 pos = hdr->header_size;
169
170 /* place the base header into memory. */
171 buffer_set_used_size(map->hdr_copy_buf, 0);
172 buffer_append(map->hdr_copy_buf, buf, pos);
173
174 if (pos != hdr->header_size) {
175 /* @UNSAFE: read the rest of the header into memory */
176 data = buffer_append_space_unsafe(map->hdr_copy_buf,
177 hdr->header_size -
178 pos);
179 ret = pread_full(index->fd, data,
180 hdr->header_size - pos, pos);
181 }
182 }
183
184 if (ret > 0) {
185 /* header read, read the records now. */
186 records_size = (size_t)hdr->messages_count * hdr->record_size;
187 records_count = hdr->messages_count;
188
189 if (file_size - hdr->header_size < records_size ||
190 (hdr->record_size != 0 &&
191 records_size / hdr->record_size != hdr->messages_count)) {
192 records_count = (file_size - hdr->header_size) /
193 hdr->record_size;
194 records_size = (size_t)records_count * hdr->record_size;
195 mail_index_set_error(index, "Corrupted index file %s: "
196 "messages_count too large (%u > %u)",
197 index->filepath, hdr->messages_count,
198 records_count);
199 }
200
201 if (map->rec_map->buffer == NULL) {
202 map->rec_map->buffer =
203 buffer_create_dynamic(default_pool,
204 records_size);
205 }
206
207 /* @UNSAFE */
208 buffer_set_used_size(map->rec_map->buffer, 0);
209 if (initial_buf_pos <= hdr->header_size)
210 extra = 0;
211 else {
212 extra = initial_buf_pos - hdr->header_size;
213 buffer_append(map->rec_map->buffer,
214 CONST_PTR_OFFSET(buf, hdr->header_size),
215 extra);
216 }
217 if (records_size > extra) {
218 data = buffer_append_space_unsafe(map->rec_map->buffer,
219 records_size - extra);
220 ret = pread_full(index->fd, data, records_size - extra,
221 hdr->header_size + extra);
222 }
223 }
224
225 if (ret < 0) {
226 if (errno == ESTALE && try_retry) {
227 /* a new index file was renamed over this one. */
228 *retry_r = TRUE;
229 return 0;
230 }
231 mail_index_set_syscall_error(index, "pread_full()");
232 return -1;
233 }
234 if (ret == 0) {
235 mail_index_set_error(index,
236 "Corrupted index file %s: File too small",
237 index->filepath);
238 return 0;
239 }
240
241 map->rec_map->records =
242 buffer_get_modifiable_data(map->rec_map->buffer, NULL);
243 map->rec_map->records_count = records_count;
244
245 mail_index_map_copy_hdr(map, hdr);
246 i_assert(map->hdr_copy_buf->used == map->hdr.header_size);
247 return 1;
248 }
249
mail_index_read_map(struct mail_index_map * map,uoff_t file_size)250 static int mail_index_read_map(struct mail_index_map *map, uoff_t file_size)
251 {
252 struct mail_index *index = map->index;
253 struct stat st;
254 unsigned int i;
255 int ret;
256 bool try_retry, retry;
257
258 for (i = 0;; i++) {
259 try_retry = i < MAIL_INDEX_ESTALE_RETRY_COUNT;
260 if (file_size == UOFF_T_MAX) {
261 /* fstat() below failed */
262 ret = 0;
263 retry = try_retry;
264 } else {
265 ret = mail_index_try_read_map(map, file_size,
266 &retry, try_retry);
267 }
268 if (ret != 0 || !retry)
269 break;
270
271 /* ESTALE - reopen index file */
272 mail_index_close_file(index);
273
274 ret = mail_index_try_open_only(index);
275 if (ret <= 0) {
276 if (ret == 0) {
277 /* the file was lost */
278 errno = ENOENT;
279 mail_index_set_syscall_error(index, "open()");
280 }
281 return -1;
282 }
283
284 if (fstat(index->fd, &st) == 0)
285 file_size = st.st_size;
286 else {
287 if (!ESTALE_FSTAT(errno)) {
288 mail_index_set_syscall_error(index, "fstat()");
289 return -1;
290 }
291 file_size = UOFF_T_MAX;
292 }
293 }
294 return ret;
295 }
296
297 /* returns -1 = error, 0 = index files are unusable,
298 1 = index files are usable or at least repairable */
299 static int
mail_index_map_latest_file(struct mail_index * index,const char ** reason_r)300 mail_index_map_latest_file(struct mail_index *index, const char **reason_r)
301 {
302 struct mail_index_map *old_map, *new_map;
303 struct stat st;
304 uoff_t file_size;
305 bool use_mmap, reopened, unusable = FALSE;
306 const char *error;
307 int ret, try;
308
309 *reason_r = NULL;
310
311 index->reopen_main_index = FALSE;
312 ret = mail_index_reopen_if_changed(index, &reopened, reason_r);
313 if (ret <= 0) {
314 if (ret < 0)
315 return -1;
316
317 /* the index file is lost/broken. let's hope that we can
318 build it from the transaction log. */
319 return 1;
320 }
321 i_assert(index->fd != -1);
322
323 if ((index->flags & MAIL_INDEX_OPEN_FLAG_NFS_FLUSH) != 0)
324 nfs_flush_attr_cache_fd_locked(index->filepath, index->fd);
325
326 if (fstat(index->fd, &st) == 0)
327 file_size = st.st_size;
328 else {
329 if (!ESTALE_FSTAT(errno)) {
330 mail_index_set_syscall_error(index, "fstat()");
331 return -1;
332 }
333 file_size = UOFF_T_MAX;
334 }
335
336 /* mmaping seems to be slower than just reading the file, so even if
337 mmap isn't disabled don't use it unless the file is large enough */
338 use_mmap = (index->flags & MAIL_INDEX_OPEN_FLAG_MMAP_DISABLE) == 0 &&
339 file_size != UOFF_T_MAX && file_size > MAIL_INDEX_MMAP_MIN_SIZE;
340
341 new_map = mail_index_map_alloc(index);
342 if (use_mmap) {
343 ret = mail_index_mmap(new_map, file_size);
344 } else {
345 ret = mail_index_read_map(new_map, file_size);
346 }
347 if (ret == 0) {
348 /* the index files are unusable */
349 unusable = TRUE;
350 }
351
352 for (try = 0; ret > 0; try++) {
353 /* make sure the header is ok before using this mapping */
354 ret = mail_index_map_check_header(new_map, &error);
355 if (ret < 0) {
356 mail_index_set_error(index,
357 "Corrupted index file %s: %s",
358 index->filepath, error);
359 }
360 if (ret > 0) T_BEGIN {
361 if (mail_index_map_parse_extensions(new_map) < 0)
362 ret = 0;
363 else if (mail_index_map_parse_keywords(new_map) < 0)
364 ret = 0;
365 } T_END;
366 if (ret != 0 || try == 2) {
367 if (ret < 0) {
368 *reason_r = "Corrupted index file";
369 unusable = TRUE;
370 ret = 0;
371 }
372 break;
373 }
374
375 /* fsck and try again */
376 old_map = index->map;
377 index->map = new_map;
378 if (mail_index_fsck(index) < 0) {
379 ret = -1;
380 break;
381 }
382
383 /* fsck replaced the map */
384 new_map = index->map;
385 index->map = old_map;
386 }
387 if (ret <= 0) {
388 mail_index_unmap(&new_map);
389 return ret < 0 ? -1 : (unusable ? 0 : 1);
390 }
391 i_assert(new_map->rec_map->records != NULL);
392
393 index->main_index_hdr_log_file_seq = new_map->hdr.log_file_seq;
394 index->main_index_hdr_log_file_tail_offset =
395 new_map->hdr.log_file_tail_offset;
396
397 mail_index_unmap(&index->map);
398 index->map = new_map;
399 *reason_r = t_strdup_printf("Index mapped (file_seq=%u)",
400 index->map->hdr.log_file_seq);
401 return 1;
402 }
403
404 static int
mail_index_map_latest_sync(struct mail_index * index,enum mail_index_sync_handler_type type,const char * reason)405 mail_index_map_latest_sync(struct mail_index *index,
406 enum mail_index_sync_handler_type type,
407 const char *reason)
408 {
409 const char *map_reason, *reopen_reason;
410 bool reopened;
411 int ret;
412
413 if (index->log->head == NULL || index->indexid == 0) {
414 /* we're creating the index file, we don't have any
415 logs yet */
416 return 1;
417 }
418
419 /* and update the map with the latest changes from transaction log */
420 ret = mail_index_sync_map(&index->map, type, &map_reason);
421 if (ret != 0)
422 return ret;
423
424 if (index->fd == -1) {
425 reopen_reason = "Index not open";
426 reopened = FALSE;
427 } else {
428 /* Check if the index was recreated while we were opening it.
429 This is unlikely, but could happen if
430 mail_index_log_optimization_settings.max_size is tiny. */
431 ret = mail_index_reopen_if_changed(index, &reopened, &reopen_reason);
432 if (ret < 0)
433 return -1;
434 if (ret == 0) {
435 /* Index was unexpectedly lost. The mailbox was
436 probably deleted while we were opening it. Handle
437 this as an error. */
438 index->index_deleted = TRUE;
439 return -1;
440 }
441 }
442 if (!reopened) {
443 /* fsck the index and try to reopen */
444 mail_index_set_error(index, "Index %s: %s: %s - fscking "
445 "(reopen_reason: %s)",
446 index->filepath, reason, map_reason,
447 reopen_reason);
448 if (!index->readonly) {
449 if (mail_index_fsck(index) < 0)
450 return -1;
451 }
452 }
453
454 ret = mail_index_map_latest_file(index, &reason);
455 if (ret > 0 && index->indexid != 0) {
456 ret = mail_index_sync_map(&index->map, type, &map_reason);
457 if (ret == 0) {
458 mail_index_set_error(index, "Index %s: %s: %s",
459 index->filepath, reason, map_reason);
460 }
461 }
462 return ret;
463 }
464
mail_index_map(struct mail_index * index,enum mail_index_sync_handler_type type)465 int mail_index_map(struct mail_index *index,
466 enum mail_index_sync_handler_type type)
467 {
468 const char *reason;
469 int ret;
470
471 i_assert(!index->mapping);
472
473 index->mapping = TRUE;
474
475 if (index->map == NULL)
476 index->map = mail_index_map_alloc(index);
477
478 /* first try updating the existing mapping from transaction log. */
479 if (!index->initial_mapped || index->reopen_main_index) {
480 /* index is being created/opened for the first time */
481 ret = 0;
482 } else if (mail_index_sync_map_want_index_reopen(index->map, type)) {
483 /* it's likely more efficient to reopen the index file than
484 sync from the transaction log. */
485 ret = 0;
486 } else {
487 /* sync the map from the transaction log. */
488 ret = mail_index_sync_map(&index->map, type, &reason);
489 if (ret == 0) {
490 e_debug(index->event,
491 "Couldn't sync map from transaction log: %s - "
492 "reopening index instead",
493 reason);
494 }
495 }
496
497 if (ret == 0) {
498 /* try to open and read the latest index. if it fails, we'll
499 fallback to updating the existing mapping from transaction
500 logs (which we'll also do even if the reopening succeeds).
501 if index files are unusable (e.g. major version change)
502 don't even try to use the transaction log. */
503 ret = mail_index_map_latest_file(index, &reason);
504 if (ret > 0) {
505 ret = mail_index_map_latest_sync(index, type, reason);
506 } else if (ret == 0 && !index->readonly) {
507 /* make sure we don't try to open the file again */
508 if (unlink(index->filepath) < 0 && errno != ENOENT)
509 mail_index_set_syscall_error(index, "unlink()");
510 }
511 }
512
513 if (ret >= 0)
514 index->initial_mapped = TRUE;
515 index->mapping = FALSE;
516 return ret;
517 }
518