1 /* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
2 
3 #include "lib.h"
4 #include "str.h"
5 #include "strescape.h"
6 #include "net.h"
7 #include "write-full.h"
8 #include "mail-search-build.h"
9 #include "index-storage.h"
10 #include "index-mailbox-size.h"
11 
12 /*
13    Saving new mails: After transaction is committed and synced, trigger
14    vsize updating. Lock vsize updates. Check if the message count +
15    last-indexed-uid are still valid. If they are, add all the missing new
16    mails. Unlock.
17 
18    Fetching vsize: Lock vsize updates. Check if the message count +
19    last-indexed-uid are still valid. If not, set them to zero. Add all
20    the missing mails. Unlock.
21 
22    Expunging mails: Check if syncing would expunge any mails. If so, lock the
23    vsize updates before locking syncing (to avoid deadlocks). Check if the
24    message count + last-indexed-uid are still valid. If not, unlock vsize and
25    do nothing else. Otherwise, for each expunged mail whose UID <=
26    last-indexed-uid, decrease the message count and the vsize in memory. After
27    syncing is successfully committed, write the changes to header. Unlock.
28 
29    Note that the final expunge handling with some mailbox formats is done while
30    syncing is no longer locked. Because of this we need to have the vsize
31    locking. The final vsize header update requires committing a transaction,
32    which internally is the same as a sync lock. So to avoid deadlocks we always
33    need to lock vsize updates before sync.
34 */
35 
36 #define VSIZE_LOCK_SUFFIX "dovecot-vsize.lock"
37 #define VSIZE_UPDATE_MAX_LOCK_SECS 10
38 
39 #define INDEXER_SOCKET_NAME "indexer"
40 #define INDEXER_HANDSHAKE "VERSION\tindexer\t1\t0\n"
41 
42 struct mailbox_vsize_update {
43 	struct mailbox *box;
44 	struct mail_index_view *view;
45 	struct mailbox_index_vsize vsize_hdr, orig_vsize_hdr;
46 
47 	struct file_lock *lock;
48 	bool lock_failed;
49 	bool skip_write;
50 	bool rebuild;
51 	bool written;
52 	bool finish_in_background;
53 };
54 
vsize_header_refresh(struct mailbox_vsize_update * update)55 static void vsize_header_refresh(struct mailbox_vsize_update *update)
56 {
57 	const void *data;
58 	size_t size;
59 
60 	if (update->view != NULL)
61 		mail_index_view_close(&update->view);
62 	(void)mail_index_refresh(update->box->index);
63 	update->view = mail_index_view_open(update->box->index);
64 
65 	mail_index_get_header_ext(update->view, update->box->vsize_hdr_ext_id,
66 				  &data, &size);
67 	if (size > 0) {
68 		memcpy(&update->orig_vsize_hdr, data,
69 		       I_MIN(size, sizeof(update->orig_vsize_hdr)));
70 	}
71 	if (size == sizeof(update->vsize_hdr))
72 		memcpy(&update->vsize_hdr, data, sizeof(update->vsize_hdr));
73 	else {
74 		if (size != 0) {
75 			mailbox_set_critical(update->box,
76 				"vsize-hdr has invalid size: %zu",
77 				size);
78 		}
79 		update->rebuild = TRUE;
80 		i_zero(&update->vsize_hdr);
81 	}
82 }
83 
84 static void
index_mailbox_vsize_check_rebuild(struct mailbox_vsize_update * update)85 index_mailbox_vsize_check_rebuild(struct mailbox_vsize_update *update)
86 {
87 	uint32_t seq1, seq2;
88 
89 	if (update->vsize_hdr.highest_uid == 0)
90 		return;
91 	if (!mail_index_lookup_seq_range(update->view, 1,
92 					 update->vsize_hdr.highest_uid,
93 					 &seq1, &seq2))
94 		seq2 = 0;
95 
96 	if (update->vsize_hdr.message_count != seq2) {
97 		if (update->vsize_hdr.message_count < seq2) {
98 			mailbox_set_critical(update->box,
99 				"vsize-hdr has invalid message-count (%u < %u)",
100 				update->vsize_hdr.message_count, seq2);
101 		} else {
102 			/* some messages have been expunged, rescan */
103 		}
104 		i_zero(&update->vsize_hdr);
105 		update->rebuild = TRUE;
106 	}
107 }
108 
109 struct mailbox_vsize_update *
index_mailbox_vsize_update_init(struct mailbox * box)110 index_mailbox_vsize_update_init(struct mailbox *box)
111 {
112 	struct mailbox_vsize_update *update;
113 
114 	i_assert(box->opened);
115 
116 	update = i_new(struct mailbox_vsize_update, 1);
117 	update->box = box;
118 
119 	vsize_header_refresh(update);
120 	return update;
121 }
122 
vsize_update_lock_full(struct mailbox_vsize_update * update,unsigned int lock_secs)123 static bool vsize_update_lock_full(struct mailbox_vsize_update *update,
124 				   unsigned int lock_secs)
125 {
126 	struct mailbox *box = update->box;
127 	const char *error;
128 	int ret;
129 
130 	if (update->lock != NULL)
131 		return TRUE;
132 	if (update->lock_failed)
133 		return FALSE;
134 	if (MAIL_INDEX_IS_IN_MEMORY(box->index))
135 		return FALSE;
136 
137 	ret = mailbox_lock_file_create(box, VSIZE_LOCK_SUFFIX, lock_secs,
138 				       &update->lock, &error);
139 	if (ret <= 0) {
140 		/* don't log lock timeouts, because we're somewhat expecting
141 		   them. Especially when lock_secs is 0. */
142 		if (ret < 0)
143 			mailbox_set_critical(box, "%s", error);
144 		update->lock_failed = TRUE;
145 		return FALSE;
146 	}
147 	update->rebuild = FALSE;
148 	vsize_header_refresh(update);
149 	index_mailbox_vsize_check_rebuild(update);
150 	return TRUE;
151 }
152 
index_mailbox_vsize_update_try_lock(struct mailbox_vsize_update * update)153 bool index_mailbox_vsize_update_try_lock(struct mailbox_vsize_update *update)
154 {
155 	return vsize_update_lock_full(update, 0);
156 }
157 
index_mailbox_vsize_update_wait_lock(struct mailbox_vsize_update * update)158 bool index_mailbox_vsize_update_wait_lock(struct mailbox_vsize_update *update)
159 {
160 	return vsize_update_lock_full(update, VSIZE_UPDATE_MAX_LOCK_SECS);
161 }
162 
index_mailbox_vsize_want_updates(struct mailbox_vsize_update * update)163 bool index_mailbox_vsize_want_updates(struct mailbox_vsize_update *update)
164 {
165 	return update->vsize_hdr.highest_uid > 0;
166 }
167 
168 static void
index_mailbox_vsize_update_write_to_index(struct mailbox_vsize_update * update)169 index_mailbox_vsize_update_write_to_index(struct mailbox_vsize_update *update)
170 {
171 	struct mail_index_transaction *trans;
172 
173 	trans = mail_index_transaction_begin(update->view,
174 				MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL);
175 	mail_index_update_header_ext(trans, update->box->vsize_hdr_ext_id,
176 				     0, &update->vsize_hdr,
177 				     sizeof(update->vsize_hdr));
178 	(void)mail_index_transaction_commit(&trans);
179 }
180 
181 static void
index_mailbox_vsize_update_write(struct mailbox_vsize_update * update)182 index_mailbox_vsize_update_write(struct mailbox_vsize_update *update)
183 {
184 	if (update->written)
185 		return;
186 	update->written = TRUE;
187 
188 	if (update->rebuild == FALSE &&
189 	    memcmp(&update->orig_vsize_hdr, &update->vsize_hdr,
190 		   sizeof(update->vsize_hdr)) == 0) {
191 		/* no changes */
192 		return;
193 	}
194 	index_mailbox_vsize_update_write_to_index(update);
195 }
196 
index_mailbox_vsize_notify_indexer(struct mailbox * box)197 static void index_mailbox_vsize_notify_indexer(struct mailbox *box)
198 {
199 	string_t *str = t_str_new(256);
200 	const char *path;
201 	int fd;
202 
203 	path = t_strconcat(box->storage->user->set->base_dir,
204 			   "/"INDEXER_SOCKET_NAME, NULL);
205 	fd = net_connect_unix(path);
206 	if (fd == -1) {
207 		mailbox_set_critical(box,
208 			"Can't start vsize building on background: "
209 			"net_connect_unix(%s) failed: %m", path);
210 		return;
211 	}
212 	str_append(str, INDEXER_HANDSHAKE);
213 	str_append(str, "APPEND\t0\t");
214 	str_append_tabescaped(str, box->storage->user->username);
215 	str_append_c(str, '\t');
216 	str_append_tabescaped(str, box->vname);
217 	str_append_c(str, '\n');
218 
219 	if (write_full(fd, str_data(str), str_len(str)) < 0) {
220 		mailbox_set_critical(box,
221 			"Can't start vsize building on background: "
222 			"write(%s) failed: %m", path);
223 	}
224 	i_close_fd(&fd);
225 }
226 
index_mailbox_vsize_update_deinit(struct mailbox_vsize_update ** _update)227 void index_mailbox_vsize_update_deinit(struct mailbox_vsize_update **_update)
228 {
229 	struct mailbox_vsize_update *update = *_update;
230 
231 	*_update = NULL;
232 
233 	if ((update->lock != NULL || update->rebuild) && !update->skip_write)
234 		index_mailbox_vsize_update_write(update);
235 	file_lock_free(&update->lock);
236 	if (update->finish_in_background)
237 		index_mailbox_vsize_notify_indexer(update->box);
238 
239 	mail_index_view_close(&update->view);
240 	i_free(update);
241 }
242 
index_mailbox_vsize_hdr_expunge(struct mailbox_vsize_update * update,uint32_t uid,uoff_t vsize)243 void index_mailbox_vsize_hdr_expunge(struct mailbox_vsize_update *update,
244 				     uint32_t uid, uoff_t vsize)
245 {
246 	i_assert(update->lock != NULL);
247 
248 	if (uid > update->vsize_hdr.highest_uid)
249 		return;
250 	if (update->vsize_hdr.message_count == 0) {
251 		mailbox_set_critical(update->box,
252 			"vsize-hdr's message_count shrank below 0");
253 		i_zero(&update->vsize_hdr);
254 		return;
255 	}
256 	update->vsize_hdr.message_count--;
257 	if (update->vsize_hdr.vsize < vsize) {
258 		mailbox_set_critical(update->box,
259 			"vsize-hdr's vsize shrank below 0");
260 		i_zero(&update->vsize_hdr);
261 		return;
262 	}
263 	update->vsize_hdr.vsize -= vsize;
264 }
265 
266 static int
index_mailbox_vsize_hdr_add_missing(struct mailbox_vsize_update * update,bool require_result)267 index_mailbox_vsize_hdr_add_missing(struct mailbox_vsize_update *update,
268 				    bool require_result)
269 {
270 	struct mailbox_index_vsize *vsize_hdr = &update->vsize_hdr;
271 	struct mailbox_transaction_context *trans;
272 	struct mail_search_context *search_ctx;
273 	struct mail_search_args *search_args;
274 	struct mailbox_status status;
275 	struct mail *mail;
276 	unsigned int idx, mails_left;
277 	uint32_t seq1, seq2;
278 	uoff_t vsize;
279 	int ret = 0;
280 
281 	mailbox_get_open_status(update->box, STATUS_UIDNEXT, &status);
282 	if (vsize_hdr->highest_uid + 1 >= status.uidnext) {
283 		/* nothing to do - we should have usually caught this already
284 		   before locking */
285 		return 0;
286 	}
287 
288 	/* note that update->view may be more up-to-date than box->view.
289 	   we'll just add whatever new mails are in box->view. if we'll notice
290 	   that some of the new mails are missing, we'll need to stop there
291 	   since that expunge will be applied later on to the vsize header. */
292 	search_args = mail_search_build_init();
293 	if (!mail_index_lookup_seq_range(update->box->view,
294 					 vsize_hdr->highest_uid + 1,
295 					 status.uidnext-1, &seq1, &seq2)) {
296 		/* nothing existed, but update uidnext */
297 		vsize_hdr->highest_uid = status.uidnext - 1;
298 		mail_search_args_unref(&search_args);
299 		return 0;
300 	}
301 	mail_search_build_add_seqset(search_args, seq1, seq2);
302 
303 	if (!mail_index_map_get_ext_idx(update->box->view->map,
304 					update->box->vsize_hdr_ext_id, &idx)) {
305 		/* vsize header doesn't exist yet. Create it here early so
306 		   that vsize mail records get created (instead of adding
307 		   size.virtuals to cache). */
308 		index_mailbox_vsize_update_write_to_index(update);
309 	}
310 
311 	trans = mailbox_transaction_begin(update->box, 0, "vsize update");
312 	search_ctx = mailbox_search_init(trans, search_args, NULL,
313 					 MAIL_FETCH_VIRTUAL_SIZE, NULL);
314 	if (!require_result)
315 		mails_left = 0;
316 	else if (update->box->storage->set->mail_vsize_bg_after_count == 0)
317 		mails_left = UINT_MAX;
318 	else
319 		mails_left = update->box->storage->set->mail_vsize_bg_after_count;
320 
321 	while (mailbox_search_next(search_ctx, &mail)) {
322 		if (mails_left == 0) {
323 			/* if there are any more mails whose vsize can't be
324 			   looked up from cache, abort and finish on
325 			   background. */
326 			mail->lookup_abort = MAIL_LOOKUP_ABORT_NOT_IN_CACHE;
327 		}
328 		ret = mail_get_virtual_size(mail, &vsize);
329 		mail->lookup_abort = MAIL_LOOKUP_ABORT_NEVER;
330 
331 		if (ret < 0 &&
332 		    mailbox_get_last_mail_error(update->box) == MAIL_ERROR_LOOKUP_ABORTED) {
333 			/* abort and finish on background */
334 			i_assert(mails_left == 0);
335 
336 			mail_storage_set_error(update->box->storage, MAIL_ERROR_INUSE,
337 				"Finishing vsize calculation on background");
338 			if (require_result)
339 				update->finish_in_background = TRUE;
340 			break;
341 		}
342 		if (mail->mail_stream_opened || mail->mail_metadata_accessed) {
343 			/* slow vsize lookup */
344 			i_assert(mails_left > 0);
345 			mails_left--;
346 		}
347 
348 		if (ret < 0) {
349 			if (mail->expunged)
350 				continue;
351 			ret = -1;
352 			break;
353 		}
354 		vsize_hdr->vsize += vsize;
355 		vsize_hdr->highest_uid = mail->uid;
356 		vsize_hdr->message_count++;
357 	}
358 	if (mailbox_search_deinit(&search_ctx) < 0)
359 		ret = -1;
360 	mail_search_args_unref(&search_args);
361 
362 	if (ret == 0) {
363 		/* success, cache all */
364 		vsize_hdr->highest_uid = status.uidnext - 1;
365 	} else {
366 		/* search failed, cache only up to highest seen uid */
367 	}
368 	(void)mailbox_transaction_commit(&trans);
369 	return ret;
370 }
371 
index_mailbox_get_virtual_size(struct mailbox * box,struct mailbox_metadata * metadata_r)372 int index_mailbox_get_virtual_size(struct mailbox *box,
373 				   struct mailbox_metadata *metadata_r)
374 {
375 	struct mailbox_vsize_update *update;
376 	struct mailbox_status status;
377 	int ret;
378 
379 	mailbox_get_open_status(box, STATUS_MESSAGES | STATUS_UIDNEXT, &status);
380 	update = index_mailbox_vsize_update_init(box);
381 	if (update->vsize_hdr.highest_uid + 1 == status.uidnext &&
382 	    update->vsize_hdr.message_count == status.messages) {
383 		/* up to date */
384 		metadata_r->virtual_size = update->vsize_hdr.vsize;
385 		index_mailbox_vsize_update_deinit(&update);
386 		return 0;
387 	}
388 
389 	/* we need to update it - lock it if possible. if not, update it
390 	   anyway internally even though we won't be saving the result. */
391 	(void)index_mailbox_vsize_update_wait_lock(update);
392 
393 	ret = index_mailbox_vsize_hdr_add_missing(update, TRUE);
394 	metadata_r->virtual_size = update->vsize_hdr.vsize;
395 	index_mailbox_vsize_update_deinit(&update);
396 	return ret;
397 }
398 
index_mailbox_get_physical_size(struct mailbox * box,struct mailbox_metadata * metadata_r)399 int index_mailbox_get_physical_size(struct mailbox *box,
400 				    struct mailbox_metadata *metadata_r)
401 {
402 	struct mailbox_transaction_context *trans;
403 	struct mail_search_context *ctx;
404 	struct mail *mail;
405 	struct mail_search_args *search_args;
406 	uoff_t size;
407 	int ret = 0;
408 
409 	/* if physical size = virtual size always for the storage, we can
410 	   use the optimized vsize code for this */
411 	if (box->mail_vfuncs->get_physical_size ==
412 	    box->mail_vfuncs->get_virtual_size) {
413 		if (index_mailbox_get_virtual_size(box, metadata_r) < 0)
414 			return -1;
415 		metadata_r->physical_size = metadata_r->virtual_size;
416 		return 0;
417 	}
418 	/* do it the slow way (we could implement similar logic as for vsize,
419 	   but for now it's not really needed) */
420 	if (mailbox_sync(box, MAILBOX_SYNC_FLAG_FULL_READ) < 0)
421 		return -1;
422 
423 	trans = mailbox_transaction_begin(box, 0, "mailbox physical size");
424 
425 	search_args = mail_search_build_init();
426 	mail_search_build_add_all(search_args);
427 	ctx = mailbox_search_init(trans, search_args, NULL,
428 				  MAIL_FETCH_PHYSICAL_SIZE, NULL);
429 	mail_search_args_unref(&search_args);
430 
431 	metadata_r->physical_size = 0;
432 	while (mailbox_search_next(ctx, &mail)) {
433 		if (mail_get_physical_size(mail, &size) == 0)
434 			metadata_r->physical_size += size;
435 		else {
436 			const char *errstr;
437 			enum mail_error error;
438 
439 			errstr = mailbox_get_last_internal_error(box, &error);
440 			if (error != MAIL_ERROR_EXPUNGED) {
441 				i_error("Couldn't get size of mail UID %u in %s: %s",
442 					mail->uid, box->vname, errstr);
443 				ret = -1;
444 				break;
445 			}
446 		}
447 	}
448 	if (mailbox_search_deinit(&ctx) < 0) {
449 		i_error("Listing mails in %s failed: %s",
450 			box->vname, mailbox_get_last_internal_error(box, NULL));
451 		ret = -1;
452 	}
453 	(void)mailbox_transaction_commit(&trans);
454 	return ret;
455 }
456 
index_mailbox_vsize_update_appends(struct mailbox * box)457 void index_mailbox_vsize_update_appends(struct mailbox *box)
458 {
459 	struct mailbox_vsize_update *update;
460 	struct mailbox_status status;
461 
462 	update = index_mailbox_vsize_update_init(box);
463 	if (update->rebuild) {
464 		/* The vsize header doesn't exist. Don't create it. */
465 		update->skip_write = TRUE;
466 	}
467 
468 	/* update here only if we don't need to rebuild the whole vsize. */
469 	index_mailbox_vsize_check_rebuild(update);
470 	if (index_mailbox_vsize_want_updates(update)) {
471 		/* Get the UIDNEXT only after checking that vsize updating is
472 		   even potentially wanted for this mailbox. We especially
473 		   don't want to do this with imapc, because it could trigger
474 		   a remote STATUS (UIDNEXT) call. */
475 		mailbox_get_open_status(update->box, STATUS_UIDNEXT, &status);
476 		if (update->vsize_hdr.highest_uid + 1 != status.uidnext &&
477 		    index_mailbox_vsize_update_try_lock(update))
478 			(void)index_mailbox_vsize_hdr_add_missing(update, FALSE);
479 	}
480 	index_mailbox_vsize_update_deinit(&update);
481 }
482