1 /* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */
2
3 /*
4 Version 1 format has been used for most versions of Dovecot up to v1.0.x.
5 It's also compatible with Courier IMAP's courierimapuiddb file.
6 The format is:
7
8 header: 1 <uid validity> <next uid>
9 entry: <uid> <filename>
10
11 --
12
13 Version 2 format was written by a few development Dovecot versions, but
14 v1.0.x still parses the format. The format has <flags> field after <uid>.
15
16 --
17
18 Version 3 format is an extensible format used by Dovecot v1.1 and later.
19 It's also parsed by v1.0.2 (and later). The format is:
20
21 header: 3 [<key><value> ...]
22 entry: <uid> [<key><value> ...] :<filename>
23
24 See enum maildir_uidlist_*_ext_key for used keys.
25 */
26
27 #include "lib.h"
28 #include "array.h"
29 #include "hash.h"
30 #include "istream.h"
31 #include "ostream.h"
32 #include "str.h"
33 #include "file-dotlock.h"
34 #include "nfs-workarounds.h"
35 #include "eacces-error.h"
36 #include "maildir-storage.h"
37 #include "maildir-filename.h"
38 #include "maildir-uidlist.h"
39
40 #include <stdio.h>
41 #include <sys/stat.h>
42
43 /* NFS: How many times to retry reading dovecot-uidlist file if ESTALE
44 error occurs in the middle of reading it */
45 #define UIDLIST_ESTALE_RETRY_COUNT NFS_ESTALE_RETRY_COUNT
46
47 #define UIDLIST_VERSION 3
48 #define UIDLIST_COMPRESS_PERCENTAGE 75
49
50 #define UIDLIST_IS_LOCKED(uidlist) \
51 ((uidlist)->lock_count > 0)
52
53 struct maildir_uidlist_rec {
54 uint32_t uid;
55 uint32_t flags;
56 char *filename;
57 unsigned char *extensions; /* <data>\0[<data>\0 ...]\0 */
58 };
59 ARRAY_DEFINE_TYPE(maildir_uidlist_rec_p, struct maildir_uidlist_rec *);
60
61 HASH_TABLE_DEFINE_TYPE(path_to_maildir_uidlist_rec,
62 char *, struct maildir_uidlist_rec *);
63
64 struct maildir_uidlist {
65 struct mailbox *box;
66 char *path;
67 struct maildir_index_header *mhdr;
68
69 int fd;
70 dev_t fd_dev;
71 ino_t fd_ino;
72 off_t fd_size;
73
74 unsigned int lock_count;
75
76 struct dotlock_settings dotlock_settings;
77 struct dotlock *dotlock;
78
79 pool_t record_pool;
80 ARRAY_TYPE(maildir_uidlist_rec_p) records;
81 HASH_TABLE_TYPE(path_to_maildir_uidlist_rec) files;
82 unsigned int change_counter;
83
84 unsigned int version;
85 unsigned int uid_validity, next_uid, prev_read_uid, last_seen_uid;
86 unsigned int hdr_next_uid;
87 unsigned int read_records_count, read_line_count;
88 uoff_t last_read_offset;
89 string_t *hdr_extensions;
90
91 guid_128_t mailbox_guid;
92
93 bool recreate:1;
94 bool recreate_on_change:1;
95 bool initial_read:1;
96 bool initial_hdr_read:1;
97 bool retry_rewind:1;
98 bool locked_refresh:1;
99 bool unsorted:1;
100 bool have_mailbox_guid:1;
101 bool opened_readonly:1;
102 };
103
104 struct maildir_uidlist_sync_ctx {
105 struct maildir_uidlist *uidlist;
106 enum maildir_uidlist_sync_flags sync_flags;
107
108 pool_t record_pool;
109 ARRAY_TYPE(maildir_uidlist_rec_p) records;
110 HASH_TABLE_TYPE(path_to_maildir_uidlist_rec) files;
111
112 unsigned int first_unwritten_pos, first_new_pos;
113 unsigned int new_files_count;
114 unsigned int finish_change_counter;
115
116 bool partial:1;
117 bool finished:1;
118 bool changed:1;
119 bool failed:1;
120 bool locked:1;
121 };
122
123 struct maildir_uidlist_iter_ctx {
124 struct maildir_uidlist *uidlist;
125 struct maildir_uidlist_rec *const *next, *const *end;
126
127 unsigned int change_counter;
128 uint32_t prev_uid;
129 };
130
131 static int maildir_uidlist_open_latest(struct maildir_uidlist *uidlist);
132 static bool maildir_uidlist_iter_next_rec(struct maildir_uidlist_iter_ctx *ctx,
133 struct maildir_uidlist_rec **rec_r);
134
maildir_uidlist_lock_timeout(struct maildir_uidlist * uidlist,bool nonblock,bool refresh,bool refresh_when_locked)135 static int maildir_uidlist_lock_timeout(struct maildir_uidlist *uidlist,
136 bool nonblock, bool refresh,
137 bool refresh_when_locked)
138 {
139 struct mailbox *box = uidlist->box;
140 const struct mailbox_permissions *perm = mailbox_get_permissions(box);
141 const char *path = uidlist->path;
142 mode_t old_mask;
143 const enum dotlock_create_flags dotlock_flags =
144 nonblock ? DOTLOCK_CREATE_FLAG_NONBLOCK : 0;
145 int i, ret;
146
147 if (uidlist->lock_count > 0) {
148 if (!uidlist->locked_refresh && refresh_when_locked) {
149 if (maildir_uidlist_refresh(uidlist) < 0)
150 return -1;
151 }
152 uidlist->lock_count++;
153 return 1;
154 }
155
156 index_storage_lock_notify_reset(box);
157
158 for (i = 0;; i++) {
159 old_mask = umask(0777 & ~perm->file_create_mode);
160 ret = file_dotlock_create(&uidlist->dotlock_settings, path,
161 dotlock_flags, &uidlist->dotlock);
162 umask(old_mask);
163 if (ret > 0)
164 break;
165
166 /* failure */
167 if (ret == 0) {
168 mail_storage_set_error(box->storage,
169 MAIL_ERROR_TEMP, MAIL_ERRSTR_LOCK_TIMEOUT);
170 return 0;
171 }
172 if (errno != ENOENT || i == MAILDIR_DELETE_RETRY_COUNT) {
173 if (errno == EACCES) {
174 mailbox_set_critical(box, "%s",
175 eacces_error_get_creating("file_dotlock_create", path));
176 } else {
177 mailbox_set_critical(box,
178 "file_dotlock_create(%s) failed: %m",
179 path);
180 }
181 return -1;
182 }
183 /* the control dir doesn't exist. create it unless the whole
184 mailbox was just deleted. */
185 if (!maildir_set_deleted(uidlist->box))
186 return -1;
187 }
188
189 uidlist->lock_count++;
190 uidlist->locked_refresh = FALSE;
191
192 if (refresh) {
193 /* make sure we have the latest changes before
194 changing anything */
195 if (maildir_uidlist_refresh(uidlist) < 0) {
196 maildir_uidlist_unlock(uidlist);
197 return -1;
198 }
199 }
200 return 1;
201 }
202
maildir_uidlist_lock(struct maildir_uidlist * uidlist)203 int maildir_uidlist_lock(struct maildir_uidlist *uidlist)
204 {
205 return maildir_uidlist_lock_timeout(uidlist, FALSE, TRUE, FALSE);
206 }
207
maildir_uidlist_try_lock(struct maildir_uidlist * uidlist)208 int maildir_uidlist_try_lock(struct maildir_uidlist *uidlist)
209 {
210 return maildir_uidlist_lock_timeout(uidlist, TRUE, TRUE, FALSE);
211 }
212
maildir_uidlist_lock_touch(struct maildir_uidlist * uidlist)213 int maildir_uidlist_lock_touch(struct maildir_uidlist *uidlist)
214 {
215 i_assert(UIDLIST_IS_LOCKED(uidlist));
216
217 return file_dotlock_touch(uidlist->dotlock);
218 }
219
maildir_uidlist_is_locked(struct maildir_uidlist * uidlist)220 bool maildir_uidlist_is_locked(struct maildir_uidlist *uidlist)
221 {
222 return UIDLIST_IS_LOCKED(uidlist);
223 }
224
maildir_uidlist_is_read(struct maildir_uidlist * uidlist)225 bool maildir_uidlist_is_read(struct maildir_uidlist *uidlist)
226 {
227 return uidlist->initial_read;
228 }
229
maildir_uidlist_is_open(struct maildir_uidlist * uidlist)230 bool maildir_uidlist_is_open(struct maildir_uidlist *uidlist)
231 {
232 return uidlist->fd != -1;
233 }
234
maildir_uidlist_unlock(struct maildir_uidlist * uidlist)235 void maildir_uidlist_unlock(struct maildir_uidlist *uidlist)
236 {
237 i_assert(uidlist->lock_count > 0);
238
239 if (--uidlist->lock_count > 0)
240 return;
241
242 uidlist->locked_refresh = FALSE;
243 file_dotlock_delete(&uidlist->dotlock);
244 }
245
dotlock_callback(unsigned int secs_left,bool stale,void * context)246 static bool dotlock_callback(unsigned int secs_left, bool stale, void *context)
247 {
248 struct mailbox *box = context;
249
250 index_storage_lock_notify(box, stale ?
251 MAILBOX_LOCK_NOTIFY_MAILBOX_OVERRIDE :
252 MAILBOX_LOCK_NOTIFY_MAILBOX_ABORT,
253 secs_left);
254 return TRUE;
255 }
256
maildir_uidlist_init(struct maildir_mailbox * mbox)257 struct maildir_uidlist *maildir_uidlist_init(struct maildir_mailbox *mbox)
258 {
259 struct mailbox *box = &mbox->box;
260 struct maildir_uidlist *uidlist;
261 const char *control_dir;
262
263 if (mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_CONTROL,
264 &control_dir) <= 0)
265 i_unreached();
266
267 uidlist = i_new(struct maildir_uidlist, 1);
268 uidlist->box = box;
269 uidlist->mhdr = &mbox->maildir_hdr;
270 uidlist->fd = -1;
271 uidlist->path = i_strconcat(control_dir, "/"MAILDIR_UIDLIST_NAME, NULL);
272 i_array_init(&uidlist->records, 128);
273 hash_table_create(&uidlist->files, default_pool, 4096,
274 maildir_filename_base_hash,
275 maildir_filename_base_cmp);
276 uidlist->next_uid = 1;
277 uidlist->hdr_extensions = str_new(default_pool, 128);
278
279 uidlist->dotlock_settings.use_io_notify = TRUE;
280 uidlist->dotlock_settings.use_excl_lock =
281 box->storage->set->dotlock_use_excl;
282 uidlist->dotlock_settings.nfs_flush =
283 box->storage->set->mail_nfs_storage;
284 uidlist->dotlock_settings.timeout =
285 mail_storage_get_lock_timeout(box->storage,
286 MAILDIR_UIDLIST_LOCK_STALE_TIMEOUT + 2);
287 uidlist->dotlock_settings.stale_timeout =
288 MAILDIR_UIDLIST_LOCK_STALE_TIMEOUT;
289 uidlist->dotlock_settings.callback = dotlock_callback;
290 uidlist->dotlock_settings.context = box;
291 uidlist->dotlock_settings.temp_prefix = mbox->storage->temp_prefix;
292 return uidlist;
293 }
294
maildir_uidlist_close(struct maildir_uidlist * uidlist)295 static void maildir_uidlist_close(struct maildir_uidlist *uidlist)
296 {
297 if (uidlist->fd != -1) {
298 if (close(uidlist->fd) < 0) {
299 mailbox_set_critical(uidlist->box,
300 "close(%s) failed: %m", uidlist->path);
301 }
302 uidlist->fd = -1;
303 uidlist->fd_ino = 0;
304 }
305 uidlist->last_read_offset = 0;
306 uidlist->read_line_count = 0;
307 }
308
maildir_uidlist_reset(struct maildir_uidlist * uidlist)309 static void maildir_uidlist_reset(struct maildir_uidlist *uidlist)
310 {
311 maildir_uidlist_close(uidlist);
312 uidlist->last_seen_uid = 0;
313 uidlist->initial_hdr_read = FALSE;
314 uidlist->read_records_count = 0;
315
316 hash_table_clear(uidlist->files, FALSE);
317 array_clear(&uidlist->records);
318 }
319
maildir_uidlist_deinit(struct maildir_uidlist ** _uidlist)320 void maildir_uidlist_deinit(struct maildir_uidlist **_uidlist)
321 {
322 struct maildir_uidlist *uidlist = *_uidlist;
323
324 i_assert(!UIDLIST_IS_LOCKED(uidlist));
325
326 *_uidlist = NULL;
327 (void)maildir_uidlist_update(uidlist);
328 maildir_uidlist_close(uidlist);
329
330 hash_table_destroy(&uidlist->files);
331 pool_unref(&uidlist->record_pool);
332
333 array_free(&uidlist->records);
334 str_free(&uidlist->hdr_extensions);
335 i_free(uidlist->path);
336 i_free(uidlist);
337 }
338
maildir_uid_cmp(struct maildir_uidlist_rec * const * rec1,struct maildir_uidlist_rec * const * rec2)339 static int maildir_uid_cmp(struct maildir_uidlist_rec *const *rec1,
340 struct maildir_uidlist_rec *const *rec2)
341 {
342 return (*rec1)->uid < (*rec2)->uid ? -1 :
343 (*rec1)->uid > (*rec2)->uid ? 1 : 0;
344 }
345
346 static void ATTR_FORMAT(2, 3)
maildir_uidlist_set_corrupted(struct maildir_uidlist * uidlist,const char * fmt,...)347 maildir_uidlist_set_corrupted(struct maildir_uidlist *uidlist,
348 const char *fmt, ...)
349 {
350 va_list args;
351
352 va_start(args, fmt);
353 if (uidlist->retry_rewind) {
354 mailbox_set_critical(uidlist->box,
355 "Broken or unexpectedly changed file %s "
356 "line %u: %s - re-reading from beginning",
357 uidlist->path, uidlist->read_line_count,
358 t_strdup_vprintf(fmt, args));
359 } else {
360 mailbox_set_critical(uidlist->box, "Broken file %s line %u: %s",
361 uidlist->path, uidlist->read_line_count,
362 t_strdup_vprintf(fmt, args));
363 }
364 va_end(args);
365 }
366
maildir_uidlist_update_hdr(struct maildir_uidlist * uidlist,const struct stat * st)367 static void maildir_uidlist_update_hdr(struct maildir_uidlist *uidlist,
368 const struct stat *st)
369 {
370 struct maildir_index_header *mhdr = uidlist->mhdr;
371
372 if (mhdr->uidlist_mtime == 0 && uidlist->version != UIDLIST_VERSION) {
373 /* upgrading from older version. don't update the
374 uidlist times until it uses the new format */
375 uidlist->recreate = TRUE;
376 return;
377 }
378 mhdr->uidlist_mtime = st->st_mtime;
379 mhdr->uidlist_mtime_nsecs = ST_MTIME_NSEC(*st);
380 mhdr->uidlist_size = st->st_size;
381 }
382
383 static unsigned int
maildir_uidlist_records_array_delete(struct maildir_uidlist * uidlist,struct maildir_uidlist_rec * rec)384 maildir_uidlist_records_array_delete(struct maildir_uidlist *uidlist,
385 struct maildir_uidlist_rec *rec)
386 {
387 struct maildir_uidlist_rec *const *recs, *const *pos;
388 unsigned int idx, count;
389
390 recs = array_get(&uidlist->records, &count);
391 if (!uidlist->unsorted) {
392 pos = array_bsearch(&uidlist->records, &rec, maildir_uid_cmp);
393 i_assert(pos != NULL);
394 idx = pos - recs;
395 } else {
396 for (idx = 0; idx < count; idx++) {
397 if (recs[idx]->uid == rec->uid)
398 break;
399 }
400 i_assert(idx != count);
401 }
402 array_delete(&uidlist->records, idx, 1);
403 return idx;
404 }
405
406 static bool
maildir_uidlist_read_extended(struct maildir_uidlist * uidlist,const char ** line_p,struct maildir_uidlist_rec * rec)407 maildir_uidlist_read_extended(struct maildir_uidlist *uidlist,
408 const char **line_p,
409 struct maildir_uidlist_rec *rec)
410 {
411 const char *start, *line = *line_p;
412 buffer_t *buf;
413
414 buf = t_buffer_create(128);
415 while (*line != '\0' && *line != ':') {
416 /* skip over an extension field */
417 start = line;
418 while (*line != ' ' && *line != '\0') line++;
419 if (MAILDIR_UIDLIST_REC_EXT_KEY_IS_VALID(*start)) {
420 buffer_append(buf, start, line - start);
421 buffer_append_c(buf, '\0');
422 } else {
423 maildir_uidlist_set_corrupted(uidlist,
424 "Invalid extension record, removing: %s",
425 t_strdup_until(start, line));
426 uidlist->recreate = TRUE;
427 }
428 while (*line == ' ') line++;
429 }
430
431 if (buf->used > 0) {
432 /* save the extensions */
433 buffer_append_c(buf, '\0');
434 rec->extensions = p_malloc(uidlist->record_pool, buf->used);
435 memcpy(rec->extensions, buf->data, buf->used);
436 }
437
438 if (*line == ':')
439 line++;
440 if (*line == '\0')
441 return FALSE;
442
443 *line_p = line;
444 return TRUE;
445 }
446
maildir_uidlist_next(struct maildir_uidlist * uidlist,const char * line)447 static bool maildir_uidlist_next(struct maildir_uidlist *uidlist,
448 const char *line)
449 {
450 struct maildir_uidlist_rec *rec, *old_rec, *const *recs;
451 unsigned int count;
452 uint32_t uid;
453
454 uid = 0;
455 while (*line >= '0' && *line <= '9') {
456 uid = uid*10 + (*line - '0');
457 line++;
458 }
459
460 if (uid == 0 || *line != ' ') {
461 /* invalid file */
462 maildir_uidlist_set_corrupted(uidlist, "Invalid data: %s",
463 line);
464 return FALSE;
465 }
466 if (uid <= uidlist->prev_read_uid) {
467 maildir_uidlist_set_corrupted(uidlist,
468 "UIDs not ordered (%u >= %u)",
469 uid, uidlist->prev_read_uid);
470 return FALSE;
471 }
472 if (uid >= (uint32_t)-1) {
473 maildir_uidlist_set_corrupted(uidlist,
474 "UID too high (%u)", uid);
475 return FALSE;
476 }
477 uidlist->prev_read_uid = uid;
478
479 if (uid <= uidlist->last_seen_uid) {
480 /* we already have this */
481 return TRUE;
482 }
483 uidlist->last_seen_uid = uid;
484
485 if (uid >= uidlist->next_uid && uidlist->version == 1) {
486 maildir_uidlist_set_corrupted(uidlist,
487 "UID larger than next_uid (%u >= %u)",
488 uid, uidlist->next_uid);
489 return FALSE;
490 }
491
492 rec = p_new(uidlist->record_pool, struct maildir_uidlist_rec, 1);
493 rec->uid = uid;
494 rec->flags = MAILDIR_UIDLIST_REC_FLAG_NONSYNCED;
495
496 while (*line == ' ') line++;
497
498 if (uidlist->version == UIDLIST_VERSION) {
499 /* read extended fields */
500 bool ret;
501
502 T_BEGIN {
503 ret = maildir_uidlist_read_extended(uidlist, &line,
504 rec);
505 } T_END;
506 if (!ret) {
507 maildir_uidlist_set_corrupted(uidlist,
508 "Invalid extended fields: %s", line);
509 return FALSE;
510 }
511 }
512
513 if (strchr(line, '/') != NULL) {
514 maildir_uidlist_set_corrupted(uidlist,
515 "%s: Broken filename at line %u: %s",
516 uidlist->path, uidlist->read_line_count, line);
517 return FALSE;
518 }
519
520 old_rec = hash_table_lookup(uidlist->files, line);
521 if (old_rec == NULL) {
522 /* no conflicts */
523 } else if (old_rec->uid == uid) {
524 /* most likely this is a record we saved ourself, but couldn't
525 update last_seen_uid because uidlist wasn't refreshed while
526 it was locked.
527
528 another possibility is a duplicate file record. currently
529 it would be a bug, but not that big of a deal. also perhaps
530 in future such duplicate lines could be used to update
531 extended fields. so just let it through anyway.
532
533 we'll waste a bit of memory here by allocating the record
534 twice, but that's not really a problem. */
535 rec->filename = old_rec->filename;
536 hash_table_update(uidlist->files, rec->filename, rec);
537 uidlist->unsorted = TRUE;
538 return TRUE;
539 } else {
540 /* This can happen if expunged file is moved back and the file
541 was appended to uidlist. */
542 i_warning("%s: Duplicate file entry at line %u: "
543 "%s (uid %u -> %u)%s",
544 uidlist->path, uidlist->read_line_count, line,
545 old_rec->uid, uid, uidlist->retry_rewind ?
546 " - retrying by re-reading from beginning" : "");
547 if (uidlist->retry_rewind)
548 return FALSE;
549 /* Delete the old UID */
550 (void)maildir_uidlist_records_array_delete(uidlist, old_rec);
551 /* Replace the old record with this new one */
552 *old_rec = *rec;
553 rec = old_rec;
554 uidlist->recreate = TRUE;
555 }
556
557 recs = array_get(&uidlist->records, &count);
558 if (count > 0 && recs[count-1]->uid > uid) {
559 /* we most likely have some records in the array that we saved
560 ourself without refreshing uidlist */
561 uidlist->unsorted = TRUE;
562 }
563
564 rec->filename = p_strdup(uidlist->record_pool, line);
565 hash_table_update(uidlist->files, rec->filename, rec);
566 array_push_back(&uidlist->records, &rec);
567 return TRUE;
568 }
569
570 static int
maildir_uidlist_read_v3_header(struct maildir_uidlist * uidlist,const char * line,unsigned int * uid_validity_r,unsigned int * next_uid_r)571 maildir_uidlist_read_v3_header(struct maildir_uidlist *uidlist,
572 const char *line,
573 unsigned int *uid_validity_r,
574 unsigned int *next_uid_r)
575 {
576 char key;
577
578 str_truncate(uidlist->hdr_extensions, 0);
579 while (*line != '\0') {
580 const char *value;
581
582 key = *line;
583 value = ++line;
584 while (*line != '\0' && *line != ' ') line++;
585 value = t_strdup_until(value, line);
586
587 switch (key) {
588 case MAILDIR_UIDLIST_HDR_EXT_UID_VALIDITY:
589 if (str_to_uint(value, uid_validity_r) < 0) {
590 maildir_uidlist_set_corrupted(uidlist,
591 "Invalid mailbox UID_VALIDITY: %s", value);
592 return -1;
593 }
594 break;
595 case MAILDIR_UIDLIST_HDR_EXT_NEXT_UID:
596 if (str_to_uint(value, next_uid_r) < 0) {
597 maildir_uidlist_set_corrupted(uidlist,
598 "Invalid mailbox NEXT_UID: %s", value);
599 return -1;
600 }
601 break;
602 case MAILDIR_UIDLIST_HDR_EXT_GUID:
603 if (guid_128_from_string(value,
604 uidlist->mailbox_guid) < 0) {
605 maildir_uidlist_set_corrupted(uidlist,
606 "Invalid mailbox GUID: %s", value);
607 return -1;
608 }
609 uidlist->have_mailbox_guid = TRUE;
610 break;
611 default:
612 if (str_len(uidlist->hdr_extensions) > 0)
613 str_append_c(uidlist->hdr_extensions, ' ');
614 str_printfa(uidlist->hdr_extensions,
615 "%c%s", key, value);
616 break;
617 }
618
619 while (*line == ' ') line++;
620 }
621 return 0;
622 }
623
maildir_uidlist_read_header(struct maildir_uidlist * uidlist,struct istream * input)624 static int maildir_uidlist_read_header(struct maildir_uidlist *uidlist,
625 struct istream *input)
626 {
627 unsigned int uid_validity = 0, next_uid = 0;
628 const char *line;
629 int ret;
630
631 line = i_stream_read_next_line(input);
632 if (line == NULL) {
633 /* I/O error / empty file */
634 return input->stream_errno == 0 ? 0 : -1;
635 }
636 uidlist->read_line_count = 1;
637
638 if (*line < '0' || *line > '9' || line[1] != ' ') {
639 maildir_uidlist_set_corrupted(uidlist,
640 "Corrupted header (invalid version number)");
641 return 0;
642 }
643
644 uidlist->version = *line - '0';
645 line += 2;
646
647 switch (uidlist->version) {
648 case 1:
649 if (sscanf(line, "%u %u", &uid_validity, &next_uid) != 2) {
650 maildir_uidlist_set_corrupted(uidlist,
651 "Corrupted header (version 1)");
652 return 0;
653 }
654 break;
655 case UIDLIST_VERSION:
656 T_BEGIN {
657 ret = maildir_uidlist_read_v3_header(uidlist, line,
658 &uid_validity,
659 &next_uid);
660 } T_END;
661 if (ret < 0)
662 return 0;
663 break;
664 default:
665 maildir_uidlist_set_corrupted(uidlist, "Unsupported version %u",
666 uidlist->version);
667 return 0;
668 }
669
670 if (uid_validity == 0 || next_uid == 0) {
671 maildir_uidlist_set_corrupted(uidlist,
672 "Broken header (uidvalidity = %u, next_uid=%u)",
673 uid_validity, next_uid);
674 return 0;
675 }
676
677 if (uid_validity == uidlist->uid_validity &&
678 next_uid < uidlist->hdr_next_uid) {
679 maildir_uidlist_set_corrupted(uidlist,
680 "next_uid header was lowered (%u -> %u)",
681 uidlist->hdr_next_uid, next_uid);
682 return 0;
683 }
684
685 uidlist->uid_validity = uid_validity;
686 uidlist->next_uid = next_uid;
687 uidlist->hdr_next_uid = next_uid;
688 return 1;
689 }
690
maildir_uidlist_records_sort_by_uid(struct maildir_uidlist * uidlist)691 static void maildir_uidlist_records_sort_by_uid(struct maildir_uidlist *uidlist)
692 {
693 array_sort(&uidlist->records, maildir_uid_cmp);
694 uidlist->unsorted = FALSE;
695 }
696
697 static int
maildir_uidlist_update_read(struct maildir_uidlist * uidlist,bool * retry_r,bool try_retry)698 maildir_uidlist_update_read(struct maildir_uidlist *uidlist,
699 bool *retry_r, bool try_retry)
700 {
701 const char *line;
702 uint32_t orig_next_uid, orig_uid_validity;
703 struct istream *input;
704 struct stat st;
705 uoff_t last_read_offset;
706 int fd, ret;
707 bool readonly = FALSE;
708
709 *retry_r = FALSE;
710
711 if (uidlist->fd == -1) {
712 fd = nfs_safe_open(uidlist->path, O_RDWR);
713 if (fd == -1 && errno == EACCES) {
714 fd = nfs_safe_open(uidlist->path, O_RDONLY);
715 readonly = TRUE;
716 }
717 if (fd == -1) {
718 if (errno != ENOENT) {
719 mailbox_set_critical(uidlist->box,
720 "open(%s) failed: %m", uidlist->path);
721 return -1;
722 }
723 return 0;
724 }
725 last_read_offset = 0;
726 } else {
727 /* the file was updated */
728 fd = uidlist->fd;
729 if (lseek(fd, 0, SEEK_SET) < 0) {
730 if (errno == ESTALE && try_retry) {
731 *retry_r = TRUE;
732 return -1;
733 }
734 mailbox_set_critical(uidlist->box,
735 "lseek(%s) failed: %m", uidlist->path);
736 return -1;
737 }
738 uidlist->fd = -1;
739 uidlist->fd_ino = 0;
740 last_read_offset = uidlist->last_read_offset;
741 uidlist->last_read_offset = 0;
742 }
743
744 if (fstat(fd, &st) < 0) {
745 i_close_fd(&fd);
746 if (errno == ESTALE && try_retry) {
747 *retry_r = TRUE;
748 return -1;
749 }
750 mailbox_set_critical(uidlist->box,
751 "fstat(%s) failed: %m", uidlist->path);
752 return -1;
753 }
754
755 if (uidlist->record_pool == NULL) {
756 uidlist->record_pool =
757 pool_alloconly_create(MEMPOOL_GROWING
758 "uidlist record_pool",
759 nearest_power(st.st_size -
760 st.st_size/8));
761 }
762
763 input = i_stream_create_fd(fd, SIZE_MAX);
764 i_stream_seek(input, last_read_offset);
765
766 orig_uid_validity = uidlist->uid_validity;
767 orig_next_uid = uidlist->next_uid;
768 ret = input->v_offset != 0 ? 1 :
769 maildir_uidlist_read_header(uidlist, input);
770 if (ret > 0) {
771 uidlist->prev_read_uid = 0;
772 uidlist->change_counter++;
773 uidlist->retry_rewind = last_read_offset != 0 && try_retry;
774
775 ret = 1;
776 while ((line = i_stream_read_next_line(input)) != NULL) {
777 uidlist->read_records_count++;
778 uidlist->read_line_count++;
779 if (!maildir_uidlist_next(uidlist, line)) {
780 if (!uidlist->retry_rewind)
781 ret = 0;
782 else {
783 ret = -1;
784 *retry_r = TRUE;
785 }
786 break;
787 }
788 }
789 uidlist->retry_rewind = FALSE;
790 if (input->stream_errno != 0)
791 ret = -1;
792
793 if (uidlist->unsorted) {
794 uidlist->recreate_on_change = TRUE;
795 maildir_uidlist_records_sort_by_uid(uidlist);
796 }
797 if (uidlist->next_uid <= uidlist->prev_read_uid)
798 uidlist->next_uid = uidlist->prev_read_uid + 1;
799 if (ret > 0 && uidlist->uid_validity != orig_uid_validity &&
800 orig_uid_validity != 0) {
801 uidlist->recreate = TRUE;
802 } else if (ret > 0 && uidlist->next_uid < orig_next_uid) {
803 mailbox_set_critical(uidlist->box,
804 "%s: next_uid was lowered (%u -> %u, hdr=%u)",
805 uidlist->path, orig_next_uid,
806 uidlist->next_uid, uidlist->hdr_next_uid);
807 uidlist->recreate = TRUE;
808 uidlist->next_uid = orig_next_uid;
809 }
810 }
811
812 if (ret == 0) {
813 /* file is broken */
814 i_unlink(uidlist->path);
815 } else if (ret > 0) {
816 /* success */
817 if (readonly)
818 uidlist->recreate_on_change = TRUE;
819 uidlist->fd = fd;
820 uidlist->fd_dev = st.st_dev;
821 uidlist->fd_ino = st.st_ino;
822 uidlist->fd_size = st.st_size;
823 uidlist->last_read_offset = input->v_offset;
824 maildir_uidlist_update_hdr(uidlist, &st);
825 } else if (!*retry_r) {
826 /* I/O error */
827 if (input->stream_errno == ESTALE && try_retry)
828 *retry_r = TRUE;
829 else {
830 mailbox_set_critical(uidlist->box,
831 "read(%s) failed: %s", uidlist->path,
832 i_stream_get_error(input));
833 }
834 uidlist->last_read_offset = 0;
835 }
836
837 i_stream_destroy(&input);
838 if (ret <= 0) {
839 if (close(fd) < 0) {
840 mailbox_set_critical(uidlist->box,
841 "close(%s) failed: %m", uidlist->path);
842 }
843 }
844 return ret;
845 }
846
847 static int
maildir_uidlist_stat(struct maildir_uidlist * uidlist,struct stat * st_r)848 maildir_uidlist_stat(struct maildir_uidlist *uidlist, struct stat *st_r)
849 {
850 struct mail_storage *storage = uidlist->box->storage;
851
852 if (storage->set->mail_nfs_storage) {
853 nfs_flush_file_handle_cache(uidlist->path);
854 nfs_flush_attr_cache_unlocked(uidlist->path);
855 }
856 if (nfs_safe_stat(uidlist->path, st_r) < 0) {
857 if (errno != ENOENT) {
858 mailbox_set_critical(uidlist->box,
859 "stat(%s) failed: %m", uidlist->path);
860 return -1;
861 }
862 return 0;
863 }
864 return 1;
865 }
866
867 static int
maildir_uidlist_has_changed(struct maildir_uidlist * uidlist,bool * recreated_r)868 maildir_uidlist_has_changed(struct maildir_uidlist *uidlist, bool *recreated_r)
869 {
870 struct mail_storage *storage = uidlist->box->storage;
871 struct stat st;
872 int ret;
873
874 *recreated_r = FALSE;
875
876 if ((ret = maildir_uidlist_stat(uidlist, &st)) < 0)
877 return -1;
878 if (ret == 0) {
879 *recreated_r = TRUE;
880 return 1;
881 }
882
883 if (st.st_ino != uidlist->fd_ino ||
884 !CMP_DEV_T(st.st_dev, uidlist->fd_dev)) {
885 /* file recreated */
886 *recreated_r = TRUE;
887 return 1;
888 }
889
890 if (storage->set->mail_nfs_storage) {
891 /* NFS: either the file hasn't been changed, or it has already
892 been deleted and the inodes just happen to be the same.
893 check if the fd is still valid. */
894 if (fstat(uidlist->fd, &st) < 0) {
895 if (errno == ESTALE) {
896 *recreated_r = TRUE;
897 return 1;
898 }
899 mailbox_set_critical(uidlist->box,
900 "fstat(%s) failed: %m", uidlist->path);
901 return -1;
902 }
903 }
904
905 if (st.st_size != uidlist->fd_size) {
906 /* file modified but not recreated */
907 return 1;
908 } else {
909 /* unchanged */
910 return 0;
911 }
912 }
913
maildir_uidlist_open_latest(struct maildir_uidlist * uidlist)914 static int maildir_uidlist_open_latest(struct maildir_uidlist *uidlist)
915 {
916 bool recreated;
917 int ret;
918
919 if (uidlist->fd != -1) {
920 ret = maildir_uidlist_has_changed(uidlist, &recreated);
921 if (ret <= 0) {
922 if (UIDLIST_IS_LOCKED(uidlist))
923 uidlist->locked_refresh = TRUE;
924 return ret < 0 ? -1 : 1;
925 }
926
927 if (!recreated)
928 return 0;
929 maildir_uidlist_reset(uidlist);
930 }
931
932 uidlist->fd = nfs_safe_open(uidlist->path, O_RDWR);
933 if (uidlist->fd == -1 && errno == EACCES) {
934 uidlist->fd = nfs_safe_open(uidlist->path, O_RDONLY);
935 uidlist->recreate_on_change = TRUE;
936 }
937 if (uidlist->fd == -1 && errno != ENOENT) {
938 mailbox_set_critical(uidlist->box,
939 "open(%s) failed: %m", uidlist->path);
940 return -1;
941 }
942 return 0;
943 }
944
maildir_uidlist_refresh(struct maildir_uidlist * uidlist)945 int maildir_uidlist_refresh(struct maildir_uidlist *uidlist)
946 {
947 unsigned int i;
948 bool retry;
949 int ret;
950
951 if (maildir_uidlist_open_latest(uidlist) < 0)
952 return -1;
953
954 for (i = 0; ; i++) {
955 ret = maildir_uidlist_update_read(uidlist, &retry,
956 i < UIDLIST_ESTALE_RETRY_COUNT);
957 if (!retry)
958 break;
959 /* ESTALE - try reopening and rereading */
960 maildir_uidlist_close(uidlist);
961 }
962 if (ret >= 0) {
963 uidlist->initial_read = TRUE;
964 uidlist->initial_hdr_read = TRUE;
965 if (UIDLIST_IS_LOCKED(uidlist))
966 uidlist->locked_refresh = TRUE;
967 if (!uidlist->have_mailbox_guid) {
968 uidlist->recreate = TRUE;
969 (void)maildir_uidlist_update(uidlist);
970 }
971 }
972 return ret;
973 }
974
maildir_uidlist_refresh_fast_init(struct maildir_uidlist * uidlist)975 int maildir_uidlist_refresh_fast_init(struct maildir_uidlist *uidlist)
976 {
977 const struct maildir_index_header *mhdr = uidlist->mhdr;
978 struct mail_index *index = uidlist->box->index;
979 struct mail_index_view *view;
980 const struct mail_index_header *hdr;
981 struct stat st;
982 int ret;
983
984 i_assert(UIDLIST_IS_LOCKED(uidlist));
985
986 if (uidlist->fd != -1)
987 return maildir_uidlist_refresh(uidlist);
988
989 if ((ret = maildir_uidlist_stat(uidlist, &st)) < 0)
990 return ret;
991
992 if (ret > 0 && st.st_size == mhdr->uidlist_size &&
993 st.st_mtime == (time_t)mhdr->uidlist_mtime &&
994 ST_NTIMES_EQUAL(ST_MTIME_NSEC(st), mhdr->uidlist_mtime_nsecs) &&
995 (!mail_index_is_in_memory(index) || st.st_mtime < ioloop_time-1)) {
996 /* index is up-to-date. look up the uidvalidity and next-uid
997 from it. we'll need to create a new view temporarily to
998 make sure we get the latest values. */
999 view = mail_index_view_open(index);
1000 hdr = mail_index_get_header(view);
1001 uidlist->uid_validity = hdr->uid_validity;
1002 uidlist->next_uid = hdr->next_uid;
1003 uidlist->initial_hdr_read = TRUE;
1004 mail_index_view_close(&view);
1005
1006 if (UIDLIST_IS_LOCKED(uidlist))
1007 uidlist->locked_refresh = TRUE;
1008 return 1;
1009 } else {
1010 return maildir_uidlist_refresh(uidlist);
1011 }
1012 }
1013
1014 static int
maildir_uid_bsearch_cmp(const uint32_t * uidp,struct maildir_uidlist_rec * const * recp)1015 maildir_uid_bsearch_cmp(const uint32_t *uidp,
1016 struct maildir_uidlist_rec *const *recp)
1017 {
1018 return *uidp < (*recp)->uid ? -1 :
1019 *uidp > (*recp)->uid ? 1 : 0;
1020 }
1021
1022 static int
maildir_uidlist_lookup_rec(struct maildir_uidlist * uidlist,uint32_t uid,struct maildir_uidlist_rec ** rec_r)1023 maildir_uidlist_lookup_rec(struct maildir_uidlist *uidlist, uint32_t uid,
1024 struct maildir_uidlist_rec **rec_r)
1025 {
1026 struct maildir_uidlist_rec *const *pos;
1027
1028 if (!uidlist->initial_read) {
1029 /* first time we need to read uidlist */
1030 if (maildir_uidlist_refresh(uidlist) < 0)
1031 return -1;
1032 }
1033
1034 pos = array_bsearch(&uidlist->records, &uid,
1035 maildir_uid_bsearch_cmp);
1036 if (pos == NULL) {
1037 *rec_r = NULL;
1038 return 0;
1039 }
1040 *rec_r = *pos;
1041 return 1;
1042 }
1043
maildir_uidlist_lookup(struct maildir_uidlist * uidlist,uint32_t uid,enum maildir_uidlist_rec_flag * flags_r,const char ** fname_r)1044 int maildir_uidlist_lookup(struct maildir_uidlist *uidlist, uint32_t uid,
1045 enum maildir_uidlist_rec_flag *flags_r,
1046 const char **fname_r)
1047 {
1048 struct maildir_uidlist_rec *rec;
1049 int ret;
1050
1051 if ((ret = maildir_uidlist_lookup_rec(uidlist, uid, &rec)) <= 0)
1052 return ret;
1053
1054 *flags_r = rec->flags;
1055 *fname_r = rec->filename;
1056 return 1;
1057 }
1058
1059 const char *
maildir_uidlist_lookup_ext(struct maildir_uidlist * uidlist,uint32_t uid,enum maildir_uidlist_rec_ext_key key)1060 maildir_uidlist_lookup_ext(struct maildir_uidlist *uidlist, uint32_t uid,
1061 enum maildir_uidlist_rec_ext_key key)
1062 {
1063 struct maildir_uidlist_rec *rec;
1064 const unsigned char *p;
1065 int ret;
1066
1067 ret = maildir_uidlist_lookup_rec(uidlist, uid, &rec);
1068 if (ret <= 0 || rec->extensions == NULL)
1069 return NULL;
1070
1071 p = rec->extensions;
1072 while (*p != '\0') {
1073 /* <key><value>\0 */
1074 if (*p == (unsigned char)key)
1075 return (const char *)p + 1;
1076
1077 p += strlen((const char *)p) + 1;
1078 }
1079 return NULL;
1080 }
1081
maildir_uidlist_get_uid_validity(struct maildir_uidlist * uidlist)1082 uint32_t maildir_uidlist_get_uid_validity(struct maildir_uidlist *uidlist)
1083 {
1084 return uidlist->uid_validity;
1085 }
1086
maildir_uidlist_get_next_uid(struct maildir_uidlist * uidlist)1087 uint32_t maildir_uidlist_get_next_uid(struct maildir_uidlist *uidlist)
1088 {
1089 return !uidlist->initial_hdr_read ? 0 : uidlist->next_uid;
1090 }
1091
maildir_uidlist_get_mailbox_guid(struct maildir_uidlist * uidlist,guid_128_t mailbox_guid)1092 int maildir_uidlist_get_mailbox_guid(struct maildir_uidlist *uidlist,
1093 guid_128_t mailbox_guid)
1094 {
1095 if (!uidlist->initial_hdr_read) {
1096 if (maildir_uidlist_refresh(uidlist) < 0)
1097 return -1;
1098 }
1099 if (!uidlist->have_mailbox_guid) {
1100 uidlist->recreate = TRUE;
1101 if (maildir_uidlist_update(uidlist) < 0)
1102 return -1;
1103 }
1104 memcpy(mailbox_guid, uidlist->mailbox_guid, GUID_128_SIZE);
1105 return 0;
1106 }
1107
maildir_uidlist_set_mailbox_guid(struct maildir_uidlist * uidlist,const guid_128_t mailbox_guid)1108 void maildir_uidlist_set_mailbox_guid(struct maildir_uidlist *uidlist,
1109 const guid_128_t mailbox_guid)
1110 {
1111 if (memcmp(uidlist->mailbox_guid, mailbox_guid,
1112 sizeof(uidlist->mailbox_guid)) != 0) {
1113 memcpy(uidlist->mailbox_guid, mailbox_guid,
1114 sizeof(uidlist->mailbox_guid));
1115 uidlist->recreate = TRUE;
1116 }
1117 }
1118
maildir_uidlist_set_uid_validity(struct maildir_uidlist * uidlist,uint32_t uid_validity)1119 void maildir_uidlist_set_uid_validity(struct maildir_uidlist *uidlist,
1120 uint32_t uid_validity)
1121 {
1122 i_assert(uid_validity != 0);
1123
1124 if (uid_validity != uidlist->uid_validity) {
1125 uidlist->uid_validity = uid_validity;
1126 uidlist->recreate = TRUE;
1127 }
1128 }
1129
maildir_uidlist_set_next_uid(struct maildir_uidlist * uidlist,uint32_t next_uid,bool force)1130 void maildir_uidlist_set_next_uid(struct maildir_uidlist *uidlist,
1131 uint32_t next_uid, bool force)
1132 {
1133 if (uidlist->next_uid < next_uid || force) {
1134 i_assert(next_uid != 0);
1135 uidlist->next_uid = next_uid;
1136 uidlist->recreate = TRUE;
1137 }
1138 }
1139
1140 static void
maildir_uidlist_rec_set_ext(struct maildir_uidlist_rec * rec,pool_t pool,enum maildir_uidlist_rec_ext_key key,const char * value)1141 maildir_uidlist_rec_set_ext(struct maildir_uidlist_rec *rec, pool_t pool,
1142 enum maildir_uidlist_rec_ext_key key,
1143 const char *value)
1144 {
1145 const unsigned char *p;
1146 buffer_t *buf;
1147 size_t len;
1148
1149 /* copy existing extensions, except for the one we're updating */
1150 buf = t_buffer_create(128);
1151 if (rec->extensions != NULL) {
1152 p = rec->extensions;
1153 while (*p != '\0') {
1154 /* <key><value>\0 */
1155 i_assert(MAILDIR_UIDLIST_REC_EXT_KEY_IS_VALID(*p));
1156
1157 len = strlen((const char *)p) + 1;
1158 if (*p != (unsigned char)key)
1159 buffer_append(buf, p, len);
1160 p += len;
1161 }
1162 }
1163 if (value != NULL) {
1164 buffer_append_c(buf, key);
1165 buffer_append(buf, value, strlen(value) + 1);
1166 }
1167 buffer_append_c(buf, '\0');
1168
1169 rec->extensions = p_malloc(pool, buf->used);
1170 memcpy(rec->extensions, buf->data, buf->used);
1171 }
1172
1173 static void ATTR_NULL(4)
maildir_uidlist_set_ext_internal(struct maildir_uidlist * uidlist,uint32_t uid,enum maildir_uidlist_rec_ext_key key,const char * value)1174 maildir_uidlist_set_ext_internal(struct maildir_uidlist *uidlist, uint32_t uid,
1175 enum maildir_uidlist_rec_ext_key key,
1176 const char *value)
1177 {
1178 struct maildir_uidlist_rec *rec;
1179 int ret;
1180
1181 i_assert(MAILDIR_UIDLIST_REC_EXT_KEY_IS_VALID(key));
1182
1183 ret = maildir_uidlist_lookup_rec(uidlist, uid, &rec);
1184 if (ret <= 0) {
1185 if (ret < 0)
1186 return;
1187
1188 /* maybe it's a new message */
1189 if (maildir_uidlist_refresh(uidlist) < 0)
1190 return;
1191 if (maildir_uidlist_lookup_rec(uidlist, uid, &rec) <= 0) {
1192 /* message is already expunged, ignore */
1193 return;
1194 }
1195 }
1196
1197 T_BEGIN {
1198 maildir_uidlist_rec_set_ext(rec, uidlist->record_pool,
1199 key, value);
1200 } T_END;
1201
1202 if (rec->uid != (uint32_t)-1) {
1203 /* message already exists in uidlist, need to recreate it */
1204 uidlist->recreate = TRUE;
1205 }
1206 }
1207
maildir_uidlist_set_ext(struct maildir_uidlist * uidlist,uint32_t uid,enum maildir_uidlist_rec_ext_key key,const char * value)1208 void maildir_uidlist_set_ext(struct maildir_uidlist *uidlist, uint32_t uid,
1209 enum maildir_uidlist_rec_ext_key key,
1210 const char *value)
1211 {
1212 maildir_uidlist_set_ext_internal(uidlist, uid, key, value);
1213 }
1214
maildir_uidlist_unset_ext(struct maildir_uidlist * uidlist,uint32_t uid,enum maildir_uidlist_rec_ext_key key)1215 void maildir_uidlist_unset_ext(struct maildir_uidlist *uidlist, uint32_t uid,
1216 enum maildir_uidlist_rec_ext_key key)
1217 {
1218 maildir_uidlist_set_ext_internal(uidlist, uid, key, NULL);
1219 }
1220
1221 static void
maildir_uidlist_generate_uid_validity(struct maildir_uidlist * uidlist)1222 maildir_uidlist_generate_uid_validity(struct maildir_uidlist *uidlist)
1223 {
1224 const struct mail_index_header *hdr;
1225
1226 if (uidlist->box->opened) {
1227 hdr = mail_index_get_header(uidlist->box->view);
1228 if (hdr->uid_validity != 0) {
1229 uidlist->uid_validity = hdr->uid_validity;
1230 return;
1231 }
1232 }
1233 uidlist->uid_validity =
1234 maildir_get_uidvalidity_next(uidlist->box->list);
1235 }
1236
maildir_uidlist_write_fd(struct maildir_uidlist * uidlist,int fd,const char * path,unsigned int first_idx,uoff_t * file_size_r)1237 static int maildir_uidlist_write_fd(struct maildir_uidlist *uidlist, int fd,
1238 const char *path, unsigned int first_idx,
1239 uoff_t *file_size_r)
1240 {
1241 struct mail_storage *storage = uidlist->box->storage;
1242 struct maildir_uidlist_iter_ctx *iter;
1243 struct ostream *output;
1244 struct maildir_uidlist_rec *rec;
1245 string_t *str;
1246 const unsigned char *p;
1247 const char *strp;
1248 size_t len;
1249
1250 i_assert(fd != -1);
1251
1252 output = o_stream_create_fd_file(fd, UOFF_T_MAX, FALSE);
1253 o_stream_cork(output);
1254 str = t_str_new(512);
1255
1256 if (output->offset == 0) {
1257 i_assert(first_idx == 0);
1258 uidlist->version = UIDLIST_VERSION;
1259
1260 if (uidlist->uid_validity == 0)
1261 maildir_uidlist_generate_uid_validity(uidlist);
1262 if (!uidlist->have_mailbox_guid)
1263 guid_128_generate(uidlist->mailbox_guid);
1264
1265 i_assert(uidlist->next_uid > 0);
1266 str_printfa(str, "%u %c%u %c%u %c%s", uidlist->version,
1267 MAILDIR_UIDLIST_HDR_EXT_UID_VALIDITY,
1268 uidlist->uid_validity,
1269 MAILDIR_UIDLIST_HDR_EXT_NEXT_UID,
1270 uidlist->next_uid,
1271 MAILDIR_UIDLIST_HDR_EXT_GUID,
1272 guid_128_to_string(uidlist->mailbox_guid));
1273 if (str_len(uidlist->hdr_extensions) > 0) {
1274 str_append_c(str, ' ');
1275 str_append_str(str, uidlist->hdr_extensions);
1276 }
1277 str_append_c(str, '\n');
1278 o_stream_nsend(output, str_data(str), str_len(str));
1279 }
1280
1281 iter = maildir_uidlist_iter_init(uidlist);
1282 i_assert(first_idx <= array_count(&uidlist->records));
1283 iter->next += first_idx;
1284
1285 while (maildir_uidlist_iter_next_rec(iter, &rec)) {
1286 uidlist->read_records_count++;
1287 str_truncate(str, 0);
1288 str_printfa(str, "%u", rec->uid);
1289 if (rec->extensions != NULL) {
1290 for (p = rec->extensions; *p != '\0'; ) {
1291 i_assert(MAILDIR_UIDLIST_REC_EXT_KEY_IS_VALID(*p));
1292 len = strlen((const char *)p);
1293 str_append_c(str, ' ');
1294 str_append_data(str, p, len);
1295 p += len + 1;
1296 }
1297 }
1298 str_append(str, " :");
1299 strp = strchr(rec->filename, *MAILDIR_INFO_SEP_S);
1300 if (strp == NULL)
1301 str_append(str, rec->filename);
1302 else
1303 str_append_data(str, rec->filename, strp - rec->filename);
1304 str_append_c(str, '\n');
1305 o_stream_nsend(output, str_data(str), str_len(str));
1306 }
1307 maildir_uidlist_iter_deinit(&iter);
1308
1309 if (o_stream_finish(output) < 0) {
1310 mailbox_set_critical(uidlist->box, "write(%s) failed: %s",
1311 path, o_stream_get_error(output));
1312 o_stream_unref(&output);
1313 return -1;
1314 }
1315
1316 *file_size_r = output->offset;
1317 o_stream_unref(&output);
1318
1319 if (storage->set->parsed_fsync_mode != FSYNC_MODE_NEVER) {
1320 if (fdatasync(fd) < 0) {
1321 mailbox_set_critical(uidlist->box,
1322 "fdatasync(%s) failed: %m", path);
1323 return -1;
1324 }
1325 }
1326 return 0;
1327 }
1328
1329 static void
maildir_uidlist_records_drop_expunges(struct maildir_uidlist * uidlist)1330 maildir_uidlist_records_drop_expunges(struct maildir_uidlist *uidlist)
1331 {
1332 struct mail_index_view *view;
1333 struct maildir_uidlist_rec *const *recs;
1334 ARRAY_TYPE(maildir_uidlist_rec_p) new_records;
1335 const struct mail_index_header *hdr;
1336 const struct mail_index_record *rec;
1337 unsigned int i, count;
1338 uint32_t seq;
1339
1340 /* we could get here when opening and locking mailbox,
1341 before index files have been opened. */
1342 if (!uidlist->box->opened)
1343 return;
1344
1345 mail_index_refresh(uidlist->box->index);
1346 view = mail_index_view_open(uidlist->box->index);
1347 count = array_count(&uidlist->records);
1348 hdr = mail_index_get_header(view);
1349 if (count * UIDLIST_COMPRESS_PERCENTAGE / 100 <= hdr->messages_count) {
1350 /* too much trouble to be worth it */
1351 mail_index_view_close(&view);
1352 return;
1353 }
1354
1355 i_array_init(&new_records, hdr->messages_count + 64);
1356 recs = array_get(&uidlist->records, &count);
1357 for (i = 0, seq = 1; i < count && seq <= hdr->messages_count; ) {
1358 rec = mail_index_lookup(view, seq);
1359 if (recs[i]->uid < rec->uid) {
1360 /* expunged entry */
1361 hash_table_remove(uidlist->files, recs[i]->filename);
1362 i++;
1363 } else if (recs[i]->uid > rec->uid) {
1364 /* index isn't up to date. we're probably just
1365 syncing it here. ignore this entry. */
1366 seq++;
1367 } else {
1368 array_push_back(&new_records, &recs[i]);
1369 seq++; i++;
1370 }
1371 }
1372
1373 /* drop messages expunged at the end of index */
1374 while (i < count && recs[i]->uid < hdr->next_uid) {
1375 hash_table_remove(uidlist->files, recs[i]->filename);
1376 i++;
1377 }
1378 /* view might not be completely up-to-date, so preserve any
1379 messages left */
1380 for (; i < count; i++)
1381 array_push_back(&new_records, &recs[i]);
1382
1383 array_free(&uidlist->records);
1384 uidlist->records = new_records;
1385
1386 mail_index_view_close(&view);
1387 }
1388
maildir_uidlist_recreate(struct maildir_uidlist * uidlist)1389 static int maildir_uidlist_recreate(struct maildir_uidlist *uidlist)
1390 {
1391 struct mailbox *box = uidlist->box;
1392 const struct mailbox_permissions *perm = mailbox_get_permissions(box);
1393 const char *control_dir, *temp_path;
1394 struct stat st;
1395 mode_t old_mask;
1396 uoff_t file_size;
1397 int i, fd, ret;
1398
1399 i_assert(uidlist->initial_read);
1400
1401 maildir_uidlist_records_drop_expunges(uidlist);
1402
1403 if (mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_CONTROL,
1404 &control_dir) <= 0)
1405 i_unreached();
1406 temp_path = t_strconcat(control_dir,
1407 "/" MAILDIR_UIDLIST_NAME ".tmp", NULL);
1408
1409 for (i = 0;; i++) {
1410 old_mask = umask(0777 & ~perm->file_create_mode);
1411 fd = open(temp_path, O_RDWR | O_CREAT | O_TRUNC, 0777);
1412 umask(old_mask);
1413 if (fd != -1)
1414 break;
1415
1416 if (errno != ENOENT || i == MAILDIR_DELETE_RETRY_COUNT) {
1417 mailbox_set_critical(box,
1418 "open(%s, O_CREAT) failed: %m", temp_path);
1419 return -1;
1420 }
1421 /* the control dir doesn't exist. create it unless the whole
1422 mailbox was just deleted. */
1423 if (!maildir_set_deleted(uidlist->box))
1424 return -1;
1425 }
1426
1427 if (perm->file_create_gid != (gid_t)-1 &&
1428 fchown(fd, (uid_t)-1, perm->file_create_gid) < 0) {
1429 if (errno == EPERM) {
1430 mailbox_set_critical(box, "%s",
1431 eperm_error_get_chgrp("fchown", temp_path,
1432 perm->file_create_gid,
1433 perm->file_create_gid_origin));
1434 } else {
1435 mailbox_set_critical(box,
1436 "fchown(%s) failed: %m", temp_path);
1437 }
1438 }
1439
1440 uidlist->read_records_count = 0;
1441 ret = maildir_uidlist_write_fd(uidlist, fd, temp_path, 0, &file_size);
1442 if (ret == 0) {
1443 if (rename(temp_path, uidlist->path) < 0) {
1444 mailbox_set_critical(box,
1445 "rename(%s, %s) failed: %m",
1446 temp_path, uidlist->path);
1447 ret = -1;
1448 }
1449 }
1450
1451 if (ret < 0)
1452 i_unlink(temp_path);
1453 else if (fstat(fd, &st) < 0) {
1454 mailbox_set_critical(box,
1455 "fstat(%s) failed: %m", temp_path);
1456 ret = -1;
1457 } else if (file_size != (uoff_t)st.st_size) {
1458 i_assert(!file_dotlock_is_locked(uidlist->dotlock));
1459 mailbox_set_critical(box,
1460 "Maildir uidlist dotlock overridden: %s",
1461 uidlist->path);
1462 ret = -1;
1463 } else {
1464 maildir_uidlist_close(uidlist);
1465 uidlist->fd = fd;
1466 uidlist->fd_dev = st.st_dev;
1467 uidlist->fd_ino = st.st_ino;
1468 uidlist->fd_size = st.st_size;
1469 uidlist->last_read_offset = st.st_size;
1470 uidlist->recreate = FALSE;
1471 uidlist->recreate_on_change = FALSE;
1472 uidlist->have_mailbox_guid = TRUE;
1473 maildir_uidlist_update_hdr(uidlist, &st);
1474 }
1475 if (ret < 0)
1476 i_close_fd(&fd);
1477 return ret;
1478 }
1479
maildir_uidlist_update(struct maildir_uidlist * uidlist)1480 int maildir_uidlist_update(struct maildir_uidlist *uidlist)
1481 {
1482 int ret;
1483
1484 if (!uidlist->recreate)
1485 return 0;
1486
1487 if (maildir_uidlist_lock(uidlist) <= 0)
1488 return -1;
1489 ret = maildir_uidlist_recreate(uidlist);
1490 maildir_uidlist_unlock(uidlist);
1491 return ret;
1492 }
1493
maildir_uidlist_want_compress(struct maildir_uidlist_sync_ctx * ctx)1494 static bool maildir_uidlist_want_compress(struct maildir_uidlist_sync_ctx *ctx)
1495 {
1496 unsigned int min_rewrite_count;
1497
1498 if (!ctx->uidlist->locked_refresh)
1499 return FALSE;
1500 if (ctx->uidlist->recreate)
1501 return TRUE;
1502
1503 min_rewrite_count =
1504 (ctx->uidlist->read_records_count + ctx->new_files_count) *
1505 UIDLIST_COMPRESS_PERCENTAGE / 100;
1506 return min_rewrite_count >= array_count(&ctx->uidlist->records);
1507 }
1508
maildir_uidlist_want_recreate(struct maildir_uidlist_sync_ctx * ctx)1509 static bool maildir_uidlist_want_recreate(struct maildir_uidlist_sync_ctx *ctx)
1510 {
1511 struct maildir_uidlist *uidlist = ctx->uidlist;
1512
1513 if (!uidlist->locked_refresh || !uidlist->initial_read)
1514 return FALSE;
1515
1516 if (ctx->finish_change_counter != uidlist->change_counter)
1517 return TRUE;
1518 if (uidlist->fd == -1 || uidlist->version != UIDLIST_VERSION ||
1519 !uidlist->have_mailbox_guid)
1520 return TRUE;
1521 return maildir_uidlist_want_compress(ctx);
1522 }
1523
maildir_uidlist_sync_update(struct maildir_uidlist_sync_ctx * ctx)1524 static int maildir_uidlist_sync_update(struct maildir_uidlist_sync_ctx *ctx)
1525 {
1526 struct maildir_uidlist *uidlist = ctx->uidlist;
1527 struct stat st;
1528 uoff_t file_size;
1529
1530 if (maildir_uidlist_want_recreate(ctx) || uidlist->recreate_on_change)
1531 return maildir_uidlist_recreate(uidlist);
1532
1533 if (!uidlist->locked_refresh || uidlist->fd == -1) {
1534 /* make sure we have the latest file (e.g. NOREFRESH used) */
1535 i_assert(uidlist->initial_hdr_read);
1536 if (maildir_uidlist_open_latest(uidlist) < 0)
1537 return -1;
1538 if (uidlist->recreate_on_change)
1539 return maildir_uidlist_recreate(uidlist);
1540 }
1541 i_assert(ctx->first_unwritten_pos != UINT_MAX);
1542
1543 if (lseek(uidlist->fd, 0, SEEK_END) < 0) {
1544 mailbox_set_critical(uidlist->box,
1545 "lseek(%s) failed: %m", uidlist->path);
1546 return -1;
1547 }
1548
1549 if (maildir_uidlist_write_fd(uidlist, uidlist->fd, uidlist->path,
1550 ctx->first_unwritten_pos, &file_size) < 0)
1551 return -1;
1552
1553 if (fstat(uidlist->fd, &st) < 0) {
1554 mailbox_set_critical(uidlist->box,
1555 "fstat(%s) failed: %m", uidlist->path);
1556 return -1;
1557 }
1558 if ((uoff_t)st.st_size != file_size) {
1559 i_warning("%s: file size changed unexpectedly after write",
1560 uidlist->path);
1561 } else if (uidlist->locked_refresh) {
1562 uidlist->fd_size = st.st_size;
1563 uidlist->last_read_offset = st.st_size;
1564 maildir_uidlist_update_hdr(uidlist, &st);
1565 }
1566 return 0;
1567 }
1568
maildir_uidlist_mark_all(struct maildir_uidlist * uidlist,bool nonsynced)1569 static void maildir_uidlist_mark_all(struct maildir_uidlist *uidlist,
1570 bool nonsynced)
1571 {
1572 struct maildir_uidlist_rec **recs;
1573 unsigned int i, count;
1574
1575 recs = array_get_modifiable(&uidlist->records, &count);
1576 if (nonsynced) {
1577 for (i = 0; i < count; i++)
1578 recs[i]->flags |= MAILDIR_UIDLIST_REC_FLAG_NONSYNCED;
1579 } else {
1580 for (i = 0; i < count; i++)
1581 recs[i]->flags &= ENUM_NEGATE(MAILDIR_UIDLIST_REC_FLAG_NONSYNCED);
1582 }
1583 }
1584
maildir_uidlist_sync_lock(struct maildir_uidlist * uidlist,enum maildir_uidlist_sync_flags sync_flags,bool * locked_r)1585 static int maildir_uidlist_sync_lock(struct maildir_uidlist *uidlist,
1586 enum maildir_uidlist_sync_flags sync_flags,
1587 bool *locked_r)
1588 {
1589 bool nonblock, refresh;
1590 int ret;
1591
1592 *locked_r = FALSE;
1593
1594 if ((sync_flags & MAILDIR_UIDLIST_SYNC_NOLOCK) != 0) {
1595 if (maildir_uidlist_refresh(uidlist) < 0)
1596 return -1;
1597 return 1;
1598 }
1599
1600 nonblock = (sync_flags & MAILDIR_UIDLIST_SYNC_TRYLOCK) != 0;
1601 refresh = (sync_flags & MAILDIR_UIDLIST_SYNC_NOREFRESH) == 0;
1602
1603 ret = maildir_uidlist_lock_timeout(uidlist, nonblock, refresh, refresh);
1604 if (ret <= 0) {
1605 if (ret < 0 || !nonblock)
1606 return ret;
1607
1608 /* couldn't lock it */
1609 if ((sync_flags & MAILDIR_UIDLIST_SYNC_FORCE) == 0)
1610 return 0;
1611 /* forcing the sync anyway */
1612 if (maildir_uidlist_refresh(uidlist) < 0)
1613 return -1;
1614 } else {
1615 *locked_r = TRUE;
1616 }
1617 return 1;
1618 }
1619
maildir_uidlist_set_all_nonsynced(struct maildir_uidlist * uidlist)1620 void maildir_uidlist_set_all_nonsynced(struct maildir_uidlist *uidlist)
1621 {
1622 maildir_uidlist_mark_all(uidlist, TRUE);
1623 }
1624
maildir_uidlist_sync_init(struct maildir_uidlist * uidlist,enum maildir_uidlist_sync_flags sync_flags,struct maildir_uidlist_sync_ctx ** sync_ctx_r)1625 int maildir_uidlist_sync_init(struct maildir_uidlist *uidlist,
1626 enum maildir_uidlist_sync_flags sync_flags,
1627 struct maildir_uidlist_sync_ctx **sync_ctx_r)
1628 {
1629 struct maildir_uidlist_sync_ctx *ctx;
1630 bool locked;
1631 int ret;
1632
1633 ret = maildir_uidlist_sync_lock(uidlist, sync_flags, &locked);
1634 if (ret <= 0)
1635 return ret;
1636
1637 *sync_ctx_r = ctx = i_new(struct maildir_uidlist_sync_ctx, 1);
1638 ctx->uidlist = uidlist;
1639 ctx->sync_flags = sync_flags;
1640 ctx->partial = !locked ||
1641 (sync_flags & MAILDIR_UIDLIST_SYNC_PARTIAL) != 0;
1642 ctx->locked = locked;
1643 ctx->first_unwritten_pos = UINT_MAX;
1644 ctx->first_new_pos = UINT_MAX;
1645
1646 if (ctx->partial) {
1647 if ((sync_flags & MAILDIR_UIDLIST_SYNC_KEEP_STATE) == 0) {
1648 /* initially mark all nonsynced */
1649 maildir_uidlist_mark_all(uidlist, TRUE);
1650 }
1651 return 1;
1652 }
1653 i_assert(uidlist->locked_refresh);
1654
1655 ctx->record_pool = pool_alloconly_create(MEMPOOL_GROWING
1656 "maildir_uidlist_sync", 16384);
1657 hash_table_create(&ctx->files, ctx->record_pool, 4096,
1658 maildir_filename_base_hash,
1659 maildir_filename_base_cmp);
1660
1661 i_array_init(&ctx->records, array_count(&uidlist->records));
1662 return 1;
1663 }
1664
1665 static int
maildir_uidlist_sync_next_partial(struct maildir_uidlist_sync_ctx * ctx,const char * filename,uint32_t uid,enum maildir_uidlist_rec_flag flags,struct maildir_uidlist_rec ** rec_r)1666 maildir_uidlist_sync_next_partial(struct maildir_uidlist_sync_ctx *ctx,
1667 const char *filename, uint32_t uid,
1668 enum maildir_uidlist_rec_flag flags,
1669 struct maildir_uidlist_rec **rec_r)
1670 {
1671 struct maildir_uidlist *uidlist = ctx->uidlist;
1672 struct maildir_uidlist_rec *rec, *const *recs;
1673 unsigned int count;
1674
1675 /* we'll update uidlist directly */
1676 rec = hash_table_lookup(uidlist->files, filename);
1677 if (rec == NULL) {
1678 /* doesn't exist in uidlist */
1679 if (!ctx->locked) {
1680 /* we can't add it, so just ignore it */
1681 return 1;
1682 }
1683 if (ctx->first_new_pos == UINT_MAX)
1684 ctx->first_new_pos = array_count(&uidlist->records);
1685 ctx->new_files_count++;
1686 ctx->changed = TRUE;
1687
1688 if (uidlist->record_pool == NULL) {
1689 uidlist->record_pool =
1690 pool_alloconly_create(MEMPOOL_GROWING
1691 "uidlist record_pool",
1692 1024);
1693 }
1694
1695 rec = p_new(uidlist->record_pool,
1696 struct maildir_uidlist_rec, 1);
1697 rec->uid = (uint32_t)-1;
1698 rec->filename = p_strdup(uidlist->record_pool, filename);
1699 array_push_back(&uidlist->records, &rec);
1700 uidlist->change_counter++;
1701
1702 hash_table_insert(uidlist->files, rec->filename, rec);
1703 } else if (strcmp(rec->filename, filename) != 0) {
1704 rec->filename = p_strdup(uidlist->record_pool, filename);
1705 }
1706 if (uid != 0) {
1707 if (rec->uid != uid && rec->uid != (uint32_t)-1) {
1708 mailbox_set_critical(uidlist->box,
1709 "Maildir: %s changed UID %u -> %u",
1710 filename, rec->uid, uid);
1711 return -1;
1712 }
1713 rec->uid = uid;
1714 if (uidlist->next_uid <= uid)
1715 uidlist->next_uid = uid + 1;
1716 else {
1717 recs = array_get(&uidlist->records, &count);
1718 if (count > 1 && uid < recs[count-1]->uid)
1719 uidlist->unsorted = TRUE;
1720 }
1721 }
1722
1723 rec->flags &= ENUM_NEGATE(MAILDIR_UIDLIST_REC_FLAG_NEW_DIR);
1724 rec->flags = (rec->flags | flags) &
1725 ENUM_NEGATE(MAILDIR_UIDLIST_REC_FLAG_NONSYNCED);
1726
1727 ctx->finished = FALSE;
1728 *rec_r = rec;
1729 return 1;
1730 }
1731
ext_dup(pool_t pool,const unsigned char * extensions)1732 static unsigned char *ext_dup(pool_t pool, const unsigned char *extensions)
1733 {
1734 unsigned char *ret;
1735
1736 if (extensions == NULL)
1737 return NULL;
1738
1739 T_BEGIN {
1740 unsigned int len;
1741
1742 for (len = 0; extensions[len] != '\0'; len++) {
1743 while (extensions[len] != '\0') len++;
1744 }
1745 ret = p_malloc(pool, len + 1);
1746 memcpy(ret, extensions, len);
1747 } T_END;
1748 return ret;
1749 }
1750
maildir_uidlist_sync_next(struct maildir_uidlist_sync_ctx * ctx,const char * filename,enum maildir_uidlist_rec_flag flags)1751 int maildir_uidlist_sync_next(struct maildir_uidlist_sync_ctx *ctx,
1752 const char *filename,
1753 enum maildir_uidlist_rec_flag flags)
1754 {
1755 struct maildir_uidlist_rec *rec;
1756
1757 return maildir_uidlist_sync_next_uid(ctx, filename, 0, flags, &rec);
1758 }
1759
maildir_uidlist_sync_next_uid(struct maildir_uidlist_sync_ctx * ctx,const char * filename,uint32_t uid,enum maildir_uidlist_rec_flag flags,struct maildir_uidlist_rec ** rec_r)1760 int maildir_uidlist_sync_next_uid(struct maildir_uidlist_sync_ctx *ctx,
1761 const char *filename, uint32_t uid,
1762 enum maildir_uidlist_rec_flag flags,
1763 struct maildir_uidlist_rec **rec_r)
1764 {
1765 struct maildir_uidlist *uidlist = ctx->uidlist;
1766 struct maildir_uidlist_rec *rec, *old_rec;
1767 const char *p;
1768
1769 *rec_r = NULL;
1770
1771 if (ctx->failed)
1772 return -1;
1773 for (p = filename; *p != '\0'; p++) {
1774 if (*p == 13 || *p == 10) {
1775 i_warning("Maildir %s: Ignoring a file with #0x%x: %s",
1776 mailbox_get_path(uidlist->box), *p, filename);
1777 return 1;
1778 }
1779 }
1780
1781 if (ctx->partial) {
1782 return maildir_uidlist_sync_next_partial(ctx, filename,
1783 uid, flags, rec_r);
1784 }
1785
1786 rec = hash_table_lookup(ctx->files, filename);
1787 if (rec != NULL) {
1788 if ((rec->flags & (MAILDIR_UIDLIST_REC_FLAG_NEW_DIR |
1789 MAILDIR_UIDLIST_REC_FLAG_MOVED)) == 0) {
1790 /* possibly duplicate */
1791 return 0;
1792 }
1793
1794 /* probably was in new/ and now we're seeing it in cur/.
1795 remove new/moved flags so if this happens again we'll know
1796 to check for duplicates. */
1797 rec->flags &= ENUM_NEGATE(MAILDIR_UIDLIST_REC_FLAG_NEW_DIR |
1798 MAILDIR_UIDLIST_REC_FLAG_MOVED);
1799 if (strcmp(rec->filename, filename) != 0)
1800 rec->filename = p_strdup(ctx->record_pool, filename);
1801 } else {
1802 old_rec = hash_table_lookup(uidlist->files, filename);
1803 i_assert(old_rec != NULL || UIDLIST_IS_LOCKED(uidlist));
1804
1805 rec = p_new(ctx->record_pool, struct maildir_uidlist_rec, 1);
1806
1807 if (old_rec != NULL) {
1808 *rec = *old_rec;
1809 rec->extensions =
1810 ext_dup(ctx->record_pool, rec->extensions);
1811 } else {
1812 rec->uid = (uint32_t)-1;
1813 ctx->new_files_count++;
1814 ctx->changed = TRUE;
1815 /* didn't exist in uidlist, it's recent */
1816 flags |= MAILDIR_UIDLIST_REC_FLAG_RECENT;
1817 }
1818 rec->filename = p_strdup(ctx->record_pool, filename);
1819 hash_table_insert(ctx->files, rec->filename, rec);
1820
1821 array_push_back(&ctx->records, &rec);
1822 }
1823 if (uid != 0) {
1824 rec->uid = uid;
1825 if (uidlist->next_uid <= uid)
1826 uidlist->next_uid = uid + 1;
1827 }
1828
1829 rec->flags = (rec->flags | flags) & ENUM_NEGATE(MAILDIR_UIDLIST_REC_FLAG_NONSYNCED);
1830 *rec_r = rec;
1831 return 1;
1832 }
1833
maildir_uidlist_sync_remove(struct maildir_uidlist_sync_ctx * ctx,const char * filename)1834 void maildir_uidlist_sync_remove(struct maildir_uidlist_sync_ctx *ctx,
1835 const char *filename)
1836 {
1837 struct maildir_uidlist_rec *rec;
1838 unsigned int idx;
1839
1840 i_assert(ctx->partial);
1841 i_assert(ctx->uidlist->locked_refresh);
1842
1843 rec = hash_table_lookup(ctx->uidlist->files, filename);
1844 i_assert(rec != NULL);
1845 i_assert(rec->uid != (uint32_t)-1);
1846
1847 hash_table_remove(ctx->uidlist->files, filename);
1848 idx = maildir_uidlist_records_array_delete(ctx->uidlist, rec);
1849
1850 if (ctx->first_unwritten_pos != UINT_MAX) {
1851 i_assert(ctx->first_unwritten_pos > idx);
1852 ctx->first_unwritten_pos--;
1853 }
1854 if (ctx->first_new_pos != UINT_MAX) {
1855 i_assert(ctx->first_new_pos > idx);
1856 ctx->first_new_pos--;
1857 }
1858
1859 ctx->changed = TRUE;
1860 ctx->uidlist->recreate = TRUE;
1861 }
1862
maildir_uidlist_sync_set_ext(struct maildir_uidlist_sync_ctx * ctx,struct maildir_uidlist_rec * rec,enum maildir_uidlist_rec_ext_key key,const char * value)1863 void maildir_uidlist_sync_set_ext(struct maildir_uidlist_sync_ctx *ctx,
1864 struct maildir_uidlist_rec *rec,
1865 enum maildir_uidlist_rec_ext_key key,
1866 const char *value)
1867 {
1868 pool_t pool = ctx->partial ?
1869 ctx->uidlist->record_pool : ctx->record_pool;
1870
1871 i_assert(MAILDIR_UIDLIST_REC_EXT_KEY_IS_VALID(key));
1872
1873 maildir_uidlist_rec_set_ext(rec, pool, key, value);
1874 }
1875
1876 const char *
maildir_uidlist_sync_get_full_filename(struct maildir_uidlist_sync_ctx * ctx,const char * filename)1877 maildir_uidlist_sync_get_full_filename(struct maildir_uidlist_sync_ctx *ctx,
1878 const char *filename)
1879 {
1880 struct maildir_uidlist_rec *rec;
1881
1882 rec = hash_table_lookup(ctx->files, filename);
1883 return rec == NULL ? NULL : rec->filename;
1884 }
1885
maildir_uidlist_get_uid(struct maildir_uidlist * uidlist,const char * filename,uint32_t * uid_r)1886 bool maildir_uidlist_get_uid(struct maildir_uidlist *uidlist,
1887 const char *filename, uint32_t *uid_r)
1888 {
1889 struct maildir_uidlist_rec *rec;
1890
1891 rec = hash_table_lookup(uidlist->files, filename);
1892 if (rec == NULL)
1893 return FALSE;
1894
1895 *uid_r = rec->uid;
1896 return TRUE;
1897 }
1898
maildir_uidlist_update_fname(struct maildir_uidlist * uidlist,const char * filename)1899 void maildir_uidlist_update_fname(struct maildir_uidlist *uidlist,
1900 const char *filename)
1901 {
1902 struct maildir_uidlist_rec *rec;
1903
1904 rec = hash_table_lookup(uidlist->files, filename);
1905 if (rec == NULL)
1906 return;
1907
1908 rec->flags &= ENUM_NEGATE(MAILDIR_UIDLIST_REC_FLAG_NONSYNCED);
1909 if (strcmp(rec->filename, filename) != 0)
1910 rec->filename = p_strdup(uidlist->record_pool, filename);
1911 }
1912
1913 const char *
maildir_uidlist_get_full_filename(struct maildir_uidlist * uidlist,const char * filename)1914 maildir_uidlist_get_full_filename(struct maildir_uidlist *uidlist,
1915 const char *filename)
1916 {
1917 struct maildir_uidlist_rec *rec;
1918
1919 rec = hash_table_lookup(uidlist->files, filename);
1920 return rec == NULL ? NULL : rec->filename;
1921 }
1922
maildir_assign_uid_cmp(const void * p1,const void * p2)1923 static int maildir_assign_uid_cmp(const void *p1, const void *p2)
1924 {
1925 const struct maildir_uidlist_rec *const *rec1 = p1, *const *rec2 = p2;
1926
1927 if ((*rec1)->uid != (*rec2)->uid) {
1928 if ((*rec1)->uid < (*rec2)->uid)
1929 return -1;
1930 else
1931 return 1;
1932 }
1933 return maildir_filename_sort_cmp((*rec1)->filename, (*rec2)->filename);
1934 }
1935
maildir_uidlist_assign_uids(struct maildir_uidlist_sync_ctx * ctx)1936 static void maildir_uidlist_assign_uids(struct maildir_uidlist_sync_ctx *ctx)
1937 {
1938 struct maildir_uidlist_rec **recs;
1939 unsigned int dest, count;
1940
1941 i_assert(UIDLIST_IS_LOCKED(ctx->uidlist));
1942 i_assert(ctx->first_new_pos != UINT_MAX);
1943
1944 if (ctx->first_unwritten_pos == UINT_MAX)
1945 ctx->first_unwritten_pos = ctx->first_new_pos;
1946
1947 /* sort new files and assign UIDs for them */
1948 recs = array_get_modifiable(&ctx->uidlist->records, &count);
1949 qsort(recs + ctx->first_new_pos, count - ctx->first_new_pos,
1950 sizeof(*recs), maildir_assign_uid_cmp);
1951
1952 for (dest = ctx->first_new_pos; dest < count; dest++) {
1953 if (recs[dest]->uid == (uint32_t)-1)
1954 break;
1955 }
1956
1957 for (; dest < count; dest++) {
1958 i_assert(recs[dest]->uid == (uint32_t)-1);
1959 i_assert(ctx->uidlist->next_uid < (uint32_t)-1);
1960 recs[dest]->uid = ctx->uidlist->next_uid++;
1961 recs[dest]->flags &= ENUM_NEGATE(MAILDIR_UIDLIST_REC_FLAG_MOVED);
1962 }
1963
1964 if (ctx->uidlist->locked_refresh && ctx->uidlist->initial_read)
1965 ctx->uidlist->last_seen_uid = ctx->uidlist->next_uid-1;
1966
1967 ctx->new_files_count = 0;
1968 ctx->first_new_pos = UINT_MAX;
1969 ctx->uidlist->change_counter++;
1970 ctx->finish_change_counter = ctx->uidlist->change_counter;
1971 }
1972
maildir_uidlist_swap(struct maildir_uidlist_sync_ctx * ctx)1973 static void maildir_uidlist_swap(struct maildir_uidlist_sync_ctx *ctx)
1974 {
1975 struct maildir_uidlist *uidlist = ctx->uidlist;
1976
1977 /* buffer is unsorted, sort it by UID */
1978 array_sort(&ctx->records, maildir_uid_cmp);
1979
1980 array_free(&uidlist->records);
1981 uidlist->records = ctx->records;
1982 ctx->records.arr.buffer = NULL;
1983 i_assert(array_is_created(&uidlist->records));
1984
1985 hash_table_destroy(&uidlist->files);
1986 uidlist->files = ctx->files;
1987 i_zero(&ctx->files);
1988
1989 pool_unref(&uidlist->record_pool);
1990 uidlist->record_pool = ctx->record_pool;
1991 ctx->record_pool = NULL;
1992
1993 if (ctx->new_files_count != 0) {
1994 ctx->first_new_pos = array_count(&uidlist->records) -
1995 ctx->new_files_count;
1996 maildir_uidlist_assign_uids(ctx);
1997 } else {
1998 ctx->uidlist->change_counter++;
1999 }
2000 }
2001
maildir_uidlist_sync_recreate(struct maildir_uidlist_sync_ctx * ctx)2002 void maildir_uidlist_sync_recreate(struct maildir_uidlist_sync_ctx *ctx)
2003 {
2004 ctx->uidlist->recreate = TRUE;
2005 }
2006
maildir_uidlist_sync_finish(struct maildir_uidlist_sync_ctx * ctx)2007 void maildir_uidlist_sync_finish(struct maildir_uidlist_sync_ctx *ctx)
2008 {
2009 if (!ctx->partial) {
2010 if (!ctx->failed)
2011 maildir_uidlist_swap(ctx);
2012 } else {
2013 if (ctx->new_files_count != 0 && !ctx->failed) {
2014 i_assert(ctx->changed);
2015 i_assert(ctx->locked);
2016 maildir_uidlist_assign_uids(ctx);
2017 }
2018 }
2019
2020 ctx->finished = TRUE;
2021
2022 /* mbox=NULL means we're coming from dbox rebuilding code.
2023 the dbox is already locked, so allow uidlist recreation */
2024 i_assert(ctx->locked || !ctx->changed);
2025 if ((ctx->changed || maildir_uidlist_want_compress(ctx)) &&
2026 !ctx->failed && ctx->locked) {
2027 T_BEGIN {
2028 if (maildir_uidlist_sync_update(ctx) < 0) {
2029 /* we couldn't write everything we wanted. make
2030 sure we don't continue using those UIDs */
2031 maildir_uidlist_reset(ctx->uidlist);
2032 ctx->failed = TRUE;
2033 }
2034 } T_END;
2035 }
2036 }
2037
maildir_uidlist_sync_deinit(struct maildir_uidlist_sync_ctx ** _ctx,bool success)2038 int maildir_uidlist_sync_deinit(struct maildir_uidlist_sync_ctx **_ctx,
2039 bool success)
2040 {
2041 struct maildir_uidlist_sync_ctx *ctx = *_ctx;
2042 int ret;
2043
2044 *_ctx = NULL;
2045
2046 if (!success)
2047 ctx->failed = TRUE;
2048 ret = ctx->failed ? -1 : 0;
2049
2050 if (!ctx->finished)
2051 maildir_uidlist_sync_finish(ctx);
2052 if (ctx->partial)
2053 maildir_uidlist_mark_all(ctx->uidlist, FALSE);
2054 if (ctx->locked)
2055 maildir_uidlist_unlock(ctx->uidlist);
2056
2057 hash_table_destroy(&ctx->files);
2058 pool_unref(&ctx->record_pool);
2059 if (array_is_created(&ctx->records))
2060 array_free(&ctx->records);
2061 i_free(ctx);
2062 return ret;
2063 }
2064
maildir_uidlist_add_flags(struct maildir_uidlist * uidlist,const char * filename,enum maildir_uidlist_rec_flag flags)2065 void maildir_uidlist_add_flags(struct maildir_uidlist *uidlist,
2066 const char *filename,
2067 enum maildir_uidlist_rec_flag flags)
2068 {
2069 struct maildir_uidlist_rec *rec;
2070
2071 rec = hash_table_lookup(uidlist->files, filename);
2072 i_assert(rec != NULL);
2073
2074 rec->flags |= flags;
2075 }
2076
2077 struct maildir_uidlist_iter_ctx *
maildir_uidlist_iter_init(struct maildir_uidlist * uidlist)2078 maildir_uidlist_iter_init(struct maildir_uidlist *uidlist)
2079 {
2080 struct maildir_uidlist_iter_ctx *ctx;
2081 unsigned int count;
2082
2083 ctx = i_new(struct maildir_uidlist_iter_ctx, 1);
2084 ctx->uidlist = uidlist;
2085 ctx->next = array_get(&uidlist->records, &count);
2086 ctx->end = ctx->next + count;
2087 ctx->change_counter = ctx->uidlist->change_counter;
2088 return ctx;
2089 }
2090
2091 static void
maildir_uidlist_iter_update_idx(struct maildir_uidlist_iter_ctx * ctx)2092 maildir_uidlist_iter_update_idx(struct maildir_uidlist_iter_ctx *ctx)
2093 {
2094 unsigned int old_rev_idx, idx, count;
2095
2096 old_rev_idx = ctx->end - ctx->next;
2097 ctx->next = array_get(&ctx->uidlist->records, &count);
2098 ctx->end = ctx->next + count;
2099
2100 idx = old_rev_idx >= count ? 0 :
2101 count - old_rev_idx;
2102 while (idx < count && ctx->next[idx]->uid <= ctx->prev_uid)
2103 idx++;
2104 while (idx > 0 && ctx->next[idx-1]->uid > ctx->prev_uid)
2105 idx--;
2106
2107 ctx->next += idx;
2108 }
2109
maildir_uidlist_iter_next_rec(struct maildir_uidlist_iter_ctx * ctx,struct maildir_uidlist_rec ** rec_r)2110 static bool maildir_uidlist_iter_next_rec(struct maildir_uidlist_iter_ctx *ctx,
2111 struct maildir_uidlist_rec **rec_r)
2112 {
2113 struct maildir_uidlist_rec *rec;
2114
2115 if (ctx->change_counter != ctx->uidlist->change_counter)
2116 maildir_uidlist_iter_update_idx(ctx);
2117
2118 if (ctx->next == ctx->end)
2119 return FALSE;
2120
2121 rec = *ctx->next;
2122 i_assert(rec->uid != (uint32_t)-1);
2123
2124 ctx->prev_uid = rec->uid;
2125 ctx->next++;
2126
2127 *rec_r = rec;
2128 return TRUE;
2129 }
2130
maildir_uidlist_iter_next(struct maildir_uidlist_iter_ctx * ctx,uint32_t * uid_r,enum maildir_uidlist_rec_flag * flags_r,const char ** filename_r)2131 bool maildir_uidlist_iter_next(struct maildir_uidlist_iter_ctx *ctx,
2132 uint32_t *uid_r,
2133 enum maildir_uidlist_rec_flag *flags_r,
2134 const char **filename_r)
2135 {
2136 struct maildir_uidlist_rec *rec;
2137
2138 if (!maildir_uidlist_iter_next_rec(ctx, &rec))
2139 return FALSE;
2140
2141 *uid_r = rec->uid;
2142 *flags_r = rec->flags;
2143 *filename_r = rec->filename;
2144 return TRUE;
2145 }
2146
maildir_uidlist_iter_deinit(struct maildir_uidlist_iter_ctx ** _ctx)2147 void maildir_uidlist_iter_deinit(struct maildir_uidlist_iter_ctx **_ctx)
2148 {
2149 i_free(*_ctx);
2150 *_ctx = NULL;
2151 }
2152