Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117878127
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
120 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/imap/mailbox.c b/imap/mailbox.c
index 7d81aa612..045fa238d 100644
--- a/imap/mailbox.c
+++ b/imap/mailbox.c
@@ -1,4247 +1,4247 @@
/* mailbox.c -- Mailbox manipulation routines
*
* Copyright (c) 1994-2008 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any legal
* details, please contact
* Carnegie Mellon University
* Center for Technology Transfer and Enterprise Creation
* 4615 Forbes Avenue
* Suite 302
* Pittsburgh, PA 15213
* (412) 268-7393, fax: (412) 268-7395
* innovation@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by Computing Services
* at Carnegie Mellon University (http://www.cmu.edu/computing/)."
*
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* $Id: mailbox.c,v 1.201 2010/06/28 12:04:20 brong Exp $
*/
#include <config.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <utime.h>
#ifdef HAVE_DIRENT_H
# include <dirent.h>
# define NAMLEN(dirent) strlen((dirent)->d_name)
#else
# define dirent direct
# define NAMLEN(dirent) (dirent)->d_namlen
# if HAVE_SYS_NDIR_H
# include <sys/ndir.h>
# endif
# if HAVE_SYS_DIR_H
# include <sys/dir.h>
# endif
# if HAVE_NDIR_H
# include <ndir.h>
# endif
#endif
#ifdef HAVE_LIBUUID
#include <uuid/uuid.h>
#endif
#include "acl.h"
#include "assert.h"
#include "crc32.h"
#include "exitcodes.h"
#include "global.h"
#include "imap_err.h"
#include "imparse.h"
#include "cyr_lock.h"
#include "mailbox.h"
#include "message.h"
#include "map.h"
#include "mboxlist.h"
#include "annotate.h"
#include "retry.h"
#include "seen.h"
#include "upgrade_index.h"
#include "util.h"
#include "annotate.h"
#include "sequence.h"
#include "statuscache.h"
#include "strarray.h"
#include "sync_log.h"
#include "xmalloc.h"
#include "xstrlcpy.h"
#include "xstrlcat.h"
struct mailboxlist {
struct mailboxlist *next;
struct mailbox m;
struct mboxlock *l;
int nopen;
};
static struct mailboxlist *open_mailboxes = NULL;
#define zeromailbox(m) { memset(&m, 0, sizeof(struct mailbox)); \
(m).index_fd = -1; \
(m).cache_fd = -1; \
(m).header_fd = -1; }
static int mailbox_index_unlink(struct mailbox *mailbox);
static int mailbox_index_repack(struct mailbox *mailbox);
static struct mailboxlist *create_listitem(const char *name)
{
struct mailboxlist *item = xmalloc(sizeof(struct mailboxlist));
item->next = open_mailboxes;
open_mailboxes = item;
item->nopen = 1;
item->l = NULL;
zeromailbox(item->m);
item->m.name = xstrdup(name);
return item;
}
static struct mailboxlist *find_listitem(const char *name)
{
struct mailboxlist *item;
for (item = open_mailboxes; item; item = item->next) {
if (!strcmp(name, item->m.name))
return item;
}
return NULL;
}
static void remove_listitem(struct mailboxlist *remitem)
{
struct mailboxlist *item;
struct mailboxlist *previtem = NULL;
for (item = open_mailboxes; item; item = item->next) {
if (item == remitem) {
if (previtem)
previtem->next = item->next;
else
open_mailboxes = item->next;
free(item);
return;
}
previtem = item;
}
fatal("didn't find item in list", EC_SOFTWARE);
}
char *mailbox_meta_fname(struct mailbox *mailbox, int metafile)
{
static char fnamebuf[MAX_MAILBOX_PATH];
const char *src;
src = mboxname_metapath(mailbox->part, mailbox->name, metafile, 0);
if (!src) return NULL;
strncpy(fnamebuf, src, MAX_MAILBOX_PATH);
return fnamebuf;
}
char *mailbox_meta_newfname(struct mailbox *mailbox, int metafile)
{
static char fnamebuf[MAX_MAILBOX_PATH];
const char *src;
src = mboxname_metapath(mailbox->part, mailbox->name, metafile, 1);
if (!src) return NULL;
strncpy(fnamebuf, src, MAX_MAILBOX_PATH);
return fnamebuf;
}
int mailbox_meta_rename(struct mailbox *mailbox, int metafile)
{
char *fname = mailbox_meta_fname(mailbox, metafile);
char *newfname = mailbox_meta_newfname(mailbox, metafile);
if (rename(newfname, fname))
return IMAP_IOERROR;
return 0;
}
char *mailbox_message_fname(struct mailbox *mailbox, unsigned long uid)
{
static char localbuf[MAX_MAILBOX_PATH];
const char *src;
src = mboxname_datapath(mailbox->part, mailbox->name, uid);
if (!src) return NULL;
strncpy(localbuf, src, MAX_MAILBOX_PATH);
return localbuf;
}
char *mailbox_datapath(struct mailbox *mailbox)
{
static char localbuf[MAX_MAILBOX_PATH];
const char *src;
src = mboxname_datapath(mailbox->part, mailbox->name, 0);
if (!src) return NULL;
strncpy(localbuf, src, MAX_MAILBOX_PATH);
return localbuf;
}
/*
* Names of the headers we cache in the cyrus.cache file.
*
* Changes to this list probably require bumping the cache version
* number (obviously)
*
* note that header names longer than MAX_CACHED_HEADER_SIZE
* won't be cached regardless
*
* xxx can we get benefits by requireing this list to be sorted?
* (see is_cached_header())
*
*/
const struct mailbox_header_cache mailbox_cache_headers[] = {
/* things we have always cached */
{ "priority", 0 },
{ "references", 0 },
{ "resent-from", 0 },
{ "newsgroups", 0 },
{ "followup-to", 0 },
/* x headers that we may want to cache anyway */
{ "x-mailer", 1 },
{ "x-trace", 1 },
/* outlook express seems to want these */
{ "x-ref", 2 },
{ "x-priority", 2 },
{ "x-msmail-priority", 2 },
{ "x-msoesrec", 2 },
/* for efficient FastMail interface display */
{ "x-spam-score", 3 },
{ "x-spam-hits", 3 },
{ "x-spam-source", 3 },
{ "x-resolved-to", 3 },
{ "x-delivered-to", 3 },
{ "x-mail-from", 3 },
{ "x-truedomain", 3 },
{ "x-truedomain-dkim", 3 },
{ "x-truedomain-spf", 3 },
{ "x-truedomain-domain", 3 },
/* things to never cache */
{ "bcc", BIT32_MAX },
{ "cc", BIT32_MAX },
{ "date", BIT32_MAX },
{ "delivery-date", BIT32_MAX },
{ "envelope-to", BIT32_MAX },
{ "from", BIT32_MAX },
{ "in-reply-to", BIT32_MAX },
{ "mime-version", BIT32_MAX },
{ "reply-to", BIT32_MAX },
{ "received", BIT32_MAX },
{ "return-path", BIT32_MAX },
{ "sender", BIT32_MAX },
{ "subject", BIT32_MAX },
{ "to", BIT32_MAX },
/* signatures tend to be large, and are useless without the body */
{ "dkim-signature", BIT32_MAX },
{ "domainkey-signature", BIT32_MAX },
{ "domainkey-x509", BIT32_MAX },
/* older versions of PINE (before 4.56) need message-id in the cache too
* though technically it is a waste of space because it is in
* ENVELOPE. We should probably uncomment the following at some
* future point [ken3 notes this may also be useful to have here for
* threading so we can avoid parsing the envelope] */
/* { "message-id", BIT32_MAX }, */
};
const int MAILBOX_NUM_CACHE_HEADERS =
sizeof(mailbox_cache_headers)/sizeof(struct mailbox_header_cache);
/*
* Function to test if a header is in the cache
*
* Assume cache entry version 1, unless other data is found
* in the table.
*/
static inline unsigned is_cached_header(const char *hdr)
{
int i;
/* xxx if we can sort the header list we can do better here */
for (i=0; i<MAILBOX_NUM_CACHE_HEADERS; i++) {
if (!strcmp(mailbox_cache_headers[i].name, hdr))
return mailbox_cache_headers[i].min_cache_version;
}
/* Don't Cache X- headers unless explicitly configured to*/
if ((hdr[0] == 'x') && (hdr[1] == '-')) return BIT32_MAX;
/* Everything else we cache in version 1 */
return 1;
}
/* External API to is_cached_header that prepares the string
*
* Returns minimum version required for lookup to succeed
* or BIT32_MAX if header not cached
*/
unsigned mailbox_cached_header(const char *s)
{
char hdr[MAX_CACHED_HEADER_SIZE];
int i;
/* Generate lower case copy of string */
/* xxx sometimes the caller has already generated this ..
* maybe we can just require callers to do it? */
for (i=0 ; *s && (i < (MAX_CACHED_HEADER_SIZE - 1)) ; i++)
hdr[i] = tolower(*s++);
if (*s) return BIT32_MAX; /* Input too long for match */
hdr[i] = '\0';
return is_cached_header(hdr);
}
/* Same as mailbox_cached_header, but for use on a header
* as it appears in the message (i.e. :-terminated, not NUL-terminated)
*/
unsigned mailbox_cached_header_inline(const char *text)
{
char buf[MAX_CACHED_HEADER_SIZE];
int i;
/* Scan for header */
for (i=0; i < (MAX_CACHED_HEADER_SIZE - 1); i++) {
if (!text[i] || text[i] == '\r' || text[i] == '\n') break;
if (text[i] == ':') {
buf[i] = '\0';
return is_cached_header(buf);
} else {
buf[i] = tolower(text[i]);
}
}
return BIT32_MAX;
}
const char *cache_base(struct index_record *record)
{
const char *base = record->crec.base->s;
return base + record->crec.offset;
}
unsigned cache_size(struct index_record *record)
{
return record->crec.len;
}
struct buf *cache_buf(struct index_record *record)
{
static struct buf staticbuf;
buf_init_ro(&staticbuf,
cache_base(record),
cache_size(record));
return &staticbuf;
}
const char *cacheitem_base(struct index_record *record, int field)
{
const char *base = record->crec.base->s;
return base + record->crec.item[field].offset;
}
unsigned cacheitem_size(struct index_record *record, int field)
{
return record->crec.item[field].len;
}
struct buf *cacheitem_buf(struct index_record *record, int field)
{
static struct buf staticbuf;
buf_init_ro(&staticbuf,
cacheitem_base(record, field),
cacheitem_size(record, field));
return &staticbuf;
}
/* parse a single cache record from the mapped file - creates buf
* records which point into the map, so you can't free it while
* you still have them around! */
int cache_parserecord(struct buf *cachebase, unsigned cache_offset,
struct cacherecord *crec)
{
unsigned cache_ent;
unsigned offset;
const char *cacheitem, *next;
offset = cache_offset;
if (offset >= cachebase->len) {
syslog(LOG_ERR, "IOERROR: offset greater than cache size");
return IMAP_IOERROR;
}
for (cache_ent = 0; cache_ent < NUM_CACHE_FIELDS; cache_ent++) {
cacheitem = cachebase->s + offset;
/* copy locations */
crec->item[cache_ent].len = CACHE_ITEM_LEN(cacheitem);
crec->item[cache_ent].offset = offset + CACHE_ITEM_SIZE_SKIP;
/* moving on */
next = CACHE_ITEM_NEXT(cacheitem);
if (next < cacheitem) {
syslog(LOG_ERR, "IOERROR: cache offset negative");
return IMAP_IOERROR;
}
offset = next - cachebase->s;
if (offset > cachebase->len) {
syslog(LOG_ERR, "IOERROR: offset greater than cache size");
return IMAP_IOERROR;
}
}
/* all fit within the cache, it's gold as far as we can tell */
crec->base = cachebase;
crec->len = offset - cache_offset;
crec->offset = cache_offset;
return 0;
}
int mailbox_open_cache(struct mailbox *mailbox)
{
struct stat sbuf;
unsigned generation;
int retry = 0;
int openflags = mailbox->is_readonly ? O_RDONLY : O_RDWR;
/* already got everything? great */
if (mailbox->cache_fd != -1 && !mailbox->need_cache_refresh)
return 0;
retry:
/* open the file */
if (mailbox->cache_fd == -1) {
char *fname;
/* it's bogus to be dirty here */
if (mailbox->cache_dirty)
abort();
fname = mailbox_meta_fname(mailbox, META_CACHE);
mailbox->cache_fd = open(fname, openflags, 0);
if (mailbox->cache_fd == -1)
goto fail;
}
/* get the size and inode */
if (fstat(mailbox->cache_fd, &sbuf) == -1) {
syslog(LOG_ERR, "IOERROR: fstating cache %s: %m", mailbox->name);
goto fail;
}
mailbox->cache_buf.len = sbuf.st_size;
if (mailbox->cache_buf.len < 4)
goto fail;
map_refresh(mailbox->cache_fd, 0, (const char **)&mailbox->cache_buf.s,
&mailbox->cache_len, mailbox->cache_buf.len, "cache",
mailbox->name);
generation = ntohl(*((bit32 *)(mailbox->cache_buf.s)));
if (generation < mailbox->i.generation_no && !retry) {
/* try a rename - maybe we got killed between renames in repack */
map_free((const char **)&mailbox->cache_buf.s, &mailbox->cache_len);
close(mailbox->cache_fd);
mailbox->cache_fd = -1;
syslog(LOG_NOTICE, "WARNING: trying to rename cache file %s (%d < %d)",
mailbox->name, generation, mailbox->i.generation_no);
mailbox_meta_rename(mailbox, META_CACHE);
retry = 1;
goto retry;
}
if (generation != mailbox->i.generation_no) {
map_free((const char **)&mailbox->cache_buf.s, &mailbox->cache_len);
goto fail;
}
mailbox->need_cache_refresh = 0;
return 0;
fail:
/* rebuild the cache from scratch! */
syslog(LOG_ERR, "IOERROR: %s failed to open cache - rebuilding",
mailbox->name);
{
struct index_record record;
const char *fname;
uint32_t recno;
uint32_t offset;
uint32_t gen;
/* make sure we have a file */
if (mailbox->cache_fd == -1) {
fname = mailbox_meta_fname(mailbox, META_CACHE);
mailbox->cache_fd = open(fname, O_RDWR|O_TRUNC|O_CREAT, 0666);
}
/* update the generation number */
gen = htonl(mailbox->i.generation_no);
retry_write(mailbox->cache_fd, (char *)&gen, 4);
for (recno = 1; recno <= mailbox->i.num_records; recno++) {
if (mailbox_read_index_record(mailbox, recno, &record))
continue;
if (record.system_flags & FLAG_UNLINKED)
continue;
fname = mailbox_message_fname(mailbox, record.uid);
offset = record.cache_offset; /* gets overwritten by parse */
if (message_parse(fname, &record))
continue;
lseek(mailbox->cache_fd, offset, SEEK_SET);
retry_write(mailbox->cache_fd, cache_base(&record),
cache_size(&record));
}
(void)fsync(mailbox->cache_fd);
/* get the size and inode */
fstat(mailbox->cache_fd, &sbuf);
mailbox->cache_buf.len = sbuf.st_size;
map_refresh(mailbox->cache_fd, 0, (const char **)&mailbox->cache_buf.s,
&mailbox->cache_len, mailbox->cache_buf.len, "cache",
mailbox->name);
}
mailbox->need_cache_refresh = 0;
return 0;
}
int mailbox_index_islocked(struct mailbox *mailbox, int write)
{
if (mailbox->index_locktype == LOCK_EXCLUSIVE) return 1;
if (mailbox->index_locktype == LOCK_SHARED && !write) return 1;
return 0;
}
/* return the offset for the start of the record! */
int mailbox_append_cache(struct mailbox *mailbox,
struct index_record *record)
{
int r;
assert(mailbox_index_islocked(mailbox, 1));
/* no cache content */
if (!record->crec.len)
return 0;
/* already been written */
if (record->cache_offset)
return 0;
/* ensure we have a cache fd */
r = mailbox_open_cache(mailbox);
if (r) {
syslog(LOG_ERR, "Failed to open cache to %s for %u",
mailbox->name, record->uid);
return r; /* unable to append */
}
r = cache_append_record(mailbox->cache_fd, record);
if (r) {
syslog(LOG_ERR, "Failed to append cache to %s for %u",
mailbox->name, record->uid);
return r;
}
mailbox->cache_dirty = 1;
mailbox->need_cache_refresh = 1;
return 0;
}
int mailbox_cacherecord(struct mailbox *mailbox,
struct index_record *record)
{
uint32_t crc;
int r = 0;
/* do we already have a record loaded? */
if (record->crec.len)
return 0;
if (!record->cache_offset)
r = IMAP_IOERROR;
if (r) goto done;
r = mailbox_open_cache(mailbox);
if (r) goto done;
/* try to parse the cache record */
r = cache_parserecord(&mailbox->cache_buf,
record->cache_offset, &record->crec);
if (r) goto done;
crc = crc32_buf(cache_buf(record));
if (crc != record->cache_crc)
r = IMAP_MAILBOX_CHECKSUM;
done:
if (r)
syslog(LOG_ERR, "IOERROR: invalid cache record for %s uid %u (%s)",
mailbox->name, record->uid, error_message(r));
return r;
}
int cache_append_record(int fd, struct index_record *record)
{
unsigned offset;
unsigned size = cache_size(record);
int n;
/* no parsed cache present */
if (!record->crec.len)
return 0;
/* cache offset already there - probably already been written */
if (record->cache_offset)
return 0;
if (record->cache_crc != crc32_buf(cache_buf(record)))
return IMAP_MAILBOX_CHECKSUM;
offset = lseek(fd, 0L, SEEK_END);
n = retry_write(fd, cache_base(record), size);
if (n < 0) {
syslog(LOG_ERR, "failed to append %u bytes to cache", size);
return IMAP_IOERROR;
}
record->cache_offset = offset;
return 0;
}
int mailbox_commit_cache(struct mailbox *mailbox)
{
if (!mailbox->cache_dirty)
return 0;
mailbox->cache_dirty = 0;
/* not open! That's bad */
if (mailbox->cache_fd == -1)
abort();
/* just fsync is all that's needed to commit */
(void)fsync(mailbox->cache_fd);
return 0;
}
/* function to be used for notification of mailbox changes/updates */
static mailbox_notifyproc_t *updatenotifier = NULL;
/*
* Set the updatenotifier function
*/
void mailbox_set_updatenotifier(mailbox_notifyproc_t *notifyproc)
{
updatenotifier = notifyproc;
}
/*
* Get the updatenotifier function
*/
mailbox_notifyproc_t *mailbox_get_updatenotifier(void)
{
return updatenotifier;
}
/*
* Create the unique identifier for a mailbox named 'name' with
* uidvalidity 'uidvalidity'. We use Ted Ts'o's libuuid if available,
* otherwise we fall back to the legacy Cyrus algorithm which uses the
* mailbox name hashed to 32 bits followed by the uid, both converted to
* hex.
*/
static void mailbox_make_uniqueid(struct mailbox *mailbox)
{
#ifdef HAVE_LIBUUID
uuid_t uu;
uuid_clear(uu); /* Just In Case */
uuid_generate(uu);
free(mailbox->uniqueid);
/* 36 bytes of uuid plus \0 */
mailbox->uniqueid = xmalloc(37);
uuid_unparse_lower(uu, mailbox->uniqueid);
#else
#define PRIME (2147484043UL)
unsigned hash = 0;
const char *name = mailbox->name;
while (*name) {
hash *= 251;
hash += *name++;
hash %= PRIME;
}
free(mailbox->uniqueid);
mailbox->uniqueid = xmalloc(32);
snprintf(mailbox->uniqueid, 32, "%08x%08x",
hash, mailbox->i.uidvalidity);
#endif /* !HAVE_LIBUUID */
mailbox->header_dirty = 1;
}
/*
* Maps in the content for the message with UID 'uid' in 'mailbox'.
* Returns map in 'basep' and 'lenp'
*/
int mailbox_map_message(struct mailbox *mailbox, unsigned long uid,
const char **basep, size_t *lenp)
{
int msgfd;
char *fname;
struct stat sbuf;
fname = mailbox_message_fname(mailbox, uid);
msgfd = open(fname, O_RDONLY, 0666);
if (msgfd == -1) return errno;
if (fstat(msgfd, &sbuf) == -1) {
syslog(LOG_ERR, "IOERROR: fstat on %s: %m", fname);
fatal("can't fstat message file", EC_OSFILE);
}
*basep = 0;
*lenp = 0;
map_refresh(msgfd, 1, basep, lenp, sbuf.st_size, fname, mailbox->name);
close(msgfd);
return 0;
}
/*
* Releases the buffer obtained from mailbox_map_message()
*/
void mailbox_unmap_message(struct mailbox *mailbox __attribute__((unused)),
unsigned long uid __attribute__((unused)),
const char **basep, size_t *lenp)
{
map_free(basep, lenp);
}
static void mailbox_release_resources(struct mailbox *mailbox)
{
if (mailbox->i.dirty || mailbox->cache_dirty)
abort();
/* just close the header */
if (mailbox->header_fd != -1) {
close(mailbox->header_fd);
mailbox->header_fd = -1;
}
/* release and unmap index */
if (mailbox->index_fd != -1) {
close(mailbox->index_fd);
mailbox->index_fd = -1;
}
if (mailbox->index_base)
map_free(&mailbox->index_base, &mailbox->index_len);
/* release and unmap cache */
if (mailbox->cache_fd != -1) {
close(mailbox->cache_fd);
mailbox->cache_fd = -1;
}
if (mailbox->cache_buf.s)
map_free((const char **)&mailbox->cache_buf.s, &mailbox->cache_len);
}
/*
* Open the index file for 'mailbox'
*/
int mailbox_open_index(struct mailbox *mailbox)
{
struct stat sbuf;
char *fname;
int openflags = mailbox->is_readonly ? O_RDONLY : O_RDWR;
mailbox_release_resources(mailbox);
/* open and map the index file */
fname = mailbox_meta_fname(mailbox, META_INDEX);
if (!fname)
return IMAP_MAILBOX_BADNAME;
mailbox->index_fd = open(fname, openflags, 0);
if (mailbox->index_fd == -1)
return IMAP_IOERROR;
/* don't open the cache yet, it will be loaded by lazy-loading
* later */
fstat(mailbox->index_fd, &sbuf);
mailbox->index_ino = sbuf.st_ino;
mailbox->index_mtime = sbuf.st_mtime;
mailbox->index_size = sbuf.st_size;
map_refresh(mailbox->index_fd, 0, &mailbox->index_base,
&mailbox->index_len, mailbox->index_size,
"index", mailbox->name);
return 0;
}
int mailbox_mboxlock_reopen(struct mailboxlist *listitem, int locktype)
{
struct mailbox *mailbox = &listitem->m;
int r;
mailbox_release_resources(mailbox);
mboxname_release(&listitem->l);
r = mboxname_lock(mailbox->name, &listitem->l, locktype);
if (r) return r;
return r;
}
/*
* Open and read the header of the mailbox with name 'name'
* The structure pointed to by 'mailbox' is initialized.
*/
static int mailbox_open_advanced(const char *name,
int locktype,
int index_locktype,
struct mailbox **mailboxptr)
{
struct mboxlist_entry *mbentry = NULL;
struct mailboxlist *listitem;
struct mailbox *mailbox = NULL;
int r = 0;
assert(*mailboxptr == NULL);
listitem = find_listitem(name);
/* already open? just use this one */
if (listitem) {
/* can't reuse an exclusive locked mailbox */
if (listitem->l->locktype == LOCK_EXCLUSIVE)
return IMAP_MAILBOX_LOCKED;
if (locktype == LOCK_EXCLUSIVE)
return IMAP_MAILBOX_LOCKED;
/* can't reuse an already locked index */
if (listitem->m.index_locktype)
return IMAP_MAILBOX_LOCKED;
r = mailbox_lock_index(&listitem->m, index_locktype);
if (r) return r;
listitem->nopen++;
mailbox = &listitem->m;
goto done;
}
listitem = create_listitem(name);
mailbox = &listitem->m;
r = mboxname_lock(name, &listitem->l, locktype);
if (r) {
/* locked is not an error - just means we asked for NONBLOCKING */
if (r != IMAP_MAILBOX_LOCKED)
syslog(LOG_ERR, "IOERROR: locking %s: %m", mailbox->name);
goto done;
}
r = mboxlist_lookup(name, &mbentry, NULL);
if (r) goto done;
mailbox->part = xstrdup(mbentry->partition);
/* Note that the header does have the ACL information, but it is only
* a backup, and the mboxlist data is considered authoritative, so
* we will just use what we were passed */
mailbox->acl = xstrdup(mbentry->acl);
mailbox->mbtype = mbentry->mbtype;
mboxlist_entry_free(&mbentry);
if (index_locktype == LOCK_SHARED)
mailbox->is_readonly = 1;
r = mailbox_open_index(mailbox);
if (r) {
syslog(LOG_ERR, "IOERROR: opening index %s: %s",
mailbox->name, error_message(r));
goto done;
}
/* this will open, map and parse the header file */
r = mailbox_lock_index(mailbox, index_locktype);
if (r) {
syslog(LOG_ERR, "IOERROR: locking index %s: %s",
mailbox->name, error_message(r));
goto done;
}
/* we may be in the process of deleting this mailbox */
if (mailbox->i.options & OPT_MAILBOX_DELETED) {
r = IMAP_MAILBOX_NONEXISTENT;
goto done;
}
done:
if (r) mailbox_close(&mailbox);
else *mailboxptr = mailbox;
return r;
}
int mailbox_open_irl(const char *name, struct mailbox **mailboxptr)
{
return mailbox_open_advanced(name, LOCK_SHARED, LOCK_SHARED,
mailboxptr);
}
int mailbox_open_iwl(const char *name, struct mailbox **mailboxptr)
{
return mailbox_open_advanced(name, LOCK_SHARED, LOCK_EXCLUSIVE,
mailboxptr);
}
int mailbox_open_exclusive(const char *name, struct mailbox **mailboxptr)
{
return mailbox_open_advanced(name, LOCK_EXCLUSIVE, LOCK_EXCLUSIVE,
mailboxptr);
}
void mailbox_index_dirty(struct mailbox *mailbox)
{
assert(mailbox_index_islocked(mailbox, 1));
mailbox->i.dirty = 1;
}
void mailbox_modseq_dirty(struct mailbox *mailbox)
{
assert(mailbox_index_islocked(mailbox, 1));
if (mailbox->modseq_dirty)
return;
mailbox->i.highestmodseq++;
mailbox->last_updated = time(0);
mailbox->modseq_dirty = 1;
mailbox_index_dirty(mailbox);
}
void mailbox_ref(struct mailbox *mailbox)
{
struct mailboxlist *listitem;
if (!mailbox) return;
listitem = find_listitem(mailbox->name);
assert(listitem && &listitem->m == mailbox);
listitem->nopen++;
}
/*
* Close the mailbox 'mailbox', freeing all associated resources.
*/
void mailbox_close(struct mailbox **mailboxptr)
{
int flag;
struct mailbox *mailbox = *mailboxptr;
struct mailboxlist *listitem;
int expunge_days = config_getint(IMAPOPT_EXPUNGE_DAYS);
/* be safe against double-close */
if (!mailbox) return;
listitem = find_listitem(mailbox->name);
assert(listitem && &listitem->m == mailbox);
*mailboxptr = NULL;
/* open multiple times? Just close this one */
if (listitem->nopen > 1) {
listitem->nopen--;
mailbox_unlock_index(mailbox, NULL);
return;
}
/* auto-cleanup */
if (mailbox->i.first_expunged &&
(mailbox->index_locktype == LOCK_EXCLUSIVE)) {
time_t floor = time(NULL) - (expunge_days * 86400);
/* but only if we're more than a full week older than
* the expunge time, * so it doesn't turn into lots
* of bitty rewrites.
* Also, cyr_expire can get first bite if it's been set
* to run... */
if (mailbox->i.first_expunged < floor - (8 * 86400)) {
mailbox_expunge_cleanup(mailbox, floor, NULL);
/* XXX - handle error code? */
}
}
/* get a re-read of the options field for cleanup purposes */
if (mailbox->index_fd != -1) {
if (!mailbox->index_locktype)
mailbox_lock_index(mailbox, LOCK_SHARED);
/* drop the index lock here because we'll lose our right to it
* when try to upgrade the mboxlock anyway. */
mailbox_unlock_index(mailbox, NULL);
}
/* do we need to try and clean up? (not if doing a shutdown,
* speed is probably more important!) */
if (!in_shutdown && (mailbox->i.options & MAILBOX_CLEANUP_MASK)) {
int r = mailbox_mboxlock_reopen(listitem, LOCK_NONBLOCKING);
if (!r) r = mailbox_open_index(mailbox);
if (!r) r = mailbox_lock_index(mailbox, LOCK_EXCLUSIVE);
if (!r) {
/* finish cleaning up */
if (mailbox->i.options & OPT_MAILBOX_DELETED)
mailbox_delete_cleanup(mailbox->part, mailbox->name);
else if (mailbox->i.options & OPT_MAILBOX_NEEDS_REPACK)
mailbox_index_repack(mailbox);
else if (mailbox->i.options & OPT_MAILBOX_NEEDS_UNLINK)
mailbox_index_unlink(mailbox);
/* or we missed out - someone else beat us to it */
mailbox_unlock_index(mailbox, NULL);
}
/* otherwise someone else has the mailbox locked
* already, so they can handle the cleanup in
* THEIR mailbox_close call */
}
mailbox_release_resources(mailbox);
free(mailbox->name);
free(mailbox->part);
free(mailbox->acl);
free(mailbox->uniqueid);
free(mailbox->quotaroot);
free(mailbox->specialuse);
for (flag = 0; flag < MAX_USER_FLAGS; flag++) {
free(mailbox->flagname[flag]);
}
if (listitem->l) mboxname_release(&listitem->l);
remove_listitem(listitem);
}
/*
* Read the header of 'mailbox'
* format:
* MAGIC
* quotaroot TAB uniqueid TAB specialuse
* userflag1 SPACE userflag2 SPACE userflag3 [...] (with no trailing space)
* user1 TAB user1acl TAB user2 TAB user2acl TAB (with trailing tab!)
*/
int mailbox_read_header(struct mailbox *mailbox, char **aclptr)
{
int r = 0;
int flag;
const char *name, *p, *tab, *eol;
const char *fname;
struct stat sbuf;
const char *base = NULL;
size_t len = 0;
unsigned magic_size = sizeof(MAILBOX_HEADER_MAGIC) - 1;
/* can't be dirty if we're reading it */
if (mailbox->header_dirty)
abort();
if (mailbox->header_fd != -1)
close(mailbox->header_fd);
fname = mailbox_meta_fname(mailbox, META_HEADER);
mailbox->header_fd = open(fname, O_RDONLY, 0);
if (mailbox->header_fd == -1) {
r = IMAP_IOERROR;
goto done;
}
if (fstat(mailbox->header_fd, &sbuf) == -1) {
close(mailbox->header_fd);
mailbox->header_fd = 1;
r = IMAP_IOERROR;
goto done;
}
map_refresh(mailbox->header_fd, 1, &base, &len,
sbuf.st_size, "header", mailbox->name);
mailbox->header_file_ino = sbuf.st_ino;
mailbox->header_file_crc = crc32_map(base, sbuf.st_size);
/* Check magic number */
if ((unsigned) sbuf.st_size < magic_size ||
strncmp(base, MAILBOX_HEADER_MAGIC, magic_size)) {
r = IMAP_MAILBOX_BADFORMAT;
goto done;
}
/* Read quota data line */
p = base + sizeof(MAILBOX_HEADER_MAGIC)-1;
tab = memchr(p, '\t', sbuf.st_size - (p - base));
eol = memchr(p, '\n', sbuf.st_size - (p - base));
if (!eol) {
r = IMAP_MAILBOX_BADFORMAT;
goto done;
}
/* quotaroot (if present) */
free(mailbox->quotaroot);
if (!tab || tab > eol) {
syslog(LOG_DEBUG, "mailbox '%s' has old cyrus.header",
mailbox->name);
tab = eol;
}
if (p < tab) {
mailbox->quotaroot = xstrndup(p, tab - p);
}
else {
mailbox->quotaroot = NULL;
}
/* read uniqueid (should always exist unless old format) */
free(mailbox->uniqueid);
if (tab < eol) {
p = tab + 1;
if (p == eol) {
r = IMAP_MAILBOX_BADFORMAT;
goto done;
}
tab = memchr(p, '\t', sbuf.st_size - (p - base));
if (!tab || tab > eol) tab = eol;
mailbox->uniqueid = xstrndup(p, tab - p);
} else {
/* uniqueid needs to be generated when we know the uidvalidity */
mailbox->uniqueid = NULL;
}
/* read special use list flags (optional) */
free(mailbox->specialuse);
mailbox->specialuse = NULL;
if (tab < eol) {
p = tab + 1;
if (p < eol)
mailbox->specialuse = xstrndup(p, eol - p);
}
/* Read names of user flags */
p = eol + 1;
eol = memchr(p, '\n', sbuf.st_size - (p - base));
if (!eol) {
r = IMAP_MAILBOX_BADFORMAT;
goto done;
}
name = p;
/* read the names of flags */
for (flag = 0; name <= eol && flag < MAX_USER_FLAGS; flag++) {
free(mailbox->flagname[flag]);
mailbox->flagname[flag] = NULL;
p = memchr(name, ' ', eol-name);
if (!p) p = eol;
if (name != p)
mailbox->flagname[flag] = xstrndup(name, p-name);
name = p+1;
}
/* zero out the rest */
for (; flag < MAX_USER_FLAGS; flag++) {
free(mailbox->flagname[flag]);
mailbox->flagname[flag] = NULL;
}
/* Read ACL */
p = eol + 1;
eol = memchr(p, '\n', sbuf.st_size - (p - base));
if (!eol) {
r = IMAP_MAILBOX_BADFORMAT;
goto done;
}
if (aclptr)
*aclptr = xstrndup(p, eol-p);
done:
if (base) map_free(&base, &len);
return r;
}
/* set a new ACL - only dirty if changed */
int mailbox_set_acl(struct mailbox *mailbox, const char *acl,
int dirty_modseq)
{
if (mailbox->acl) {
if (!strcmp(mailbox->acl, acl))
return 0; /* no change */
free(mailbox->acl);
}
mailbox->acl = xstrdup(acl);
mailbox->header_dirty = 1;
if (dirty_modseq)
mailbox_modseq_dirty(mailbox);
return 0;
}
/* set a new QUOTAROOT - only dirty if changed */
int mailbox_set_quotaroot(struct mailbox *mailbox, const char *quotaroot)
{
if (mailbox->quotaroot) {
if (quotaroot && !strcmp(mailbox->quotaroot, quotaroot))
return 0; /* no change */
free(mailbox->quotaroot);
mailbox->quotaroot = NULL;
}
else {
if (!quotaroot)
return 0; /* no change */
}
if (quotaroot)
mailbox->quotaroot = xstrdup(quotaroot);
/* either way, it's changed, so dirty */
mailbox->header_dirty = 1;
return 0;
}
/* set a new XLISTFLAG - only dirty if changed */
int mailbox_set_specialuse(struct mailbox *mailbox, const char *specialuse)
{
if (mailbox->specialuse) {
if (specialuse && !strcmp(mailbox->specialuse, specialuse))
return 0; /* no change */
free(mailbox->specialuse);
mailbox->specialuse = NULL;
}
if (specialuse)
mailbox->specialuse = xstrdup(specialuse);
mailbox->header_dirty = 1;
return 0;
}
/* find or create a user flag - dirty header if change needed */
int mailbox_user_flag(struct mailbox *mailbox, const char *flag,
int *flagnum, int create)
{
int userflag;
int emptyflag = -1;
if (!imparse_isatom(flag))
return IMAP_INVALID_IDENTIFIER;
for (userflag = 0; userflag < MAX_USER_FLAGS; userflag++) {
if (mailbox->flagname[userflag]) {
if (!strcasecmp(flag, mailbox->flagname[userflag]))
break;
}
else if (emptyflag == -1) {
emptyflag = userflag;
}
}
if (userflag == MAX_USER_FLAGS) {
if (!create)
return IMAP_NOTFOUND;
if (emptyflag == -1)
return IMAP_USERFLAG_EXHAUSTED;
/* need to be index locked to make flag changes */
if (!mailbox_index_islocked(mailbox, 1))
return IMAP_MAILBOX_LOCKED;
/* set the flag and mark the header dirty */
userflag = emptyflag;
mailbox->flagname[userflag] = xstrdup(flag);
mailbox->header_dirty = 1;
}
if (flagnum) *flagnum = userflag;
return 0;
}
int mailbox_record_hasflag(struct mailbox *mailbox,
struct index_record *record,
const char *flag)
{
int userflag;
if (!mailbox) return 0;
if (!flag) return 0;
if (!record) return 0;
if (flag[0] == '\\') {
if (!strcasecmp(flag, "\\answered"))
return ((record->system_flags & FLAG_ANSWERED) ? 1 : 0);
if (!strcasecmp(flag, "\\deleted"))
return ((record->system_flags & FLAG_DELETED) ? 1 : 0);
if (!strcasecmp(flag, "\\draft"))
return ((record->system_flags & FLAG_DRAFT) ? 1 : 0);
if (!strcasecmp(flag, "\\flagged"))
return ((record->system_flags & FLAG_FLAGGED) ? 1 : 0);
if (!strcasecmp(flag, "\\seen")) {
/* NOTE: this is a special case because it depends
* who the userid is. We will only return the user
* or global seen value */
return ((record->system_flags & FLAG_SEEN) ? 1 : 0);
}
/* unknown system flag is never present */
return 0;
}
if (mailbox_user_flag(mailbox, flag, &userflag, 0))
return 0;
return ((record->user_flags[userflag/32] & (1<<(userflag&31))) ? 1 : 0);
}
int mailbox_buf_to_index_header(const char *buf, struct index_header *i)
{
uint32_t crc;
bit32 qannot;
i->dirty = 0;
i->generation_no = ntohl(*((bit32 *)(buf+OFFSET_GENERATION_NO)));
i->format = ntohl(*((bit32 *)(buf+OFFSET_FORMAT)));
i->minor_version = ntohl(*((bit32 *)(buf+OFFSET_MINOR_VERSION)));
i->start_offset = ntohl(*((bit32 *)(buf+OFFSET_START_OFFSET)));
i->record_size = ntohl(*((bit32 *)(buf+OFFSET_RECORD_SIZE)));
i->num_records = ntohl(*((bit32 *)(buf+OFFSET_NUM_RECORDS)));
i->last_appenddate = ntohl(*((bit32 *)(buf+OFFSET_LAST_APPENDDATE)));
i->last_uid = ntohl(*((bit32 *)(buf+OFFSET_LAST_UID)));
i->quota_mailbox_used = align_ntohll(buf+OFFSET_QUOTA_MAILBOX_USED);
i->pop3_last_login = ntohl(*((bit32 *)(buf+OFFSET_POP3_LAST_LOGIN)));
i->uidvalidity = ntohl(*((bit32 *)(buf+OFFSET_UIDVALIDITY)));
i->deleted = ntohl(*((bit32 *)(buf+OFFSET_DELETED)));
i->answered = ntohl(*((bit32 *)(buf+OFFSET_ANSWERED)));
i->flagged = ntohl(*((bit32 *)(buf+OFFSET_FLAGGED)));
i->options = ntohl(*((bit32 *)(buf+OFFSET_MAILBOX_OPTIONS)));
i->leaked_cache_records = ntohl(*((bit32 *)(buf+OFFSET_LEAKED_CACHE)));
i->highestmodseq = align_ntohll(buf+OFFSET_HIGHESTMODSEQ);
i->deletedmodseq = align_ntohll(buf+OFFSET_DELETEDMODSEQ);
i->exists = ntohl(*((bit32 *)(buf+OFFSET_EXISTS)));
i->first_expunged = ntohl(*((bit32 *)(buf+OFFSET_FIRST_EXPUNGED)));
i->last_repack_time = ntohl(*((bit32 *)(buf+OFFSET_LAST_REPACK_TIME)));
i->header_file_crc = ntohl(*((bit32 *)(buf+OFFSET_HEADER_FILE_CRC)));
/* sync_crc was here */
i->recentuid = ntohl(*((bit32 *)(buf+OFFSET_RECENTUID)));
i->recenttime = ntohl(*((bit32 *)(buf+OFFSET_RECENTTIME)));
i->header_crc = ntohl(*((bit32 *)(buf+OFFSET_HEADER_CRC)));
i->pop3_show_after = ntohl(*((bit32 *)(buf+OFFSET_POP3_SHOW_AFTER)));
qannot = ntohl(*((bit32 *)(buf+OFFSET_QUOTA_ANNOT_USED)));
/* this field is stored as a 32b unsigned on disk but 64b signed
* in memory, so we need to be careful about sign extension */
i->quota_annot_used = (quota_t)((unsigned long long)qannot);
if (!i->exists)
i->options |= OPT_POP3_NEW_UIDL;
crc = crc32_map(buf, OFFSET_HEADER_CRC);
if (crc != i->header_crc)
return IMAP_MAILBOX_CHECKSUM;
return 0;
}
static int mailbox_refresh_index_map(struct mailbox *mailbox)
{
size_t need_size;
struct stat sbuf;
/* check if we need to extend the mmaped space for the index file
* (i.e. new records appended since last read) */
need_size = mailbox->i.start_offset +
mailbox->i.num_records * mailbox->i.record_size;
if (mailbox->index_size < need_size) {
if (fstat(mailbox->index_fd, &sbuf) == -1)
return IMAP_IOERROR;
if (sbuf.st_size < (int)need_size)
return IMAP_MAILBOX_BADFORMAT;
mailbox->index_size = sbuf.st_size;
/* need to map some more space! */
map_refresh(mailbox->index_fd, 1, &mailbox->index_base,
&mailbox->index_len, mailbox->index_size,
"index", mailbox->name);
/* and the cache will be stale too */
mailbox->need_cache_refresh = 1;
}
return 0;
}
static int mailbox_read_index_header(struct mailbox *mailbox)
{
int r;
/* no dirty mailboxes please */
if (mailbox->i.dirty)
abort();
/* need to be locked to ensure a consistent read - otherwise
* a busy mailbox will get CRC errors due to rewrite happening
* under our feet! */
if (!mailbox_index_islocked(mailbox, 0))
return IMAP_MAILBOX_LOCKED;
/* and of course it needs to exist and have at least a full
* sized header */
if (!mailbox->index_base)
return IMAP_MAILBOX_BADFORMAT;
if (mailbox->index_size < INDEX_HEADER_SIZE)
return IMAP_MAILBOX_BADFORMAT;
/* need to make sure we're reading fresh data! */
map_refresh(mailbox->index_fd, 1, &mailbox->index_base,
&mailbox->index_len, mailbox->index_size,
"index", mailbox->name);
r = mailbox_buf_to_index_header(mailbox->index_base, &mailbox->i);
if (r) return r;
r = mailbox_refresh_index_map(mailbox);
if (r) return r;
return 0;
}
/*
* Read an index record from a mapped index file
*/
int mailbox_buf_to_index_record(const char *buf,
struct index_record *record)
{
uint32_t crc;
int n;
/* tracking fields - initialise */
memset(record, 0, sizeof(struct index_record));
/* parse buffer in to structure */
record->uid = ntohl(*((bit32 *)(buf+OFFSET_UID)));
record->internaldate = ntohl(*((bit32 *)(buf+OFFSET_INTERNALDATE)));
record->sentdate = ntohl(*((bit32 *)(buf+OFFSET_SENTDATE)));
record->size = ntohl(*((bit32 *)(buf+OFFSET_SIZE)));
record->header_size = ntohl(*((bit32 *)(buf+OFFSET_HEADER_SIZE)));
record->gmtime = ntohl(*((bit32 *)(buf+OFFSET_GMTIME)));
record->cache_offset = ntohl(*((bit32 *)(buf+OFFSET_CACHE_OFFSET)));
record->last_updated = ntohl(*((bit32 *)(buf+OFFSET_LAST_UPDATED)));
record->system_flags = ntohl(*((bit32 *)(buf+OFFSET_SYSTEM_FLAGS)));
for (n = 0; n < MAX_USER_FLAGS/32; n++) {
record->user_flags[n] = ntohl(*((bit32 *)(buf+OFFSET_USER_FLAGS+4*n)));
}
record->content_lines = ntohl(*((bit32 *)(buf+OFFSET_CONTENT_LINES)));
record->cache_version = ntohl(*((bit32 *)(buf+OFFSET_CACHE_VERSION)));
message_guid_import(&record->guid, (unsigned char *)buf+OFFSET_MESSAGE_GUID);
record->modseq = ntohll(*((bit64 *)(buf+OFFSET_MODSEQ)));
record->cid = ntohll(*(bit64 *)(buf+OFFSET_CID));
record->cache_crc = ntohl(*((bit32 *)(buf+OFFSET_CACHE_CRC)));
record->record_crc = ntohl(*((bit32 *)(buf+OFFSET_RECORD_CRC)));
/* check CRC32 */
crc = crc32_map(buf, OFFSET_RECORD_CRC);
if (crc != record->record_crc)
return IMAP_MAILBOX_CHECKSUM;
return 0;
}
/*
* Read an index record from a mailbox
*/
int mailbox_read_index_record(struct mailbox *mailbox,
uint32_t recno,
struct index_record *record)
{
const char *buf;
unsigned offset;
int r;
offset = mailbox->i.start_offset + (recno-1) * mailbox->i.record_size;
if (offset + mailbox->i.record_size > mailbox->index_size) {
syslog(LOG_ERR,
"IOERROR: index record %u for %s past end of file",
recno, mailbox->name);
return IMAP_IOERROR;
}
buf = mailbox->index_base + offset;
r = mailbox_buf_to_index_record(buf, record);
if (!r) record->recno = recno;
return r;
}
/*
* bsearch() function to compare two index record buffers by UID
*/
static int rec_compar(const void *key, const void *mem)
{
uint32_t uid = *((uint32_t *) key);
struct index_record record;
int r;
if ((r = mailbox_buf_to_index_record(mem, &record))) return r;
if (uid < record.uid) return -1;
return (uid > record.uid);
}
/*
* Find the index record in mailbox corresponding to UID
*/
int mailbox_find_index_record(struct mailbox *mailbox, uint32_t uid,
struct index_record *record)
{
const char *mem, *base = mailbox->index_base + mailbox->i.start_offset;
size_t num_records = mailbox->i.num_records;
size_t size = mailbox->i.record_size;
int r;
mem = bsearch(&uid, base, num_records, size, rec_compar);
if (!mem) return CYRUSDB_NOTFOUND;
if ((r = mailbox_buf_to_index_record(mem, record))) return r;
record->recno = ((mem - base) / size) + 1;
return 0;
}
/*
* Lock the index file for 'mailbox'. Reread index file header if necessary.
*/
int mailbox_lock_index(struct mailbox *mailbox, int locktype)
{
char *fname;
struct stat sbuf;
int r = 0;
assert(mailbox->index_fd != -1);
assert(!mailbox->index_locktype);
restart:
if (locktype == LOCK_EXCLUSIVE) {
/* handle read-only case cleanly - we need to re-open read-write first! */
if (mailbox->is_readonly) {
mailbox->is_readonly = 0;
r = mailbox_open_index(mailbox);
}
if (!r) r = lock_blocking(mailbox->index_fd);
}
else if (locktype == LOCK_SHARED) {
r = lock_shared(mailbox->index_fd);
}
else {
fatal("invalid locktype for index", EC_SOFTWARE);
}
/* double check that the index exists and has at least enough
* data to check the version number */
if (!r) {
if (!mailbox->index_base)
r = IMAP_MAILBOX_BADFORMAT;
else if (mailbox->index_size < OFFSET_NUM_RECORDS)
r = IMAP_MAILBOX_BADFORMAT;
if (r)
lock_unlock(mailbox->index_fd);
}
if (r) {
syslog(LOG_ERR, "IOERROR: locking index for %s: %s",
mailbox->name, error_message(r));
return IMAP_IOERROR;
}
mailbox->index_locktype = locktype;
fname = mailbox_meta_fname(mailbox, META_HEADER);
r = stat(fname, &sbuf);
if (r == -1) {
syslog(LOG_ERR, "IOERROR: stating header %s for %s: %m",
fname, mailbox->name);
mailbox_unlock_index(mailbox, NULL);
return IMAP_IOERROR;
}
/* has the header file changed? */
if (sbuf.st_ino != mailbox->header_file_ino) {
r = mailbox_read_header(mailbox, NULL);
if (r) {
syslog(LOG_ERR, "IOERROR: reading header for %s: %m",
mailbox->name);
mailbox_unlock_index(mailbox, NULL);
return r;
}
}
/* make sure the mailbox is up to date if we haven't
* already had a successful load */
if (!mailbox->i.minor_version) {
bit32 minor_version = ntohl(*((bit32 *)(mailbox->index_base+OFFSET_MINOR_VERSION)));
if (minor_version != MAILBOX_MINOR_VERSION) {
struct mailboxlist *listitem = find_listitem(mailbox->name);
int prev_locktype;
assert(listitem);
prev_locktype = listitem->l->locktype;
if (prev_locktype != LOCK_EXCLUSIVE) {
/* we need to switch to an exclusive lock while upgrading */
r = mailbox_mboxlock_reopen(listitem, LOCK_EXCLUSIVE);
if (r) return r;
r = mailbox_open_index(mailbox);
if (r) return r;
}
/* lie about our index lock status - the exclusive namelock
* provides equivalent properties - and we know it won't
* leak because the 'restart' above will cover up our sins */
mailbox->index_locktype = LOCK_EXCLUSIVE;
r = upgrade_index(mailbox);
if (r) return r;
/* we have to downgrade again afterwards so a "SELECT" won't
* hold an exclusive lock forever */
if (prev_locktype != LOCK_EXCLUSIVE) {
r = mailbox_mboxlock_reopen(listitem, prev_locktype);
if (r) return r;
}
/* either way, must refresh index here. The old one is stale */
r = mailbox_open_index(mailbox);
if (r) return r;
goto restart;
}
}
/* note: it's guaranteed by our outer cyrus.lock lock that the
* cyrus.index and cyrus.cache files are never rewritten, so
* we're safe to just extend the map if needed */
r = mailbox_read_index_header(mailbox);
if (r) {
syslog(LOG_ERR, "IOERROR: refreshing index for %s: %m",
mailbox->name);
mailbox_unlock_index(mailbox, NULL);
return r;
}
/* check the CRC */
if (mailbox->header_file_crc != mailbox->i.header_file_crc) {
syslog(LOG_ERR, "IOERROR: header CRC mismatch %s: %08X %08X",
mailbox->name, (unsigned int)mailbox->header_file_crc,
(unsigned int)mailbox->i.header_file_crc);
mailbox_unlock_index(mailbox, NULL);
return IMAP_MAILBOX_CHECKSUM;
}
/* fix up 2.4.0 bug breakage */
if (mailbox->i.uidvalidity == 0) {
mailbox->i.uidvalidity = time(0);
if (locktype == LOCK_EXCLUSIVE) {
mailbox_index_dirty(mailbox);
mailbox_commit(mailbox);
syslog(LOG_ERR, "%s: fixing zero uidvalidity", mailbox->name);
}
}
if (mailbox->i.highestmodseq == 0) {
mailbox->i.highestmodseq = 1;
if (locktype == LOCK_EXCLUSIVE) {
mailbox_index_dirty(mailbox);
mailbox_commit(mailbox);
syslog(LOG_ERR, "%s: fixing zero highestmodseq", mailbox->name);
}
}
return 0;
}
/*
* Release lock on the index file for 'mailbox'
*/
void mailbox_unlock_index(struct mailbox *mailbox, struct statusdata *sdata)
{
/* naughty - you can't unlock a dirty mailbox! */
int r = mailbox_commit(mailbox);
if (r) {
syslog(LOG_ERR, "IOERROR: failed to commit mailbox %s, "
"probably need to reconstruct",
mailbox->name);
abort();
}
if (mailbox->has_changed) {
if (updatenotifier) updatenotifier(mailbox->name);
sync_log_mailbox(mailbox->name);
if (config_getswitch(IMAPOPT_STATUSCACHE))
statuscache_invalidate(mailbox->name, sdata);
mailbox->has_changed = 0;
}
if (mailbox->index_locktype) {
if (lock_unlock(mailbox->index_fd))
syslog(LOG_ERR, "IOERROR: unlocking index of %s: %m",
mailbox->name);
mailbox->index_locktype = 0;
}
}
/*
* Write the header file for 'mailbox'
*/
int mailbox_commit_header(struct mailbox *mailbox)
{
int flag;
int fd;
int r = 0;
const char *quotaroot;
const char *newfname;
struct iovec iov[10];
int niov;
if (!mailbox->header_dirty)
return 0; /* nothing to write! */
/* we actually do all header actions under an INDEX lock, because
* we need to write the crc32 to be consistent! */
assert(mailbox_index_islocked(mailbox, 1));
newfname = mailbox_meta_newfname(mailbox, META_HEADER);
fd = open(newfname, O_CREAT | O_TRUNC | O_RDWR, 0666);
if (fd == -1) {
syslog(LOG_ERR, "IOERROR: opening %s: %m", newfname);
return IMAP_IOERROR;
}
/* Write magic header, do NOT write the trailing NUL */
r = write(fd, MAILBOX_HEADER_MAGIC,
sizeof(MAILBOX_HEADER_MAGIC) - 1);
if (r != -1) {
niov = 0;
quotaroot = mailbox->quotaroot ? mailbox->quotaroot : "";
WRITEV_ADDSTR_TO_IOVEC(iov,niov,quotaroot);
WRITEV_ADD_TO_IOVEC(iov,niov,"\t",1);
WRITEV_ADDSTR_TO_IOVEC(iov,niov,mailbox->uniqueid);
if (mailbox->specialuse) {
WRITEV_ADD_TO_IOVEC(iov,niov,"\t",1);
WRITEV_ADDSTR_TO_IOVEC(iov,niov,mailbox->specialuse);
}
WRITEV_ADD_TO_IOVEC(iov,niov,"\n",1);
r = retry_writev(fd, iov, niov);
}
if (r != -1) {
for (flag = 0; flag < MAX_USER_FLAGS; flag++) {
if (mailbox->flagname[flag]) {
niov = 0;
WRITEV_ADDSTR_TO_IOVEC(iov,niov,mailbox->flagname[flag]);
WRITEV_ADD_TO_IOVEC(iov,niov," ",1);
r = retry_writev(fd, iov, niov);
if(r == -1) break;
}
}
}
if (r != -1) {
niov = 0;
WRITEV_ADD_TO_IOVEC(iov,niov,"\n",1);
WRITEV_ADDSTR_TO_IOVEC(iov,niov,mailbox->acl);
WRITEV_ADD_TO_IOVEC(iov,niov,"\n",1);
r = retry_writev(fd, iov, niov);
}
if (r == -1 || fsync(fd)) {
syslog(LOG_ERR, "IOERROR: writing %s: %m", newfname);
close(fd);
unlink(newfname);
return IMAP_IOERROR;
}
close(fd);
/* rename the new header file over the old one */
r = mailbox_meta_rename(mailbox, META_HEADER);
if (r) return r;
mailbox->header_dirty = 0; /* we wrote it out, so not dirty any more */
/* re-read the header */
r = mailbox_read_header(mailbox, NULL);
if (r) return r;
/* copy the new CRC into the index header */
mailbox->i.header_file_crc = mailbox->header_file_crc;
mailbox_index_dirty(mailbox);
return 0;
}
bit32 mailbox_index_header_to_buf(struct index_header *i, unsigned char *buf)
{
bit32 crc;
bit32 options = i->options & MAILBOX_OPT_VALID;
*((bit32 *)(buf+OFFSET_GENERATION_NO)) = htonl(i->generation_no);
*((bit32 *)(buf+OFFSET_FORMAT)) = htonl(i->format);
*((bit32 *)(buf+OFFSET_MINOR_VERSION)) = htonl(i->minor_version);
*((bit32 *)(buf+OFFSET_START_OFFSET)) = htonl(i->start_offset);
*((bit32 *)(buf+OFFSET_RECORD_SIZE)) = htonl(i->record_size);
*((bit32 *)(buf+OFFSET_NUM_RECORDS)) = htonl(i->num_records);
*((bit32 *)(buf+OFFSET_LAST_APPENDDATE)) = htonl(i->last_appenddate);
*((bit32 *)(buf+OFFSET_LAST_UID)) = htonl(i->last_uid);
/* quotas may be 64bit now */
align_htonll(buf+OFFSET_QUOTA_MAILBOX_USED, i->quota_mailbox_used);
*((bit32 *)(buf+OFFSET_POP3_LAST_LOGIN)) = htonl(i->pop3_last_login);
*((bit32 *)(buf+OFFSET_UIDVALIDITY)) = htonl(i->uidvalidity);
*((bit32 *)(buf+OFFSET_DELETED)) = htonl(i->deleted);
*((bit32 *)(buf+OFFSET_ANSWERED)) = htonl(i->answered);
*((bit32 *)(buf+OFFSET_FLAGGED)) = htonl(i->flagged);
*((bit32 *)(buf+OFFSET_MAILBOX_OPTIONS)) = htonl(options);
*((bit32 *)(buf+OFFSET_LEAKED_CACHE)) = htonl(i->leaked_cache_records);
align_htonll(buf+OFFSET_HIGHESTMODSEQ, i->highestmodseq);
align_htonll(buf+OFFSET_DELETEDMODSEQ, i->deletedmodseq);
*((bit32 *)(buf+OFFSET_EXISTS)) = htonl(i->exists);
*((bit32 *)(buf+OFFSET_FIRST_EXPUNGED)) = htonl(i->first_expunged);
*((bit32 *)(buf+OFFSET_LAST_REPACK_TIME)) = htonl(i->last_repack_time);
*((bit32 *)(buf+OFFSET_HEADER_FILE_CRC)) = htonl(i->header_file_crc);
*((bit32 *)(buf+OFFSET_SYNC_CRC)) = 0; /* sync_crc was here */
*((bit32 *)(buf+OFFSET_RECENTUID)) = htonl(i->recentuid);
*((bit32 *)(buf+OFFSET_RECENTTIME)) = htonl(i->recenttime);
*((bit32 *)(buf+OFFSET_POP3_SHOW_AFTER)) = htonl(i->pop3_show_after);
/* this field is 64b in memory but 32b on disk - as it counts
* bytes stored in dbs and the dbs are 32b anyway there should
* be no problem */
*((bit32 *)(buf+OFFSET_QUOTA_ANNOT_USED)) = htonl((bit32)i->quota_annot_used);
*((bit32 *)(buf+OFFSET_SPARE2)) = htonl(0); /* RESERVED */
/* Update checksum */
crc = htonl(crc32_map((char *)buf, OFFSET_HEADER_CRC));
*((bit32 *)(buf+OFFSET_HEADER_CRC)) = crc;
return crc;
}
int mailbox_commit_quota(struct mailbox *mailbox)
{
int res;
int changed = 0;
quota_t quota_usage[QUOTA_NUMRESOURCES];
/* not dirty */
if (!mailbox->quota_dirty)
return 0;
mailbox->quota_dirty = 0;
/* no quota root means we don't track quota. That's OK */
if (!mailbox->quotaroot)
return 0;
mailbox_get_usage(mailbox, quota_usage);
for (res = 0; res < QUOTA_NUMRESOURCES; res++) {
quota_usage[res] -= mailbox->quota_previously_used[res];
if (quota_usage[res] != 0) {
changed++;
}
}
/* unchanged */
if (!changed)
return 0;
assert(mailbox_index_islocked(mailbox, 1));
quota_update_useds(mailbox->quotaroot, quota_usage,
(mailbox->i.options & OPT_MAILBOX_QUOTA_SCANNED));
/* XXX - fail upon issue? It's tempting */
return 0;
}
/*
* Write the index header for 'mailbox'
*/
int mailbox_commit(struct mailbox *mailbox)
{
/* XXX - ibuf for alignment? */
static unsigned char buf[INDEX_HEADER_SIZE];
int n, r;
/* try to commit sub parts first */
r = mailbox_commit_cache(mailbox);
if (r) return r;
r = mailbox_commit_quota(mailbox);
if (r) return r;
r = mailbox_commit_header(mailbox);
if (r) return r;
if (!mailbox->i.dirty)
return 0;
assert(mailbox_index_islocked(mailbox, 1));
if (mailbox->i.start_offset < INDEX_HEADER_SIZE)
fatal("Mailbox offset bug", EC_SOFTWARE);
mailbox_index_header_to_buf(&mailbox->i, buf);
lseek(mailbox->index_fd, 0, SEEK_SET);
n = retry_write(mailbox->index_fd, buf, INDEX_HEADER_SIZE);
if ((unsigned long)n != INDEX_HEADER_SIZE || fsync(mailbox->index_fd)) {
syslog(LOG_ERR, "IOERROR: writing index header for %s: %m",
mailbox->name);
return IMAP_IOERROR;
}
/* remove all dirty flags! */
mailbox->i.dirty = 0;
mailbox->modseq_dirty = 0;
mailbox->header_dirty = 0;
/* label changes for later logging */
mailbox->has_changed = 1;
return 0;
}
/*
* Put an index record into a buffer suitable for writing to a file.
*/
bit32 mailbox_index_record_to_buf(struct index_record *record,
unsigned char *buf)
{
int n;
bit32 crc;
*((bit32 *)(buf+OFFSET_UID)) = htonl(record->uid);
*((bit32 *)(buf+OFFSET_INTERNALDATE)) = htonl(record->internaldate);
*((bit32 *)(buf+OFFSET_SENTDATE)) = htonl(record->sentdate);
*((bit32 *)(buf+OFFSET_SIZE)) = htonl(record->size);
*((bit32 *)(buf+OFFSET_HEADER_SIZE)) = htonl(record->header_size);
*((bit32 *)(buf+OFFSET_GMTIME)) = htonl(record->gmtime);
*((bit32 *)(buf+OFFSET_CACHE_OFFSET)) = htonl(record->cache_offset);
*((bit32 *)(buf+OFFSET_LAST_UPDATED)) = htonl(record->last_updated);
*((bit32 *)(buf+OFFSET_SYSTEM_FLAGS)) = htonl(record->system_flags);
for (n = 0; n < MAX_USER_FLAGS/32; n++) {
*((bit32 *)(buf+OFFSET_USER_FLAGS+4*n)) = htonl(record->user_flags[n]);
}
*((bit32 *)(buf+OFFSET_CONTENT_LINES)) = htonl(record->content_lines);
*((bit32 *)(buf+OFFSET_CACHE_VERSION)) = htonl(record->cache_version);
message_guid_export(&record->guid, buf+OFFSET_MESSAGE_GUID);
*((bit64 *)(buf+OFFSET_MODSEQ)) = htonll(record->modseq);
*((bit64 *)(buf+OFFSET_CID)) = htonll(record->cid);
*((bit32 *)(buf+OFFSET_CACHE_CRC)) = htonl(record->cache_crc);
/* calculate the checksum */
crc = crc32_map((char *)buf, OFFSET_RECORD_CRC);
*((bit32 *)(buf+OFFSET_RECORD_CRC)) = htonl(crc);
return crc;
}
static void mailbox_quota_dirty(struct mailbox *mailbox)
{
/* track quota use */
if (!mailbox->quota_dirty) {
mailbox->quota_dirty = 1;
mailbox_get_usage(mailbox, mailbox->quota_previously_used);
}
}
static void header_update_counts(struct index_header *i,
struct index_record *record,
int is_add)
{
int num = is_add ? 1 : -1;
/* we don't track counts for EXPUNGED records */
if (record->system_flags & FLAG_EXPUNGED)
return;
/* update mailbox header fields */
if (record->system_flags & FLAG_ANSWERED)
i->answered += num;
if (record->system_flags & FLAG_FLAGGED)
i->flagged += num;
if (record->system_flags & FLAG_DELETED)
i->deleted += num;
if (is_add) {
i->exists++;
i->quota_mailbox_used += record->size;
}
else {
if (i->exists) i->exists--;
/* corruption prevention - check we don't go negative */
if (i->quota_mailbox_used > record->size)
i->quota_mailbox_used -= record->size;
else
i->quota_mailbox_used = 0;
}
}
static void mailbox_index_update_counts(struct mailbox *mailbox,
struct index_record *record,
int is_add)
{
mailbox_quota_dirty(mailbox);
mailbox_index_dirty(mailbox);
header_update_counts(&mailbox->i, record, is_add);
}
static int mailbox_index_recalc(struct mailbox *mailbox)
{
struct index_record record;
int r = 0;
uint32_t recno;
annotate_reconstruct_state_t *ars = NULL;
assert(mailbox_index_islocked(mailbox, 1));
/* cache the old used quota */
mailbox_quota_dirty(mailbox);
mailbox_index_dirty(mailbox);
mailbox->i.answered = 0;
mailbox->i.flagged = 0;
mailbox->i.deleted = 0;
mailbox->i.exists = 0;
mailbox->i.quota_mailbox_used = 0;
mailbox->i.quota_annot_used = 0;
annotate_reconstruct_begin(mailbox, &ars);
for (recno = 1; recno <= mailbox->i.num_records; recno++) {
r = mailbox_read_index_record(mailbox, recno, &record);
if (r) goto out;
mailbox_index_update_counts(mailbox, &record, 1);
if (!(record.system_flags & FLAG_UNLINKED))
annotate_reconstruct_add(ars, record.uid);
}
out:
if (r)
annotate_reconstruct_abort(ars);
else
annotate_reconstruct_commit(ars);
return r;
}
/*
* Rewrite an index record in a mailbox - updates all
* necessary tracking fields automatically.
*/
int mailbox_rewrite_index_record(struct mailbox *mailbox,
struct index_record *record)
{
int n;
int r;
struct index_record oldrecord;
indexbuffer_t ibuf;
unsigned char *buf = ibuf.buf;
size_t offset;
int expunge_mode = config_getenum(IMAPOPT_EXPUNGE_MODE);
int immediate = (expunge_mode == IMAP_ENUM_EXPUNGE_MODE_IMMEDIATE ||
expunge_mode == IMAP_ENUM_EXPUNGE_MODE_DEFAULT);
assert(mailbox_index_islocked(mailbox, 1));
assert(record->recno > 0 &&
record->recno <= mailbox->i.num_records);
r = mailbox_read_index_record(mailbox, record->recno, &oldrecord);
if (r) return r;
/* the UID has to match, of course, for it to be the same
* record. XXX - test fields like "internaldate", etc here
* too? Maybe replication should be more strict about it */
assert(record->uid == oldrecord.uid);
assert(message_guid_equal(&oldrecord.guid, &record->guid));
assert(record->modseq >= oldrecord.modseq);
if (oldrecord.system_flags & FLAG_EXPUNGED) {
/* it is a sin to unexpunge a message. unexpunge.c copies
* the data from the old record and appends it with a new
* UID, which is righteous in the eyes of the IMAP client */
assert(record->system_flags & FLAG_EXPUNGED);
}
/* handle immediate expunges here... */
if (immediate && (record->system_flags & FLAG_EXPUNGED))
record->system_flags |= FLAG_UNLINKED;
if (record->system_flags & FLAG_UNLINKED) {
if (expunge_mode == IMAP_ENUM_EXPUNGE_MODE_IMMEDIATE)
mailbox->i.options |= OPT_MAILBOX_NEEDS_REPACK;
mailbox->i.options |= OPT_MAILBOX_NEEDS_UNLINK;
}
else {
/* write the cache record before buffering the message, it
* will set the cache_offset field. */
r = mailbox_append_cache(mailbox, record);
if (r) return r;
}
/* make sure highestmodseq gets updated unless we're
* being silent about it (i.e. marking an already EXPUNGED
* message as UNLINKED, or just updating the content_lines
* field or cache_offset) */
if (record->silent) {
mailbox_index_dirty(mailbox);
}
else {
mailbox_modseq_dirty(mailbox);
record->modseq = mailbox->i.highestmodseq;
record->last_updated = mailbox->last_updated;
}
/* remove the counts for the old copy, and add them for
* the new copy */
mailbox_index_update_counts(mailbox, &oldrecord, 0);
mailbox_index_update_counts(mailbox, record, 1);
mailbox_index_record_to_buf(record, buf);
offset = mailbox->i.start_offset +
(record->recno-1) * mailbox->i.record_size;
n = lseek(mailbox->index_fd, offset, SEEK_SET);
if (n == -1) {
syslog(LOG_ERR, "IOERROR: seeking index record %u for %s: %m",
record->recno, mailbox->name);
return IMAP_IOERROR;
}
n = retry_write(mailbox->index_fd, buf, INDEX_RECORD_SIZE);
if (n != INDEX_RECORD_SIZE) {
syslog(LOG_ERR, "IOERROR: writing index record %u for %s: %m",
record->recno, mailbox->name);
return IMAP_IOERROR;
}
/* expunged tracking */
if ((record->system_flags & FLAG_EXPUNGED) &&
!(oldrecord.system_flags & FLAG_EXPUNGED)) {
if (!mailbox->i.first_expunged ||
mailbox->i.first_expunged > record->last_updated)
mailbox->i.first_expunged = record->last_updated;
if (config_auditlog)
syslog(LOG_NOTICE, "auditlog: expunge sessionid=<%s> "
"mailbox=<%s> uniqueid=<%s> uid=<%u> guid=<%s>",
session_id(), mailbox->name, mailbox->uniqueid,
record->uid, message_guid_encode(&record->guid));
}
return 0;
}
/* append a single message to a mailbox - also updates everything
* automatically. These two functions are the ONLY way to modify
* the contents or tracking fields of a message */
int mailbox_append_index_record(struct mailbox *mailbox,
struct index_record *record)
{
indexbuffer_t ibuf;
unsigned char *buf = ibuf.buf;
size_t offset;
int r;
int n;
struct utimbuf settime;
uint32_t recno;
assert(mailbox_index_islocked(mailbox, 1));
/* Append MUST be a higher UID than any we've yet seen */
assert(record->uid > mailbox->i.last_uid)
/* Append MUST have a message with data */
assert(record->size);
/* GUID must not be null */
assert(!message_guid_isnull(&record->guid));
/* belt AND suspenders - check the previous record too */
if (mailbox->i.num_records) {
struct index_record prev;
r = mailbox_read_index_record(mailbox, mailbox->i.num_records, &prev);
if (r) return r;
assert(prev.uid <= mailbox->i.last_uid);
if (message_guid_equal(&prev.guid, &record->guid)) {
syslog(LOG_INFO, "%s: same message appears twice %u %u",
mailbox->name, prev.uid, record->uid);
/* but it's OK, we won't reject it */
}
}
if (!record->internaldate)
record->internaldate = time(NULL);
if (!record->gmtime)
record->gmtime = record->internaldate;
if (!record->sentdate) {
struct tm *tm = localtime(&record->internaldate);
/* truncate to the day */
tm->tm_sec = 0;
tm->tm_min = 0;
tm->tm_hour = 0;
record->sentdate = mktime(tm);
}
if (!(record->system_flags & FLAG_UNLINKED)) {
/* make the file timestamp correct */
settime.actime = settime.modtime = record->internaldate;
if (utime(mailbox_message_fname(mailbox, record->uid), &settime) == -1)
return IMAP_IOERROR;
/* write the cache record before buffering the message, it
* will set the cache_offset field. */
r = mailbox_append_cache(mailbox, record);
if (r) return r;
}
/* update the highestmodseq if needed */
if (record->silent) {
mailbox_index_dirty(mailbox);
}
else {
mailbox_modseq_dirty(mailbox);
record->modseq = mailbox->i.highestmodseq;
record->last_updated = mailbox->last_updated;
}
/* add counts */
mailbox_index_update_counts(mailbox, record, 1);
mailbox_index_record_to_buf(record, buf);
recno = mailbox->i.num_records + 1;
offset = mailbox->i.start_offset +
((recno - 1) * mailbox->i.record_size);
n = lseek(mailbox->index_fd, offset, SEEK_SET);
if (n == -1) {
syslog(LOG_ERR, "IOERROR: seeking to append for %s: %m",
mailbox->name);
return IMAP_IOERROR;
}
n = retry_write(mailbox->index_fd, buf, INDEX_RECORD_SIZE);
if (n != INDEX_RECORD_SIZE) {
syslog(LOG_ERR, "IOERROR: appending index record for %s: %m",
mailbox->name);
return IMAP_IOERROR;
}
mailbox->i.last_uid = record->uid;
mailbox->i.num_records = recno;
mailbox->index_size += INDEX_RECORD_SIZE;
/* extend the mmaped space for the index file */
if (mailbox->index_len < mailbox->index_size) {
map_refresh(mailbox->index_fd, 1, &mailbox->index_base,
&mailbox->index_len, mailbox->index_size,
"index", mailbox->name);
}
if (config_auditlog)
syslog(LOG_NOTICE, "auditlog: append sessionid=<%s> mailbox=<%s> uniqueid=<%s> uid=<%u> guid=<%s>",
session_id(), mailbox->name, mailbox->uniqueid, record->uid,
message_guid_encode(&record->guid));
/* expunged tracking */
if (record->system_flags & FLAG_EXPUNGED) {
if (!mailbox->i.first_expunged ||
mailbox->i.first_expunged > record->last_updated)
mailbox->i.first_expunged = record->last_updated;
if (config_auditlog)
syslog(LOG_NOTICE, "auditlog: expunge sessionid=<%s> "
"mailbox=<%s> uniqueid=<%s> uid=<%u> guid=<%s>",
session_id(), mailbox->name, mailbox->uniqueid,
record->uid, message_guid_encode(&record->guid));
}
/* yep, it could even be pre-unlinked in 'default' expunge mode, joy */
if (record->system_flags & FLAG_UNLINKED) {
if (config_auditlog)
syslog(LOG_NOTICE, "auditlog: unlink sessionid=<%s> "
"mailbox=<%s> uniqueid=<%s> uid=<%u>",
session_id(), mailbox->name, mailbox->uniqueid,
record->uid);
}
return 0;
}
static void mailbox_message_unlink(struct mailbox *mailbox, uint32_t uid)
{
const char *fname = mailbox_message_fname(mailbox, uid);
/* no error, we removed a file */
if (unlink(fname) == 0) {
if (config_auditlog)
syslog(LOG_NOTICE, "auditlog: unlink sessionid=<%s> "
"mailbox=<%s> uniqueid=<%s> uid=<%u>",
session_id(), mailbox->name, mailbox->uniqueid, uid);
}
}
/* need a mailbox exclusive lock, we're removing files */
static int mailbox_index_unlink(struct mailbox *mailbox)
{
struct index_record record;
uint32_t recno;
int r;
syslog(LOG_INFO, "Unlinking files in mailbox %s", mailbox->name);
/* note: this may try to unlink the same files more than once,
* but them's the breaks - the alternative is yet another
* system flag which gets updated once done! */
for (recno = 1; recno <= mailbox->i.num_records; recno++) {
r = mailbox_read_index_record(mailbox, recno, &record);
if (r) return r;
if (record.system_flags & FLAG_UNLINKED)
mailbox_message_unlink(mailbox, record.uid);
}
/* need to clear the flag, even if nothing needed unlinking! */
mailbox_index_dirty(mailbox);
mailbox->i.options &= ~OPT_MAILBOX_NEEDS_UNLINK;
mailbox_commit(mailbox);
return 0;
}
int mailbox_repack_setup(struct mailbox *mailbox,
struct mailbox_repack **repackptr)
{
struct mailbox_repack *repack = xzmalloc(sizeof(struct mailbox_repack));
const char *fname;
indexbuffer_t ibuf;
unsigned char *buf = ibuf.buf;
int n;
/* init */
repack->mailbox = mailbox;
repack->i = mailbox->i; /* struct copy */
repack->newindex_fd = -1;
repack->newcache_fd = -1;
/* new files */
fname = mailbox_meta_newfname(mailbox, META_INDEX);
repack->newindex_fd = open(fname, O_RDWR|O_TRUNC|O_CREAT, 0666);
if (repack->newindex_fd == -1) goto fail;
fname = mailbox_meta_newfname(mailbox, META_CACHE);
repack->newcache_fd = open(fname, O_RDWR|O_TRUNC|O_CREAT, 0666);
if (repack->newcache_fd == -1) goto fail;
/* update the generation number */
repack->i.generation_no++;
/* zero out some values */
repack->i.num_records = 0;
repack->i.quota_mailbox_used = 0;
repack->i.num_records = 0;
repack->i.answered = 0;
repack->i.deleted = 0;
repack->i.flagged = 0;
repack->i.exists = 0;
repack->i.first_expunged = 0;
repack->i.leaked_cache_records = 0;
/* prepare initial header buffer */
mailbox_index_header_to_buf(&repack->i, buf);
/* write initial headers */
n = retry_write(repack->newcache_fd, buf, 4);
if (n == -1) goto fail;
n = retry_write(repack->newindex_fd, buf, INDEX_HEADER_SIZE);
if (n == -1) goto fail;
*repackptr = repack;
return 0;
fail:
mailbox_repack_abort(&repack);
return IMAP_IOERROR;
}
int mailbox_repack_add(struct mailbox_repack *repack,
struct index_record *record)
{
int r;
int n;
indexbuffer_t ibuf;
unsigned char *buf = ibuf.buf;
/* write out the new cache record - need to clear the cache_offset
* so it gets reset in the new record */
record->cache_offset = 0;
r = cache_append_record(repack->newcache_fd, record);
if (r) return r;
/* update counters */
header_update_counts(&repack->i, record, 1);
/* write the index record out */
mailbox_index_record_to_buf(record, buf);
n = retry_write(repack->newindex_fd, buf, INDEX_RECORD_SIZE);
if (n == -1)
return IMAP_IOERROR;
repack->i.num_records++;
return 0;
}
/* clean up memory structures and abort repack */
void mailbox_repack_abort(struct mailbox_repack **repackptr)
{
struct mailbox_repack *repack = *repackptr;
if (!repack) return; /* safe against double-free */
if (repack->newcache_fd != -1) close(repack->newcache_fd);
unlink(mailbox_meta_newfname(repack->mailbox, META_CACHE));
if (repack->newindex_fd != -1) close(repack->newindex_fd);
unlink(mailbox_meta_newfname(repack->mailbox, META_INDEX));
free(repack);
*repackptr = NULL;
}
int mailbox_repack_commit(struct mailbox_repack **repackptr)
{
indexbuffer_t ibuf;
unsigned char *buf = ibuf.buf;
struct mailbox_repack *repack = *repackptr;
int n;
int r = 0;
assert(repack);
repack->i.last_repack_time = time(0);
/* rewrite the header with updated details */
mailbox_index_header_to_buf(&repack->i, buf);
n = lseek(repack->newindex_fd, 0, SEEK_SET);
if (n == -1) {
r = IMAP_IOERROR;
goto fail;
}
n = retry_write(repack->newindex_fd, buf, INDEX_HEADER_SIZE);
if (n == -1) {
r = IMAP_IOERROR;
goto fail;
}
/* ensure everything is committed to disk */
if (fsync(repack->newindex_fd) || fsync(repack->newcache_fd)) {
r = IMAP_IOERROR;
goto fail;
}
close(repack->newcache_fd);
repack->newcache_fd = -1;
close(repack->newindex_fd);
repack->newindex_fd = -1;
/* rename index first - loader will handle un-renamed cache if
* the generation is lower */
r = mailbox_meta_rename(repack->mailbox, META_INDEX);
if (r) goto fail;
mailbox_meta_rename(repack->mailbox, META_CACHE);
free(repack);
*repackptr = NULL;
return 0;
fail:
mailbox_repack_abort(repackptr);
return r;
}
/* need a mailbox exclusive lock, we're rewriting files */
static int mailbox_index_repack(struct mailbox *mailbox)
{
struct mailbox_repack *repack = NULL;
uint32_t recno;
struct index_record record;
int r = IMAP_IOERROR;
syslog(LOG_INFO, "Repacking mailbox %s", mailbox->name);
r = annotatemore_begin();
if (r) return r;
r = mailbox_repack_setup(mailbox, &repack);
if (r) goto fail;
for (recno = 1; recno <= mailbox->i.num_records; recno++) {
r = mailbox_read_index_record(mailbox, recno, &record);
if (r) goto fail;
/* been marked for removal, just skip */
if (!record.uid) continue;
/* we aren't keeping unlinked files, that's kind of the point */
if (record.system_flags & FLAG_UNLINKED) {
/* just in case it was left lying around */
/* XXX - log error if unlink fails */
mailbox_message_unlink(mailbox, record.uid);
if (record.modseq > repack->i.deletedmodseq)
repack->i.deletedmodseq = record.modseq;
r = annotate_msg_expunge(mailbox, record.uid);
if (r) goto fail;
continue;
}
/* read in the old cache record */
r = mailbox_cacherecord(mailbox, &record);
if (r) goto fail;
r = mailbox_repack_add(repack, &record);
if (r) goto fail;
}
/* we unlinked any "needs unlink" in the process */
repack->i.options &= ~(OPT_MAILBOX_NEEDS_REPACK|OPT_MAILBOX_NEEDS_UNLINK);
r = mailbox_repack_commit(&repack);
if (r) goto fail;
r = annotatemore_commit();
if (r) goto fail;
return 0;
fail:
annotatemore_abort();
mailbox_repack_abort(&repack);
return r;
}
/*
* Used by mailbox_rename() to expunge all messages in INBOX
*/
static unsigned expungeall(struct mailbox *mailbox __attribute__((unused)),
struct index_record *record __attribute__((unused)),
void *rock __attribute__((unused)))
{
return 1;
}
/*
* Expunge decision proc used by mailbox_expunge()
* to expunge \Deleted messages.
*/
static unsigned expungedeleted(struct mailbox *mailbox __attribute__((unused)),
struct index_record *record,
void *rock __attribute__((unused)))
{
if (record->system_flags & FLAG_DELETED)
return 1;
return 0;
}
/*
* Perform an expunge operation on 'mailbox'. If nonzero, the
* function pointed to by 'decideproc' is called (with 'deciderock') to
* determine which messages to expunge. If 'decideproc' is a null pointer,
* then messages with the \Deleted flag are expunged.
*/
int mailbox_expunge(struct mailbox *mailbox,
mailbox_decideproc_t *decideproc, void *deciderock,
unsigned *nexpunged)
{
int r = 0;
int numexpunged = 0;
uint32_t recno;
struct index_record record;
assert(mailbox_index_islocked(mailbox, 1));
/* anything to do? */
if (!mailbox->i.num_records) {
if (nexpunged) *nexpunged = 0;
return 0;
}
if (!decideproc) decideproc = expungedeleted;
for (recno = 1; recno <= mailbox->i.num_records; recno++) {
r = mailbox_read_index_record(mailbox, recno, &record);
if (r) continue;
/* skip already expunged records */
if (record.system_flags & FLAG_EXPUNGED)
continue;
if (decideproc(mailbox, &record, deciderock)) {
numexpunged++;
/* mark deleted */
record.system_flags |= FLAG_EXPUNGED;
r = mailbox_rewrite_index_record(mailbox, &record);
if (r) return IMAP_IOERROR;
}
}
if (numexpunged > 0) {
syslog(LOG_NOTICE, "Expunged %d messages from %s",
numexpunged, mailbox->name);
}
if (nexpunged) *nexpunged = numexpunged;
return 0;
}
int mailbox_expunge_cleanup(struct mailbox *mailbox, time_t expunge_mark,
unsigned *ndeleted)
{
uint32_t recno;
int dirty = 0;
unsigned numdeleted = 0;
struct index_record record;
time_t first_expunged = 0;
int r = 0;
/* run the actual expunge phase */
for (recno = 1; recno <= mailbox->i.num_records; recno++) {
if (mailbox_read_index_record(mailbox, recno, &record))
continue;
/* already unlinked, skip it (but dirty so we mark a repack is needed) */
if (record.system_flags & FLAG_UNLINKED) {
dirty = 1;
continue;
}
/* not actually expunged, skip it */
if (!(record.system_flags & FLAG_EXPUNGED))
continue;
/* not stale enough yet, skip it - but track the updated time
* so we know when to run again */
if (record.last_updated > expunge_mark) {
if (!first_expunged || (first_expunged > record.last_updated))
first_expunged = record.last_updated;
continue;
}
dirty = 1;
numdeleted++;
record.system_flags |= FLAG_UNLINKED;
record.silent = 1;
if (mailbox_rewrite_index_record(mailbox, &record)) {
syslog(LOG_ERR, "failed to write changes to %s recno %d",
mailbox->name, recno);
break;
}
}
if (dirty) {
mailbox_index_dirty(mailbox);
mailbox->i.options |= OPT_MAILBOX_NEEDS_REPACK;
mailbox->i.first_expunged = first_expunged;
}
if (ndeleted) *ndeleted = numdeleted;
return r;
}
int mailbox_internal_seen(struct mailbox *mailbox, const char *userid)
{
/* shared seen - everyone's state is internal */
if (mailbox->i.options & OPT_IMAP_SHAREDSEEN)
return 1;
/* no username => use internal as well */
if (!userid)
return 1;
/* otherwise the owner's seen state is internal */
return mboxname_userownsmailbox(userid, mailbox->name);
}
/* returns a mailbox locked in MAILBOX EXCLUSIVE mode, so you
* don't need to lock the index file to work with it :) */
int mailbox_create(const char *name,
const char *part,
const char *acl,
const char *uniqueid,
const char *specialuse,
int options,
unsigned uidvalidity,
struct mailbox **mailboxptr)
{
int r = 0;
char quotaroot[MAX_MAILBOX_BUFFER];
int hasquota;
char *fname;
struct mailbox *mailbox = NULL;
int n;
uint32_t generation_buf;
int createfnames[] = { META_INDEX, META_CACHE, META_HEADER, 0 };
struct mailboxlist *listitem;
strarray_t *initial_flags = NULL;
/* if we already have this name open then that's an error too */
listitem = find_listitem(name);
if (listitem) return IMAP_MAILBOX_LOCKED;
listitem = create_listitem(name);
mailbox = &listitem->m;
/* if we can't get an exclusive lock first try, there's something
* racy going on! */
r = mboxname_lock(name, &listitem->l, LOCK_NONBLOCKING);
if (r) goto done;
mailbox->part = xstrdup(part);
mailbox->acl = xstrdup(acl);
if (specialuse) mailbox->specialuse = xstrdup(specialuse);
hasquota = quota_findroot(quotaroot, sizeof(quotaroot), name);
/* ensure all paths exist */
for (n = 0; createfnames[n]; n++) {
fname = mailbox_meta_fname(mailbox, createfnames[n]);
if (!fname) {
syslog(LOG_ERR, "IOERROR: Mailbox name too long (%s)", mailbox->name);
r = IMAP_MAILBOX_BADNAME;
goto done;
}
if (cyrus_mkdir(fname, 0755) == -1) {
syslog(LOG_ERR, "IOERROR: creating %s: %m", fname);
r = IMAP_IOERROR;
goto done;
}
}
/* ensure we can fit the longest possible file name */
fname = mailbox_message_fname(mailbox, UINT32_MAX);
if (!fname) {
syslog(LOG_ERR, "IOERROR: Mailbox name too long (%s)", mailbox->name);
r = IMAP_MAILBOX_BADNAME;
goto done;
}
/* and create the directory too :) */
if (cyrus_mkdir(fname, 0755) == -1) {
syslog(LOG_ERR, "IOERROR: creating %s: %m", fname);
r = IMAP_IOERROR;
goto done;
}
fname = mailbox_meta_fname(mailbox, META_INDEX);
if (!fname) {
syslog(LOG_ERR, "IOERROR: Mailbox name too long (%s)", mailbox->name);
r = IMAP_MAILBOX_BADNAME;
goto done;
}
mailbox->index_fd = open(fname, O_RDWR|O_TRUNC|O_CREAT, 0666);
if (mailbox->index_fd == -1) {
syslog(LOG_ERR, "IOERROR: creating %s: %m", fname);
r = IMAP_IOERROR;
goto done;
}
r = lock_blocking(mailbox->index_fd);
if (r) {
syslog(LOG_ERR, "IOERROR: locking %s: %m", fname);
r = IMAP_IOERROR;
goto done;
}
mailbox->index_locktype = LOCK_EXCLUSIVE;
fname = mailbox_meta_fname(mailbox, META_CACHE);
if (!fname) {
syslog(LOG_ERR, "IOERROR: Mailbox name too long (%s)", mailbox->name);
r = IMAP_MAILBOX_BADNAME;
goto done;
}
mailbox->cache_fd = open(fname, O_RDWR|O_TRUNC|O_CREAT, 0666);
if (mailbox->cache_fd == -1) {
syslog(LOG_ERR, "IOERROR: creating %s: %m", fname);
r = IMAP_IOERROR;
goto done;
}
if (hasquota) mailbox_set_quotaroot(mailbox, quotaroot);
/* ensure a UIDVALIDITY is set */
if (!uidvalidity)
uidvalidity = time(0);
/* init non-zero fields */
mailbox_index_dirty(mailbox);
mailbox->i.minor_version = MAILBOX_MINOR_VERSION;
mailbox->i.start_offset = INDEX_HEADER_SIZE;
mailbox->i.record_size = INDEX_RECORD_SIZE;
mailbox->i.uidvalidity = uidvalidity;
mailbox->i.options = options;
mailbox->i.highestmodseq = 1;
/* initialise header size field so appends calculate the
* correct map size */
mailbox->index_size = INDEX_HEADER_SIZE;
mailbox->header_dirty = 1;
if (!uniqueid) {
mailbox_make_uniqueid(mailbox);
} else {
mailbox->uniqueid = xstrdup(uniqueid);
}
/* pre-set any required permanent flags */
if (config_getstring(IMAPOPT_MAILBOX_INITIAL_FLAGS)) {
const char *val = config_getstring(IMAPOPT_MAILBOX_INITIAL_FLAGS);
int i;
initial_flags = strarray_split(val, NULL);
for (i = 0; i < initial_flags->count; i++) {
const char *flag = strarray_nth(initial_flags, i);
r = mailbox_user_flag(mailbox, flag, NULL, /*create*/1);
if (r) goto done;
}
}
/* write out the initial generation number to the cache file */
generation_buf = htonl(mailbox->i.generation_no);
n = retry_write(mailbox->cache_fd, (char *)&generation_buf, 4);
if (n != 4 || fsync(mailbox->cache_fd)) {
syslog(LOG_ERR, "IOERROR: writing initial cache for %s: %m",
mailbox->name);
r = IMAP_IOERROR;
goto done;
}
r = seen_create_mailbox(NULL, mailbox);
if (r) goto done;
r = mailbox_commit(mailbox);
if (r) goto done;
if (config_auditlog)
syslog(LOG_NOTICE, "auditlog: create sessionid=<%s> "
"mailbox=<%s> uniqueid=<%s>",
session_id(),
mailbox->name, mailbox->uniqueid);
done:
if (!r && mailboxptr)
*mailboxptr = mailbox;
else
mailbox_close(&mailbox);
strarray_free(initial_flags);
return r;
}
/*
* Remove all files in directory
*/
static void mailbox_delete_files(char *path)
{
DIR *dirp;
struct dirent *f;
char buf[MAX_MAILBOX_PATH+1];
char *tail;
strlcpy(buf, path, sizeof(buf));
if(strlen(buf) >= sizeof(buf) - 2) {
syslog(LOG_ERR, "IOERROR: Path too long (%s)", buf);
fatal("path too long", EC_OSFILE);
}
tail = buf + strlen(buf);
*tail++ = '/';
*tail = '\0';
dirp = opendir(path);
if (dirp) {
while ((f = readdir(dirp))!=NULL) {
if (f->d_name[0] == '.'
&& (f->d_name[1] == '\0'
|| (f->d_name[1] == '.' &&
f->d_name[2] == '\0'))) {
/* readdir() can return "." or "..", and I got a bug report
that SCO might blow the file system to smithereens if we
unlink(".."). Let's not do that. */
continue;
}
if(strlen(buf) + strlen(f->d_name) >= sizeof(buf)) {
syslog(LOG_ERR, "IOERROR: Path too long (%s + %s)",
buf, f->d_name);
fatal("Path too long", EC_OSFILE);
}
strcpy(tail, f->d_name);
unlink(buf);
*tail = '\0';
}
closedir(dirp);
}
}
/* Callback for use by cmd_delete */
static int chkchildren(char *name,
int matchlen __attribute__((unused)),
int maycreate __attribute__((unused)),
void *rock)
{
const char *part = (const char *)rock;
struct mboxlist_entry *mbentry;
int r;
r = mboxlist_lookup(name, &mbentry, 0);
if (r) return r;
if (!strcmp(part, mbentry->partition))
r = CYRUSDB_DONE;
mboxlist_entry_free(&mbentry);
- return 0;
+ return r;
}
/*
* Delete and close the mailbox 'mailbox'. Closes 'mailbox' whether
* or not the deletion was successful. Requires a locked mailbox.
*/
int mailbox_delete(struct mailbox **mailboxptr)
{
int r = 0;
struct mailbox *mailbox = *mailboxptr;
/* mark the quota removed */
mailbox_quota_dirty(mailbox);
/* mark the mailbox deleted */
mailbox_index_dirty(mailbox);
mailbox->i.options |= OPT_MAILBOX_DELETED;
/* commit the changes */
r = mailbox_commit(mailbox);
if (r) return r;
/* remove any seen */
seen_delete_mailbox(NULL, mailbox);
/* can't unlink any files yet, because our promise to other
* users of the mailbox applies! Can only unlink with an
* exclusive lock. mailbox_close will try to get one of
* those.
*/
syslog(LOG_NOTICE, "Deleted mailbox %s", mailbox->name);
if (config_auditlog)
syslog(LOG_NOTICE, "auditlog: delete sessionid=<%s> "
"mailbox=<%s> uniqueid=<%s>",
session_id(),
mailbox->name, mailbox->uniqueid);
mailbox_close(mailboxptr);
return 0;
}
/* XXX - move this part of cleanup into mboxlist. Really
* needs to be done with mailboxes.db locked so nobody can
* try to create a mailbox while the delete is underway.
* VERY tight race condition exists right now... */
/* we need an exclusive namelock for this */
int mailbox_delete_cleanup(const char *part, const char *name)
{
char nbuf[MAX_MAILBOX_BUFFER];
char pbuf[MAX_MAILBOX_PATH+1], mbuf[MAX_MAILBOX_PATH+1];
char *ntail, *ptail, *mtail = NULL;
char *path, *mpath;
struct mboxlist_entry *mbentry;
int r;
/* XXX - use explicit paths to each type of file */
/* Flush data (message file) directory */
path = mboxname_datapath(part, name, 0);
mailbox_delete_files(path);
strlcpy(pbuf, path, sizeof(pbuf));
ptail = pbuf + strlen(pbuf);
/* Flush metadata directory */
mpath = mboxname_metapath(part, name, 0, 0);
if (strcmp(path, mpath)) {
mailbox_delete_files(mpath);
strlcpy(mbuf, mpath, sizeof(mbuf));
mtail = mbuf + strlen(mbuf);
}
strlcpy(nbuf, name, sizeof(nbuf));
ntail = nbuf + strlen(nbuf);
do {
/* Check if the mailbox has children */
strcpy(ntail, ".*");
r = mboxlist_findall(NULL, nbuf, 1, NULL, NULL, chkchildren, (void *)part);
if (r != 0) break; /* We short-circuit with CYRUSDB_DONE */
/* No children, remove mailbox spool dir(s) */
if (rmdir(pbuf)) {
syslog(LOG_NOTICE,
"Remove of supposedly empty directory %s failed: %m",
pbuf);
}
ptail = strrchr(pbuf, '/');
*ptail ='\0';
if (mtail) {
if (rmdir(mbuf)) {
syslog(LOG_NOTICE,
"Remove of supposedly empty directory %s failed: %m",
mbuf);
}
mtail = strrchr(mbuf, '/');
*mtail ='\0';
}
/* Check if parent mailbox exists */
*ntail = '\0';
ntail = strrchr(nbuf, '.');
if (!ntail || strchr(ntail, '!')) {
/* Hit top of hierarchy or domain separator */
break;
}
*ntail = '\0';
if (!strcmp(nbuf, "user") ||
((ntail - nbuf > 5) && !strcmp(ntail-5, "!user"))) {
/* Hit top of 'user' hierarchy */
break;
}
r = mboxlist_lookup(nbuf, &mbentry, NULL);
/* not the same partition, we can keep cleaning up */
if (!r) {
if (strcmp(mbentry->partition, part))
r = IMAP_MAILBOX_NONEXISTENT;
mboxlist_entry_free(&mbentry);
}
} while (r == IMAP_MAILBOX_NONEXISTENT);
return 0;
}
struct meta_file {
unsigned long metaflag;
int optional;
int nolink;
};
static struct meta_file meta_files[] = {
{ META_HEADER, 0, 1 },
{ META_INDEX, 0, 1 },
{ META_CACHE, 0, 1 },
{ META_SQUAT, 1, 0 },
{ META_ANNOTATIONS, 1, 0 },
{ 0, 0, 0 }
};
int mailbox_copy_files(struct mailbox *mailbox, const char *newpart,
const char *newname)
{
char oldbuf[MAX_MAILBOX_PATH], newbuf[MAX_MAILBOX_PATH];
struct meta_file *mf;
uint32_t recno;
struct index_record record;
int r = 0;
/* Copy over meta files */
for (mf = meta_files; mf->metaflag; mf++) {
struct stat sbuf;
strncpy(oldbuf, mailbox_meta_fname(mailbox, mf->metaflag),
MAX_MAILBOX_PATH);
strncpy(newbuf, mboxname_metapath(newpart, newname, mf->metaflag, 0),
MAX_MAILBOX_PATH);
unlink(newbuf); /* Make link() possible */
if (!mf->optional || stat(oldbuf, &sbuf) != -1) {
r = mailbox_copyfile(oldbuf, newbuf, mf->nolink);
if (r) return r;
}
}
for (recno = 1; recno <= mailbox->i.num_records; recno++) {
r = mailbox_read_index_record(mailbox, recno, &record);
if (r) return r;
if (record.system_flags & FLAG_UNLINKED)
continue;
strncpy(oldbuf, mailbox_message_fname(mailbox, record.uid),
MAX_MAILBOX_PATH);
strncpy(newbuf, mboxname_datapath(newpart, newname, record.uid),
MAX_MAILBOX_PATH);
r = mailbox_copyfile(oldbuf, newbuf, 0);
if (r) return r;
}
return 0;
}
/* if 'userid' is set, we perform the funky RENAME INBOX INBOX.old
semantics, regardless of whether or not the name of the mailbox is
'user.foo'.*/
/* requires a write-locked oldmailbox pointer, since we delete it
immediately afterwards */
int mailbox_rename_copy(struct mailbox *oldmailbox,
const char *newname,
const char *newpartition,
const char *userid, int ignorequota,
struct mailbox **newmailboxptr)
{
int r;
struct mailbox *newmailbox = NULL;
char *newquotaroot = NULL;
assert(mailbox_index_islocked(oldmailbox, 1));
/* Create new mailbox */
r = mailbox_create(newname, newpartition,
oldmailbox->acl, (userid ? NULL : oldmailbox->uniqueid),
oldmailbox->specialuse, oldmailbox->i.options,
0, &newmailbox);
if (r) return r;
/* Check quota if necessary */
if (!ignorequota && newmailbox->quotaroot && (!oldmailbox->quotaroot ||
strcmp(oldmailbox->quotaroot, newmailbox->quotaroot))) {
quota_t usage[QUOTA_NUMRESOURCES];
mailbox_get_usage(oldmailbox, usage);
r = mailbox_quota_check(newmailbox, usage);
/* then we abort - no space to rename */
if (r)
goto fail;
}
if (newmailbox->quotaroot) newquotaroot = xstrdup(newmailbox->quotaroot);
r = mailbox_copy_files(oldmailbox, newpartition, newname);
if (r) goto fail;
/* Re-open index file */
r = mailbox_open_index(newmailbox);
if (r) goto fail;
/* Re-open header file */
r = mailbox_read_index_header(newmailbox);
if (r) goto fail;
/* read in the flags */
r = mailbox_read_header(newmailbox, NULL);
if (r) goto fail;
/* update uidvalidity */
newmailbox->i.uidvalidity = time(0);
/* INBOX rename - change uniqueid */
if (userid) mailbox_make_uniqueid(newmailbox);
r = seen_copy(userid, oldmailbox, newmailbox);
if (r) goto fail;
/* mark the "used" back to zero, so it updates the new quota! */
mailbox_set_quotaroot(newmailbox, newquotaroot);
mailbox_quota_dirty(newmailbox);
memset(newmailbox->quota_previously_used, 0, sizeof(newmailbox->quota_previously_used));
/* commit the index changes */
r = mailbox_commit(newmailbox);
if (r) goto fail;
if (config_auditlog)
syslog(LOG_NOTICE, "auditlog: rename sessionid=<%s> "
"oldmailbox=<%s> newmailbox=<%s> uniqueid=<%s>",
session_id(),
oldmailbox->name, newname, newmailbox->uniqueid);
if (newmailboxptr) *newmailboxptr = newmailbox;
else mailbox_close(&newmailbox);
free(newquotaroot);
return 0;
fail:
/* failure and back out */
/* XXX - per file paths, need to clean up individual filenames */
mailbox_delete_cleanup(newmailbox->part, newmailbox->name);
mailbox_close(&newmailbox);
free(newquotaroot);
return r;
}
int mailbox_rename_cleanup(struct mailbox **mailboxptr, int isinbox)
{
int r = 0;
struct mailbox *oldmailbox = *mailboxptr;
char *name = xstrdup(oldmailbox->name);
if (isinbox) {
/* Expunge old mailbox */
r = mailbox_expunge(oldmailbox, expungeall, (char *)0, NULL);
if (!r) r = mailbox_commit(oldmailbox);
mailbox_close(mailboxptr);
} else {
r = mailbox_delete(mailboxptr);
}
if (r) {
syslog(LOG_CRIT,
"Rename Failure during mailbox_rename_cleanup (%s), " \
"potential leaked space (%s)", name,
error_message(r));
}
free(name);
return r;
}
/*
* Copy (or link) the file 'from' to the file 'to'
*/
int mailbox_copyfile(const char *from, const char *to, int nolink)
{
int flags = 0;
if (nolink) flags |= COPYFILE_NOLINK;
return cyrus_copyfile(from, to, flags);
}
/* ---------------------------------------------------------------------- */
/* RECONSTRUCT SUPPORT */
/* ---------------------------------------------------------------------- */
#define UIDGROW 300
struct found_files {
unsigned long *uids;
unsigned nalloc;
unsigned nused;
};
#define FOUND_FILES_INITIALIZER \
{ NULL, 0, 0 }
static int sort_uid(const void *a, const void *b)
{
return *(unsigned long *)a - *(unsigned long *)b;
}
static void add_files(struct found_files *ff, unsigned long uid)
{
/* make sure there's space */
if (ff->nused >= ff->nalloc) {
ff->nalloc += UIDGROW;
ff->uids = xrealloc(ff->uids, ff->nalloc * sizeof(unsigned long));
}
ff->uids[ff->nused++] = uid;
}
static void free_files(struct found_files *ff)
{
free(ff->uids);
ff->uids = NULL;
ff->nalloc = 0;
ff->nused = 0;
}
static int parse_datafilename(const char *name, uint32_t *uidp)
{
const char *p = name;
/* must be at least one digit */
if (!cyrus_isdigit(*p)) return IMAP_MAILBOX_BADNAME;
do {
p++;
} while cyrus_isdigit(*p);
/* has to end with a dot */
if (*p != '.') return IMAP_MAILBOX_BADNAME;
if (p[1]) return IMAP_MAILBOX_BADNAME;
return parseuint32(name, &p, uidp);
}
static int find_files(struct mailbox *mailbox, struct found_files *files,
int flags)
{
const char *dirpath;
DIR *dirp;
struct dirent *dirent;
uint32_t uid;
const char *p;
char buf[MAX_MAILBOX_PATH];
struct stat sbuf;
int r;
dirpath = mailbox_datapath(mailbox);
if (!dirpath) return IMAP_MAILBOX_BADNAME;
dirp = opendir(dirpath);
if (!dirp) {
printf("%s data directory is missing %s\n", mailbox->name, dirpath);
/* need to re-create data directory */
if (cyrus_mkdir(dirpath, 0755) == -1)
return IMAP_IOERROR;
if (mkdir(dirpath, 0755) == -1)
return IMAP_IOERROR;
return 0;
}
/* data directory is fine */
while ((dirent = readdir(dirp)) != NULL) {
p = dirent->d_name;
if (*p == '.') continue; /* dot files */
if (!strncmp(p, "cyrus.", 6)) continue; /* cyrus.* files */
r = parse_datafilename(p, &uid);
if (r) {
/* check if it's a directory */
snprintf(buf, MAX_MAILBOX_PATH, "%s/%s", dirpath, dirent->d_name);
if (stat(buf, &sbuf) == -1) continue; /* ignore ephemeral */
if (!S_ISDIR(sbuf.st_mode)) {
if (!(flags & RECONSTRUCT_IGNORE_ODDFILES)) {
printf("%s odd file %s\n", mailbox->name, buf);
syslog(LOG_ERR, "%s odd file %s", mailbox->name, buf);
if (flags & RECONSTRUCT_REMOVE_ODDFILES)
unlink(buf);
else {
printf("run reconstruct with -O to remove odd files\n");
syslog(LOG_ERR, "run reconstruct with -O to "
"remove odd files");
}
}
}
}
else {
/* it's one of ours :) */
add_files(files, uid);
}
}
closedir(dirp);
/* make sure UIDs are sorted for comparison */
qsort(files->uids, files->nused, sizeof(unsigned long), sort_uid);
return 0;
}
static void cleanup_stale_expunged(struct mailbox *mailbox)
{
const char *fname;
int expunge_fd = -1;
const char *expunge_base = NULL;
size_t expunge_len = 0; /* mapped size */
unsigned long expunge_num;
unsigned long emapnum;
uint32_t erecno;
uint32_t uid;
bit32 eoffset, expungerecord_size;
const char *bufp;
struct stat sbuf;
int count = 0;
int r;
/* it's always read-writes */
fname = mailbox_meta_fname(mailbox, META_EXPUNGE);
expunge_fd = open(fname, O_RDWR, 0);
if (expunge_fd == -1)
goto done; /* yay, no crappy expunge file */
/* boo - gotta read and find out the UIDs */
r = fstat(expunge_fd, &sbuf);
if (r == -1)
goto done;
if (sbuf.st_size < INDEX_HEADER_SIZE)
goto done;
map_refresh(expunge_fd, 1, &expunge_base,
&expunge_len, sbuf.st_size, "expunge",
mailbox->name);
/* use the expunge file's header information just in case
* versions are skewed for some reason */
eoffset = ntohl(*((bit32 *)(expunge_base+OFFSET_START_OFFSET)));
expungerecord_size = ntohl(*((bit32 *)(expunge_base+OFFSET_RECORD_SIZE)));
/* bogus data at the start of the expunge file? */
if (!eoffset || !expungerecord_size)
goto done;
expunge_num = ntohl(*((bit32 *)(expunge_base+OFFSET_NUM_RECORDS)));
emapnum = (sbuf.st_size - eoffset) / expungerecord_size;
if (emapnum < expunge_num) {
expunge_num = emapnum;
}
/* add every UID to the files list */
for (erecno = 1; erecno <= expunge_num; erecno++) {
bufp = expunge_base + eoffset + (erecno-1)*expungerecord_size;
uid = ntohl(*((bit32 *)(bufp+OFFSET_UID)));
fname = mailbox_message_fname(mailbox, uid);
unlink(fname);
count++;
}
printf("%s removed %d records from stale cyrus.expunge\n",
mailbox->name, count);
fname = mailbox_meta_fname(mailbox, META_EXPUNGE);
unlink(fname);
done:
if (expunge_base) map_free(&expunge_base, &expunge_len);
if (expunge_fd != -1) close(expunge_fd);
}
/* this is kind of like mailbox_create, but we try to rescue
* what we can from the filesystem! */
static int mailbox_reconstruct_create(const char *name, struct mailbox **mbptr)
{
struct mailbox *mailbox = NULL;
int options = config_getint(IMAPOPT_MAILBOX_DEFAULT_OPTIONS)
| OPT_POP3_NEW_UIDL;
struct mboxlist_entry *mbentry = NULL;
struct mailboxlist *listitem;
int r;
/* make sure it's not already open. Very odd, since we already
* discovered it's not openable! */
listitem = find_listitem(name);
if (listitem) return IMAP_MAILBOX_LOCKED;
listitem = create_listitem(name);
mailbox = &listitem->m;
/* if we can't get an exclusive lock first try, there's something
* racy going on! */
r = mboxname_lock(name, &listitem->l, LOCK_NONBLOCKING);
if (r) goto done;
/* Start by looking up current data in mailbox list */
/* XXX - no mboxlist entry? Can we recover? */
r = mboxlist_lookup(name, &mbentry, NULL);
if (r) goto done;
mailbox->part = xstrdup(mbentry->partition);
mailbox->acl = xstrdup(mbentry->acl);
syslog(LOG_NOTICE, "create new mailbox %s", name);
/* Attempt to open index */
r = mailbox_open_index(mailbox);
if (!r) r = mailbox_read_index_header(mailbox);
if (r) {
printf("%s: failed to read index header\n", mailbox->name);
syslog(LOG_ERR, "failed to read index header for %s", mailbox->name);
/* no cyrus.index file at all - well, we're in a pickle!
* no point trying to rescue anything else... */
mailbox_close(&mailbox);
r = mailbox_create(name, mbentry->partition, mbentry->acl,
mbentry->specialuse, NULL, options, 0, mbptr);
mboxlist_entry_free(&mbentry);
return r;
}
mboxlist_entry_free(&mbentry);
/* read header, if it is not there, we need to create it */
r = mailbox_read_header(mailbox, NULL);
if (r) {
/* Header failed to read - recreate it */
printf("%s: failed to read header file\n", mailbox->name);
syslog(LOG_ERR, "failed to read header file for %s", mailbox->name);
mailbox_make_uniqueid(mailbox);
r = mailbox_commit(mailbox);
if (r) goto done;
}
if (mailbox->header_file_crc != mailbox->i.header_file_crc) {
mailbox->i.header_file_crc = mailbox->header_file_crc;
printf("%s: header file CRC mismatch, correcting\n", mailbox->name);
syslog(LOG_ERR, "%s: header file CRC mismatch, correcting", mailbox->name);
mailbox_index_dirty(mailbox);
r = mailbox_commit(mailbox);
if (r) goto done;
}
done:
if (r) mailbox_close(&mailbox);
else *mbptr = mailbox;
return r;
}
static int mailbox_reconstruct_acl(struct mailbox *mailbox, int flags)
{
int make_changes = flags & RECONSTRUCT_MAKE_CHANGES;
char *acl = NULL;
int r;
r = mailbox_read_header(mailbox, &acl);
if (r) return r;
if (strcmp(mailbox->acl, acl)) {
printf("%s: update acl from header %s => %s\n", mailbox->name,
mailbox->acl, acl);
if (make_changes) {
struct mboxlist_entry *mbentry = NULL;
r = mboxlist_lookup(mailbox->name, &mbentry, NULL);
if (!r) {
mbentry->acl = acl;
r = mboxlist_update(mbentry, 0);
}
mboxlist_entry_free(&mbentry);
}
}
free(acl);
return r;
}
static int records_match(const char *mboxname,
struct index_record *old,
struct index_record *new)
{
int i;
int match = 1;
int userflags_dirty = 0;
if (old->internaldate != new->internaldate) {
printf("%s uid %u mismatch: internaldate\n",
mboxname, new->uid);
match = 0;
}
if (old->sentdate != new->sentdate) {
printf("%s uid %u mismatch: sentdate\n",
mboxname, new->uid);
match = 0;
}
if (old->size != new->size) {
printf("%s uid %u mismatch: size\n",
mboxname, new->uid);
match = 0;
}
if (old->header_size != new->header_size) {
printf("%s uid %u mismatch: header_size\n",
mboxname, new->uid);
match = 0;
}
if (old->gmtime != new->gmtime) {
printf("%s uid %u mismatch: gmtime\n",
mboxname, new->uid);
match = 0;
}
if (old->content_lines != new->content_lines) {
printf("%s uid %u mismatch: content_lines\n",
mboxname, new->uid);
match = 0;
}
if (old->system_flags != new->system_flags) {
printf("%s uid %u mismatch: systemflags\n",
mboxname, new->uid);
match = 0;
}
for (i = 0; i < MAX_USER_FLAGS/32; i++) {
if (old->user_flags[i] != new->user_flags[i])
userflags_dirty = 1;
}
if (userflags_dirty) {
printf("%s uid %u mismatch: userflags\n",
mboxname, new->uid);
match = 0;
}
if (!message_guid_equal(&old->guid, &new->guid)) {
printf("%s uid %u mismatch: guid\n",
mboxname, new->uid);
match = 0;
}
if (!match) {
syslog(LOG_ERR, "%s uid %u record mismatch, rewriting",
mboxname, new->uid);
}
/* cache issues - don't print, probably just a version
* upgrade... */
if (old->cache_version != new->cache_version) {
match = 0;
}
if (old->cache_crc != new->cache_crc) {
match = 0;
}
if (cache_size(old) != cache_size(new)) {
match = 0;
}
/* only compare cache records if size matches */
else if (memcmp(cache_base(old), cache_base(new), cache_size(new))) {
match = 0;
}
return match;
}
static int mailbox_reconstruct_compare_update(struct mailbox *mailbox,
struct index_record *record,
bit32 *valid_user_flags,
int flags, int have_file,
struct found_files *discovered)
{
char *fname = mailbox_message_fname(mailbox, record->uid);
int r = 0;
int i;
struct index_record copy;
struct stat sbuf;
int make_changes = flags & RECONSTRUCT_MAKE_CHANGES;
int re_parse = flags & RECONSTRUCT_ALWAYS_PARSE;
int do_stat = flags & RECONSTRUCT_DO_STAT;
int re_pack = 0;
int did_stat = 0;
/* does the file actually exist? */
if (have_file && do_stat) {
if (stat(fname, &sbuf) == -1 || (sbuf.st_size == 0)) {
have_file = 0;
}
else if (record->size != (unsigned) sbuf.st_size) {
re_parse = 1;
}
did_stat = 1;
}
if (!have_file) {
/* well, that's OK if it's supposed to be missing! */
if (record->system_flags & FLAG_UNLINKED)
return 0;
printf("%s uid %u not found\n", mailbox->name, record->uid);
syslog(LOG_ERR, "%s uid %u not found", mailbox->name, record->uid);
if (!make_changes) return 0;
/* otherwise we have issues, mark it unlinked */
unlink(fname);
record->system_flags |= FLAG_EXPUNGED | FLAG_UNLINKED;
mailbox->i.options |= OPT_MAILBOX_NEEDS_REPACK;
return mailbox_rewrite_index_record(mailbox, record);
}
if (mailbox_cacherecord(mailbox, record) || record->crec.len == 0) {
re_parse = 1;
re_pack = 1; /* cache record will have to be rewritten */
}
/* copy once the cache record is read in... */
copy = *record;
if (!record->internaldate) {
re_parse = 1;
}
/* re-calculate all the "derived" fields by parsing the file on disk */
if (re_parse) {
/* set NULL in case parse finds a new value */
record->internaldate = 0;
r = message_parse(fname, record);
if (r) return r;
/* unchanged, keep the old value */
if (!record->internaldate)
record->internaldate = copy.internaldate;
/* it's not the same message! */
if (!message_guid_equal(&record->guid, ©.guid)) {
int do_unlink = 0;
printf("%s uid %u guid mismatch\n",
mailbox->name, record->uid);
syslog(LOG_ERR, "%s uid %u guid mismatch",
mailbox->name, record->uid);
if (!make_changes) return 0;
if (record->system_flags & FLAG_EXPUNGED) {
/* already expunged, just unlink it */
printf("%s uid %u already expunged, unlinking\n",
mailbox->name, record->uid);
syslog(LOG_ERR, "%s uid %u already expunged, unlinking",
mailbox->name, record->uid);
do_unlink = 1;
}
else if (flags & RECONSTRUCT_GUID_REWRITE) {
/* treat this file as discovered */
add_files(discovered, record->uid);
printf("%s uid %u marking for uid upgrade\n",
mailbox->name, record->uid);
syslog(LOG_ERR, "%s uid %u marking for uid upgrade",
mailbox->name, record->uid);
do_unlink = 1;
}
else if (flags & RECONSTRUCT_GUID_UNLINK) {
printf("%s uid %u unlinking as requested with -U\n",
mailbox->name, record->uid);
syslog(LOG_ERR, "%s uid %u unlinking as requested with -U",
mailbox->name, record->uid);
do_unlink = 1;
}
if (do_unlink) {
/* rewrite with the original so we don't break the
* expectation that GUID never changes */
copy.system_flags |= FLAG_EXPUNGED | FLAG_UNLINKED;
mailbox->i.options |= OPT_MAILBOX_NEEDS_UNLINK;
return mailbox_rewrite_index_record(mailbox, ©);
}
/* otherwise we just report it and move on - hopefully the
* correct file can be restored from backup or something */
printf("run reconstruct with -R to fix or -U to remove\n");
syslog(LOG_ERR, "run reconstruct with -R to fix or -U to remove");
return 0;
}
}
/* get internaldate from the file if not set */
if (!record->internaldate) {
if (did_stat || stat(fname, &sbuf) != -1)
record->internaldate = sbuf.st_mtime;
else
record->internaldate = time(NULL);
}
/* XXX - conditions under which modseq or uid or internaldate could be bogus? */
if (record->modseq > mailbox->i.highestmodseq) {
printf("%s uid %u future modseq " MODSEQ_FMT " found\n",
mailbox->name, record->uid, record->modseq);
syslog(LOG_ERR, "%s uid %u future modseq " MODSEQ_FMT " found",
mailbox->name, record->uid, record->modseq);
mailbox_index_dirty(mailbox);
mailbox->i.highestmodseq = record->modseq;
}
if (record->uid > mailbox->i.last_uid) {
printf("%s future uid %u found\n",
mailbox->name, record->uid);
syslog(LOG_ERR, "%s future uid %u found",
mailbox->name, record->uid);
mailbox_index_dirty(mailbox);
mailbox->i.last_uid = record->uid;
}
/* remove any user_flags that are missing from the header */
for (i = 0; i < MAX_USER_FLAGS/32; i++) {
record->user_flags[i] &= valid_user_flags[i];
}
/* after all this - if it still matches in every respect, we don't need
* to rewrite the record - just return */
if (records_match(mailbox->name, ©, record))
return 0;
/* XXX - inform of changes */
if (!make_changes)
return 0;
/* rewrite the cache record */
if (re_pack || record->cache_crc != copy.cache_crc) {
mailbox->i.options |= OPT_MAILBOX_NEEDS_REPACK;
record->cache_offset = 0;
r = mailbox_append_cache(mailbox, record);
if (r) return r;
}
return mailbox_rewrite_index_record(mailbox, record);
}
static int mailbox_reconstruct_append(struct mailbox *mailbox, uint32_t uid,
int flags)
{
char *fname = mailbox_message_fname(mailbox, uid);
int r = 0;
struct index_record record;
struct stat sbuf;
int make_changes = flags & RECONSTRUCT_MAKE_CHANGES;
/* possible if '0.' file exists */
if (!uid) {
/* filthy hack - copy the path to '1.' and replace 1 with 0 */
fname = xstrdup(mailbox_message_fname(mailbox, 1));
fname[strlen(fname)-2] = '0';
}
if (stat(fname, &sbuf) == -1) r = IMAP_MAILBOX_NONEXISTENT;
else if (sbuf.st_size == 0) r = IMAP_MAILBOX_NONEXISTENT;
/* no file, nothing to do! */
if (r) {
syslog(LOG_ERR, "%s uid %u not found", mailbox->name, uid);
printf("%s uid %u not found", mailbox->name, uid);
if (!make_changes) return 0;
unlink(fname);
return 0;
}
memset(&record, 0, sizeof(struct index_record));
r = message_parse(fname, &record);
if (r) return r;
if (uid > mailbox->i.last_uid) {
printf("%s uid %u found - adding\n", mailbox->name, uid);
syslog(LOG_ERR, "%s uid %u found - adding", mailbox->name, uid);
record.uid = uid;
}
else {
char *oldfname;
char *newfname;
printf("%s uid %u rediscovered - appending\n", mailbox->name, uid);
syslog(LOG_ERR, "%s uid %u rediscovered - appending", mailbox->name, uid);
/* XXX - check firstexpunged? */
record.uid = mailbox->i.last_uid + 1;
if (!make_changes) return 0;
oldfname = xstrdup(fname);
newfname = xstrdup(mailbox_message_fname(mailbox, record.uid));
r = rename(oldfname, newfname);
free(oldfname);
free(newfname);
if (r) return IMAP_IOERROR;
}
/* XXX - inform of changes */
if (!make_changes)
return 0;
return mailbox_append_index_record(mailbox, &record);
}
static void reconstruct_compare_headers(struct mailbox *mailbox,
struct index_header *old,
struct index_header *new)
{
if (old->quota_mailbox_used != new->quota_mailbox_used) {
printf("%s updating quota_mailbox_used: "
QUOTA_T_FMT " => " QUOTA_T_FMT "\n", mailbox->name,
old->quota_mailbox_used, new->quota_mailbox_used);
syslog(LOG_ERR, "%s updating quota_mailbox_used: "
QUOTA_T_FMT " => " QUOTA_T_FMT, mailbox->name,
old->quota_mailbox_used, new->quota_mailbox_used);
}
if (old->quota_annot_used != new->quota_annot_used) {
printf("%s updating quota_annot_used: "
QUOTA_T_FMT " => " QUOTA_T_FMT "\n", mailbox->name,
old->quota_annot_used, new->quota_annot_used);
syslog(LOG_ERR, "%s updating quota_annot_used: "
QUOTA_T_FMT " => " QUOTA_T_FMT, mailbox->name,
old->quota_annot_used, new->quota_annot_used);
}
if (old->answered != new->answered) {
syslog(LOG_ERR, "%s: updating answered %u => %u",
mailbox->name, old->answered, new->answered);
printf("%s: updating answered %u => %u\n",
mailbox->name, old->answered, new->answered);
}
if (old->flagged != new->flagged) {
syslog(LOG_ERR, "%s: updating flagged %u => %u",
mailbox->name, old->flagged, new->flagged);
printf("%s: updating flagged %u => %u\n",
mailbox->name, old->flagged, new->flagged);
}
if (old->deleted != new->deleted) {
syslog(LOG_ERR, "%s: updating deleted %u => %u",
mailbox->name, old->deleted, new->deleted);
printf("%s: updating deleted %u => %u\n",
mailbox->name, old->deleted, new->deleted);
}
if (old->exists != new->exists) {
syslog(LOG_ERR, "%s: updating exists %u => %u",
mailbox->name, old->exists, new->exists);
printf("%s: updating exists %u => %u\n",
mailbox->name, old->exists, new->exists);
}
}
static int mailbox_wipe_index_record(struct mailbox *mailbox,
struct index_record *record)
{
int n;
indexbuffer_t ibuf;
unsigned char *buf = ibuf.buf;
size_t offset;
assert(mailbox_index_islocked(mailbox, 1));
assert(record->recno > 0 &&
record->recno <= mailbox->i.num_records);
record->uid = 0;
record->system_flags |= FLAG_EXPUNGED | FLAG_UNLINKED;
mailbox->i.options |= OPT_MAILBOX_NEEDS_REPACK;
mailbox_index_dirty(mailbox);
mailbox_index_record_to_buf(record, buf);
offset = mailbox->i.start_offset +
(record->recno-1) * mailbox->i.record_size;
n = lseek(mailbox->index_fd, offset, SEEK_SET);
if (n == -1) {
syslog(LOG_ERR, "IOERROR: seeking index record %u for %s: %m",
record->recno, mailbox->name);
return IMAP_IOERROR;
}
n = retry_write(mailbox->index_fd, buf, INDEX_RECORD_SIZE);
if (n != INDEX_RECORD_SIZE) {
syslog(LOG_ERR, "IOERROR: writing index record %u for %s: %m",
record->recno, mailbox->name);
return IMAP_IOERROR;
}
return 0;
}
/*
* Reconstruct the single mailbox named 'name'
*/
int mailbox_reconstruct(const char *name, int flags)
{
/* settings */
int make_changes = (flags & RECONSTRUCT_MAKE_CHANGES);
int r = 0;
uint32_t msg;
int i, flag;
struct index_record record;
struct mailbox *mailbox = NULL;
struct found_files files = FOUND_FILES_INITIALIZER;
struct found_files discovered = FOUND_FILES_INITIALIZER;
struct index_header old_header;
int have_file;
uint32_t recno;
uint32_t last_seen_uid = 0;
bit32 valid_user_flags[MAX_USER_FLAGS/32];
if (make_changes && !(flags & RECONSTRUCT_QUIET)) {
syslog(LOG_NOTICE, "reconstructing %s", name);
}
r = mailbox_open_iwl(name, &mailbox);
if (r) {
if (!make_changes) return r;
/* returns a locktype == LOCK_EXCLUSIVE mailbox */
r = mailbox_reconstruct_create(name, &mailbox);
}
if (r) return r;
r = mailbox_reconstruct_acl(mailbox, flags);
if (r) goto close;
/* Validate user flags */
for (i = 0; i < MAX_USER_FLAGS/32; i++) {
valid_user_flags[i] = 0;
}
for (flag = 0; flag < MAX_USER_FLAGS; flag++) {
if (!mailbox->flagname[flag]) continue;
if ((flag && !mailbox->flagname[flag-1]) ||
!imparse_isatom(mailbox->flagname[flag])) {
printf("%s: bogus flag name %d:%s",
mailbox->name, flag, mailbox->flagname[flag]);
syslog(LOG_ERR, "%s: bogus flag name %d:%s",
mailbox->name, flag, mailbox->flagname[flag]);
mailbox->header_dirty = 1;
free(mailbox->flagname[flag]);
mailbox->flagname[flag] = NULL;
continue;
}
valid_user_flags[flag/32] |= 1<<(flag&31);
}
r = mailbox_open_cache(mailbox);
if (r) {
const char *fname = mailbox_meta_fname(mailbox, META_CACHE);
uint32_t buf;
int n;
printf("%s: missing cache file, recreating\n",
mailbox->name);
syslog(LOG_ERR, "%s: missing cache file, recreating",
mailbox->name);
if (!make_changes) goto close;
if (cyrus_mkdir(fname, 0755)) goto close;
mailbox->cache_fd = open(fname, O_RDWR|O_TRUNC|O_CREAT, 0666);
if (mailbox->cache_fd == -1) goto close;
/* set the generation number */
buf = htonl(mailbox->i.generation_no);
n = retry_write(mailbox->cache_fd, (char *)&buf, 4);
if (n != 4) goto close;
/* ensure that next user will create the MMAPing */
mailbox->need_cache_refresh = 1;
}
/* find cyrus.expunge file if present */
cleanup_stale_expunged(mailbox);
r = find_files(mailbox, &files, flags);
if (r) goto close;
msg = 0;
for (recno = 1; recno <= mailbox->i.num_records; recno++) {
r = mailbox_read_index_record(mailbox, recno, &record);
if (r) {
printf("%s: record corrupted %u (maybe uid %u)\n",
mailbox->name, recno, record.uid);
continue;
}
if (record.uid <= last_seen_uid) {
if (record.uid)
syslog(LOG_ERR, "%s out of order uid %u at record %u, wiping",
mailbox->name, record.uid, recno);
mailbox_wipe_index_record(mailbox, &record);
continue;
}
last_seen_uid = record.uid;
/* lower UID file exists */
while (msg < files.nused && files.uids[msg] < record.uid) {
add_files(&discovered, files.uids[msg]);
msg++;
}
/* if they match, advance the pointer */
have_file = 0;
if (msg < files.nused && files.uids[msg] == record.uid) {
have_file = 1;
msg++;
}
r = mailbox_reconstruct_compare_update(mailbox, &record,
valid_user_flags,
flags, have_file,
&discovered);
if (r) goto close;
}
/* add discovered messages before last_uid to the list in order */
while (msg < files.nused && files.uids[msg] <= mailbox->i.last_uid) {
add_files(&discovered, files.uids[msg]);
msg++;
}
/* messages AFTER last_uid can keep the same UID (see also, restore
* from list .index file) - so don't bother moving those */
while (msg < files.nused) {
r = mailbox_reconstruct_append(mailbox, files.uids[msg], flags);
if (r) goto close;
msg++;
}
/* handle new list */
msg = 0;
while (msg < discovered.nused) {
r = mailbox_reconstruct_append(mailbox, discovered.uids[msg], flags);
if (r) goto close;
msg++;
}
/* make sure we have enough index file mmaped */
r = mailbox_refresh_index_map(mailbox);
old_header = mailbox->i;
/* re-calculate derived fields */
r = mailbox_index_recalc(mailbox);
if (r) goto close;
/* inform users of any changed header fields */
reconstruct_compare_headers(mailbox, &old_header, &mailbox->i);
/* fix up 2.4.0 bug breakage */
if (mailbox->i.uidvalidity == 0) {
if (make_changes) {
mailbox->i.uidvalidity = time(0);
mailbox_index_dirty(mailbox);
}
syslog(LOG_ERR, "%s: zero uidvalidity", mailbox->name);
}
if (mailbox->i.highestmodseq == 0) {
if (make_changes) {
mailbox_index_dirty(mailbox);
mailbox->i.highestmodseq = 1;
}
syslog(LOG_ERR, "%s: zero highestmodseq", mailbox->name);
}
if (make_changes) {
r = mailbox_commit(mailbox);
}
else {
/* undo any dirtyness before we close, we didn't actually
* write any changes */
mailbox->i.dirty = 0;
mailbox->quota_dirty = 0;
mailbox->cache_dirty = 0;
mailbox->modseq_dirty = 0;
mailbox->header_dirty = 0;
}
close:
free_files(&files);
free_files(&discovered);
mailbox_close(&mailbox);
return r;
}
/*
* Gets messages usage.
*/
void mailbox_get_usage(struct mailbox *mailbox,
quota_t usage[QUOTA_NUMRESOURCES])
{
int res;
for (res = 0; res < QUOTA_NUMRESOURCES; res++) {
usage[res] = 0;
}
if (!(mailbox->i.options & OPT_MAILBOX_DELETED)) {
usage[QUOTA_STORAGE] = mailbox->i.quota_mailbox_used;
usage[QUOTA_MESSAGE] = mailbox->i.exists;
usage[QUOTA_ANNOTSTORAGE] = mailbox->i.quota_annot_used;
}
/* else: mailbox is being deleted, thus its new usage is 0 */
}
void mailbox_use_annot_quota(struct mailbox *mailbox, quota_t diff)
{
/* we are dirtying both index and quota */
mailbox_index_dirty(mailbox);
mailbox_quota_dirty(mailbox);
mailbox->i.quota_annot_used += diff;
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Apr 5, 10:00 PM (2 w, 6 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18831333
Default Alt Text
(120 KB)
Attached To
Mode
R111 cyrus-imapd
Attached
Detach File
Event Timeline