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