maildir-mail.c revision 8b58939517a381db55670089c0984da39fc0f099
c25356d5978632df6203437e1953bcb29e0c736fTimo Sirainen/* Copyright (c) 2003-2007 Dovecot authors, see the included COPYING file */
c25356d5978632df6203437e1953bcb29e0c736fTimo Sirainen
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen#include "lib.h"
be5c76fabc7439fd33bc799bc3ab3f570799977bTimo Sirainen#include "istream.h"
be5c76fabc7439fd33bc799bc3ab3f570799977bTimo Sirainen#include "index-mail.h"
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen#include "maildir-storage.h"
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen#include "maildir-filename.h"
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen#include "maildir-uidlist.h"
1e21e6be70994b1aa9e52ca0e2f51afefca6d0dfTimo Sirainen
04052d7cacaa866a3f00afb4e104fa46c04c1dd7Timo Sirainen#include <stdlib.h>
1e21e6be70994b1aa9e52ca0e2f51afefca6d0dfTimo Sirainen#include <fcntl.h>
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen#include <unistd.h>
86bea1f8bffc2d98196f8655eecea9174c4f458aTimo Sirainen#include <sys/stat.h>
86bea1f8bffc2d98196f8655eecea9174c4f458aTimo Sirainen
86bea1f8bffc2d98196f8655eecea9174c4f458aTimo Sirainenstatic int
86bea1f8bffc2d98196f8655eecea9174c4f458aTimo Sirainendo_open(struct maildir_mailbox *mbox, const char *path, int *fd)
86bea1f8bffc2d98196f8655eecea9174c4f458aTimo Sirainen{
86bea1f8bffc2d98196f8655eecea9174c4f458aTimo Sirainen *fd = open(path, O_RDONLY);
6e5a4cdf7ef123589e2409e0012b1024c97957d5Aki Tuomi if (*fd != -1)
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen return 1;
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen if (errno == ENOENT)
b82474d60c15409eda71c55971710fd3b12b8a0fTimo Sirainen return 0;
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen mail_storage_set_critical(&mbox->storage->storage,
7d6389e4053c2dac1fb37180b5756b00785983dcTimo Sirainen "open(%s) failed: %m", path);
7d6389e4053c2dac1fb37180b5756b00785983dcTimo Sirainen return -1;
f968e62caa52a8924bd05ebf76ff515b5c18e17bTimo Sirainen}
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainenstatic int
7d6389e4053c2dac1fb37180b5756b00785983dcTimo Sirainendo_stat(struct maildir_mailbox *mbox, const char *path, struct stat *st)
484e12acec34f16e5a8adc001e23ae48f1dda8c7Timo Sirainen{
35136dd2baf8dc30e4e754294ed81ff48e8c1e64Timo Sirainen if (stat(path, st) == 0)
22535a9e685e29214082878e37a267157044618eTimo Sirainen return 1;
b321df9603081896b70ec44635af96d674a9839aTimo Sirainen if (errno == ENOENT)
b321df9603081896b70ec44635af96d674a9839aTimo Sirainen return 0;
b321df9603081896b70ec44635af96d674a9839aTimo Sirainen
849969f639a00eab26791db3cb1b66430420c0cdTimo Sirainen mail_storage_set_critical(&mbox->storage->storage,
f3d506e525a720f214020ca0f989a1966b30edaeTimo Sirainen "stat(%s) failed: %m", path);
b321df9603081896b70ec44635af96d674a9839aTimo Sirainen return -1;
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen}
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen
7d6389e4053c2dac1fb37180b5756b00785983dcTimo Sirainenstatic struct istream *
7d6389e4053c2dac1fb37180b5756b00785983dcTimo Sirainenmaildir_open_mail(struct maildir_mailbox *mbox, struct mail *mail,
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen bool *deleted_r)
a3dd97fb6d92a89c3de0597fed2d4b044c7aeb84Timo Sirainen{
a3dd97fb6d92a89c3de0597fed2d4b044c7aeb84Timo Sirainen const char *path;
7d6389e4053c2dac1fb37180b5756b00785983dcTimo Sirainen int fd = -1;
7d6389e4053c2dac1fb37180b5756b00785983dcTimo Sirainen
35136dd2baf8dc30e4e754294ed81ff48e8c1e64Timo Sirainen *deleted_r = FALSE;
35136dd2baf8dc30e4e754294ed81ff48e8c1e64Timo Sirainen
adea1e1e46ccb4ae107767fd930e3d1fb4f1d11dTimo Sirainen if (mail->uid != 0) {
adea1e1e46ccb4ae107767fd930e3d1fb4f1d11dTimo Sirainen if (maildir_file_do(mbox, mail->uid, do_open, &fd) < 0)
adea1e1e46ccb4ae107767fd930e3d1fb4f1d11dTimo Sirainen return NULL;
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen } else {
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen path = maildir_save_file_get_path(mail->transaction, mail->seq);
25ee72451d16374ed27fdbf829f4ec756c778352Timo Sirainen if (do_open(mbox, path, &fd) <= 0)
f3d506e525a720f214020ca0f989a1966b30edaeTimo Sirainen return NULL;
74674a53a72dab535c61f455b2246ef2797844eaTimo Sirainen }
74674a53a72dab535c61f455b2246ef2797844eaTimo Sirainen
74674a53a72dab535c61f455b2246ef2797844eaTimo Sirainen if (fd == -1) {
25ee72451d16374ed27fdbf829f4ec756c778352Timo Sirainen *deleted_r = TRUE;
1ae87afde32c1ac73909dfacfd59641b470a3e93Martti Rannanjärvi return NULL;
25ee72451d16374ed27fdbf829f4ec756c778352Timo Sirainen }
10f6f2224c897fc543973efd2f46b86a3ab1148dAki Tuomi
10f6f2224c897fc543973efd2f46b86a3ab1148dAki Tuomi return i_stream_create_fd(fd, MAIL_READ_BLOCK_SIZE, TRUE);
10f6f2224c897fc543973efd2f46b86a3ab1148dAki Tuomi}
25ee72451d16374ed27fdbf829f4ec756c778352Timo Sirainen
25ee72451d16374ed27fdbf829f4ec756c778352Timo Sirainenstatic int maildir_mail_stat(struct mail *mail, struct stat *st)
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainen{
3cf67672fdc87583cb23ce088c95bb5dee60e74dTimo Sirainen struct maildir_mailbox *mbox = (struct maildir_mailbox *)mail->box;
3cf67672fdc87583cb23ce088c95bb5dee60e74dTimo Sirainen struct index_mail_data *data = &((struct index_mail *)mail)->data;
25ee72451d16374ed27fdbf829f4ec756c778352Timo Sirainen const char *path;
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen int fd, ret;
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen
e48d89622047bd8bbd0475b881ca9377d592f535Timo Sirainen if (data->access_part != 0 && data->stream == NULL) {
2d8f66596f445dd8b399b7032c3f0e9202015b63Timo Sirainen /* we're going to open the mail anyway */
2d8f66596f445dd8b399b7032c3f0e9202015b63Timo Sirainen struct istream *input;
2d8f66596f445dd8b399b7032c3f0e9202015b63Timo Sirainen
04052d7cacaa866a3f00afb4e104fa46c04c1dd7Timo Sirainen (void)mail_get_stream(mail, NULL, NULL, &input);
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen }
25ee72451d16374ed27fdbf829f4ec756c778352Timo Sirainen
25ee72451d16374ed27fdbf829f4ec756c778352Timo Sirainen if (data->stream != NULL) {
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen fd = i_stream_get_fd(data->stream);
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen i_assert(fd != -1);
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen if (fstat(fd, st) < 0) {
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen mail_storage_set_critical(&mbox->storage->storage,
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen "fstat(maildir) failed: %m");
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen return -1;
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen }
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen } else if (mail->uid != 0) {
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen ret = maildir_file_do(mbox, mail->uid, do_stat, st);
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen if (ret <= 0) {
a3dd97fb6d92a89c3de0597fed2d4b044c7aeb84Timo Sirainen if (ret == 0)
f968e62caa52a8924bd05ebf76ff515b5c18e17bTimo Sirainen mail_set_expunged(mail);
dc9bfb7dc057964238e181d3d8b08751527bb08aTimo Sirainen return -1;
473080c7c0d25ddfdf77e7dfa0ba8f73c6c669d5Timo Sirainen }
f968e62caa52a8924bd05ebf76ff515b5c18e17bTimo Sirainen } else {
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen path = maildir_save_file_get_path(mail->transaction, mail->seq);
e48d89622047bd8bbd0475b881ca9377d592f535Timo Sirainen if (stat(path, st) < 0) {
04052d7cacaa866a3f00afb4e104fa46c04c1dd7Timo Sirainen mail_storage_set_critical(mail->box->storage,
f3d506e525a720f214020ca0f989a1966b30edaeTimo Sirainen "stat(%s) failed: %m", path);
849969f639a00eab26791db3cb1b66430420c0cdTimo Sirainen return -1;
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen }
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen }
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen return 0;
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen}
9625595c47c665f5aee57ebfcb1fcbe9ad1bf3a0Martti Rannanjärvi
be5c76fabc7439fd33bc799bc3ab3f570799977bTimo Sirainenstatic int maildir_mail_get_received_date(struct mail *_mail, time_t *date_r)
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen{
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen struct index_mail *mail = (struct index_mail *)_mail;
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen struct index_mail_data *data = &mail->data;
1e21e6be70994b1aa9e52ca0e2f51afefca6d0dfTimo Sirainen struct stat st;
1e21e6be70994b1aa9e52ca0e2f51afefca6d0dfTimo Sirainen
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen 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 data->save_date;
}
static bool
maildir_mail_get_fname(struct maildir_mailbox *mbox, struct mail *mail,
const char **fname_r)
{
enum maildir_uidlist_rec_flag flags;
*fname_r = maildir_uidlist_lookup(mbox->uidlist, mail->uid, &flags);
if (*fname_r == NULL) {
mail_set_expunged(mail);
return FALSE;
}
return TRUE;
}
static int maildir_get_pop3_state(struct index_mail *mail)
{
const struct mail_cache_field *fields;
unsigned int i, count, 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 decision */
vsize_idx = mail->ibox->cache_fields[MAIL_CACHE_VIRTUAL_FULL_SIZE].idx;
if (not_pop3_only) {
vsize_dec = mail_cache_field_get_decision(mail->ibox->cache,
vsize_idx);
vsize_dec &= ~MAIL_CACHE_DECISION_FORCED;
} else {
/* also check if there are any non-vsize cached fields */
vsize_dec = MAIL_CACHE_DECISION_NO;
fields = mail_cache_register_get_list(mail->ibox->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)
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) {
/* 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_virtual_size_lookup(struct index_mail *mail)
{
struct mail *_mail = &mail->mail.mail;
struct maildir_mailbox *mbox = (struct maildir_mailbox *)mail->ibox;
struct index_mail_data *data = &mail->data;
const char *path, *fname, *value;
uoff_t size;
char *p;
if (_mail->uid != 0) {
if (!maildir_mail_get_fname(mbox, _mail, &fname))
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, MAILDIR_EXTRA_VIRTUAL_SIZE,
&data->virtual_size))
return 1;
/* size can be included in uidlist entry */
if (_mail->uid != 0) {
value = maildir_uidlist_lookup_ext(mbox->uidlist, _mail->uid,
MAILDIR_UIDLIST_REC_EXT_VSIZE);
if (value != NULL) {
size = strtoull(value, &p, 10);
if (*p == '\0') {
data->virtual_size = size;
return 1;
}
}
}
return 0;
}
static void
maildir_handle_virtual_size_caching(struct index_mail *mail, bool quick_check)
{
struct maildir_mailbox *mbox = (struct maildir_mailbox *)mail->ibox;
int pop3_state;
i_assert(mail->data.virtual_size != (uoff_t)-1);
if ((mail->data.dont_cache_fetch_fields & MAIL_FETCH_VIRTUAL_SIZE) != 0)
return;
if (quick_check && maildir_quick_virtual_size_lookup(mail) > 0)
mail->data.dont_cache_fetch_fields |= MAIL_FETCH_VIRTUAL_SIZE;
/* 1 = pop3-only, 0 = mixed, -1 = no pop3 */
pop3_state = maildir_get_pop3_state(mail);
if (pop3_state >= 0) {
/* if virtual 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 |= MAIL_FETCH_VIRTUAL_SIZE;
maildir_uidlist_set_ext(mbox->uidlist, mail->mail.mail.uid,
MAILDIR_UIDLIST_REC_EXT_VSIZE,
dec2str(mail->data.virtual_size));
}
}
static int maildir_mail_get_virtual_size(struct mail *_mail, uoff_t *size_r)
{
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 (index_mail_get_cached_virtual_size(mail, size_r)) {
maildir_handle_virtual_size_caching(mail, TRUE);
return 0;
}
if (maildir_quick_virtual_size_lookup(mail) < 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_virtual_size_caching(mail, FALSE);
*size_r = data->virtual_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->ibox;
const char *path, *fname, *end;
if (field == MAIL_FETCH_UIDL_FILE_NAME) {
if (_mail->uid != 0) {
if (!maildir_mail_get_fname(mbox, _mail, &fname))
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);
*value_r = end == NULL ? fname : t_strdup_until(fname, end);
return 0;
}
return index_mail_get_special(_mail, field, value_r);
}
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->ibox;
struct index_mail_data *data = &mail->data;
struct stat st;
const char *path, *fname;
uoff_t size;
int ret;
if (index_mail_get_physical_size(_mail, size_r) == 0)
return 0;
if (_mail->uid != 0) {
if (!maildir_mail_get_fname(mbox, _mail, &fname))
return -1;
path = NULL;
} 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, MAILDIR_EXTRA_FILE_SIZE, &size)) {
/* since the size is already in the file name, don't bother
adding it to cache file */
data->dont_cache_fetch_fields |= MAIL_FETCH_PHYSICAL_SIZE;
} else {
if (_mail->uid != 0) {
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 */
if (stat(path, &st) < 0) {
mail_storage_set_critical(_mail->box->storage,
"stat(%s) failed: %m", path);
return -1;
}
}
size = st.st_size;
}
data->physical_size = size;
*size_r = size;
return 0;
}
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->ibox;
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;
}
}
return index_mail_init_stream(mail, hdr_size, body_size, stream_r);
}
struct mail_vfuncs maildir_mail_vfuncs = {
index_mail_close,
index_mail_free,
index_mail_set_seq,
index_mail_set_uid,
index_mail_get_flags,
index_mail_get_keywords,
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_update_flags,
index_mail_update_keywords,
index_mail_expunge
};