-/* Copyright (C) 2002 Timo Sirainen */
+/* Copyright (C) 2002-2003 Timo Sirainen */
#include "lib.h"
-#include "ioloop.h"
+#include "buffer.h"
#include "hash.h"
+#include "ioloop.h"
#include "maildir-index.h"
+#include "maildir-uidlist.h"
#include "mail-index-data.h"
#include "mail-index-util.h"
#include <utime.h>
#include <sys/stat.h>
-static int maildir_index_sync_file(struct mail_index *index,
- struct mail_index_record *rec,
- unsigned int seq, const char *fname,
- const char *path, int fname_changed)
-{
- struct mail_index_update *update;
- enum mail_flags flags;
+enum maildir_file_action {
+ MAILDIR_FILE_ACTION_EXPUNGE,
+ MAILDIR_FILE_ACTION_UPDATE_FLAGS,
+ MAILDIR_FILE_ACTION_UPDATE_CONTENT,
+ MAILDIR_FILE_ACTION_NEW,
+ MAILDIR_FILE_ACTION_NONE
+};
+
+struct maildir_hash_context {
+ struct mail_index *index;
+ struct mail_index_record *new_mail;
+
int failed;
+};
- i_assert(fname != NULL);
- i_assert(path != NULL);
+struct maildir_hash_rec {
+ struct mail_index_record *rec;
+ enum maildir_file_action action;
+};
- if (!index->set_lock(index, MAIL_LOCK_EXCLUSIVE))
- return FALSE;
+static int maildir_update_filename(struct mail_index *index,
+ struct mail_index_record *rec,
+ const char *new_fname)
+{
+ struct mail_index_update *update;
- failed = FALSE;
update = index->update_begin(index, rec);
+ index->update_field(update, DATA_FIELD_LOCATION, new_fname, 0);
+ return index->update_end(update);
+}
- if (fname_changed)
- index->update_field(update, DATA_FIELD_LOCATION, fname, 0);
-
- if (!index->update_end(update))
- failed = TRUE;
+static int maildir_update_flags(struct mail_index *index,
+ struct mail_index_record *rec,
+ unsigned int seq, const char *new_fname)
+{
+ enum mail_flags flags;
- /* update flags after filename has been updated, so it can be
- compared correctly */
- flags = maildir_filename_get_flags(fname, rec->msg_flags);
- if (!failed && flags != rec->msg_flags) {
+ flags = maildir_filename_get_flags(new_fname, rec->msg_flags);
+ if (flags != rec->msg_flags) {
if (!index->update_flags(index, rec, seq, flags, TRUE))
- failed = TRUE;
+ return FALSE;
}
- return !failed;
+ return TRUE;
}
-static int maildir_index_sync_files(struct mail_index *index, const char *dir,
- struct hash_table *files,
- int check_content_changes)
+static int is_file_content_changed(struct mail_index *index,
+ struct mail_index_record *rec,
+ const char *dir, const char *fname)
{
- struct mail_index_record *rec;
+#define DATA_HDR_SIZE (DATA_HDR_HEADER_SIZE | DATA_HDR_BODY_SIZE)
struct mail_index_data_record_header *data_hdr;
struct stat st;
- const char *fname, *base_fname, *value;
- char path[PATH_MAX];
- unsigned int seq;
- int fname_changed;
+ const char *path;
- i_assert(dir != NULL);
+ if ((rec->data_fields & DATA_HDR_INTERNAL_DATE) == 0 &&
+ (rec->data_fields & DATA_HDR_SIZE) != DATA_HDR_SIZE) {
+ /* nothing in cache, we can't know if it's changed */
+ return FALSE;
+ }
- rec = index->lookup(index, 1);
- for (seq = 1; rec != NULL; rec = index->next(index, rec)) {
- fname = maildir_get_location(index, rec);
- if (fname == NULL)
- return FALSE;
+ t_push();
+ path = t_strdup_printf("%s/%s", dir, fname);
- t_push();
+ if (stat(path, &st) < 0) {
+ if (errno != ENOENT)
+ index_file_set_syscall_error(index, path, "stat()");
+ t_pop();
+ return FALSE;
+ }
+ t_pop();
- /* get the filename without the ":flags" part */
- base_fname = t_strcut(fname, ':');
+ data_hdr = mail_index_data_lookup_header(index->data, rec);
+ if (data_hdr == NULL)
+ return FALSE;
- value = hash_lookup(files, base_fname);
- if (value != NULL)
- hash_remove(files, base_fname);
+ if ((rec->data_fields & DATA_HDR_INTERNAL_DATE) != 0 &&
+ st.st_mtime != data_hdr->internal_date)
+ return TRUE;
- t_pop();
+ if ((rec->data_fields & DATA_HDR_SIZE) == DATA_HDR_SIZE &&
+ (uoff_t)st.st_size != data_hdr->body_size + data_hdr->header_size)
+ return TRUE;
- if (value == NULL) {
- /* mail is expunged */
- if (!index->set_lock(index, MAIL_LOCK_EXCLUSIVE))
- return FALSE;
+ return FALSE;
+}
- if (!index->expunge(index, rec, seq, TRUE))
+/* a char* hash function from ASU -- from glib */
+static unsigned int maildir_hash(const void *p)
+{
+ const unsigned char *s = p;
+ unsigned int g, h = 0;
+
+ while (*s != ':' && *s != '\0') {
+ h = (h << 4) + *s;
+ if ((g = h & 0xf0000000UL)) {
+ h = h ^ (g >> 24);
+ h = h ^ g;
+ }
+ s++;
+ }
+
+ return h;
+}
+
+static int maildir_cmp(const void *p1, const void *p2)
+{
+ const char *s1 = p1, *s2 = p2;
+
+ while (*s1 == *s2 && *s1 != ':' && *s1 != '\0') {
+ s1++; s2++;
+ }
+ if ((*s1 == '\0' || *s1 == ':') &&
+ (*s2 == '\0' || *s2 == '\0'))
+ return 0;
+ return *s1 - *s2;
+}
+
+static void uidlist_hash_get_filenames(void *key, void *value, void *context)
+{
+ buffer_t *buf = context;
+ struct maildir_hash_rec *hash_rec = value;
+
+ if (hash_rec->action == MAILDIR_FILE_ACTION_NEW)
+ buffer_append(buf, (const void *) &key, sizeof(key));
+}
+
+static int maildir_sync_uidlist(struct mail_index *index, const char *dir,
+ struct maildir_uidlist *uidlist,
+ struct hash_table *files, pool_t pool,
+ unsigned int new_count)
+{
+ struct mail_index_record *rec;
+ struct maildir_hash_rec *hash_rec;
+ struct maildir_uidlist_rec uid_rec;
+ const char *fname, **new_files;
+ void *orig_key, *orig_value;
+ unsigned int seq, last_uid, i;
+ buffer_t *buf;
+
+ seq = 0;
+ rec = index->lookup(index, 1);
+
+ if (uidlist == NULL)
+ memset(&uid_rec, 0, sizeof(uid_rec));
+ else {
+ if (maildir_uidlist_next(uidlist, &uid_rec) < 0)
+ return FALSE;
+ }
+
+ while (rec != NULL) {
+ seq++;
+
+ /* skip over the expunged records in uidlist */
+ while (uid_rec.uid != 0 && uid_rec.uid < rec->uid) {
+ uidlist->rewrite = TRUE;
+ if (!maildir_uidlist_next(uidlist, &uid_rec))
return FALSE;
- continue;
}
- /* file still exists */
- if (str_path(path, sizeof(path), dir, value) < 0) {
- index_set_error(index, "Path too long: %s/%s",
- dir, value);
+ fname = maildir_get_location(index, rec);
+ if (fname == NULL) {
+ hash_destroy(files);
return FALSE;
}
- if (check_content_changes) {
- if (stat(path, &st) < 0) {
- index_file_set_syscall_error(index, path,
- "stat()");
- return FALSE;
- }
+ hash_rec = hash_lookup(files, fname);
+ if (hash_rec == NULL) {
+ index_set_corrupted(index, "Unexpectedly lost file "
+ "%s from hash", fname);
+ return FALSE;
+ }
- data_hdr = mail_index_data_lookup_header(index->data,
- rec);
- if (data_hdr != NULL &&
- (st.st_mtime != data_hdr->internal_date ||
- (uoff_t)st.st_size !=
- data_hdr->body_size + data_hdr->header_size)) {
- /* file changed. IMAP doesn't allow that, so
- we have to treat it as a new message. */
- if (!index->set_lock(index,
- MAIL_LOCK_EXCLUSIVE))
- return FALSE;
-
- if (!index->expunge(index, rec, seq, TRUE))
- return FALSE;
- continue;
- }
+ if (uid_rec.uid != 0 &&
+ maildir_cmp(fname, uid_rec.filename) != 0) {
+ index_set_corrupted(index, "Filename mismatch for UID "
+ "%u: %s vs %s", rec->uid, fname,
+ uid_rec.filename);
+ return FALSE;
}
- /* changed - update */
- fname_changed = strcmp(value, fname) != 0;
- if (fname_changed) {
- if (!maildir_index_sync_file(index, rec, seq, value,
- path, fname_changed))
+ switch (hash_rec->action) {
+ case MAILDIR_FILE_ACTION_EXPUNGE:
+ if (!index->expunge(index, rec, seq, TRUE))
+ return FALSE;
+ seq--;
+ break;
+ case MAILDIR_FILE_ACTION_UPDATE_FLAGS:
+ if (!maildir_update_flags(index, rec, seq, fname))
return FALSE;
+ break;
+ case MAILDIR_FILE_ACTION_UPDATE_CONTENT:
+ if (!index->expunge(index, rec, seq, TRUE))
+ return FALSE;
+ seq--;
+ hash_rec->action = MAILDIR_FILE_ACTION_NEW;
+ new_count++;
+ break;
+ case MAILDIR_FILE_ACTION_NONE:
+ break;
+ default:
+ i_unreached();
}
- seq++;
+ if (uid_rec.uid != 0) {
+ if (maildir_uidlist_next(uidlist, &uid_rec) < 0)
+ return FALSE;
+ }
+ rec = index->next(index, rec);
}
- if (seq-1 != index->header->messages_count) {
+ if (seq != index->header->messages_count) {
index_set_corrupted(index, "Wrong messages_count in header "
- "(%u != %u)", seq-1,
+ "(%u != %u)", seq,
index->header->messages_count);
return FALSE;
}
- return TRUE;
-}
-
-struct hash_append_context {
- struct mail_index *index;
- const char *dir;
- int failed;
-};
+ /* if there's mails with UIDs in uidlist, write them */
+ last_uid = 0;
+ while (uid_rec.uid != 0) {
+ if (!hash_lookup_full(files, uid_rec.filename,
+ &orig_key, &orig_value)) {
+ /* expunged */
+ if (uidlist != NULL)
+ uidlist->rewrite = TRUE;
+ } else {
+ hash_rec = orig_value;
+ i_assert(hash_rec->action == MAILDIR_FILE_ACTION_NEW);
+
+ /* make sure we set the same UID for it. */
+ i_assert(index->header->next_uid <= uid_rec.uid);
+ index->header->next_uid = uid_rec.uid;
+
+ hash_rec->action = MAILDIR_FILE_ACTION_NONE;
+ new_count--;
+
+ if (!maildir_index_append_file(index, dir, orig_key))
+ return FALSE;
+ }
-static void maildir_index_hash_append_file(void *key __attr_unused__,
- void *value, void *context)
-{
- struct hash_append_context *ctx = context;
+ if (maildir_uidlist_next(uidlist, &uid_rec) < 0)
+ return FALSE;
+ }
- t_push();
- if (!maildir_index_append_file(ctx->index, ctx->dir, value)) {
- ctx->failed = TRUE;
- hash_foreach_stop();
+ if (new_count == 0) {
+ /* all done */
+ return TRUE;
}
- t_pop();
-}
-static int maildir_index_append_files(struct mail_index *index, const char *dir,
- struct hash_table *files)
-{
- struct hash_append_context ctx;
+ if (uidlist != NULL)
+ uidlist->rewrite = TRUE;
- ctx.failed = FALSE;
- ctx.index = index;
- ctx.dir = dir;
- hash_foreach(files, maildir_index_hash_append_file, &ctx);
+ /* then there's the completely new mails. sort them by the filename
+ so we should get them to same order as they were created. */
+ buf = buffer_create_static_hard(pool, new_count * sizeof(const char *));
+ hash_foreach(files, uidlist_hash_get_filenames, buf);
+ i_assert(buffer_get_used_size(buf) / sizeof(const char *) <= new_count);
- return !ctx.failed;
+ new_files = buffer_get_modifyable_data(buf, NULL);
+ qsort(new_files, new_count, sizeof(const char *),
+ (int (*)(const void *, const void *)) strcmp);
+
+ /* and finally write */
+ for (i = 0; i < new_count; i++) {
+ if (!maildir_index_append_file(index, dir, new_files[i]))
+ return FALSE;
+ }
+
+ return TRUE;
}
-static int maildir_index_sync_dir(struct mail_index *index, const char *dir)
+static int maildir_index_sync_dir(struct mail_index *index, const char *dir,
+ struct maildir_uidlist *uidlist)
{
pool_t pool;
struct hash_table *files;
+ struct mail_index_record *rec;
+ struct maildir_hash_rec *hash_rec;
DIR *dirp;
struct dirent *d;
- char *key, *value, *p;
- unsigned int count;
+ const char *fname;
+ void *orig_key, *orig_value;
+ unsigned int new_count;
int failed, check_content_changes;
i_assert(dir != NULL);
-
- /* get exclusive lock always, this way the index file's timestamp
- is updated even if there's no changes, which is useful to make
- sure the cur/ directory isn't scanned all the time when it's
- timestamp has changed but hasn't had any other changes. */
- if (!index->set_lock(index, MAIL_LOCK_EXCLUSIVE))
- return FALSE;
+ i_assert(index->lock_type == MAIL_LOCK_EXCLUSIVE);
if (index->header->messages_count >= INT_MAX/32) {
index_set_corrupted(index, "Header says %u messages",
return FALSE;
}
- /* we need to find out the new and the deleted files. do this by
- first building a hash of what files really exist, then go through
- the index and after updated/removed the index, remove the file
- from hash, so finally the hash should contain only the new
- files which will be added then. */
+ /* read current messages in index into hash */
+ pool = pool_alloconly_create("maildir sync", 16384);
+ files = hash_create(default_pool, pool, index->header->messages_count*2,
+ maildir_hash, maildir_cmp);
+
+ rec = index->lookup(index, 1);
+ while (rec != NULL) {
+ fname = maildir_get_location(index, rec);
+ if (fname == NULL) {
+ hash_destroy(files);
+ return FALSE;
+ }
+ hash_rec = p_new(pool, struct maildir_hash_rec, 1);
+ hash_rec->rec = rec;
+ hash_rec->action = MAILDIR_FILE_ACTION_EXPUNGE;
+ hash_insert(files, (void *) fname, hash_rec);
+
+ rec = index->next(index, rec);
+ }
+
+ /* Do we want to check changes in file contents? This slows down
+ things as we need to do extra stat() for all files. */
+ check_content_changes = getenv("MAILDIR_CHECK_CONTENT_CHANGES") != NULL;
+
dirp = opendir(dir);
if (dirp == NULL)
return index_file_set_syscall_error(index, dir, "opendir()");
- count = index->header->messages_count + 16;
- pool = pool_alloconly_create("Maildir sync", count*30);
- files = hash_create(default_pool, pool, index->header->messages_count*2,
- str_hash, (hash_cmp_callback_t *)strcmp);
-
+ new_count = 0; failed = FALSE;
while ((d = readdir(dirp)) != NULL) {
if (d->d_name[0] == '.')
continue;
- /* hash key is the file name without the ":flags" part */
- p = strrchr(d->d_name, ':');
- if (p == d->d_name)
- continue;
+ if (!hash_lookup_full(files, d->d_name,
+ &orig_key, &orig_value)) {
+ hash_rec = p_new(pool, struct maildir_hash_rec, 1);
+ } else {
+ hash_rec = orig_value;
+ if (hash_rec->action != MAILDIR_FILE_ACTION_EXPUNGE) {
+ /* FIXME: duplicate */
+ continue;
+ }
+ }
- value = p_strdup(pool, d->d_name);
- key = p == NULL ? value : p_strdup_until(pool, d->d_name, p);
- hash_insert(files, key, value);
+ if (hash_rec->rec == NULL) {
+ /* new message */
+ new_count++;
+ hash_rec->action = MAILDIR_FILE_ACTION_NEW;
+ hash_insert(files, p_strdup(pool, d->d_name), hash_rec);
+ } else if (check_content_changes &&
+ is_file_content_changed(index, rec,
+ dir, d->d_name)) {
+ /* file content changed, treat it as new message */
+ hash_rec->action = MAILDIR_FILE_ACTION_UPDATE_CONTENT;
+
+ /* make sure filename is not invalidated by expunge
+ later. the file name may have changed also. */
+ hash_insert(files, p_strdup(pool, d->d_name), hash_rec);
+ } else if (strcmp(orig_key, d->d_name) != 0) {
+ /* update filename now, flags later */
+ hash_rec->action = MAILDIR_FILE_ACTION_UPDATE_FLAGS;
+ if (!maildir_update_filename(index, hash_rec->rec,
+ d->d_name)) {
+ failed = TRUE;
+ break;
+ }
+ } else {
+ hash_rec->action = MAILDIR_FILE_ACTION_NONE;
+ }
}
if (closedir(dirp) < 0)
index_file_set_syscall_error(index, dir, "closedir()");
- /* Do we want to check changes in file contents? This slows down
- things as we need to do extra stat() for all files. */
- check_content_changes = getenv("MAILDIR_CHECK_CONTENT_CHANGES") != NULL;
-
- /* now walk through the index syncing and expunging existing mails */
- failed = !maildir_index_sync_files(index, dir, files,
- check_content_changes);
-
if (!failed) {
- /* then add the new mails */
- failed = !maildir_index_append_files(index, dir, files);
+ failed = !maildir_sync_uidlist(index, dir, uidlist,
+ files, pool, new_count);
}
-
hash_destroy(files);
pool_unref(pool);
return !failed;
}
-int maildir_index_sync(struct mail_index *index,
- enum mail_lock_type data_lock_type __attr_unused__,
- int *changes)
+static int maildir_new_scan_first_file(struct mail_index *index,
+ const char *dir, DIR **dirp,
+ struct dirent **d)
+{
+ *dirp = opendir(dir);
+ if (*dirp == NULL)
+ return index_file_set_syscall_error(index, dir, "opendir()");
+
+ /* find first file */
+ while ((*d = readdir(*dirp)) != NULL) {
+ if ((*d)->d_name[0] != '.')
+ break;
+ }
+
+ if (*d == NULL) {
+ if (closedir(*dirp) < 0)
+ index_file_set_syscall_error(index, dir, "closedir()");
+ *dirp = NULL;
+ }
+
+ return TRUE;
+}
+
+static int maildir_index_lock_and_sync(struct mail_index *index, int *changes,
+ DIR *new_dirp, struct dirent *new_dent,
+ struct maildir_uidlist **uidlist_r)
{
- struct stat sti, std;
+ struct stat st, std;
struct utimbuf ut;
- const char *cur_dir, *new_dir;
+ struct maildir_uidlist *uidlist;
+ const char *uidlist_path, *cur_dir, *new_dir;
time_t index_mtime;
+ int cur_changed;
- i_assert(index->lock_type != MAIL_LOCK_SHARED);
-
- if (changes != NULL)
- *changes = FALSE;
+ *uidlist_r = uidlist = NULL;
if (index->fd == -1) {
/* anon-mmaped */
index_mtime = index->file_sync_stamp;
} else {
- if (fstat(index->fd, &sti) < 0)
+ if (fstat(index->fd, &st) < 0)
return index_set_syscall_error(index, "fstat()");
- index_mtime = sti.st_mtime;
+ index_mtime = st.st_mtime;
}
- /* cur/ and new/ directories can have new mail - sync the cur/ first
- so it'll be a bit bit faster since we haven't yet added the new
- mail. */
cur_dir = t_strconcat(index->mailbox_path, "/cur", NULL);
if (stat(cur_dir, &std) < 0)
return index_file_set_syscall_error(index, cur_dir, "stat()");
- if (std.st_mtime != index_mtime) {
- if (changes != NULL) *changes = TRUE;
- if (!maildir_index_sync_dir(index, cur_dir))
- return FALSE;
+ uidlist_path = t_strconcat(index->mailbox_path,
+ "/" MAILDIR_UIDLIST_NAME, NULL);
+ if (stat(uidlist_path, &st) < 0) {
+ if (errno != ENOENT) {
+ return index_file_set_syscall_error(index, uidlist_path,
+ "stat()");
+ }
+
+ memset(&st, 0, sizeof(st));
+ cur_changed = TRUE;
+ } else {
+ /* FIXME: save device and inode into index header, so we don't
+ have to read it every time mailbox is opened */
+ cur_changed = index_mtime != std.st_mtime ||
+ st.st_ino != index->uidlist_ino ||
+ !CMP_DEV_T(st.st_dev, index->uidlist_dev);
}
- /* move mail from new/ to cur/ */
- new_dir = t_strconcat(index->mailbox_path, "/new", NULL);
- if (stat(new_dir, &std) < 0)
- return index_file_set_syscall_error(index, new_dir, "stat()");
+ if (new_dirp != NULL || cur_changed) {
+ if (maildir_uidlist_try_lock(index) < 0)
+ return FALSE;
+
+ /* we may or may not have succeeded. if we didn't,
+ just continue by syncing with existing uidlist file */
+
+ if (!cur_changed && !INDEX_IS_UIDLIST_LOCKED(index)) {
+ /* just new mails in new/ dir, we can't sync them
+ if we can't get the lock. */
+ return TRUE;
+ }
+
+ if (!index->set_lock(index, MAIL_LOCK_EXCLUSIVE))
+ return FALSE;
+
+ *uidlist_r = uidlist = maildir_uidlist_open(index);
+ if (uidlist != NULL &&
+ uidlist->uid_validity != index->header->uid_validity) {
+ /* uidvalidity changed */
+ if (!index->rebuilding) {
+ index_set_corrupted(index,
+ "UIDVALIDITY changed in uidlist");
+ return FALSE;
+ }
+
+ index->header->uid_validity = uidlist->uid_validity;
+ }
+
+ if (uidlist != NULL &&
+ index->header->next_uid > uidlist->next_uid) {
+ index_set_corrupted(index, "index.next_uid (%u) > "
+ "uidlist.next_uid (%u)",
+ index->header->next_uid,
+ uidlist->next_uid);
+ return FALSE;
+ }
- if (std.st_mtime != index_mtime) {
- if (changes != NULL) *changes = TRUE;
+ if (changes != NULL)
+ *changes = TRUE;
+ }
- if (!maildir_index_build_dir(index, new_dir, cur_dir))
+ /* move mail from new/ to cur/ */
+ if (new_dirp != NULL && INDEX_IS_UIDLIST_LOCKED(index)) {
+ new_dir = t_strconcat(index->mailbox_path, "/new", NULL);
+ if (!maildir_index_build_dir(index, new_dir, cur_dir,
+ new_dirp, new_dent))
return FALSE;
- /* set cur/ and new/ directory's timestamp into past to
- make sure if someone adds new mail it the new/ dir's
- timestamp isn't set to same as cur/ directory's. */
+ if (uidlist != NULL)
+ uidlist->rewrite = TRUE;
+
+ /* set cur/ directory's timestamp into past to make sure we
+ notice if new mail is moved there */
ut.actime = ut.modtime = ioloop_time-60;
if (utime(cur_dir, &ut) < 0) {
- return index_file_set_syscall_error(index, cur_dir,
- "utime()");
+ index_file_set_syscall_error(index, cur_dir, "utime()");
+ return FALSE;
}
- if (utime(new_dir, &ut) < 0) {
- return index_file_set_syscall_error(index, new_dir,
- "utime()");
+
+ /* We have to always scan the cur/ directory to make
+ sure we don't miss any mails some other non-Dovecot
+ client may have moved there. FIXME: make it
+ optional, it's unnecessary with Dovecot-only setup */
+ cur_changed = TRUE;
+
+ /* set the cur/ directory's timestamp */
+ std.st_mtime = ut.modtime;
+ }
+
+ if (cur_changed) {
+ if (!maildir_index_sync_dir(index, cur_dir, uidlist))
+ return FALSE;
+ }
+
+ if (uidlist != NULL && uidlist->next_uid > index->header->next_uid)
+ index->header->next_uid = uidlist->next_uid;
+
+ if ((new_dirp != NULL || cur_changed) &&
+ (uidlist == NULL || uidlist->rewrite)) {
+ if (!INDEX_IS_UIDLIST_LOCKED(index)) {
+ /* there's more new mails, but we need .lock file to
+ be able to sync them. */
+ return TRUE;
}
- /* it's possible that new mail came in just after we
- scanned the directory. scan the directory again, this will
- update the directory's timestamps so at next sync we'll
- always check the new/ dir once more, but at least we can be
- sure that no mail got lost. */
- if (!maildir_index_build_dir(index, new_dir, cur_dir))
+ if (fstat(index->maildir_lock_fd, &st) < 0) {
+ return index_file_set_syscall_error(index, uidlist_path,
+ "fstat()");
+ }
+
+ if (!maildir_uidlist_rewrite(index))
return FALSE;
}
+ /* uidlist file synced */
+ index->uidlist_ino = st.st_ino;
+ index->uidlist_dev = st.st_dev;
+
/* update sync stamp */
- if (stat(cur_dir, &std) < 0)
- return index_file_set_syscall_error(index, cur_dir, "stat()");
index->file_sync_stamp = std.st_mtime;
- if (index->fd != -1 && index->lock_type == MAIL_LOCK_UNLOCK) {
- /* no changes, we need to update index's timestamp
+ if (index->lock_type == MAIL_LOCK_UNLOCK && !index->anon_mmap) {
+ /* no changes to index, we need to update it's timestamp
ourself to get it changed */
ut.actime = ioloop_time;
ut.modtime = index->file_sync_stamp;
return TRUE;
}
+
+int maildir_index_sync(struct mail_index *index,
+ enum mail_lock_type data_lock_type __attr_unused__,
+ int *changes)
+{
+ struct maildir_uidlist *uidlist;
+ DIR *new_dirp;
+ struct dirent *new_dent;
+ const char *new_dir;
+ int ret;
+
+ i_assert(index->lock_type != MAIL_LOCK_SHARED);
+
+ if (changes != NULL)
+ *changes = FALSE;
+
+ new_dir = t_strconcat(index->mailbox_path, "/new", NULL);
+ if (!maildir_new_scan_first_file(index, new_dir, &new_dirp, &new_dent))
+ return FALSE;
+
+ ret = maildir_index_lock_and_sync(index, changes, new_dirp, new_dent,
+ &uidlist);
+
+ if (uidlist != NULL)
+ maildir_uidlist_close(uidlist);
+
+ if (new_dirp != NULL) {
+ if (closedir(new_dirp) < 0) {
+ index_file_set_syscall_error(index, new_dir,
+ "closedir()");
+ }
+ }
+
+ maildir_uidlist_unlock(index);
+ return ret;
+}
--- /dev/null
+/* Copyright (C) 2003 Timo Sirainen */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "str.h"
+#include "write-full.h"
+#include "mail-index.h"
+#include "mail-index-util.h"
+#include "maildir-index.h"
+#include "maildir-uidlist.h"
+
+#include <stdio.h>
+#include <sys/stat.h>
+
+/* how many seconds to wait before overriding uidlist.lock */
+#define UIDLIST_LOCK_STALE_TIMEOUT (60*5)
+
+int maildir_uidlist_try_lock(struct mail_index *index)
+{
+ struct stat st;
+ const char *path;
+ int fd, i;
+
+ i_assert(!INDEX_IS_UIDLIST_LOCKED(index));
+
+ path = t_strconcat(index->mailbox_path,
+ "/" MAILDIR_UIDLIST_NAME ".lock", NULL);
+ for (i = 0; i < 2; i++) {
+ fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0644);
+ if (fd != -1)
+ break;
+
+ if (errno != EEXIST) {
+ index_file_set_syscall_error(index, path, "open()");
+ return -1;
+ }
+
+ /* exists, is it stale? */
+ if (stat(path, &st) < 0) {
+ if (errno == ENOENT) {
+ /* try again */
+ continue;
+ }
+ index_file_set_syscall_error(index, path, "stat()");
+ return -1;
+ }
+
+ if (st.st_mtime < ioloop_time - UIDLIST_LOCK_STALE_TIMEOUT) {
+ if (unlink(path) < 0 && errno != ENOENT) {
+ return index_file_set_syscall_error(index, path,
+ "unlink()");
+ }
+ /* try again */
+ continue;
+ }
+ return 0;
+ }
+
+ index->maildir_lock_fd = fd;
+ return 1;
+}
+
+void maildir_uidlist_unlock(struct mail_index *index)
+{
+ const char *path;
+
+ if (!INDEX_IS_UIDLIST_LOCKED(index))
+ return;
+
+ path = t_strconcat(index->mailbox_path,
+ "/" MAILDIR_UIDLIST_NAME ".lock", NULL);
+ if (unlink(path) < 0 && errno != ENOENT)
+ index_file_set_syscall_error(index, path, "unlink()");
+
+ if (close(index->maildir_lock_fd) < 0)
+ index_file_set_syscall_error(index, path, "close()");
+ index->maildir_lock_fd = -1;
+}
+
+struct maildir_uidlist *maildir_uidlist_open(struct mail_index *index)
+{
+ const char *path, *line;
+ struct maildir_uidlist *uidlist;
+ unsigned int version;
+ int fd;
+
+ path = t_strconcat(index->mailbox_path, "/" MAILDIR_UIDLIST_NAME, NULL);
+ fd = open(path, O_RDONLY);
+ if (fd == -1) {
+ if (errno != ENOENT)
+ index_file_set_syscall_error(index, path, "open()");
+ return NULL;
+ }
+
+ uidlist = i_new(struct maildir_uidlist, 1);
+ uidlist->index = index;
+ uidlist->fname = i_strdup(path);
+ uidlist->input = i_stream_create_file(fd, default_pool, 4096, TRUE);
+
+ /* get header */
+ line = i_stream_read_next_line(uidlist->input);
+ if (line == NULL || sscanf(line, "%u %u %u", &version,
+ &uidlist->uid_validity,
+ &uidlist->next_uid) != 3 ||
+ version != 1) {
+ /* broken file */
+ (void)unlink(path);
+ maildir_uidlist_close(uidlist);
+ return NULL;
+ }
+
+ return uidlist;
+}
+
+int maildir_uidlist_next(struct maildir_uidlist *uidlist,
+ struct maildir_uidlist_rec *uid_rec)
+{
+ const char *line;
+ unsigned int uid;
+
+ memset(uid_rec, 0, sizeof(*uid_rec));
+
+ line = i_stream_read_next_line(uidlist->input);
+ if (line == NULL)
+ return 0;
+
+ uid = 0;
+ while (*line >= '0' && *line <= '9') {
+ uid = uid*10 + (*line - '0');
+ line++;
+ }
+
+ if (uid == 0 || *line != ' ') {
+ /* invalid file */
+ index_set_error(uidlist->index, "Invalid data in file %s",
+ uidlist->fname);
+ (void)unlink(uidlist->fname);
+ return -1;
+ }
+ if (uid <= uidlist->last_read_uid) {
+ index_set_error(uidlist->index,
+ "UIDs not ordered in file %s (%u > %u)",
+ uidlist->fname, uid, uidlist->last_read_uid);
+ (void)unlink(uidlist->fname);
+ return -1;
+ }
+ if (uid >= uidlist->next_uid) {
+ index_set_error(uidlist->index,
+ "UID larger than next_uid in file %s "
+ "(%u >= %u)", uidlist->fname,
+ uid, uidlist->next_uid);
+ (void)unlink(uidlist->fname);
+ return -1;
+ }
+
+ while (*line == ' ') line++;
+
+ uid_rec->uid = uid;
+ uid_rec->filename = line;
+ return 1;
+}
+
+void maildir_uidlist_close(struct maildir_uidlist *uidlist)
+{
+ i_stream_unref(uidlist->input);
+ i_free(uidlist->fname);
+ i_free(uidlist);
+}
+
+int maildir_uidlist_rewrite(struct mail_index *index)
+{
+ struct mail_index_record *rec;
+ const char *temp_path, *db_path, *p, *fname;
+ string_t *str;
+ size_t len;
+ int failed = FALSE;
+
+ i_assert(INDEX_IS_UIDLIST_LOCKED(index));
+
+ if (index->lock_type == MAIL_LOCK_UNLOCK) {
+ if (!index->set_lock(index, MAIL_LOCK_SHARED))
+ return FALSE;
+ }
+
+ temp_path = t_strconcat(index->mailbox_path,
+ "/" MAILDIR_UIDLIST_NAME ".lock", NULL);
+
+ str = t_str_new(4096);
+ str_printfa(str, "1 %u %u\n",
+ index->header->uid_validity, index->header->next_uid);
+
+ rec = index->lookup(index, 1);
+ while (rec != NULL) {
+ fname = maildir_get_location(index, rec);
+ if (fname == NULL)
+ break;
+
+ p = strchr(fname, ':');
+ len = p == NULL ? strlen(fname) : (size_t)(p-fname);
+
+ if (str_len(str) + MAX_INT_STRLEN + len + 2 >= 4096) {
+ /* flush buffer */
+ if (write_full(index->maildir_lock_fd,
+ str_data(str), str_len(str)) < 0) {
+ index_file_set_syscall_error(index, temp_path,
+ "write_full()");
+ break;
+ }
+ str_truncate(str, 0);
+ }
+
+ str_printfa(str, "%u ", rec->uid);
+ str_append_n(str, fname, len);
+ str_append_c(str, '\n');
+
+ rec = index->next(index, rec);
+ }
+
+ if (write_full(index->maildir_lock_fd,
+ str_data(str), str_len(str)) < 0) {
+ index_file_set_syscall_error(index, temp_path, "write_full()");
+ failed = TRUE;
+ }
+
+ if (fdatasync(index->maildir_lock_fd) < 0) {
+ index_file_set_syscall_error(index, temp_path, "fdatasync()");
+ failed = TRUE;
+ }
+ if (close(index->maildir_lock_fd) < 0) {
+ index_file_set_syscall_error(index, temp_path, "close()");
+ failed = TRUE;
+ }
+ index->maildir_lock_fd = -1;
+
+ if (rec == NULL) {
+ db_path = t_strconcat(index->mailbox_path,
+ "/" MAILDIR_UIDLIST_NAME, NULL);
+
+ if (rename(temp_path, db_path) < 0) {
+ index_set_error(index, "rename(%s, %s) failed: %m",
+ temp_path, db_path);
+ failed = TRUE;
+ }
+ }
+
+ if (failed)
+ (void)unlink(temp_path);
+ return !failed;
+}