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