maildir-uidlist.c revision 14c3da8abe2e666ecdb2900c8000478ef303161b
/* Copyright (c) 2003-2008 Dovecot authors, see the included COPYING file */
/*
Version 1 format has been used for most versions of Dovecot up to v1.0.x.
It's also compatible with Courier IMAP's courierimapuiddb file.
The format is:
header: 1 <uid validity> <next uid>
entry: <uid> <filename>
--
Version 2 format was written by a few development Dovecot versions, but
v1.0.x still parses the format. The format has <flags> field after <uid>.
--
Version 3 format is an extensible format used by Dovecot v1.1 and later.
It's also parsed by v1.0.2 (and later). The format is:
header: 3 [<key><value> ...]
entry: <uid> [<key><value> ...] :<filename>
See enum maildir_uidlist_*_ext_key for used keys.
*/
#include "lib.h"
#include "array.h"
#include "hash.h"
#include "istream.h"
#include "ostream.h"
#include "str.h"
#include "file-dotlock.h"
#include "close-keep-errno.h"
#include "nfs-workarounds.h"
#include "maildir-storage.h"
#include "maildir-sync.h"
#include "maildir-filename.h"
#include "maildir-uidlist.h"
#include <stdio.h>
#include <stdlib.h>
/* NFS: How many times to retry reading dovecot-uidlist file if ESTALE
error occurs in the middle of reading it */
/* how many seconds to wait before overriding uidlist.lock */
#define UIDLIST_VERSION 3
#define UIDLIST_COMPRESS_PERCENTAGE 75
#define UIDLIST_IS_LOCKED(uidlist) \
((uidlist)->lock_count > 0)
struct maildir_uidlist_rec {
char *filename;
unsigned char *extensions; /* <data>\0[<data>\0 ...]\0 */
};
struct maildir_uidlist {
struct maildir_mailbox *mbox;
struct index_mailbox *ibox;
char *path;
int fd;
unsigned int lock_count;
struct dotlock_settings dotlock_settings;
struct hash_table *files;
unsigned int change_counter;
unsigned int version;
unsigned int read_records_count, read_line_count;
unsigned int recreate:1;
unsigned int initial_read:1;
unsigned int initial_hdr_read:1;
unsigned int initial_sync:1;
};
struct maildir_uidlist_sync_ctx {
struct maildir_uidlist *uidlist;
struct hash_table *files;
unsigned int first_unwritten_pos, first_nouid_pos;
unsigned int new_files_count;
unsigned int finish_change_counter;
unsigned int partial:1;
unsigned int finished:1;
unsigned int changed:1;
unsigned int failed:1;
unsigned int locked:1;
};
struct maildir_uidlist_iter_ctx {
struct maildir_uidlist *uidlist;
unsigned int change_counter;
};
struct maildir_uidlist_rec **rec_r);
{
const char *control_dir, *path;
const enum dotlock_create_flags dotlock_flags =
int i, ret;
if (uidlist->lock_count > 0) {
uidlist->lock_count++;
return 1;
}
for (i = 0;; i++) {
if (ret > 0)
break;
/* failure */
if (ret == 0) {
return 0;
}
"file_dotlock_create(%s) failed: %m", path);
return -1;
}
/* the control dir doesn't exist. create it unless the whole
mailbox was just deleted. */
return -1;
}
uidlist->lock_count++;
if (refresh) {
/* make sure we have the latest changes before
changing anything */
if (maildir_uidlist_refresh(uidlist) < 0) {
return -1;
}
}
return 1;
}
{
}
{
}
{
}
{
return UIDLIST_IS_LOCKED(uidlist);
}
{
if (--uidlist->lock_count > 0)
return;
}
struct maildir_uidlist *
{
struct maildir_uidlist *uidlist;
const char *control_dir;
return uidlist;
}
{
struct maildir_uidlist *uidlist;
return uidlist;
}
{
}
uidlist->last_read_offset = 0;
uidlist->read_line_count = 0;
}
{
}
{
}
const char *fmt, ...)
{
}
{
}
static void
struct maildir_uidlist_rec *rec)
{
unsigned int count;
}
static bool
const char **line_p,
struct maildir_uidlist_rec *rec)
{
/* skip over an extension field */
}
/* save the extensions */
}
if (*line == ':')
line++;
if (*line == '\0')
return FALSE;
return TRUE;
}
const char *line)
{
uid = 0;
line++;
}
/* invalid file */
line);
return 0;
}
"UIDs not ordered (%u > %u)",
return 0;
}
/* we already have this */
return 1;
}
"UID larger than next_uid (%u >= %u)",
return 0;
}
/* read extended fields */
bool ret;
T_BEGIN {
rec);
} T_END;
if (!ret) {
"Invalid extended fields: %s", line);
return 0;
}
}
/* This can happen if expunged file is moved back and the file
was appended to uidlist. */
i_warning("%s: Duplicate file entry at line %u: "
"%s (uid %u -> %u)",
/* Delete the old UID */
/* Replace the old record with this new one */
}
return 1;
}
{
unsigned int uid_validity, next_uid;
const char *line;
char key;
/* I/O error / empty file */
}
"Corrupted header (invalid version number)");
return 0;
}
line += 2;
case 1:
"Corrupted header (version 1)");
return 0;
}
"next_uid was lowered (v1, %u -> %u)",
return 0;
}
break;
case UIDLIST_VERSION:
str_truncate(ext_hdr, 0);
const char *value;
switch (key) {
break;
break;
default:
break;
}
} T_END;
break;
default:
return 0;
}
if (uid_validity == 0 || next_uid == 0) {
"Broken header (uidvalidity = %u, next_uid=%u)",
return 0;
}
return 1;
}
static int
{
const char *line;
unsigned int orig_next_uid;
if (fd == -1) {
return -1;
}
return 0;
}
last_read_offset = 0;
} else {
/* the file was updated */
return -1;
}
uidlist->last_read_offset = 0;
}
return -1;
}
return -1;
}
"uidlist record_pool",
}
if (ret > 0) {
uidlist->prev_read_uid = 0;
uidlist->read_records_count = 0;
ret = 1;
ret = 0;
break;
}
}
if (input->stream_errno != 0)
ret = -1;
"%s: next_uid was lowered (%u -> %u)",
}
}
if (ret == 0) {
/* file is broken */
} else if (ret > 0) {
/* success */
} else {
/* I/O error */
else {
}
}
if (ret <= 0) {
}
return ret;
}
static int
{
}
return -1;
}
return 0;
}
return 1;
}
static int
{
int ret;
*recreated_r = FALSE;
return ret;
/* file recreated */
*recreated_r = TRUE;
return 1;
}
/* NFS: either the file hasn't been changed, or it has already
been deleted and the inodes just happen to be the same.
check if the fd is still valid. */
*recreated_r = TRUE;
return 1;
}
return -1;
}
}
/* file modified but not recreated */
return 1;
} else {
/* unchanged */
return 0;
}
}
{
unsigned int i;
int ret;
if (ret <= 0)
return ret;
if (recreated)
}
for (i = 0; ; i++) {
if (!retry)
break;
/* ESTALE - try reopening and rereading */
}
if (ret >= 0) {
}
return ret;
}
{
const struct mail_index_header *hdr;
int ret;
return maildir_uidlist_refresh(uidlist);
return ret;
/* index is up-to-date */
return 1;
} else {
return maildir_uidlist_refresh(uidlist);
}
}
static struct maildir_uidlist_rec *
unsigned int *idx_r)
{
struct maildir_uidlist_rec *const *recs;
if (!uidlist->initial_read) {
/* first time we need to read uidlist */
if (maildir_uidlist_refresh(uidlist) < 0)
return NULL;
}
else {
}
}
return NULL;
}
const char *
enum maildir_uidlist_rec_flag *flags_r)
{
const char *fname;
return NULL;
/* the uidlist doesn't exist. */
return NULL;
/* try again */
}
return fname;
}
const char *
enum maildir_uidlist_rec_flag *flags_r)
{
const struct maildir_uidlist_rec *rec;
unsigned int idx;
return NULL;
}
const char *
{
const struct maildir_uidlist_rec *rec;
unsigned int idx;
const unsigned char *p;
const char *value;
return NULL;
while (*p != '\0') {
/* <key><value>\0 */
if (*p == (char)key)
return (const char *)p + 1;
p += strlen((const char *)p) + 1;
}
return NULL;
}
{
return uidlist->uid_validity;
}
{
}
{
i_assert(uid_validity != 0);
}
{
}
static void
const char *value)
{
struct maildir_uidlist_rec *rec;
unsigned int idx;
const unsigned char *p;
unsigned int len;
/* maybe it's a new message */
if (maildir_uidlist_refresh(uidlist) < 0)
return;
/* message is already expunged, ignore */
return;
}
}
/* copy existing extensions, except for the one we're updating */
p = rec->extensions;
while (*p != '\0') {
/* <key><value>\0 */
if (*p != (char)key)
p += len;
}
}
}
const char *value)
{
T_BEGIN {
} T_END;
}
{
struct maildir_uidlist_iter_ctx *iter;
struct maildir_uidlist_rec *rec;
const unsigned char *p;
unsigned int len;
int ret;
}
}
str_truncate(str, 0);
p += len + 1;
}
}
}
if (ret < 0) {
"o_stream_send(%s) failed: %m", path);
return -1;
}
"fdatasync(%s) failed: %m", path);
return -1;
}
}
return 0;
}
{
const char *control_dir, *temp_path;
for (i = 0;; i++) {
if (fd != -1)
break;
"open(%s, O_CREAT) failed: %m", temp_path);
return -1;
}
/* the control dir doesn't exist. create it unless the whole
mailbox was just deleted. */
return -1;
}
"fchown(%s) failed: %m", temp_path);
}
}
if (ret == 0) {
"rename(%s, %s) failed: %m",
ret = -1;
}
}
if (ret < 0) {
"unlink(%s) failed: %m", temp_path);
}
"fstat(%s) failed: %m", temp_path);
ret = -1;
"Maildir uidlist dotlock overridden: %s",
ret = -1;
} else {
}
if (ret < 0)
return ret;
}
{
int ret;
return 0;
if (maildir_uidlist_lock(uidlist) <= 0)
return -1;
return ret;
}
{
unsigned int min_rewrite_count;
if (!uidlist->initial_read)
return FALSE;
return TRUE;
UIDLIST_COMPRESS_PERCENTAGE / 100;
}
{
if (uidlist->uid_validity == 0) {
/* saving a message to a newly created maildir. */
const struct mail_index_header *hdr;
}
return maildir_uidlist_recreate(uidlist);
/* NOREFRESH flag used. we're just appending some messages. */
return -1;
}
}
return -1;
}
return -1;
return -1;
}
i_warning("%s: file size changed unexpectedly after write",
} else {
}
return 0;
}
bool nonsynced)
{
struct maildir_uidlist_rec **recs;
unsigned int i, count;
if (nonsynced) {
for (i = 0; i < count; i++)
} else {
for (i = 0; i < count; i++)
}
}
struct maildir_uidlist_sync_ctx **sync_ctx_r)
{
struct maildir_uidlist_sync_ctx *ctx;
int ret;
if (ret <= 0) {
return ret;
/* couldn't lock it */
if ((sync_flags & MAILDIR_UIDLIST_SYNC_FORCE) == 0)
return 0;
/* forcing the sync anyway */
if (maildir_uidlist_refresh(uidlist) < 0)
return -1;
} else {
}
(sync_flags & MAILDIR_UIDLIST_SYNC_PARTIAL) != 0;
if ((sync_flags & MAILDIR_UIDLIST_SYNC_KEEP_STATE) == 0) {
/* initially mark all nonsynced */
}
return 1;
}
"maildir_uidlist_sync", 16384);
return 1;
}
static void
const char *filename,
{
struct maildir_uidlist_rec *rec;
/* we'll update uidlist directly */
/* doesn't exist in uidlist */
/* we can't add it, so just ignore it */
return;
}
ctx->new_files_count++;
"uidlist record_pool",
1024);
}
struct maildir_uidlist_rec, 1);
}
}
{
unsigned char *ret;
if (extensions == NULL)
return NULL;
T_BEGIN {
unsigned int len;
}
} T_END;
return ret;
}
const char *filename,
{
return -1;
return 1;
}
MAILDIR_UIDLIST_REC_FLAG_MOVED)) == 0) {
/* possibly duplicate */
return 0;
}
/* probably was in new/ and now we're seeing it in cur/.
to check for duplicates. */
} else {
rec->extensions =
} else {
ctx->new_files_count++;
/* didn't exist in uidlist, it's recent */
}
}
return 1;
}
const char *filename)
{
struct maildir_uidlist_rec *rec;
}
ctx->first_nouid_pos--;
}
}
const char *
const char *filename)
{
struct maildir_uidlist_rec *rec;
}
{
struct maildir_uidlist_rec *rec;
return FALSE;
return TRUE;
}
const char *
const char *filename)
{
struct maildir_uidlist_rec *rec;
}
{
}
{
struct maildir_uidlist_rec **recs;
/* sort new files and assign UIDs for them */
sizeof(*recs), maildir_time_cmp);
}
ctx->new_files_count = 0;
}
{
struct maildir_uidlist_rec **recs;
unsigned int count;
/* buffer is unsorted, sort it by UID */
if (ctx->new_files_count != 0) {
} else {
}
}
{
} else {
if (ctx->new_files_count != 0)
}
}
{
T_BEGIN {
} T_END;
}
return ret;
}
const char *filename,
{
struct maildir_uidlist_rec *rec;
}
struct maildir_uidlist_iter_ctx *
{
struct maildir_uidlist_iter_ctx *ctx;
unsigned int count;
return ctx;
}
static void
{
count - old_rev_idx;
idx++;
idx--;
}
struct maildir_uidlist_rec **rec_r)
{
struct maildir_uidlist_rec *rec;
return FALSE;
return TRUE;
}
enum maildir_uidlist_rec_flag *flags_r,
const char **filename_r)
{
struct maildir_uidlist_rec *rec;
return FALSE;
return TRUE;
}
{
}