maildir-uidlist.c revision e30b748edcef3cf3352478bf21fa8f785bdc773a
1516N/A/* Copyright (c) 2003-2007 Dovecot authors, see the included COPYING file */
39N/A
39N/A/*
39N/A Version 1 format has been used for most versions of Dovecot up to v1.0.x.
39N/A It's also compatible with Courier IMAP's courierimapuiddb file.
39N/A The format is:
39N/A
39N/A header: 1 <uid validity> <next uid>
39N/A entry: <uid> <filename>
39N/A
39N/A --
39N/A
39N/A Version 2 format was written by a few development Dovecot versions, but
39N/A v1.0.x still parses the format. The format has <flags> field after <uid>.
39N/A
39N/A --
39N/A
39N/A Version 3 format is an extensible format used by Dovecot v1.1 and later.
39N/A It's also parsed by v1.0.2 (and later). The format is:
39N/A
39N/A header: 3 [<key><value> ...]
926N/A entry: <uid> [<key><value> ...] :<filename>
926N/A
2238N/A See enum maildir_uidlist_*_ext_key for used keys.
926N/A*/
39N/A
2453N/A#include "lib.h"
342N/A#include "array.h"
1516N/A#include "hash.h"
1636N/A#include "istream.h"
1386N/A#include "ostream.h"
838N/A#include "str.h"
39N/A#include "file-dotlock.h"
51N/A#include "close-keep-errno.h"
2073N/A#include "nfs-workarounds.h"
2144N/A#include "maildir-storage.h"
1066N/A#include "maildir-sync.h"
1231N/A#include "maildir-filename.h"
2453N/A#include "maildir-uidlist.h"
1352N/A
1890N/A#include <stdio.h>
296N/A#include <stdlib.h>
39N/A#include <sys/stat.h>
1713N/A
1713N/A/* NFS: How many times to retry reading dovecot-uidlist file if ESTALE
39N/A error occurs in the middle of reading it */
39N/A#define UIDLIST_ESTALE_RETRY_COUNT NFS_ESTALE_RETRY_COUNT
39N/A
39N/A/* how many seconds to wait before overriding uidlist.lock */
39N/A#define UIDLIST_LOCK_STALE_TIMEOUT (60*2)
205N/A
39N/A#define UIDLIST_COMPRESS_PERCENTAGE 75
205N/A
39N/A#define UIDLIST_IS_LOCKED(uidlist) \
39N/A ((uidlist)->lock_count > 0)
39N/A
39N/Astruct maildir_uidlist_rec {
39N/A uint32_t uid;
39N/A uint32_t flags;
39N/A char *filename;
39N/A char *extensions; /* <data>\0[<data>\0 ...]\0 */
39N/A};
39N/AARRAY_DEFINE_TYPE(maildir_uidlist_rec_p, struct maildir_uidlist_rec *);
39N/A
39N/Astruct maildir_uidlist {
39N/A struct maildir_mailbox *mbox;
39N/A struct index_mailbox *ibox;
39N/A char *path;
39N/A
39N/A int fd;
39N/A dev_t fd_dev;
39N/A ino_t fd_ino;
39N/A off_t fd_size;
39N/A
39N/A unsigned int lock_count;
48N/A
48N/A struct dotlock_settings dotlock_settings;
48N/A struct dotlock *dotlock;
48N/A
48N/A pool_t record_pool;
59N/A ARRAY_TYPE(maildir_uidlist_rec_p) records;
48N/A struct hash_table *files;
2073N/A unsigned int change_counter;
2073N/A
39N/A unsigned int version;
39N/A unsigned int uid_validity, next_uid, prev_read_uid, last_seen_uid;
567N/A unsigned int read_records_count;
838N/A uoff_t last_read_offset;
838N/A string_t *hdr_extensions;
838N/A
1890N/A unsigned int recreate:1;
2453N/A unsigned int initial_read:1;
39N/A unsigned int initial_sync:1;
39N/A};
39N/A
1431N/Astruct maildir_uidlist_sync_ctx {
1431N/A struct maildir_uidlist *uidlist;
39N/A enum maildir_uidlist_sync_flags sync_flags;
227N/A
315N/A pool_t record_pool;
315N/A ARRAY_TYPE(maildir_uidlist_rec_p) records;
39N/A struct hash_table *files;
1352N/A
1352N/A unsigned int first_unwritten_pos, first_nouid_pos;
1352N/A unsigned int new_files_count;
1352N/A
1431N/A unsigned int partial:1;
1431N/A unsigned int finished:1;
429N/A unsigned int changed:1;
315N/A unsigned int failed:1;
1352N/A unsigned int locked:1;
429N/A};
1352N/A
1352N/Astruct maildir_uidlist_iter_ctx {
39N/A struct maildir_uidlist *uidlist;
926N/A struct maildir_uidlist_rec *const *next, *const *end;
926N/A
203N/A unsigned int change_counter;
203N/A uint32_t prev_uid;
203N/A};
203N/A
1045N/Astatic bool maildir_uidlist_iter_next_rec(struct maildir_uidlist_iter_ctx *ctx,
1045N/A struct maildir_uidlist_rec **rec_r);
72N/A
72N/Astatic int maildir_uidlist_lock_timeout(struct maildir_uidlist *uidlist,
59N/A bool nonblock)
2054N/A{
1045N/A struct mailbox *box = &uidlist->ibox->box;
1045N/A const char *control_dir, *path;
1045N/A mode_t old_mask;
1045N/A const enum dotlock_create_flags dotlock_flags =
1713N/A nonblock ? DOTLOCK_CREATE_FLAG_NONBLOCK : 0;
1045N/A int i, ret;
1045N/A
1045N/A if (uidlist->lock_count > 0) {
2084N/A uidlist->lock_count++;
2084N/A return 1;
2084N/A }
2084N/A
2084N/A control_dir = mailbox_list_get_path(box->storage->list, box->name,
2084N/A MAILBOX_LIST_PATH_TYPE_CONTROL);
2084N/A path = t_strconcat(control_dir, "/" MAILDIR_UIDLIST_NAME, NULL);
72N/A
2084N/A for (i = 0;; i++) {
838N/A old_mask = umask(0777 & ~box->file_create_mode);
72N/A ret = file_dotlock_create(&uidlist->dotlock_settings, path,
72N/A dotlock_flags, &uidlist->dotlock);
2084N/A umask(old_mask);
838N/A if (ret > 0)
72N/A break;
59N/A
72N/A /* failure */
72N/A if (ret == 0) {
59N/A mail_storage_set_error(box->storage,
72N/A MAIL_ERROR_TEMP, MAIL_ERRSTR_LOCK_TIMEOUT);
72N/A return 0;
72N/A }
72N/A if (errno != ENOENT || i == MAILDIR_DELETE_RETRY_COUNT ||
72N/A uidlist->mbox == NULL) {
2144N/A mail_storage_set_critical(box->storage,
72N/A "file_dotlock_create(%s) failed: %m", path);
59N/A return -1;
72N/A }
72N/A /* the control dir doesn't exist. create it unless the whole
72N/A mailbox was just deleted. */
72N/A if (maildir_set_deleted(uidlist->mbox))
59N/A return -1;
72N/A }
72N/A
72N/A uidlist->lock_count++;
202N/A
72N/A /* make sure we have the latest changes before changing anything */
72N/A if (maildir_uidlist_refresh(uidlist) < 0) {
59N/A maildir_uidlist_unlock(uidlist);
1713N/A return -1;
59N/A }
838N/A return 1;
2240N/A}
838N/A
838N/Aint maildir_uidlist_lock(struct maildir_uidlist *uidlist)
838N/A{
926N/A return maildir_uidlist_lock_timeout(uidlist, FALSE);
838N/A}
838N/A
2240N/Aint maildir_uidlist_try_lock(struct maildir_uidlist *uidlist)
2240N/A{
2284N/A return maildir_uidlist_lock_timeout(uidlist, TRUE);
2240N/A}
838N/A
2240N/Aint maildir_uidlist_lock_touch(struct maildir_uidlist *uidlist)
2240N/A{
2240N/A i_assert(UIDLIST_IS_LOCKED(uidlist));
2240N/A
2240N/A return file_dotlock_touch(uidlist->dotlock);
2240N/A}
2240N/A
2240N/Abool maildir_uidlist_is_locked(struct maildir_uidlist *uidlist)
2240N/A{
2317N/A return UIDLIST_IS_LOCKED(uidlist);
2240N/A}
2240N/A
2240N/Avoid maildir_uidlist_unlock(struct maildir_uidlist *uidlist)
2240N/A{
2240N/A i_assert(uidlist->lock_count > 0);
2240N/A
2240N/A if (--uidlist->lock_count > 0)
2240N/A return;
2240N/A
2240N/A (void)file_dotlock_delete(&uidlist->dotlock);
2240N/A}
2284N/A
2284N/Astruct maildir_uidlist *
2284N/Amaildir_uidlist_init_readonly(struct index_mailbox *ibox)
2284N/A{
2240N/A struct mailbox *box = &ibox->box;
2240N/A struct maildir_uidlist *uidlist;
2284N/A const char *control_dir;
2284N/A
2284N/A control_dir = mailbox_list_get_path(box->storage->list, box->name,
838N/A MAILBOX_LIST_PATH_TYPE_CONTROL);
838N/A
838N/A uidlist = i_new(struct maildir_uidlist, 1);
838N/A uidlist->fd = -1;
838N/A uidlist->ibox = ibox;
838N/A uidlist->path = i_strconcat(control_dir, "/"MAILDIR_UIDLIST_NAME, NULL);
838N/A i_array_init(&uidlist->records, 128);
926N/A uidlist->files = hash_create(default_pool, default_pool, 4096,
838N/A maildir_filename_base_hash,
838N/A maildir_filename_base_cmp);
838N/A uidlist->next_uid = 1;
838N/A uidlist->hdr_extensions = str_new(default_pool, 128);
838N/A
838N/A uidlist->dotlock_settings.use_io_notify = TRUE;
838N/A uidlist->dotlock_settings.use_excl_lock =
845N/A (box->storage->flags & MAIL_STORAGE_FLAG_DOTLOCK_USE_EXCL) != 0;
845N/A uidlist->dotlock_settings.nfs_flush =
926N/A (box->storage->flags &
838N/A MAIL_STORAGE_FLAG_NFS_FLUSH_STORAGE) != 0;
845N/A uidlist->dotlock_settings.timeout = UIDLIST_LOCK_STALE_TIMEOUT + 2;
845N/A uidlist->dotlock_settings.stale_timeout = UIDLIST_LOCK_STALE_TIMEOUT;
845N/A
838N/A return uidlist;
845N/A}
845N/A
838N/Astruct maildir_uidlist *maildir_uidlist_init(struct maildir_mailbox *mbox)
926N/A{
203N/A struct maildir_uidlist *uidlist;
315N/A
838N/A uidlist = maildir_uidlist_init_readonly(&mbox->ibox);
203N/A uidlist->mbox = mbox;
838N/A uidlist->dotlock_settings.temp_prefix = mbox->storage->temp_prefix;
48N/A return uidlist;
48N/A}
48N/A
48N/Astatic void maildir_uidlist_close(struct maildir_uidlist *uidlist)
48N/A{
838N/A if (uidlist->fd != -1) {
203N/A if (close(uidlist->fd) < 0)
48N/A i_error("close(%s) failed: %m", uidlist->path);
203N/A uidlist->fd = -1;
72N/A uidlist->fd_ino = 0;
181N/A }
72N/A uidlist->last_read_offset = 0;
181N/A}
46N/A
181N/Avoid maildir_uidlist_deinit(struct maildir_uidlist **_uidlist)
237N/A{
46N/A struct maildir_uidlist *uidlist = *_uidlist;
2453N/A
2453N/A i_assert(!UIDLIST_IS_LOCKED(uidlist));
2453N/A
2453N/A *_uidlist = NULL;
2339N/A maildir_uidlist_update(uidlist);
2453N/A maildir_uidlist_close(uidlist);
2453N/A
2453N/A hash_destroy(&uidlist->files);
2453N/A if (uidlist->record_pool != NULL)
2453N/A pool_unref(&uidlist->record_pool);
2339N/A
2339N/A array_free(&uidlist->records);
2339N/A str_free(&uidlist->hdr_extensions);
2339N/A i_free(uidlist->path);
2339N/A i_free(uidlist);
2339N/A}
2453N/A
2453N/Astatic int maildir_uid_cmp(const void *p1, const void *p2)
2453N/A{
2453N/A const struct maildir_uidlist_rec *const *rec1 = p1, *const *rec2 = p2;
2453N/A
2453N/A return (*rec1)->uid < (*rec2)->uid ? -1 :
2453N/A (*rec1)->uid > (*rec2)->uid ? 1 : 0;
2453N/A}
2453N/A
2453N/Astatic void
2453N/Amaildir_uidlist_records_array_delete(struct maildir_uidlist *uidlist,
2453N/A struct maildir_uidlist_rec *rec)
2339N/A{
2453N/A struct maildir_uidlist_rec *const *recs, *const *pos;
2453N/A unsigned int count;
2453N/A
2453N/A recs = array_get(&uidlist->records, &count);
2453N/A pos = bsearch(&rec, recs, count, sizeof(*recs), maildir_uid_cmp);
2453N/A i_assert(pos != NULL);
2453N/A
2453N/A array_delete(&uidlist->records, pos - recs, 1);
2453N/A}
2453N/A
2453N/Astatic bool
2453N/Amaildir_uidlist_read_extended(struct maildir_uidlist *uidlist,
2453N/A const char **line_p,
2453N/A struct maildir_uidlist_rec *rec)
2453N/A{
2453N/A const char *start, *line = *line_p;
2453N/A buffer_t *buf;
2453N/A
2453N/A t_push();
2453N/A buf = buffer_create_dynamic(pool_datastack_create(), 128);
2453N/A while (*line != '\0' && *line != ':') {
2453N/A /* skip over an extension field */
2453N/A start = line;
2453N/A while (*line != ' ' && *line != '\0') line++;
2453N/A buffer_append(buf, start, line - start);
2453N/A buffer_append_c(buf, '\0');
2453N/A while (*line == ' ') line++;
2453N/A }
2339N/A
2339N/A if (buf->used > 0) {
2339N/A /* save the extensions */
2339N/A buffer_append_c(buf, '\0');
2339N/A rec->extensions = p_malloc(uidlist->record_pool, buf->used);
2453N/A memcpy(rec->extensions, buf->data, buf->used);
2453N/A }
2453N/A t_pop();
2453N/A
2453N/A if (*line == ':')
2339N/A line++;
2453N/A if (*line == '\0')
2453N/A return FALSE;
2453N/A
2453N/A *line_p = line;
2339N/A return TRUE;
2453N/A}
2453N/A
2453N/Astatic int maildir_uidlist_next(struct maildir_uidlist *uidlist,
2453N/A const char *line)
2453N/A{
2339N/A struct mail_storage *storage = uidlist->ibox->box.storage;
2339N/A struct maildir_uidlist_rec *rec, *old_rec;
2339N/A uint32_t uid;
2339N/A
2339N/A uid = 0;
2339N/A while (*line >= '0' && *line <= '9') {
2453N/A uid = uid*10 + (*line - '0');
2453N/A line++;
2339N/A }
2453N/A
2339N/A if (uid == 0 || *line != ' ') {
2453N/A /* invalid file */
2339N/A mail_storage_set_critical(storage,
2339N/A "Invalid data in file %s", uidlist->path);
2339N/A return 0;
2339N/A }
2339N/A if (uid <= uidlist->prev_read_uid) {
2339N/A mail_storage_set_critical(storage,
2339N/A "UIDs not ordered in file %s (%u > %u)",
2339N/A uidlist->path, uid, uidlist->prev_read_uid);
2339N/A return 0;
2339N/A }
2453N/A uidlist->prev_read_uid = uid;
2453N/A
2453N/A if (uid <= uidlist->last_seen_uid) {
2453N/A /* we already have this */
2453N/A return 1;
2453N/A }
2453N/A uidlist->last_seen_uid = uid;
2453N/A
2453N/A if (uid >= uidlist->next_uid && uidlist->version == 1) {
2453N/A mail_storage_set_critical(storage,
2453N/A "UID larger than next_uid in file %s (%u >= %u)",
2453N/A uidlist->path, uid, uidlist->next_uid);
2453N/A return 0;
2453N/A }
2453N/A
2453N/A rec = p_new(uidlist->record_pool, struct maildir_uidlist_rec, 1);
2453N/A rec->uid = uid;
2453N/A rec->flags = MAILDIR_UIDLIST_REC_FLAG_NONSYNCED;
2453N/A
2453N/A while (*line == ' ') line++;
2453N/A
2453N/A if (uidlist->version == 3) {
2453N/A /* read extended fields */
2453N/A if (!maildir_uidlist_read_extended(uidlist, &line, rec)) {
2453N/A mail_storage_set_critical(storage,
2453N/A "Invalid data in file %s", uidlist->path);
2453N/A return 0;
2453N/A }
2453N/A }
2453N/A
2453N/A old_rec = hash_lookup(uidlist->files, line);
2453N/A if (old_rec != NULL) {
2453N/A /* This can happen if expunged file is moved back and the file
2453N/A was appended to uidlist. */
2453N/A i_warning("%s: Duplicate file entry: %s", uidlist->path, line);
2453N/A /* Delete the old UID */
2453N/A maildir_uidlist_records_array_delete(uidlist, old_rec);
2453N/A /* Replace the old record with this new one */
2453N/A *old_rec = *rec;
2453N/A rec = old_rec;
2453N/A uidlist->recreate = TRUE;
2453N/A }
838N/A
838N/A rec->filename = p_strdup(uidlist->record_pool, line);
838N/A hash_insert(uidlist->files, rec->filename, rec);
838N/A array_append(&uidlist->records, &rec, 1);
838N/A return 1;
838N/A}
838N/A
838N/Astatic int maildir_uidlist_read_header(struct maildir_uidlist *uidlist,
342N/A struct istream *input)
926N/A{
838N/A struct mail_storage *storage = uidlist->ibox->box.storage;
838N/A unsigned int uid_validity, next_uid;
926N/A string_t *ext_hdr;
838N/A const char *line, *value;
838N/A char key;
838N/A
838N/A line = i_stream_read_next_line(input);
838N/A if (line == NULL) {
926N/A /* I/O error / empty file */
2453N/A return input->stream_errno == 0 ? 0 : -1;
2453N/A }
2453N/A
2453N/A if (*line < '0' || *line > '9' || line[1] != ' ') {
2453N/A mail_storage_set_critical(storage,
2453N/A "%s: Corrupted header (invalid version number)",
2453N/A uidlist->path);
2453N/A return 0;
926N/A }
615N/A
615N/A uidlist->version = *line - '0';
615N/A line += 2;
615N/A
615N/A switch (uidlist->version) {
926N/A case 1:
615N/A if (sscanf(line, "%u %u", &uid_validity, &next_uid) != 2) {
615N/A mail_storage_set_critical(storage,
838N/A "%s: Corrupted header (version 1)",
111N/A uidlist->path);
111N/A return 0;
111N/A }
111N/A if (uid_validity == uidlist->uid_validity &&
111N/A next_uid < uidlist->next_uid) {
111N/A mail_storage_set_critical(storage,
111N/A "%s: next_uid was lowered (v1, %u -> %u)",
111N/A uidlist->path, uidlist->next_uid, next_uid);
113N/A return 0;
926N/A }
838N/A break;
926N/A case 3:
113N/A ext_hdr = uidlist->hdr_extensions;
113N/A str_truncate(ext_hdr, 0);
113N/A while (*line != '\0') {
113N/A t_push();
113N/A key = *line;
113N/A value = ++line;
113N/A while (*line != '\0' && *line != ' ') line++;
113N/A value = t_strdup_until(value, line);
113N/A
111N/A switch (key) {
1500N/A case MAILDIR_UIDLIST_HDR_EXT_UID_VALIDITY:
1432N/A uid_validity = strtoul(value, NULL, 10);
1542N/A break;
1970N/A case MAILDIR_UIDLIST_HDR_EXT_NEXT_UID:
2073N/A next_uid = strtoul(value, NULL, 10);
2073N/A break;
2073N/A default:
2073N/A if (str_len(ext_hdr) > 0)
2073N/A str_append_c(ext_hdr, ' ');
2073N/A str_printfa(ext_hdr, "%c%s", key, value);
1542N/A break;
146N/A }
1432N/A
1432N/A while (*line == ' ') line++;
50N/A t_pop();
1432N/A }
1432N/A break;
1432N/A default:
51N/A mail_storage_set_critical(storage, "%s: Unsupported version %u",
1636N/A uidlist->path, uidlist->version);
1432N/A return 0;
1507N/A }
51N/A
1500N/A if (uid_validity == 0 || next_uid == 0) {
591N/A mail_storage_set_critical(storage,
1970N/A "%s: Broken header (uidvalidity = %u, next_uid=%u)",
1970N/A uidlist->path, uid_validity, next_uid);
1970N/A return 0;
591N/A }
1542N/A
1970N/A uidlist->uid_validity = uid_validity;
1970N/A uidlist->next_uid = next_uid;
1970N/A return 1;
2073N/A}
1500N/A
2073N/Astatic int
2073N/Amaildir_uidlist_update_read(struct maildir_uidlist *uidlist,
1713N/A bool *retry_r, bool try_retry)
1713N/A{
2073N/A struct mail_storage *storage = uidlist->ibox->box.storage;
2073N/A const char *line;
2073N/A unsigned int orig_next_uid;
1713N/A struct istream *input;
2073N/A struct stat st;
1713N/A uoff_t last_read_offset;
2284N/A int fd, ret;
2073N/A
2073N/A *retry_r = FALSE;
1713N/A
2073N/A if (uidlist->fd == -1) {
2073N/A fd = nfs_safe_open(uidlist->path, O_RDWR);
2073N/A if (fd == -1) {
1713N/A if (errno != ENOENT) {
1713N/A mail_storage_set_critical(storage,
2073N/A "open(%s) failed: %m", uidlist->path);
2073N/A return -1;
2073N/A }
1500N/A return 0;
1500N/A }
1500N/A last_read_offset = 0;
1500N/A } else {
1500N/A /* the file was updated */
51N/A fd = uidlist->fd;
1500N/A if (lseek(fd, 0, SEEK_SET) < 0) {
1500N/A mail_storage_set_critical(storage,
1500N/A "lseek(%s) failed: %m", uidlist->path);
1500N/A return -1;
1500N/A }
1500N/A uidlist->fd = -1;
1500N/A uidlist->fd_ino = 0;
2073N/A last_read_offset = uidlist->last_read_offset;
2073N/A uidlist->last_read_offset = 0;
2073N/A }
2073N/A
2073N/A if (fstat(fd, &st) < 0) {
2073N/A close_keep_errno(fd);
1890N/A if (errno == ESTALE && try_retry) {
1500N/A *retry_r = TRUE;
2073N/A return -1;
2073N/A }
2073N/A mail_storage_set_critical(storage,
1500N/A "fstat(%s) failed: %m", uidlist->path);
1500N/A return -1;
1500N/A }
1500N/A
926N/A if (uidlist->record_pool == NULL) {
1500N/A uidlist->record_pool =
2026N/A pool_alloconly_create(MEMPOOL_GROWING
1500N/A "uidlist record_pool",
2026N/A nearest_power(st.st_size -
2026N/A st.st_size/8));
2026N/A }
2026N/A
2073N/A input = i_stream_create_fd(fd, 4096, FALSE);
2026N/A i_stream_seek(input, uidlist->last_read_offset);
2026N/A
1500N/A orig_next_uid = uidlist->next_uid;
1500N/A ret = input->v_offset != 0 ? 1 :
1500N/A maildir_uidlist_read_header(uidlist, input);
1500N/A if (ret > 0) {
1500N/A uidlist->prev_read_uid = 0;
123N/A uidlist->change_counter++;
1500N/A uidlist->read_records_count = 0;
1500N/A
877N/A ret = 1;
1500N/A while ((line = i_stream_read_next_line(input)) != NULL) {
1500N/A uidlist->read_records_count++;
1500N/A if (!maildir_uidlist_next(uidlist, line)) {
1500N/A ret = 0;
1500N/A break;
1500N/A }
1500N/A }
1500N/A if (input->stream_errno != 0)
1500N/A ret = -1;
1500N/A
1500N/A if (uidlist->next_uid <= uidlist->prev_read_uid)
1500N/A uidlist->next_uid = uidlist->prev_read_uid + 1;
1500N/A if (uidlist->next_uid < orig_next_uid) {
1500N/A mail_storage_set_critical(storage,
39N/A "%s: next_uid was lowered (%u -> %u)",
1500N/A uidlist->path, orig_next_uid,
1500N/A uidlist->next_uid);
1500N/A uidlist->recreate = TRUE;
1500N/A uidlist->next_uid = orig_next_uid;
1500N/A }
838N/A }
1500N/A
1500N/A if (ret == 0) {
1500N/A /* file is broken */
1500N/A (void)unlink(uidlist->path);
1500N/A } else if (ret > 0) {
1500N/A /* success */
2238N/A uidlist->fd = fd;
2238N/A uidlist->fd_dev = st.st_dev;
2238N/A uidlist->fd_ino = st.st_ino;
2238N/A uidlist->fd_size = st.st_size;
1500N/A uidlist->last_read_offset = input->v_offset;
1500N/A } else {
2238N/A /* I/O error */
2238N/A if (input->stream_errno == ESTALE && try_retry)
2238N/A *retry_r = TRUE;
2238N/A else {
2238N/A errno = input->stream_errno;
2238N/A mail_storage_set_critical(storage,
39N/A "read(%s) failed: %m", uidlist->path);
956N/A }
956N/A }
956N/A
956N/A i_stream_destroy(&input);
1431N/A if (ret <= 0) {
1431N/A if (close(fd) < 0)
956N/A i_error("close(%s) failed: %m", uidlist->path);
956N/A }
956N/A return ret;
956N/A}
956N/A
956N/Astatic int
941N/Amaildir_uidlist_has_changed(struct maildir_uidlist *uidlist, bool *recreated_r)
1007N/A{
1007N/A struct mail_storage *storage = uidlist->ibox->box.storage;
1007N/A struct stat st;
1007N/A
1007N/A *recreated_r = FALSE;
1007N/A
1007N/A if ((storage->flags & MAIL_STORAGE_FLAG_NFS_FLUSH_STORAGE) != 0) {
1007N/A nfs_flush_file_handle_cache(uidlist->path);
1007N/A nfs_flush_attr_cache_unlocked(uidlist->path);
1007N/A }
1007N/A if (nfs_safe_stat(uidlist->path, &st) < 0) {
1007N/A if (errno != ENOENT) {
1007N/A mail_storage_set_critical(storage,
1007N/A "stat(%s) failed: %m", uidlist->path);
1100N/A return -1;
1407N/A }
941N/A return 0;
941N/A }
144N/A
941N/A if (st.st_ino != uidlist->fd_ino ||
1100N/A !CMP_DEV_T(st.st_dev, uidlist->fd_dev)) {
1100N/A /* file recreated */
1100N/A *recreated_r = TRUE;
1100N/A return 1;
1100N/A }
1100N/A
1100N/A if ((storage->flags & MAIL_STORAGE_FLAG_NFS_FLUSH_STORAGE) != 0) {
1100N/A /* NFS: either the file hasn't been changed, or it has already
1100N/A been deleted and the inodes just happen to be the same.
1100N/A check if the fd is still valid. */
941N/A if (fstat(uidlist->fd, &st) < 0) {
941N/A if (errno == ESTALE) {
941N/A *recreated_r = TRUE;
941N/A return 1;
941N/A }
941N/A mail_storage_set_critical(storage,
941N/A "fstat(%s) failed: %m", uidlist->path);
941N/A return -1;
941N/A }
941N/A }
941N/A
941N/A if (st.st_size != uidlist->fd_size) {
941N/A /* file modified but not recreated */
941N/A return 1;
941N/A } else {
429N/A /* unchanged */
941N/A return 0;
941N/A }
941N/A}
941N/A
941N/Aint maildir_uidlist_refresh(struct maildir_uidlist *uidlist)
429N/A{
941N/A unsigned int i;
941N/A bool retry, recreated;
941N/A int ret;
941N/A
941N/A if (uidlist->fd != -1) {
1007N/A ret = maildir_uidlist_has_changed(uidlist, &recreated);
1007N/A if (ret <= 0)
1234N/A return ret;
1007N/A
1007N/A if (recreated)
1007N/A maildir_uidlist_close(uidlist);
1007N/A }
1007N/A
1007N/A for (i = 0; ; i++) {
1007N/A ret = maildir_uidlist_update_read(uidlist, &retry,
1007N/A i < UIDLIST_ESTALE_RETRY_COUNT);
1007N/A if (!retry)
1007N/A break;
1007N/A /* ESTALE - try reopening and rereading */
1007N/A maildir_uidlist_close(uidlist);
1007N/A }
1007N/A if (ret >= 0)
1007N/A uidlist->initial_read = TRUE;
1007N/A return ret;
1007N/A}
1007N/A
1007N/Astatic struct maildir_uidlist_rec *
1007N/Amaildir_uidlist_lookup_rec(struct maildir_uidlist *uidlist, uint32_t uid,
1007N/A unsigned int *idx_r)
1007N/A{
1007N/A struct maildir_uidlist_rec *const *recs;
1007N/A unsigned int idx, left_idx, right_idx;
1007N/A
1007N/A if (!uidlist->initial_read) {
1007N/A /* first time we need to read uidlist */
1007N/A if (maildir_uidlist_refresh(uidlist) < 0)
941N/A return NULL;
941N/A }
941N/A
144N/A idx = left_idx = 0;
144N/A recs = array_get(&uidlist->records, &right_idx);
1472N/A while (left_idx < right_idx) {
1472N/A idx = (left_idx + right_idx) / 2;
1472N/A
1472N/A if (recs[idx]->uid < uid)
1352N/A left_idx = idx+1;
1516N/A else if (recs[idx]->uid > uid)
1890N/A right_idx = idx;
1890N/A else {
1890N/A *idx_r = idx;
1890N/A return recs[idx];
1890N/A }
1352N/A }
1472N/A
1352N/A if (idx > 0) idx--;
1352N/A *idx_r = idx;
1352N/A return NULL;
1352N/A}
1352N/A
1352N/Aconst char *
1352N/Amaildir_uidlist_lookup(struct maildir_uidlist *uidlist, uint32_t uid,
2073N/A enum maildir_uidlist_rec_flag *flags_r)
1352N/A{
429N/A const struct maildir_uidlist_rec *rec;
429N/A unsigned int idx;
144N/A
1386N/A rec = maildir_uidlist_lookup_rec(uidlist, uid, &idx);
1386N/A if (rec == NULL) {
1386N/A if (uidlist->fd != -1 || uidlist->mbox == NULL)
1636N/A return NULL;
1636N/A
1636N/A /* the uidlist doesn't exist. */
1636N/A if (maildir_storage_sync_force(uidlist->mbox) < 0)
2073N/A return NULL;
1636N/A
2073N/A /* try again */
1636N/A rec = maildir_uidlist_lookup_rec(uidlist, uid, &idx);
1636N/A if (rec == NULL)
1636N/A return NULL;
1386N/A }
1636N/A
1636N/A *flags_r = rec->flags;
1636N/A return rec->filename;
1636N/A}
2073N/A
1636N/Aconst char *
2284N/Amaildir_uidlist_lookup_ext(struct maildir_uidlist *uidlist, uint32_t uid,
1636N/A enum maildir_uidlist_rec_ext_key key)
1636N/A{
1636N/A const struct maildir_uidlist_rec *rec;
1386N/A unsigned int idx;
315N/A const char *p, *value;
315N/A
315N/A rec = maildir_uidlist_lookup_rec(uidlist, uid, &idx);
315N/A if (rec == NULL || rec->extensions == NULL)
315N/A return NULL;
315N/A
315N/A p = rec->extensions; value = NULL;
315N/A while (*p != '\0') {
1636N/A /* <key><value>\0 */
1636N/A if (*p == (char)key)
1636N/A return p + 1;
1636N/A
1636N/A p += strlen(p) + 1;
1636N/A }
2073N/A return NULL;
1636N/A}
2073N/A
1636N/Auint32_t maildir_uidlist_get_uid_validity(struct maildir_uidlist *uidlist)
1636N/A{
144N/A return uidlist->uid_validity;
838N/A}
838N/A
838N/Auint32_t maildir_uidlist_get_next_uid(struct maildir_uidlist *uidlist)
926N/A{
926N/A return !uidlist->initial_read ? 0 : uidlist->next_uid;
926N/A}
926N/A
838N/Avoid maildir_uidlist_set_uid_validity(struct maildir_uidlist *uidlist,
1231N/A uint32_t uid_validity)
1231N/A{
2091N/A i_assert(uid_validity != 0);
1231N/A
1231N/A uidlist->uid_validity = uid_validity;
1231N/A}
1231N/A
1231N/Avoid maildir_uidlist_set_next_uid(struct maildir_uidlist *uidlist,
205N/A uint32_t next_uid, bool force)
257N/A{
257N/A if (uidlist->next_uid < next_uid || force)
257N/A uidlist->next_uid = next_uid;
205N/A}
205N/A
1461N/Avoid maildir_uidlist_set_ext(struct maildir_uidlist *uidlist, uint32_t uid,
1461N/A enum maildir_uidlist_rec_ext_key key,
1461N/A const char *value)
1461N/A{
1461N/A struct maildir_uidlist_rec *rec;
1461N/A unsigned int idx;
1461N/A const char *p;
1461N/A buffer_t *buf;
1461N/A unsigned int len;
1461N/A
1461N/A rec = maildir_uidlist_lookup_rec(uidlist, uid, &idx);
1461N/A i_assert(rec != NULL);
1044N/A
1044N/A t_push();
1044N/A buf = buffer_create_dynamic(pool_datastack_create(), 128);
1044N/A
1044N/A /* copy existing extensions, except for the one we're updating */
1044N/A if (rec->extensions != NULL) {
1044N/A p = rec->extensions;
1044N/A while (*p != '\0') {
1044N/A /* <key><value>\0 */
1044N/A len = strlen(p) + 1;
2286N/A if (*p != (char)key)
1044N/A buffer_append(buf, p, len);
1044N/A p += len;
205N/A }
838N/A }
838N/A buffer_append_c(buf, key);
205N/A buffer_append(buf, value, strlen(value) + 1);
296N/A buffer_append_c(buf, '\0');
296N/A
838N/A rec->extensions = p_malloc(uidlist->record_pool, buf->used);
296N/A memcpy(rec->extensions, buf->data, buf->used);
296N/A
296N/A uidlist->recreate = TRUE;
296N/A t_pop();
296N/A}
296N/A
296N/Astatic int maildir_uidlist_write_fd(struct maildir_uidlist *uidlist, int fd,
1461N/A const char *path, unsigned int first_idx,
296N/A uoff_t *file_size_r)
205N/A{
838N/A struct mail_storage *storage = uidlist->ibox->box.storage;
205N/A struct maildir_uidlist_iter_ctx *iter;
48N/A struct ostream *output;
956N/A struct maildir_uidlist_rec *rec;
2054N/A string_t *str;
2054N/A const char *p;
2054N/A int ret;
2054N/A
2054N/A i_assert(fd != -1);
2054N/A
2054N/A output = o_stream_create_fd_file(fd, (uoff_t)-1, FALSE);
956N/A str = t_str_new(512);
2073N/A
2073N/A if (output->offset == 0) {
2073N/A i_assert(first_idx == 0);
2073N/A uidlist->version = 3;
2073N/A
1386N/A i_assert(uidlist->uid_validity != 0);
2073N/A i_assert(uidlist->next_uid > 0);
2073N/A str_printfa(str, "%u V%u N%u", uidlist->version,
2073N/A uidlist->uid_validity, uidlist->next_uid);
2073N/A if (str_len(uidlist->hdr_extensions) > 0) {
2073N/A str_append_c(str, ' ');
2073N/A str_append_str(str, uidlist->hdr_extensions);
2073N/A }
2073N/A str_append_c(str, '\n');
956N/A o_stream_send(output, str_data(str), str_len(str));
2073N/A }
2073N/A
956N/A iter = maildir_uidlist_iter_init(uidlist);
2073N/A iter->next += first_idx;
2073N/A
2073N/A while (maildir_uidlist_iter_next_rec(iter, &rec)) {
2144N/A str_truncate(str, 0);
2144N/A str_printfa(str, "%u", rec->uid);
2144N/A if (rec->extensions != NULL) {
2073N/A for (p = rec->extensions; *p != '\0'; ) {
2073N/A str_append_c(str, ' ');
2073N/A str_append(str, p);
2073N/A p += strlen(p) + 1;
2073N/A }
956N/A }
2073N/A str_printfa(str, " :%s\n", rec->filename);
956N/A o_stream_send(output, str_data(str), str_len(str));
956N/A }
2073N/A maildir_uidlist_iter_deinit(&iter);
956N/A o_stream_flush(output);
956N/A
956N/A ret = output->stream_errno == 0 ? 0 : -1;
956N/A
956N/A *file_size_r = output->offset;
2073N/A o_stream_unref(&output);
956N/A
967N/A if (ret < 0) {
967N/A mail_storage_set_critical(storage,
967N/A "o_stream_send(%s) failed: %m", path);
2073N/A (void)close(fd);
956N/A return -1;
956N/A }
956N/A
2073N/A if (!uidlist->ibox->fsync_disable) {
1507N/A if (fdatasync(fd) < 0) {
956N/A mail_storage_set_critical(storage,
956N/A "fdatasync(%s) failed: %m", path);
956N/A (void)close(fd);
956N/A return -1;
967N/A }
967N/A }
967N/A return 0;
956N/A}
956N/A
956N/Astatic int maildir_uidlist_recreate(struct maildir_uidlist *uidlist)
2073N/A{
2073N/A struct mailbox *box = &uidlist->ibox->box;
2073N/A const char *control_dir, *temp_path;
956N/A struct stat st;
956N/A mode_t old_mask;
2073N/A uoff_t file_size;
956N/A int i, fd, ret;
956N/A
967N/A control_dir = mailbox_list_get_path(box->storage->list, box->name,
967N/A MAILBOX_LIST_PATH_TYPE_CONTROL);
967N/A temp_path = t_strconcat(control_dir,
967N/A "/" MAILDIR_UIDLIST_NAME ".tmp", NULL);
967N/A
1507N/A for (i = 0;; i++) {
1507N/A old_mask = umask(0777 & ~box->file_create_mode);
1507N/A fd = open(temp_path, O_RDWR | O_CREAT | O_TRUNC, 0777);
967N/A umask(old_mask);
1507N/A if (fd != -1)
956N/A break;
1507N/A
956N/A if (errno != ENOENT || i == MAILDIR_DELETE_RETRY_COUNT ||
956N/A uidlist->mbox == NULL) {
956N/A mail_storage_set_critical(box->storage,
956N/A "open(%s, O_CREAT) failed: %m", temp_path);
956N/A return -1;
967N/A }
956N/A /* the control dir doesn't exist. create it unless the whole
956N/A mailbox was just deleted. */
2073N/A if (maildir_set_deleted(uidlist->mbox))
956N/A return -1;
967N/A }
2073N/A
956N/A if (box->file_create_gid != (gid_t)-1) {
1672N/A if (fchown(fd, (uid_t)-1, box->file_create_gid) < 0) {
967N/A mail_storage_set_critical(box->storage,
956N/A "fchown(%s) failed: %m", temp_path);
956N/A }
956N/A }
1507N/A
956N/A ret = maildir_uidlist_write_fd(uidlist, fd, temp_path, 0, &file_size);
956N/A if (ret == 0) {
956N/A if (rename(temp_path, uidlist->path) < 0) {
956N/A mail_storage_set_critical(box->storage,
2073N/A "rename(%s, %s) failed: %m",
1386N/A temp_path, uidlist->path);
2144N/A ret = -1;
2144N/A }
2144N/A }
956N/A
1507N/A if (ret < 0) {
956N/A if (unlink(temp_path) < 0) {
1386N/A mail_storage_set_critical(box->storage,
1386N/A "unlink(%s) failed: %m", temp_path);
1386N/A }
1386N/A } else if (fstat(fd, &st) < 0) {
1386N/A i_error("fstat(%s) failed: %m", temp_path);
956N/A (void)close(fd);
956N/A ret = -1;
956N/A } else {
1507N/A i_assert(file_size == (uoff_t)st.st_size);
2073N/A maildir_uidlist_close(uidlist);
1386N/A uidlist->fd = fd;
2453N/A uidlist->fd_dev = st.st_dev;
2453N/A uidlist->fd_ino = st.st_ino;
2453N/A uidlist->fd_size = st.st_size;
2453N/A uidlist->last_read_offset = st.st_size;
2453N/A uidlist->recreate = FALSE;
2453N/A }
2453N/A return ret;
2453N/A}
2453N/A
2453N/Aint maildir_uidlist_update(struct maildir_uidlist *uidlist)
956N/A{
2453N/A int ret;
2453N/A
2453N/A if (!uidlist->recreate)
956N/A return 0;
1352N/A
2073N/A if (maildir_uidlist_lock(uidlist) <= 0)
2073N/A return -1;
2073N/A ret = maildir_uidlist_recreate(uidlist);
2073N/A maildir_uidlist_unlock(uidlist);
2035N/A return ret;
2035N/A}
2073N/A
2035N/Astatic int maildir_uidlist_sync_update(struct maildir_uidlist_sync_ctx *ctx)
2035N/A{
2035N/A struct maildir_uidlist *uidlist = ctx->uidlist;
2035N/A uoff_t file_size;
2073N/A
2035N/A if (uidlist->uid_validity == 0) {
2035N/A /* saving a message to a newly created maildir. */
2035N/A const struct mail_index_header *hdr;
2035N/A
2475N/A hdr = mail_index_get_header(uidlist->ibox->view);
2475N/A uidlist->uid_validity = hdr->uid_validity != 0 ?
2475N/A hdr->uid_validity : (uint32_t)ioloop_time;
2475N/A }
2035N/A
2453N/A if (ctx->uidlist->recreate || uidlist->fd == -1 ||
2453N/A uidlist->version != 3 ||
2453N/A (uidlist->read_records_count + ctx->new_files_count) *
2453N/A UIDLIST_COMPRESS_PERCENTAGE / 100 >= array_count(&uidlist->records))
1507N/A return maildir_uidlist_recreate(uidlist);
2453N/A
956N/A i_assert(ctx->first_unwritten_pos != (unsigned int)-1);
956N/A
956N/A if (lseek(uidlist->fd, 0, SEEK_END) < 0) {
956N/A mail_storage_set_critical(uidlist->ibox->box.storage,
956N/A "lseek(%s) failed: %m", uidlist->path);
2339N/A return -1;
2453N/A }
2339N/A
2453N/A if (maildir_uidlist_write_fd(uidlist, uidlist->fd, uidlist->path,
2453N/A ctx->first_unwritten_pos, &file_size) < 0)
2453N/A return -1;
2453N/A
2453N/A uidlist->last_read_offset = file_size;
2453N/A return 0;
2453N/A}
2453N/A
2453N/Astatic void maildir_uidlist_mark_all(struct maildir_uidlist *uidlist,
2453N/A bool nonsynced)
2453N/A{
2453N/A struct maildir_uidlist_rec **recs;
2453N/A unsigned int i, count;
2453N/A
2453N/A recs = array_get_modifiable(&uidlist->records, &count);
2339N/A if (nonsynced) {
956N/A for (i = 0; i < count; i++)
956N/A recs[i]->flags |= MAILDIR_UIDLIST_REC_FLAG_NONSYNCED;
956N/A } else {
956N/A for (i = 0; i < count; i++)
956N/A recs[i]->flags &= ~MAILDIR_UIDLIST_REC_FLAG_NONSYNCED;
956N/A }
956N/A}
956N/A
1507N/Aint maildir_uidlist_sync_init(struct maildir_uidlist *uidlist,
956N/A enum maildir_uidlist_sync_flags sync_flags,
956N/A struct maildir_uidlist_sync_ctx **sync_ctx_r)
956N/A{
956N/A struct maildir_uidlist_sync_ctx *ctx;
2205N/A int ret;
2205N/A
2205N/A if ((sync_flags & (MAILDIR_UIDLIST_SYNC_TRYLOCK |
2073N/A MAILDIR_UIDLIST_SYNC_FORCE)) == 0) {
956N/A if ((ret = maildir_uidlist_lock(uidlist)) <= 0)
956N/A return ret;
956N/A } else {
956N/A if ((ret = maildir_uidlist_try_lock(uidlist)) < 0)
956N/A return -1;
956N/A if (ret == 0 && (sync_flags & MAILDIR_UIDLIST_SYNC_FORCE) == 0)
956N/A return 0;
1507N/A }
956N/A
956N/A *sync_ctx_r = ctx = i_new(struct maildir_uidlist_sync_ctx, 1);
956N/A ctx->uidlist = uidlist;
956N/A ctx->sync_flags = sync_flags;
2073N/A ctx->partial = (sync_flags & MAILDIR_UIDLIST_SYNC_PARTIAL) != 0;
956N/A ctx->locked = ret > 0;
956N/A ctx->first_unwritten_pos = (unsigned int)-1;
956N/A ctx->first_nouid_pos = (unsigned int)-1;
956N/A
956N/A if (ctx->partial) {
956N/A if ((sync_flags & MAILDIR_UIDLIST_SYNC_KEEP_STATE) == 0) {
956N/A /* initially mark all nonsynced */
956N/A maildir_uidlist_mark_all(uidlist, TRUE);
956N/A }
956N/A return 1;
956N/A }
2453N/A
2453N/A ctx->record_pool = pool_alloconly_create(MEMPOOL_GROWING
2453N/A "maildir_uidlist_sync", 16384);
2453N/A ctx->files = hash_create(default_pool, ctx->record_pool, 4096,
2453N/A maildir_filename_base_hash,
2453N/A maildir_filename_base_cmp);
2453N/A
956N/A i_array_init(&ctx->records, array_count(&uidlist->records));
956N/A return 1;
956N/A}
956N/A
2073N/Astatic void
956N/Amaildir_uidlist_sync_next_partial(struct maildir_uidlist_sync_ctx *ctx,
956N/A const char *filename,
956N/A enum maildir_uidlist_rec_flag flags)
956N/A{
956N/A struct maildir_uidlist *uidlist = ctx->uidlist;
956N/A struct maildir_uidlist_rec *rec;
956N/A
956N/A /* we'll update uidlist directly */
1507N/A rec = hash_lookup(uidlist->files, filename);
1507N/A i_assert(rec != NULL || UIDLIST_IS_LOCKED(uidlist));
956N/A
956N/A if (rec == NULL) {
956N/A /* doesn't exist in uidlist */
956N/A if (!ctx->locked) {
1507N/A /* we can't add it, so just ignore it */
956N/A return;
2054N/A }
2054N/A if (ctx->first_nouid_pos == (unsigned int)-1)
956N/A ctx->first_nouid_pos = array_count(&uidlist->records);
956N/A ctx->new_files_count++;
956N/A ctx->changed = TRUE;
956N/A
956N/A if (uidlist->record_pool == NULL) {
956N/A uidlist->record_pool =
956N/A pool_alloconly_create(MEMPOOL_GROWING
956N/A "uidlist record_pool",
956N/A 1024);
956N/A }
956N/A
956N/A rec = p_new(uidlist->record_pool,
956N/A struct maildir_uidlist_rec, 1);
956N/A rec->uid = (uint32_t)-1;
956N/A array_append(&uidlist->records, &rec, 1);
956N/A uidlist->change_counter++;
956N/A }
1376N/A
1376N/A rec->flags = (rec->flags | flags) & ~MAILDIR_UIDLIST_REC_FLAG_NONSYNCED;
1376N/A rec->filename = p_strdup(uidlist->record_pool, filename);
1376N/A hash_insert(uidlist->files, rec->filename, rec);
1376N/A
956N/A ctx->finished = FALSE;
2073N/A}
2073N/A
956N/Aint maildir_uidlist_sync_next_pre(struct maildir_uidlist_sync_ctx *ctx,
956N/A const char *filename)
956N/A{
956N/A if (!UIDLIST_IS_LOCKED(ctx->uidlist) &&
956N/A hash_lookup(ctx->uidlist->files, filename) == NULL &&
956N/A (ctx->partial || hash_lookup(ctx->files, filename) == NULL)) {
956N/A if (!ctx->uidlist->initial_read) {
2026N/A /* first time reading the uidlist */
2026N/A if (maildir_uidlist_refresh(ctx->uidlist) < 0) {
2026N/A ctx->failed = TRUE;
2026N/A return -1;
2026N/A }
956N/A return maildir_uidlist_sync_next_pre(ctx, filename);
956N/A }
956N/A
956N/A return 0;
956N/A }
1352N/A
1352N/A return 1;
956N/A}
956N/A
1507N/Aint maildir_uidlist_sync_next(struct maildir_uidlist_sync_ctx *ctx,
1507N/A const char *filename,
956N/A enum maildir_uidlist_rec_flag flags)
956N/A{
2073N/A struct maildir_uidlist *uidlist = ctx->uidlist;
2073N/A struct maildir_uidlist_rec *rec, *old_rec;
2073N/A
2073N/A if (ctx->failed)
2073N/A return -1;
2073N/A
2073N/A if (ctx->partial) {
2073N/A maildir_uidlist_sync_next_partial(ctx, filename, flags);
1507N/A return 1;
2054N/A }
964N/A
964N/A rec = hash_lookup(ctx->files, filename);
2054N/A if (rec != NULL) {
964N/A if ((rec->flags & (MAILDIR_UIDLIST_REC_FLAG_NEW_DIR |
2073N/A MAILDIR_UIDLIST_REC_FLAG_MOVED)) == 0) {
964N/A /* possibly duplicate */
964N/A return 0;
964N/A }
1045N/A
1045N/A /* probably was in new/ and now we're seeing it in cur/.
1045N/A remove new/moved flags so if this happens again we'll know
1045N/A to check for duplicates. */
1045N/A rec->flags &= ~(MAILDIR_UIDLIST_REC_FLAG_NEW_DIR |
1045N/A MAILDIR_UIDLIST_REC_FLAG_MOVED);
1045N/A } else {
1045N/A old_rec = hash_lookup(uidlist->files, filename);
1045N/A i_assert(old_rec != NULL || UIDLIST_IS_LOCKED(uidlist));
1045N/A
1045N/A rec = p_new(ctx->record_pool, struct maildir_uidlist_rec, 1);
1045N/A
1713N/A if (old_rec != NULL) {
1505N/A *rec = *old_rec;
1045N/A rec->extensions =
1352N/A p_strdup(ctx->record_pool, rec->extensions);
1352N/A } else {
964N/A rec->uid = (uint32_t)-1;
964N/A ctx->new_files_count++;
2054N/A ctx->changed = TRUE;
2284N/A /* didn't exist in uidlist, it's recent */
2284N/A flags |= MAILDIR_UIDLIST_REC_FLAG_RECENT;
2284N/A }
2284N/A
2284N/A array_append(&ctx->records, &rec, 1);
2453N/A }
2284N/A
2284N/A rec->flags = (rec->flags | flags) & ~MAILDIR_UIDLIST_REC_FLAG_NONSYNCED;
2284N/A rec->filename = p_strdup(ctx->record_pool, filename);
2284N/A hash_insert(ctx->files, rec->filename, rec);
2284N/A return 1;
2284N/A}
2284N/A
2284N/Avoid maildir_uidlist_sync_remove(struct maildir_uidlist_sync_ctx *ctx,
2284N/A const char *filename)
{
struct maildir_uidlist_rec *rec;
i_assert(ctx->partial);
if (ctx->first_unwritten_pos != (unsigned int)-1) {
i_assert(ctx->first_unwritten_pos > 0);
ctx->first_unwritten_pos--;
}
if (ctx->first_nouid_pos != (unsigned int)-1) {
i_assert(ctx->first_nouid_pos > 0);
ctx->first_nouid_pos--;
}
rec = hash_lookup(ctx->uidlist->files, filename);
i_assert(rec != NULL);
i_assert(rec->uid != (uint32_t)-1);
hash_remove(ctx->uidlist->files, filename);
maildir_uidlist_records_array_delete(ctx->uidlist, rec);
ctx->changed = TRUE;
ctx->uidlist->recreate = TRUE;
}
const char *
maildir_uidlist_sync_get_full_filename(struct maildir_uidlist_sync_ctx *ctx,
const char *filename)
{
struct maildir_uidlist_rec *rec;
rec = hash_lookup(ctx->files, filename);
return rec == NULL ? NULL : rec->filename;
}
bool maildir_uidlist_get_uid(struct maildir_uidlist *uidlist,
const char *filename, uint32_t *uid_r)
{
struct maildir_uidlist_rec *rec;
rec = hash_lookup(uidlist->files, filename);
if (rec == NULL)
return FALSE;
*uid_r = rec->uid;
return TRUE;
}
const char *
maildir_uidlist_get_full_filename(struct maildir_uidlist *uidlist,
const char *filename)
{
struct maildir_uidlist_rec *rec;
rec = hash_lookup(uidlist->files, filename);
return rec == NULL ? NULL : rec->filename;
}
static int maildir_time_cmp(const void *p1, const void *p2)
{
const struct maildir_uidlist_rec *const *rec1 = p1, *const *rec2 = p2;
return maildir_filename_sort_cmp((*rec1)->filename, (*rec2)->filename);
}
static void maildir_uidlist_assign_uids(struct maildir_uidlist_sync_ctx *ctx)
{
struct maildir_uidlist_rec **recs;
unsigned int dest, count;
i_assert(UIDLIST_IS_LOCKED(ctx->uidlist));
i_assert(ctx->first_nouid_pos != (unsigned int)-1);
if (ctx->first_unwritten_pos == (unsigned int)-1)
ctx->first_unwritten_pos = ctx->first_nouid_pos;
/* sort new files and assign UIDs for them */
recs = array_get_modifiable(&ctx->uidlist->records, &count);
qsort(recs + ctx->first_nouid_pos, count - ctx->first_nouid_pos,
sizeof(*recs), maildir_time_cmp);
for (dest = ctx->first_nouid_pos; dest < count; dest++) {
i_assert(recs[dest]->uid == (uint32_t)-1);
recs[dest]->uid = ctx->uidlist->next_uid++;
recs[dest]->flags &= ~MAILDIR_UIDLIST_REC_FLAG_MOVED;
}
ctx->new_files_count = 0;
ctx->first_nouid_pos = (unsigned int)-1;
ctx->uidlist->last_seen_uid = ctx->uidlist->next_uid-1;
ctx->uidlist->change_counter++;
}
static void maildir_uidlist_swap(struct maildir_uidlist_sync_ctx *ctx)
{
struct maildir_uidlist *uidlist = ctx->uidlist;
struct maildir_uidlist_rec **recs;
unsigned int count;
/* buffer is unsorted, sort it by UID */
recs = array_get_modifiable(&ctx->records, &count);
qsort(recs, count, sizeof(*recs), maildir_uid_cmp);
array_free(&uidlist->records);
uidlist->records = ctx->records;
ctx->records.arr.buffer = NULL;
hash_destroy(&uidlist->files);
uidlist->files = ctx->files;
ctx->files = NULL;
if (uidlist->record_pool != NULL)
pool_unref(&uidlist->record_pool);
uidlist->record_pool = ctx->record_pool;
ctx->record_pool = NULL;
if (ctx->new_files_count != 0) {
ctx->first_nouid_pos = count - ctx->new_files_count;
maildir_uidlist_assign_uids(ctx);
}
ctx->uidlist->change_counter++;
}
void maildir_uidlist_sync_finish(struct maildir_uidlist_sync_ctx *ctx)
{
if (!ctx->partial) {
if (!ctx->failed)
maildir_uidlist_swap(ctx);
} else {
if (ctx->new_files_count != 0)
maildir_uidlist_assign_uids(ctx);
}
ctx->finished = TRUE;
ctx->uidlist->initial_sync = TRUE;
}
int maildir_uidlist_sync_deinit(struct maildir_uidlist_sync_ctx **_ctx)
{
struct maildir_uidlist_sync_ctx *ctx = *_ctx;
int ret = ctx->failed ? -1 : 0;
*_ctx = NULL;
if (!ctx->finished)
maildir_uidlist_sync_finish(ctx);
if (ctx->partial)
maildir_uidlist_mark_all(ctx->uidlist, FALSE);
if ((ctx->changed || ctx->uidlist->recreate) && !ctx->failed) {
t_push();
ret = maildir_uidlist_sync_update(ctx);
t_pop();
}
if (ctx->locked)
maildir_uidlist_unlock(ctx->uidlist);
if (ctx->files != NULL)
hash_destroy(&ctx->files);
if (ctx->record_pool != NULL)
pool_unref(&ctx->record_pool);
if (array_is_created(&ctx->records))
array_free(&ctx->records);
i_free(ctx);
return ret;
}
void maildir_uidlist_add_flags(struct maildir_uidlist *uidlist,
const char *filename,
enum maildir_uidlist_rec_flag flags)
{
struct maildir_uidlist_rec *rec;
rec = hash_lookup(uidlist->files, filename);
i_assert(rec != NULL);
rec->flags |= flags;
}
struct maildir_uidlist_iter_ctx *
maildir_uidlist_iter_init(struct maildir_uidlist *uidlist)
{
struct maildir_uidlist_iter_ctx *ctx;
unsigned int count;
ctx = i_new(struct maildir_uidlist_iter_ctx, 1);
ctx->uidlist = uidlist;
ctx->next = array_get(&uidlist->records, &count);
ctx->end = ctx->next + count;
ctx->change_counter = ctx->uidlist->change_counter;
return ctx;
}
static void
maildir_uidlist_iter_update_idx(struct maildir_uidlist_iter_ctx *ctx)
{
unsigned int old_rev_idx, idx, count;
old_rev_idx = ctx->end - ctx->next;
ctx->next = array_get(&ctx->uidlist->records, &count);
ctx->end = ctx->next + count;
idx = old_rev_idx >= count ? 0 :
count - old_rev_idx;
while (idx < count && ctx->next[idx]->uid <= ctx->prev_uid)
idx++;
while (idx > 0 && ctx->next[idx-1]->uid > ctx->prev_uid)
idx--;
ctx->next += idx;
}
static bool maildir_uidlist_iter_next_rec(struct maildir_uidlist_iter_ctx *ctx,
struct maildir_uidlist_rec **rec_r)
{
struct maildir_uidlist_rec *rec;
if (ctx->change_counter != ctx->uidlist->change_counter)
maildir_uidlist_iter_update_idx(ctx);
if (ctx->next == ctx->end)
return FALSE;
rec = *ctx->next;
i_assert(rec->uid != (uint32_t)-1);
ctx->prev_uid = rec->uid;
ctx->next++;
*rec_r = rec;
return TRUE;
}
bool 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)
{
struct maildir_uidlist_rec *rec;
if (!maildir_uidlist_iter_next_rec(ctx, &rec))
return FALSE;
*uid_r = rec->uid;
*flags_r = rec->flags;
*filename_r = rec->filename;
return TRUE;
}
void maildir_uidlist_iter_deinit(struct maildir_uidlist_iter_ctx **_ctx)
{
i_free(*_ctx);
*_ctx = NULL;
}