maildir-uidlist.c revision ce84075e7ad4aa67fd45d12e75feceaffc64e523
/* Copyright (C) 2003 Timo Sirainen */
/*
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_COMPRESS_PERCENTAGE 75
#define UIDLIST_IS_LOCKED(uidlist) \
((uidlist)->lock_count > 0)
struct maildir_uidlist_rec {
char *filename;
char *extensions; /* <data>\0[<data>\0 ...]\0 */
};
struct maildir_uidlist {
struct maildir_mailbox *mbox;
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;
unsigned int recreate:1;
unsigned int initial_read:1;
unsigned int initial_sync:1;
};
struct maildir_uidlist_sync_ctx {
struct maildir_uidlist *uidlist;
struct hash_table *files;
unsigned int first_new_pos;
unsigned int new_files_count;
unsigned int partial:1;
unsigned int finished:1;
unsigned int changed:1;
unsigned int failed:1;
};
struct maildir_uidlist_iter_ctx {
struct maildir_uidlist *uidlist;
unsigned int change_counter;
};
struct maildir_uidlist_rec **rec_r);
bool nonblock)
{
const char *path;
int ret;
if (uidlist->lock_count > 0) {
uidlist->lock_count++;
return 1;
}
if (ret <= 0) {
if (ret == 0) {
return 0;
}
"file_dotlock_open(%s) failed: %m", path);
return -1;
}
uidlist->lock_count++;
/* 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 *uidlist;
return uidlist;
}
{
}
uidlist->last_read_offset = 0;
}
{
}
static void
{
if (uidlist->first_recent_uid == 0 ||
}
static bool
const char **line_p,
struct maildir_uidlist_rec *rec)
{
t_push();
/* skip over an extension field */
}
/* save the extensions */
}
t_pop();
if (*line == ':')
line++;
if (*line == '\0')
return FALSE;
return TRUE;
}
const char *line)
{
struct maildir_uidlist_rec *rec;
uid = 0;
line++;
}
/* invalid file */
return 0;
}
"UIDs not ordered in file %s (%u > %u)",
return 0;
}
/* we already have this */
return 1;
}
"UID larger than next_uid in file %s (%u >= %u)",
return 0;
}
/* read extended fields */
return 0;
}
}
"Duplicate file in uidlist file %s: %s",
return 0;
}
return 1;
}
{
unsigned int uid_validity, next_uid;
char key;
/* I/O error / empty file */
}
"%s: Corrupted header (invalid version number)",
return 0;
}
line += 2;
case 1:
"%s: Corrupted header (version 1)",
return 0;
}
"%s: next_uid was lowered (v1, %u -> %u)",
return 0;
}
break;
case 3:
str_truncate(ext_hdr, 0);
while (*line != '\0') {
t_push();
switch (key) {
break;
break;
default:
break;
}
t_pop();
}
break;
default:
return 0;
}
if (uid_validity == 0 || next_uid == 0) {
"%s: 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
{
*recreated_r = FALSE;
/* FIXME: nfs attribute cache flush */
return -1;
}
return 0;
}
/* file recreated */
*recreated_r = TRUE;
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;
}
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 struct maildir_uidlist_rec *rec;
unsigned int idx;
return NULL;
/* the uidlist doesn't exist. */
return NULL;
/* try again */
return NULL;
}
}
const char *
{
const struct maildir_uidlist_rec *rec;
unsigned int idx;
const char *p, *value;
return NULL;
while (*p != '\0') {
/* <key><value>\0 */
if (*p == (char)key)
return p + 1;
p += strlen(p) + 1;
}
return NULL;
}
{
return FALSE;
return FALSE;
(flags & MAILDIR_UIDLIST_REC_FLAG_RECENT) != 0);
return (flags & MAILDIR_UIDLIST_REC_FLAG_RECENT) != 0;
}
{
struct maildir_uidlist_rec *const *recs;
if (!uidlist->initial_sync) {
/* we haven't synced yet, trust index */
const struct mail_index_header *hdr;
return hdr->recent_messages_count;
}
/* all recent messages were in new/ dir, so even if we did only
a partial sync we should know all the recent messages. */
if (uidlist->first_recent_uid == 0)
return 0;
recent_count++;
}
return recent_count;
}
{
return uidlist->uid_validity;
}
{
}
{
}
{
}
const char *value)
{
struct maildir_uidlist_rec *rec;
unsigned int idx;
const char *p;
unsigned int len;
t_push();
/* 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;
}
}
t_pop();
}
{
struct maildir_uidlist_iter_ctx *iter;
struct maildir_uidlist_rec *rec;
const char *p;
int ret;
}
} else {
}
str_truncate(str, 0);
str_append(str, p);
p += strlen(p) + 1;
}
}
}
if (ret < 0) {
"o_stream_send(%s) failed: %m", path);
return -1;
}
"fsync(%s) failed: %m", path);
return -1;
}
}
return 0;
}
{
const char *temp_path;
if (fd == -1) {
"open(%s, O_CREAT) failed: %m", temp_path);
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);
}
ret = -1;
} else {
}
return ret;
}
{
int ret;
return 0;
if (maildir_uidlist_lock(uidlist) <= 0)
return -1;
return ret;
}
{
return maildir_uidlist_recreate(uidlist);
return -1;
}
return -1;
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;
return ret;
/* 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 */
if (ctx->new_files_count == 0)
ctx->new_files_count++;
"uidlist record_pool",
1024);
}
struct maildir_uidlist_rec, 1);
}
if ((flags & MAILDIR_UIDLIST_REC_FLAG_RECENT) != 0 &&
}
const char *filename)
{
/* first time reading the uidlist */
return -1;
}
}
return 0;
}
return 1;
}
const char *filename,
{
return -1;
return 1;
}
MAILDIR_UIDLIST_REC_FLAG_MOVED)) == 0) {
/* possibly duplicate */
return 0;
}
} else {
else {
ctx->new_files_count++;
}
}
if ((flags & MAILDIR_UIDLIST_REC_FLAG_RECENT) != 0 &&
return 1;
}
{
}
const char *filename)
{
unsigned int count;
if (ctx->first_new_pos != 0)
ctx->first_new_pos--;
}
const char *
const char *filename)
{
struct maildir_uidlist_rec *rec;
}
const char *
const char *filename)
{
struct maildir_uidlist_rec *rec;
}
{
}
unsigned int first_new_pos)
{
struct maildir_uidlist_rec **recs;
/* sort new files and assign UIDs for them */
sizeof(*recs), maildir_time_cmp);
MAILDIR_UIDLIST_REC_FLAG_RECENT) != 0) {
}
}
}
{
struct maildir_uidlist_rec **recs;
unsigned int count;
/* buffer is unsorted, sort it by UID */
if (ctx->new_files_count != 0) {
}
}
{
} else {
}
}
{
t_push();
t_pop();
}
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;
}
{
}