maildir-uidlist.c revision 5b440b4d921cb1a36d74b4082599ccd3bb0f0401
5f5870385cff47efd2f58e7892f251cf13761528Timo Sirainen/* Copyright (c) 2003-2009 Dovecot authors, see the included COPYING file */
16f816d3f3c32ae3351834253f52ddd0212bcbf3Timo Sirainen Version 1 format has been used for most versions of Dovecot up to v1.0.x.
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen It's also compatible with Courier IMAP's courierimapuiddb file.
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen The format is:
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen header: 1 <uid validity> <next uid>
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen entry: <uid> <filename>
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen Version 2 format was written by a few development Dovecot versions, but
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen v1.0.x still parses the format. The format has <flags> field after <uid>.
e8fd7988ec183fb6c104aed19a61f1a096c51d34Timo Sirainen Version 3 format is an extensible format used by Dovecot v1.1 and later.
e8fd7988ec183fb6c104aed19a61f1a096c51d34Timo Sirainen It's also parsed by v1.0.2 (and later). The format is:
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen header: 3 [<key><value> ...]
6bc0f424bcdb9119d8159874cf98adfa53eefd9aTimo Sirainen entry: <uid> [<key><value> ...] :<filename>
6bc0f424bcdb9119d8159874cf98adfa53eefd9aTimo Sirainen See enum maildir_uidlist_*_ext_key for used keys.
19e8adccba16ff419f5675b1575358c2956dce83Timo Sirainen/* NFS: How many times to retry reading dovecot-uidlist file if ESTALE
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen error occurs in the middle of reading it */
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen#define UIDLIST_ESTALE_RETRY_COUNT NFS_ESTALE_RETRY_COUNT
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen (UIDLIST_IS_LOCKED(uidlist) || (uidlist)->mbox == NULL)
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen unsigned char *extensions; /* <data>\0[<data>\0 ...]\0 */
6e235046e1d8e9d89fc948f5c623676c20421a28Timo SirainenARRAY_DEFINE_TYPE(maildir_uidlist_rec_p, struct maildir_uidlist_rec *);
2131ef7a3390f15ea6a958256ea54908f1096350Timo Sirainen unsigned int version;
abe7afb8f1766fbcef1b9df513109e43d7d16e49Timo Sirainen unsigned int uid_validity, next_uid, prev_read_uid, last_seen_uid;
2131ef7a3390f15ea6a958256ea54908f1096350Timo Sirainen unsigned int read_records_count, read_line_count;
69e03a846f6980144aa75bff0590c04852bffbbcTimo Sirainen unsigned int first_unwritten_pos, first_nouid_pos;
bc564f1d3d953cf724828322b11ae89e0f59ffc9Timo Sirainen struct maildir_uidlist_rec *const *next, *const *end;
bc564f1d3d953cf724828322b11ae89e0f59ffc9Timo Sirainenstatic bool maildir_uidlist_iter_next_rec(struct maildir_uidlist_iter_ctx *ctx,
85da8c055280cd45553b6b335e9fb226d6e2801eTimo Sirainenstatic int maildir_uidlist_lock_timeout(struct maildir_uidlist *uidlist,
69e03a846f6980144aa75bff0590c04852bffbbcTimo Sirainen const enum dotlock_create_flags dotlock_flags =
6a19e109ee8c5a6f688da83a86a7f6abeb71abddTimo Sirainen if (!uidlist->locked_refresh && refresh_when_locked) {
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen index_storage_lock_notify_reset(&uidlist->mbox->ibox);
fd2f5fbc1f07aa93e2214a28cdf02437fb7d06c8Timo Sirainen control_dir = mailbox_list_get_path(box->list, box->name,
c53e8ee216904ffe6de4f6518d9f9f5107b7610eTimo Sirainen path = t_strconcat(control_dir, "/" MAILDIR_UIDLIST_NAME, NULL);
fd2f5fbc1f07aa93e2214a28cdf02437fb7d06c8Timo Sirainen for (i = 0;; i++) {
df6478c4cf605bd81b3891c148b84c14eb6c4035Timo Sirainen old_mask = umask(0777 & ~box->file_create_mode);
fd2f5fbc1f07aa93e2214a28cdf02437fb7d06c8Timo Sirainen ret = file_dotlock_create(&uidlist->dotlock_settings, path,
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen /* failure */
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen if (errno != ENOENT || i == MAILDIR_DELETE_RETRY_COUNT ||
fd2f5fbc1f07aa93e2214a28cdf02437fb7d06c8Timo Sirainen eacces_error_get_creating("file_dotlock_create", path));
fd2f5fbc1f07aa93e2214a28cdf02437fb7d06c8Timo Sirainen "file_dotlock_create(%s) failed: %m",
fd2f5fbc1f07aa93e2214a28cdf02437fb7d06c8Timo Sirainen /* the control dir doesn't exist. create it unless the whole
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainen mailbox was just deleted. */
6b85bc4b03e552cfaeeae872d63c2d8ac5fcb7c4Timo Sirainen if (!maildir_set_deleted(&uidlist->mbox->ibox.box))
25c22e54d1071d120641e9eecd0023e7373e65ffTimo Sirainen /* make sure we have the latest changes before
25c22e54d1071d120641e9eecd0023e7373e65ffTimo Sirainen changing anything */
25c22e54d1071d120641e9eecd0023e7373e65ffTimo Sirainenint maildir_uidlist_lock(struct maildir_uidlist *uidlist)
25c22e54d1071d120641e9eecd0023e7373e65ffTimo Sirainen return maildir_uidlist_lock_timeout(uidlist, FALSE, TRUE, FALSE);
25c22e54d1071d120641e9eecd0023e7373e65ffTimo Sirainenint maildir_uidlist_try_lock(struct maildir_uidlist *uidlist)
25c22e54d1071d120641e9eecd0023e7373e65ffTimo Sirainen return maildir_uidlist_lock_timeout(uidlist, TRUE, TRUE, FALSE);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainenint maildir_uidlist_lock_touch(struct maildir_uidlist *uidlist)
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainenbool maildir_uidlist_is_locked(struct maildir_uidlist *uidlist)
a40d26f83af808a0ea1e212c001d682a96d870b0Timo Sirainenvoid maildir_uidlist_unlock(struct maildir_uidlist *uidlist)
fd2f5fbc1f07aa93e2214a28cdf02437fb7d06c8Timo Sirainenstatic bool dotlock_callback(unsigned int secs_left, bool stale, void *context)
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainenmaildir_uidlist_init_readonly(struct index_mailbox *ibox)
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen control_dir = mailbox_list_get_path(box->list, box->name,
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen uidlist->path = i_strconcat(control_dir, "/"MAILDIR_UIDLIST_NAME, NULL);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen uidlist->files = hash_table_create(default_pool, default_pool, 4096,
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen uidlist->hdr_extensions = str_new(default_pool, 128);
6b85bc4b03e552cfaeeae872d63c2d8ac5fcb7c4Timo Sirainen uidlist->dotlock_settings.use_io_notify = TRUE;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen uidlist->dotlock_settings.callback = dotlock_callback;
1e47cfede3a0b62654105daab00e97b5d660bc6bTimo Sirainenstruct maildir_uidlist *maildir_uidlist_init(struct maildir_mailbox *mbox)
1e47cfede3a0b62654105daab00e97b5d660bc6bTimo Sirainen uidlist = maildir_uidlist_init_readonly(&mbox->ibox);
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen uidlist->dotlock_settings.temp_prefix = mbox->storage->temp_prefix;
f501ad38c51cf1d8f4f84313922c785e6ae6e81fTimo Sirainenstatic void maildir_uidlist_close(struct maildir_uidlist *uidlist)
f501ad38c51cf1d8f4f84313922c785e6ae6e81fTimo Sirainen struct mail_storage *storage = uidlist->ibox->box.storage;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainenstatic void maildir_uidlist_reset(struct maildir_uidlist *uidlist)
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainenvoid maildir_uidlist_deinit(struct maildir_uidlist **_uidlist)
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainenstatic int maildir_uid_cmp(struct maildir_uidlist_rec *const *rec1,
df6478c4cf605bd81b3891c148b84c14eb6c4035Timo Sirainenmaildir_uidlist_set_corrupted(struct maildir_uidlist *uidlist,
d565eaa943f29a49b97230ced57eec40ee65b4f9Timo Sirainen const char *fmt, ...)
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen struct mail_storage *storage = uidlist->ibox->box.storage;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen "Broken or unexpectedly changed file %s "
6a8a4c9f530668cd8961b73d702856ed94f05f80Timo Sirainen "line %u: %s - re-reading from beginning",
4b231ca0bbe3b536acbd350101e183441ce0247aTimo Sirainen mail_storage_set_critical(storage, "Broken file %s line %u: %s",
df6478c4cf605bd81b3891c148b84c14eb6c4035Timo Sirainenstatic void maildir_uidlist_update_hdr(struct maildir_uidlist *uidlist,
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen /* dbox is using this */
6a8a4c9f530668cd8961b73d702856ed94f05f80Timo Sirainen /* upgrading from older verson. don't update the
6a8a4c9f530668cd8961b73d702856ed94f05f80Timo Sirainen uidlist times until it uses the new format */
6a8a4c9f530668cd8961b73d702856ed94f05f80Timo Sirainen mhdr->uidlist_mtime_nsecs = ST_MTIME_NSEC(*st);
6a8a4c9f530668cd8961b73d702856ed94f05f80Timo Sirainenstatic unsigned int
6a8a4c9f530668cd8961b73d702856ed94f05f80Timo Sirainenmaildir_uidlist_records_array_delete(struct maildir_uidlist *uidlist,
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen struct maildir_uidlist_rec *const *recs, *const *pos;
f239eb76f77afcbc0bfc97c9b52b4407d1bc3fe6Timo Sirainen unsigned int idx;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen pos = array_bsearch(&uidlist->records, &rec, maildir_uid_cmp);
3ccab0bac68040f179a7de45c516cec258e28fdbTimo Sirainenmaildir_uidlist_read_extended(struct maildir_uidlist *uidlist,
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen const char **line_p,
036626b19f14bef582f96e556913ae91b1d67881Timo Sirainen buf = buffer_create_dynamic(pool_datastack_create(), 128);
13c6532dc104d23061e6901783ceb1ff8872c206Timo Sirainen /* skip over an extension field */
59151b71059df1190acd75d8717ed04a7920c862Timo Sirainen /* save the extensions */
5626ae5e3316eced244adb6485c0927f1c7fdc41Timo Sirainen rec->extensions = p_malloc(uidlist->record_pool, buf->used);
9315dd69233d554452df0c12bc57002d2042a8f4Timo Sirainen memcpy(rec->extensions, buf->data, buf->used);
f239eb76f77afcbc0bfc97c9b52b4407d1bc3fe6Timo Sirainenstatic bool maildir_uidlist_next(struct maildir_uidlist *uidlist,
f239eb76f77afcbc0bfc97c9b52b4407d1bc3fe6Timo Sirainen struct maildir_uidlist_rec *rec, *old_rec, *const *recs;
f239eb76f77afcbc0bfc97c9b52b4407d1bc3fe6Timo Sirainen unsigned int count;
5196f9ea42d02000f9c3d22f20aa816140af4422Timo Sirainen /* invalid file */
5196f9ea42d02000f9c3d22f20aa816140af4422Timo Sirainen maildir_uidlist_set_corrupted(uidlist, "Invalid data: %s",
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen "UIDs not ordered (%u >= %u)",
519e0a461271843833a2b42626ad93f6e7ddc497Timo Sirainen /* we already have this */
648d24583c1574441c4fa0331a90bd4d6e7996c5Timo Sirainen if (uid >= uidlist->next_uid && uidlist->version == 1) {
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen "UID larger than next_uid (%u >= %u)",
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen rec = p_new(uidlist->record_pool, struct maildir_uidlist_rec, 1);
2af769daebd83719ac696a440e06f6020471cec0Timo Sirainen rec->flags = MAILDIR_UIDLIST_REC_FLAG_NONSYNCED;
763f83d3cc47bce05cbc396419c4db2b71dd8e68Timo Sirainen /* read extended fields */
763f83d3cc47bce05cbc396419c4db2b71dd8e68Timo Sirainen ret = maildir_uidlist_read_extended(uidlist, &line,
d0bbbc7057aa33b52ee378196dee7d773437468fTimo Sirainen "%s: Broken filename at line %u: %s",
d0bbbc7057aa33b52ee378196dee7d773437468fTimo Sirainen uidlist->path, uidlist->read_line_count, line);
d0bbbc7057aa33b52ee378196dee7d773437468fTimo Sirainen old_rec = hash_table_lookup(uidlist->files, line);
5bdad39213d28ab35e615a7f4ea1712ab25b6a80Timo Sirainen /* no conflicts */
5bdad39213d28ab35e615a7f4ea1712ab25b6a80Timo Sirainen /* most likely this is a record we saved ourself, but couldn't
d9fdacd5fb3e07997e5c389739d2054f0c8441d8Timo Sirainen update last_seen_uid because uidlist wasn't refreshed while
763f83d3cc47bce05cbc396419c4db2b71dd8e68Timo Sirainen it was locked.
4aae8acbcfa9cac96b4af39bfabcbe569e804827Timo Sirainen another possibility is a duplicate file record. currently
4aae8acbcfa9cac96b4af39bfabcbe569e804827Timo Sirainen it would be a bug, but not that big of a deal. also perhaps
4aae8acbcfa9cac96b4af39bfabcbe569e804827Timo Sirainen in future such duplicate lines could be used to update
51b979b6414b940f04677a7e2d064be119345954Timo Sirainen extended fields. so just let it through anyway.
51b979b6414b940f04677a7e2d064be119345954Timo Sirainen we'll waste a bit of memory here by allocating the record
bc564f1d3d953cf724828322b11ae89e0f59ffc9Timo Sirainen twice, but that's not really a problem. */
51b979b6414b940f04677a7e2d064be119345954Timo Sirainen hash_table_insert(uidlist->files, rec->filename, rec);
d9fdacd5fb3e07997e5c389739d2054f0c8441d8Timo Sirainen /* This can happen if expunged file is moved back and the file
d9fdacd5fb3e07997e5c389739d2054f0c8441d8Timo Sirainen was appended to uidlist. */
d9fdacd5fb3e07997e5c389739d2054f0c8441d8Timo Sirainen i_warning("%s: Duplicate file entry at line %u: "
d9fdacd5fb3e07997e5c389739d2054f0c8441d8Timo Sirainen "%s (uid %u -> %u)",
6cb2c6ecddcdbeac9e6c73a292244747e12a793eTimo Sirainen uidlist->path, uidlist->read_line_count, line,
2af769daebd83719ac696a440e06f6020471cec0Timo Sirainen /* Delete the old UID */
d0bbbc7057aa33b52ee378196dee7d773437468fTimo Sirainen maildir_uidlist_records_array_delete(uidlist, old_rec);
d0bbbc7057aa33b52ee378196dee7d773437468fTimo Sirainen /* Replace the old record with this new one */
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen /* we most likely have some records in the array that we saved
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen ourself without refreshing uidlist */
0d0451206a91e9f96e522075dce28a89adc2325dTimo Sirainen rec->filename = p_strdup(uidlist->record_pool, line);
1c0590b2729567ad60dafde4d2c5f19635755a3dTimo Sirainen hash_table_insert(uidlist->files, rec->filename, rec);
df6478c4cf605bd81b3891c148b84c14eb6c4035Timo Sirainenmaildir_uidlist_read_v3_header(struct maildir_uidlist *uidlist,
df6478c4cf605bd81b3891c148b84c14eb6c4035Timo Sirainen unsigned int *next_uid_r)
84da9c6d6e162b064608cbfa9a47e0d60553c593Timo Sirainen buf = buffer_create_dynamic(pool_datastack_create(),
a817fdcc43aedf423e2134091d5f83f91d64bcc9Timo Sirainenstatic int maildir_uidlist_read_header(struct maildir_uidlist *uidlist,
d0bbbc7057aa33b52ee378196dee7d773437468fTimo Sirainen /* I/O error / empty file */
d0bbbc7057aa33b52ee378196dee7d773437468fTimo Sirainen if (*line < '0' || *line > '9' || line[1] != ' ') {
d0bbbc7057aa33b52ee378196dee7d773437468fTimo Sirainen "Corrupted header (invalid version number)");
4bbee99b3aef449a9a2a11a5b5cf1ca486915c49Timo Sirainen if (sscanf(line, "%u %u", &uid_validity, &next_uid) != 2) {
4bbee99b3aef449a9a2a11a5b5cf1ca486915c49Timo Sirainen "Corrupted header (version 1)");
a817fdcc43aedf423e2134091d5f83f91d64bcc9Timo Sirainen ret = maildir_uidlist_read_v3_header(uidlist, line,
f501ad38c51cf1d8f4f84313922c785e6ae6e81fTimo Sirainen maildir_uidlist_set_corrupted(uidlist, "Unsupported version %u",
51b979b6414b940f04677a7e2d064be119345954Timo Sirainen "Broken header (uidvalidity = %u, next_uid=%u)",
dbd9604da561399cc6255289d5b6f6f662ab2d00Timo Sirainen "next_uid header was lowered (%u -> %u)",
9566c1b4506d49778659e3dc65997f3c0399cb7eTimo Sirainenstatic void maildir_uidlist_records_sort_by_uid(struct maildir_uidlist *uidlist)
dbd9604da561399cc6255289d5b6f6f662ab2d00Timo Sirainen array_sort(&uidlist->records, maildir_uid_cmp);
f3976df875193529127d584cb713983e8160bdcfTimo Sirainenmaildir_uidlist_update_read(struct maildir_uidlist *uidlist,
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen struct mail_storage *storage = uidlist->ibox->box.storage;
5724e7103eed12fe36b55a7b5a8653284a2184b9Timo Sirainen /* the file was updated */
if (ret > 0) {
ret = 0;
if (ret == 0) {
} else if (ret > 0) {
} else if (!*retry_r) {
if (ret <= 0) {
return ret;
int ret;
return ret;
int ret;
if (ret <= 0) {
return ret;
if (recreated)
if (!retry)
if (ret >= 0) {
return ret;
int ret;
return ret;
unsigned int *idx_r,
const char **fname_r)
int ret;
if (ret <= 0) {
if (ret < 0)
return ret;
const char **fname_r)
unsigned int idx;
int ret;
return ret;
unsigned int idx;
int ret;
return NULL;
if (*p == (char)key)
return NULL;
const char *value)
unsigned int idx;
unsigned int len;
int ret;
if (ret <= 0) {
if (ret < 0)
if (*p != (char)key)
p += len;
const char *value)
T_BEGIN {
} T_END;
unsigned int len;
int ret;
if (ret < 0) {
if (ret == 0) {
if (ret < 0) {
if (ret < 0)
return ret;
int ret;
return ret;
unsigned int min_rewrite_count;
return FALSE;
return TRUE;
return FALSE;
return TRUE;
return TRUE;
bool nonsynced)
unsigned int i, count;
if (nonsynced) {
for (i = 0; i < count; i++)
for (i = 0; i < count; i++)
bool *locked_r)
int ret;
if (ret <= 0) {
return ret;
bool locked;
int ret;
if (ret <= 0)
return ret;
const char *filename,
unsigned char *ret;
return NULL;
T_BEGIN {
unsigned int len;
} T_END;
return ret;
const char *filename,
const char *p, *dir;
MAILDIR_UIDLIST_REC_FLAG_MOVED)) == 0) {
const char *filename)
unsigned int idx;
const char *filename)
return FALSE;
return TRUE;
const char *filename)
T_BEGIN {
} T_END;
bool success)
int ret;
if (!success)
return ret;
const char *filename,
struct maildir_uidlist_iter_ctx *
unsigned int count;
return ctx;
idx++;
idx--;
return FALSE;
return TRUE;
const char **filename_r)
return FALSE;
return TRUE;