Mercurial > dovecot > core-2.2
view src/lib-storage/index/maildir/maildir-mail.c @ 12874:0461e23ae57c
Search supports now prefetching data for returned mails. Dropped imapc's own prefetching.
mail_prefetch_count specifies how many mails can be kept open and issue a
prefetch.
This works using posix_fadvise(POSIX_FADV_WILLNEED) for maildir, sdbox and
cydir backends. Apparently only Linux supports this.
imapc backend also implements this internally by sending wanted IMAP
commands to remote server. The command pipelining helps with latency. This
change also makes it actually possible for imapc backend to first check if
wanted data is already cached in local index and avoid sending unnecessary
IMAP commands to remote server.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Thu, 31 Mar 2011 11:10:22 +0300 |
parents | 80e1881d1d8b |
children | 80eef14e9e15 |
line wrap: on
line source
/* Copyright (c) 2003-2011 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "istream.h" #include "index-mail.h" #include "maildir-storage.h" #include "maildir-filename.h" #include "maildir-uidlist.h" #include "maildir-sync.h" #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <sys/stat.h> struct maildir_open_context { int fd; char *path; }; static int do_open(struct maildir_mailbox *mbox, const char *path, struct maildir_open_context *ctx) { ctx->fd = open(path, O_RDONLY); if (ctx->fd != -1) { ctx->path = i_strdup(path); return 1; } if (errno == ENOENT) return 0; if (errno == EACCES) { mail_storage_set_critical(&mbox->storage->storage, "%s", mail_error_eacces_msg("open", path)); } else { mail_storage_set_critical(&mbox->storage->storage, "open(%s) failed: %m", path); } return -1; } static int do_stat(struct maildir_mailbox *mbox, const char *path, struct stat *st) { if (stat(path, st) == 0) return 1; if (errno == ENOENT) return 0; if (errno == EACCES) { mail_storage_set_critical(&mbox->storage->storage, "%s", mail_error_eacces_msg("stat", path)); } else { mail_storage_set_critical(&mbox->storage->storage, "stat(%s) failed: %m", path); } return -1; } static struct istream * maildir_open_mail(struct maildir_mailbox *mbox, struct mail *mail, bool *deleted_r) { struct mail_private *p = (struct mail_private *)mail; struct istream *input; const char *path; struct maildir_open_context ctx; *deleted_r = FALSE; ctx.fd = -1; ctx.path = NULL; p->stats_open_lookup_count++; if (!mail->saving) { if (maildir_file_do(mbox, mail->uid, do_open, &ctx) < 0) return NULL; } else { path = maildir_save_file_get_path(mail->transaction, mail->seq); if (do_open(mbox, path, &ctx) <= 0) return NULL; } if (ctx.fd == -1) { *deleted_r = TRUE; return NULL; } input = i_stream_create_fd(ctx.fd, 0, TRUE); if (input->stream_errno == EISDIR) { i_stream_destroy(&input); if (maildir_lose_unexpected_dir(&mbox->storage->storage, ctx.path) >= 0) *deleted_r = TRUE; } else { i_stream_set_name(input, ctx.path); index_mail_set_read_buffer_size(mail, input); } i_free(ctx.path); return input; } static int maildir_mail_stat(struct mail *mail, struct stat *st) { struct maildir_mailbox *mbox = (struct maildir_mailbox *)mail->box; struct index_mail *imail = (struct index_mail *)mail; const struct stat *stp; const char *path; int ret; if (mail->lookup_abort == MAIL_LOOKUP_ABORT_NOT_IN_CACHE) return mail_set_aborted(mail); if (imail->data.access_part != 0 && imail->data.stream == NULL) { /* we're going to open the mail anyway */ struct istream *input; (void)mail_get_stream(mail, NULL, NULL, &input); } if (imail->data.stream != NULL) { imail->mail.stats_fstat_lookup_count++; stp = i_stream_stat(imail->data.stream, FALSE); if (stp == NULL) return -1; *st = *stp; } else if (!mail->saving) { imail->mail.stats_stat_lookup_count++; ret = maildir_file_do(mbox, mail->uid, do_stat, st); if (ret <= 0) { if (ret == 0) mail_set_expunged(mail); return -1; } } else { imail->mail.stats_stat_lookup_count++; path = maildir_save_file_get_path(mail->transaction, mail->seq); if (stat(path, st) < 0) { mail_storage_set_critical(mail->box->storage, "stat(%s) failed: %m", path); return -1; } } return 0; } static int maildir_mail_get_received_date(struct mail *_mail, time_t *date_r) { struct index_mail *mail = (struct index_mail *)_mail; struct index_mail_data *data = &mail->data; struct stat st; if (index_mail_get_received_date(_mail, date_r) == 0) return 0; if (maildir_mail_stat(_mail, &st) < 0) return -1; *date_r = data->received_date = st.st_mtime; return 0; } static int maildir_mail_get_save_date(struct mail *_mail, time_t *date_r) { struct index_mail *mail = (struct index_mail *)_mail; struct index_mail_data *data = &mail->data; struct stat st; if (index_mail_get_save_date(_mail, date_r) == 0) return 0; if (maildir_mail_stat(_mail, &st) < 0) return -1; *date_r = data->save_date = st.st_ctime; return 0; } static int maildir_mail_get_fname(struct maildir_mailbox *mbox, struct mail *mail, const char **fname_r) { enum maildir_uidlist_rec_flag flags; struct mail_index_view *view; uint32_t seq; bool exists; int ret; ret = maildir_sync_lookup(mbox, mail->uid, &flags, fname_r); if (ret != 0) return ret; /* file exists in index file, but not in dovecot-uidlist anymore. */ mail_set_expunged(mail); /* one reason this could happen is if we delayed opening dovecot-uidlist and we're trying to open a mail that got recently expunged. Let's test this theory first: */ (void)mail_index_refresh(mbox->box.index); view = mail_index_view_open(mbox->box.index); exists = mail_index_lookup_seq(view, mail->uid, &seq); mail_index_view_close(&view); if (exists) { /* the message still exists in index. this means there's some kind of a desync, which doesn't get fixed if cur/ mtime is the same as in index. fix this by forcing a resync. */ (void)maildir_storage_sync_force(mbox, mail->uid); } return 0; } static int maildir_get_pop3_state(struct index_mail *mail) { struct mailbox *box = mail->mail.mail.box; struct index_mailbox_context *ibox = INDEX_STORAGE_CONTEXT(box); const struct mail_cache_field *fields; unsigned int i, count, psize_idx, vsize_idx; enum mail_cache_decision_type dec, vsize_dec; enum mail_fetch_field allowed_pop3_fields; bool not_pop3_only = FALSE; if (mail->pop3_state_set) return mail->pop3_state; /* if this mail itself has non-pop3 fields we know we're not pop3-only */ allowed_pop3_fields = MAIL_FETCH_FLAGS | MAIL_FETCH_STREAM_HEADER | MAIL_FETCH_STREAM_BODY | MAIL_FETCH_UIDL_FILE_NAME | MAIL_FETCH_VIRTUAL_SIZE; if (mail->wanted_headers != NULL || (mail->wanted_fields & ~allowed_pop3_fields) != 0) not_pop3_only = TRUE; /* get vsize decisions */ psize_idx = ibox->cache_fields[MAIL_CACHE_PHYSICAL_FULL_SIZE].idx; vsize_idx = ibox->cache_fields[MAIL_CACHE_VIRTUAL_FULL_SIZE].idx; if (not_pop3_only) { vsize_dec = mail_cache_field_get_decision(box->cache, vsize_idx); vsize_dec &= ~MAIL_CACHE_DECISION_FORCED; } else { /* also check if there are any non-[pv]size cached fields */ vsize_dec = MAIL_CACHE_DECISION_NO; fields = mail_cache_register_get_list(box->cache, pool_datastack_create(), &count); for (i = 0; i < count; i++) { dec = fields[i].decision & ~MAIL_CACHE_DECISION_FORCED; if (fields[i].idx == vsize_idx) vsize_dec = dec; else if (dec != MAIL_CACHE_DECISION_NO && fields[i].idx != psize_idx) not_pop3_only = TRUE; } } if (!not_pop3_only) { /* either nothing is cached, or only vsize is cached. */ mail->pop3_state = 1; } else if (vsize_dec != MAIL_CACHE_DECISION_YES && (box->flags & MAILBOX_FLAG_POP3_SESSION) == 0) { /* if virtual size isn't cached permanently, POP3 isn't being used */ mail->pop3_state = -1; } else { /* possibly a mixed pop3/imap */ mail->pop3_state = 0; } mail->pop3_state_set = TRUE; return mail->pop3_state; } static int maildir_quick_size_lookup(struct index_mail *mail, bool vsize, uoff_t *size_r) { struct mail *_mail = &mail->mail.mail; struct maildir_mailbox *mbox = (struct maildir_mailbox *)_mail->box; enum maildir_uidlist_rec_ext_key key; const char *path, *fname, *value; if (!_mail->saving) { if (maildir_mail_get_fname(mbox, _mail, &fname) <= 0) return -1; } else { path = maildir_save_file_get_path(_mail->transaction, _mail->seq); fname = strrchr(path, '/'); fname = fname != NULL ? fname + 1 : path; } /* size can be included in filename */ if (maildir_filename_get_size(fname, vsize ? MAILDIR_EXTRA_VIRTUAL_SIZE : MAILDIR_EXTRA_FILE_SIZE, size_r)) return 1; /* size can be included in uidlist entry */ if (!_mail->saving) { key = vsize ? MAILDIR_UIDLIST_REC_EXT_VSIZE : MAILDIR_UIDLIST_REC_EXT_PSIZE; value = maildir_uidlist_lookup_ext(mbox->uidlist, _mail->uid, key); if (value != NULL && str_to_uoff(value, size_r) == 0) return 1; } return 0; } static void maildir_handle_size_caching(struct index_mail *mail, bool quick_check, bool vsize) { struct mailbox *box = mail->mail.mail.box; struct maildir_mailbox *mbox = (struct maildir_mailbox *)box; enum mail_fetch_field field; uoff_t size; int pop3_state; field = vsize ? MAIL_FETCH_VIRTUAL_SIZE : MAIL_FETCH_PHYSICAL_SIZE; if ((mail->data.dont_cache_fetch_fields & field) != 0) return; if (quick_check && maildir_quick_size_lookup(mail, vsize, &size) > 0) { /* already in filename / uidlist. don't add it anywhere, including to the uidlist if it's already in filename. do some extra checks here to catch potential cache bugs. */ if (vsize && mail->data.virtual_size != size) { mail_cache_set_corrupted(box->cache, "Corrupted virtual size for uid=%u: " "%"PRIuUOFF_T" != %"PRIuUOFF_T, mail->mail.mail.uid, mail->data.virtual_size, size); mail->data.virtual_size = size; } else if (!vsize && mail->data.physical_size != size) { mail_cache_set_corrupted(box->cache, "Corrupted physical size for uid=%u: " "%"PRIuUOFF_T" != %"PRIuUOFF_T, mail->mail.mail.uid, mail->data.physical_size, size); mail->data.physical_size = size; } mail->data.dont_cache_fetch_fields |= field; return; } /* 1 = pop3-only, 0 = mixed, -1 = no pop3 */ pop3_state = maildir_get_pop3_state(mail); if (pop3_state >= 0 && mail->mail.mail.uid != 0) { /* if size is wanted permanently, store it to uidlist so that in case cache file gets lost we can get it quickly */ mail->data.dont_cache_fetch_fields |= field; size = vsize ? mail->data.virtual_size : mail->data.physical_size; maildir_uidlist_set_ext(mbox->uidlist, mail->mail.mail.uid, vsize ? MAILDIR_UIDLIST_REC_EXT_VSIZE : MAILDIR_UIDLIST_REC_EXT_PSIZE, dec2str(size)); } } static int maildir_mail_get_virtual_size(struct mail *_mail, uoff_t *size_r) { struct maildir_mailbox *mbox = (struct maildir_mailbox *)_mail->box; struct index_mail *mail = (struct index_mail *)_mail; struct index_mail_data *data = &mail->data; struct message_size hdr_size, body_size; struct istream *input; uoff_t old_offset; if (maildir_uidlist_is_read(mbox->uidlist) || (_mail->box->flags & MAILBOX_FLAG_POP3_SESSION) != 0) { /* try to get the size from uidlist. this is especially useful with pop3 to avoid unnecessarily opening the cache file. */ if (maildir_quick_size_lookup(mail, TRUE, &data->virtual_size) < 0) return -1; } if (data->virtual_size == (uoff_t)-1) { if (index_mail_get_cached_virtual_size(mail, size_r)) { i_assert(mail->data.virtual_size != (uoff_t)-1); maildir_handle_size_caching(mail, TRUE, TRUE); return 0; } if (maildir_quick_size_lookup(mail, TRUE, &data->virtual_size) < 0) return -1; } if (data->virtual_size != (uoff_t)-1) { data->dont_cache_fetch_fields |= MAIL_FETCH_VIRTUAL_SIZE; *size_r = data->virtual_size; return 0; } /* fallback to reading the file */ old_offset = data->stream == NULL ? 0 : data->stream->v_offset; if (mail_get_stream(_mail, &hdr_size, &body_size, &input) < 0) return -1; i_stream_seek(data->stream, old_offset); maildir_handle_size_caching(mail, FALSE, TRUE); *size_r = data->virtual_size; return 0; } static int maildir_mail_get_physical_size(struct mail *_mail, uoff_t *size_r) { struct index_mail *mail = (struct index_mail *)_mail; struct maildir_mailbox *mbox = (struct maildir_mailbox *)_mail->box; struct index_mail_data *data = &mail->data; struct stat st; const char *path; int ret; if (maildir_uidlist_is_read(mbox->uidlist) || (_mail->box->flags & MAILBOX_FLAG_POP3_SESSION) != 0) { /* try to get the size from uidlist (see virtual size above) */ if (maildir_quick_size_lookup(mail, FALSE, &data->physical_size) < 0) return -1; } if (data->physical_size == (uoff_t)-1) { if (index_mail_get_physical_size(_mail, size_r) == 0) { i_assert(mail->data.physical_size != (uoff_t)-1); maildir_handle_size_caching(mail, TRUE, FALSE); return 0; } if (maildir_quick_size_lookup(mail, FALSE, &data->physical_size) < 0) return -1; } if (data->physical_size != (uoff_t)-1) { data->dont_cache_fetch_fields |= MAIL_FETCH_PHYSICAL_SIZE; *size_r = data->physical_size; return 0; } if (!_mail->saving) { ret = maildir_file_do(mbox, _mail->uid, do_stat, &st); if (ret <= 0) { if (ret == 0) mail_set_expunged(_mail); return -1; } } else { /* saved mail which hasn't been committed yet */ path = maildir_save_file_get_path(_mail->transaction, _mail->seq); if (stat(path, &st) < 0) { mail_storage_set_critical(_mail->box->storage, "stat(%s) failed: %m", path); return -1; } } data->physical_size = st.st_size; maildir_handle_size_caching(mail, FALSE, FALSE); *size_r = st.st_size; return 0; } static int maildir_mail_get_special(struct mail *_mail, enum mail_fetch_field field, const char **value_r) { struct index_mail *mail = (struct index_mail *)_mail; struct maildir_mailbox *mbox = (struct maildir_mailbox *)_mail->box; const char *path, *fname = NULL, *end, *guid, *uidl; switch (field) { case MAIL_FETCH_GUID: /* use GUID from uidlist if it exists */ i_assert(!_mail->saving); /* first make sure that we have a refreshed uidlist */ if (maildir_mail_get_fname(mbox, _mail, &fname) <= 0) return -1; guid = maildir_uidlist_lookup_ext(mbox->uidlist, _mail->uid, MAILDIR_UIDLIST_REC_EXT_GUID); if (guid != NULL) { *value_r = p_strdup(mail->data_pool, guid); return 0; } /* default to base filename: */ case MAIL_FETCH_UIDL_FILE_NAME: if (mail->data.guid != NULL) { *value_r = mail->data.guid; return 0; } if (fname != NULL) { /* we came here from MAIL_FETCH_GUID, avoid a second lookup */ } else if (!_mail->saving) { if (maildir_mail_get_fname(mbox, _mail, &fname) <= 0) return -1; } else { path = maildir_save_file_get_path(_mail->transaction, _mail->seq); fname = strrchr(path, '/'); fname = fname != NULL ? fname + 1 : path; } end = strchr(fname, MAILDIR_INFO_SEP); mail->data.guid = end == NULL ? p_strdup(mail->data_pool, fname) : p_strdup_until(mail->data_pool, fname, end); *value_r = mail->data.guid; return 0; case MAIL_FETCH_UIDL_BACKEND: uidl = maildir_uidlist_lookup_ext(mbox->uidlist, _mail->uid, MAILDIR_UIDLIST_REC_EXT_POP3_UIDL); if (uidl == NULL) { /* use the default */ *value_r = ""; } else if (*uidl == '\0') { /* special optimization case: use the base file name */ return maildir_mail_get_special(_mail, MAIL_FETCH_UIDL_FILE_NAME, value_r); } else { *value_r = p_strdup(mail->data_pool, uidl); } return 0; default: return index_mail_get_special(_mail, field, value_r); } } static int maildir_mail_get_stream(struct mail *_mail, struct message_size *hdr_size, struct message_size *body_size, struct istream **stream_r) { struct index_mail *mail = (struct index_mail *)_mail; struct maildir_mailbox *mbox = (struct maildir_mailbox *)_mail->box; struct index_mail_data *data = &mail->data; bool deleted; if (data->stream == NULL) { data->stream = maildir_open_mail(mbox, _mail, &deleted); if (data->stream == NULL) { if (deleted) mail_set_expunged(_mail); return -1; } if (mail->mail.v.istream_opened != NULL) { if (mail->mail.v.istream_opened(_mail, &data->stream) < 0) return -1; } } return index_mail_init_stream(mail, hdr_size, body_size, stream_r); } static void maildir_update_pop3_uidl(struct mail *_mail, const char *uidl) { struct maildir_mailbox *mbox = (struct maildir_mailbox *)_mail->box; const char *fname; if (maildir_mail_get_special(_mail, MAIL_FETCH_UIDL_FILE_NAME, &fname) == 0 && strcmp(uidl, fname) == 0) { /* special case optimization: empty UIDL means the same as base filename */ uidl = ""; } maildir_uidlist_set_ext(mbox->uidlist, _mail->uid, MAILDIR_UIDLIST_REC_EXT_POP3_UIDL, uidl); } static void maildir_mail_set_cache_corrupted(struct mail *_mail, enum mail_fetch_field field) { struct maildir_mailbox *mbox = (struct maildir_mailbox *)_mail->box; enum maildir_uidlist_rec_flag flags; const char *fname; uoff_t size; int ret; if (field == MAIL_FETCH_VIRTUAL_SIZE) { /* make sure it gets removed from uidlist. if it's in file name, we can't really do more than log it. */ ret = maildir_sync_lookup(mbox, _mail->uid, &flags, &fname); if (ret <= 0) return; if (maildir_filename_get_size(fname, MAILDIR_EXTRA_VIRTUAL_SIZE, &size)) { const char *subdir = (flags & MAILDIR_UIDLIST_REC_FLAG_NEW_DIR) != 0 ? "new" : "cur"; mail_storage_set_critical(_mail->box->storage, "Maildir filename has wrong W value: %s/%s/%s", mailbox_get_path(&mbox->box), subdir, fname); } else if (maildir_uidlist_lookup_ext(mbox->uidlist, _mail->uid, MAILDIR_UIDLIST_REC_EXT_VSIZE) != NULL) { maildir_uidlist_set_ext(mbox->uidlist, _mail->uid, MAILDIR_UIDLIST_REC_EXT_VSIZE, NULL); } } index_mail_set_cache_corrupted(_mail, field); } struct mail_vfuncs maildir_mail_vfuncs = { index_mail_close, index_mail_free, index_mail_set_seq, index_mail_set_uid, index_mail_set_uid_cache_updates, index_mail_prefetch, index_mail_get_flags, index_mail_get_keywords, index_mail_get_keyword_indexes, index_mail_get_modseq, index_mail_get_parts, index_mail_get_date, maildir_mail_get_received_date, maildir_mail_get_save_date, maildir_mail_get_virtual_size, maildir_mail_get_physical_size, index_mail_get_first_header, index_mail_get_headers, index_mail_get_header_stream, maildir_mail_get_stream, maildir_mail_get_special, index_mail_get_real_mail, index_mail_update_flags, index_mail_update_keywords, index_mail_update_modseq, maildir_update_pop3_uidl, index_mail_expunge, maildir_mail_set_cache_corrupted, index_mail_opened };