Page MenuHomePhorge

mailbox.c
No OneTemporary

Authored By
Unknown
Size
102 KB
Referenced Files
None
Subscribers
None

mailbox.c

/* 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
#include "acl.h"
#include "assert.h"
#include "crc32.h"
#include "exitcodes.h"
#include "global.h"
#include "imap_err.h"
#include "imparse.h"
#include "lock.h"
#include "mailbox.h"
#include "message.h"
#include "map.h"
#include "mboxlist.h"
#include "retry.h"
#include "seen.h"
#include "upgrade_index.h"
#include "util.h"
#include "sequence.h"
#include "statuscache.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; \
(m).lock_fd = -1; }
static int mailbox_delete_cleanup(struct mailbox *mailbox);
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;
struct mailboxlist *previtem = NULL;
/* remove from the active list */
for (item = open_mailboxes; item; item = item->next) {
if (!strcmp(name, item->m.name))
return item;
previtem = 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;
staticbuf.s = (char *)cache_base(record);
staticbuf.len = 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;
staticbuf.s = (char *)cacheitem_base(record, field);
staticbuf.len = 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;
/* 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, O_RDWR, 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;
char buf[4];
/* 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 */
*((bit32 *)(buf)) = htonl(mailbox->i.generation_no);
retry_write(mailbox->cache_fd, buf, 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;
/* 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_CRC;
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_CRC;
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'. 'uniqueid' should be at least 17 bytes
* long. the unique identifier is just the mailbox name hashed to 32
* bits followed by the uid, both converted to hex.
*/
#define PRIME (2147484043UL)
void mailbox_make_uniqueid(struct mailbox *mailbox)
{
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);
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, unsigned long *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, unsigned long *lenp)
{
map_free(basep, lenp);
}
int mailbox_mboxlock_upgrade(struct mailboxlist *listitem, int locktype)
{
struct mailbox *mailbox = &listitem->m;
int r;
if (listitem->l->locktype == LOCK_EXCLUSIVE)
return 0;
mboxname_release(&listitem->l);
r = mboxname_lock(mailbox->name, &listitem->l, locktype);
if (r) return r;
if (mailbox->index_fd != -1)
close(mailbox->index_fd);
if (mailbox->cache_fd != -1)
close(mailbox->cache_fd);
if (mailbox->index_base)
map_free(&mailbox->index_base, &mailbox->index_len);
if (mailbox->cache_buf.s)
map_free((const char **)&mailbox->cache_buf.s, &mailbox->cache_len);
r = mailbox_open_index(mailbox);
return r;
}
/*
* Open and read the header of the mailbox with name 'name'
* The structure pointed to by 'mailbox' is initialized.
*/
int mailbox_open_advanced(const char *name,
struct mailbox **mailboxptr,
int locktype,
int index_locktype)
{
struct mboxlist_entry mbentry;
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;
r = mailbox_open_index(mailbox);
if (r) {
syslog(LOG_ERR, "IOERROR: opening index %s: %m", mailbox->name);
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: %m", mailbox->name);
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, mailboxptr, LOCK_SHARED, LOCK_SHARED);
}
int mailbox_open_iwl(const char *name, struct mailbox **mailboxptr)
{
return mailbox_open_advanced(name, mailboxptr, LOCK_SHARED, LOCK_EXCLUSIVE);
}
int mailbox_open_exclusive(const char *name, struct mailbox **mailboxptr)
{
return mailbox_open_advanced(name, mailboxptr, LOCK_EXCLUSIVE,
LOCK_EXCLUSIVE);
}
/*
* Open the index file for 'mailbox'
*/
int mailbox_open_index(struct mailbox *mailbox)
{
struct stat sbuf;
char *fname;
if (mailbox->i.dirty || mailbox->cache_dirty)
abort();
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);
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 and map the index file */
fname = mailbox_meta_fname(mailbox, META_INDEX);
if (!fname)
return IMAP_MAILBOX_BADNAME;
mailbox->index_fd = open(fname, O_RDWR, 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;
}
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);
}
/*
* 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);
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? */
}
}
/* 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_upgrade(listitem, LOCK_NONBLOCKING);
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);
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 */
}
/* otherwise someone else has the mailbox locked
* already, so they can handle the cleanup in
* THEIR mailbox_close call */
}
if (mailbox->index_base)
map_free(&mailbox->index_base, &mailbox->index_len);
if (mailbox->cache_buf.s)
map_free((const char **)&mailbox->cache_buf.s, &mailbox->cache_len);
if (mailbox->index_fd != -1)
close(mailbox->index_fd);
if (mailbox->cache_fd != -1)
close(mailbox->cache_fd);
if (mailbox->header_fd != -1)
close(mailbox->header_fd);
/* once we close this we're committed - no consistency
* guarantees left */
if (mailbox->lock_fd != -1)
close(mailbox->lock_fd);
free(mailbox->name);
free(mailbox->part);
free(mailbox->acl);
free(mailbox->uniqueid);
free(mailbox->quotaroot);
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'
*/
int mailbox_read_header(struct mailbox *mailbox, char **aclptr)
{
int r = 0;
int flag;
const char *name, *p, *tab, *eol;
int oldformat = 0;
const char *fname;
struct stat sbuf;
const char *base = NULL;
unsigned long 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 file pathname */
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 (!tab || tab > eol || !eol) {
oldformat = 1;
if (!eol) {
r = IMAP_MAILBOX_BADFORMAT;
goto done;
}
else {
syslog(LOG_DEBUG, "mailbox '%s' has old cyrus.header",
mailbox->name);
}
tab = eol;
}
free(mailbox->quotaroot);
mailbox->quotaroot = NULL;
if (p < tab) {
mailbox->quotaroot = xstrndup(p, tab - p);
}
if (oldformat) {
/* uniqueid needs to be generated when we know the uidvalidity */
mailbox->uniqueid = NULL;
} else {
/* read uniqueid */
p = tab + 1;
if (p == eol) {
r = IMAP_MAILBOX_BADFORMAT;
goto done;
}
mailbox->uniqueid = 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)
{
if (mailbox->acl) {
if (!strcmp(mailbox->acl, acl))
return 0; /* no change */
free(mailbox->acl);
}
mailbox->acl = xstrdup(acl);
mailbox->header_dirty = 1;
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;
}
if (quotaroot)
mailbox->quotaroot = xstrdup(quotaroot);
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 userflag;
int emptyflag = -1;
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 (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;
}
*flagnum = userflag;
return 0;
}
int mailbox_buf_to_index_header(struct index_header *i, const char *buf)
{
uint32_t crc;
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)));
#ifdef HAVE_LONG_LONG_INT
i->quota_mailbox_used = align_ntohll(buf+OFFSET_QUOTA_MAILBOX_USED64);
#else
i->quota_mailbox_used = ntohl(*((bit32 *)(buf+OFFSET_QUOTA_MAILBOX_USED)));
#endif
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)));
#ifdef HAVE_LONG_LONG_INT
i->highestmodseq = align_ntohll(buf+OFFSET_HIGHESTMODSEQ_64);
i->deletedmodseq = align_ntohll(buf+OFFSET_DELETEDMODSEQ_64);
#else
i->highestmodseq = ntohl(*((bit32 *)(buf+OFFSET_HIGHESTMODSEQ)));
i->deletedmodseq = ntohl(*((bit32 *)(buf+OFFSET_DELETEDMODSEQ)));
#endif
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)));
i->sync_crc = ntohl(*((bit32 *)(buf+OFFSET_SYNC_CRC)));
i->recentuid = ntohl(*((bit32 *)(buf+OFFSET_RECENTUID)));
i->recenttime = ntohl(*((bit32 *)(buf+OFFSET_RECENTTIME)));
i->header_crc = ntohl(*((bit32 *)(buf+OFFSET_HEADER_CRC)));
if (!i->exists)
i->options |= OPT_POP3_NEW_UIDL;
crc = crc32_map(buf, OFFSET_HEADER_CRC);
if (crc != i->header_crc)
return IMAP_MAILBOX_CRC;
return 0;
}
static int mailbox_read_index_header(struct mailbox *mailbox)
{
size_t need_size;
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 < OFFSET_HEADER_SIZE)
return IMAP_MAILBOX_BADFORMAT;
r = mailbox_buf_to_index_header(&mailbox->i, mailbox->index_base);
if (r) return r;
/* 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) {
struct stat sbuf;
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;
}
/*
* 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);
#ifdef HAVE_LONG_LONG_INT
record->modseq = ntohll(*((bit64 *)(buf+OFFSET_MODSEQ_64)));
#else
record->modseq = ntohl(*((bit32 *)(buf+OFFSET_MODSEQ)));
#endif
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_CRC;
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;
}
/*
* 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)
r = lock_blocking(mailbox->index_fd);
else
r = lock_shared(mailbox->index_fd);
if (r == -1) {
syslog(LOG_ERR, "IOERROR: locking index for %s: %m",
mailbox->name);
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);
r = mailbox_mboxlock_upgrade(listitem, LOCK_EXCLUSIVE);
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;
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_CRC;
}
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! */
if (mailbox->i.dirty || mailbox->header_dirty ||
mailbox->modseq_dirty || mailbox->quota_dirty)
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;
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);
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 *)(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 */
#ifdef HAVE_LONG_LONG_INT
*((bit64 *)(buf+OFFSET_QUOTA_MAILBOX_USED64)) = htonll(i->quota_mailbox_used);
#else
/* zero the unused 32bits */
*((bit32 *)(buf+OFFSET_QUOTA_MAILBOX_USED64)) = htonl(0);
*((bit32 *)(buf+OFFSET_QUOTA_MAILBOX_USED)) = htonl(i->quota_mailbox_used);
#endif
*((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(i->options & OPT_VALID);
*((bit32 *)(buf+OFFSET_LEAKED_CACHE)) = htonl(i->leaked_cache_records);
#ifdef HAVE_LONG_LONG_INT
align_htonll(buf+OFFSET_HIGHESTMODSEQ_64, i->highestmodseq);
align_htonll(buf+OFFSET_DELETEDMODSEQ_64, i->deletedmodseq);
#else
/* zero the unused 32bits */
*((bit32 *)(buf+OFFSET_HIGHESTMODSEQ_64)) = htonl(0);
*((bit32 *)(buf+OFFSET_HIGHESTMODSEQ)) = htonl(i->highestmodseq);
/* zero the unused 32bits */
*((bit32 *)(buf+OFFSET_DELETEDMODSEQ_64)) = htonl(0);
*((bit32 *)(buf+OFFSET_DELETEDMODSEQ)) = htonl(i->deletedmodseq);
#endif
*((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)) = htonl(i->sync_crc);
*((bit32 *)(buf+OFFSET_RECENTUID)) = htonl(i->recentuid);
*((bit32 *)(buf+OFFSET_RECENTTIME)) = htonl(i->recenttime);
*((bit32 *)(buf+OFFSET_SPARE0)) = htonl(0); /* RESERVED */
*((bit32 *)(buf+OFFSET_SPARE1)) = htonl(0); /* RESERVED */
*((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)
{
struct txn *tid = NULL;
int r;
struct quota q;
quota_t qdiff;
/* not dirty */
if (!mailbox->quota_dirty)
return 0;
mailbox->quota_dirty = 0;
/* unchanged */
qdiff = mailbox->i.quota_mailbox_used - mailbox->quota_previously_used;
if (!qdiff)
return 0;
/* no quota root means we don't track quota. That's OK */
if (!mailbox->quotaroot)
return 0;
assert(mailbox_index_islocked(mailbox, 1));
q.root = mailbox->quotaroot;
r = quota_read(&q, &tid, 1);
if (!r) {
/* check we won't underflow */
if ((quota_t)-qdiff > (quota_t)q.used)
q.used = 0;
else
q.used += qdiff;
r = quota_write(&q, &tid);
}
if (!r) quota_commit(&tid);
else {
quota_abort(&tid);
/* XXX - fail here? It's tempting */
syslog(LOG_ERR, "LOSTQUOTA: unable to record quota file %s",
mailbox->quotaroot);
}
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);
#ifdef HAVE_LONG_LONG_INT
*((bit64 *)(buf+OFFSET_MODSEQ_64)) = htonll(record->modseq);
#else
/* zero the unused 32bits */
*((bit32 *)(buf+OFFSET_MODSEQ_64)) = htonl(0);
*((bit32 *)(buf+OFFSET_MODSEQ)) = htonl(record->modseq);
#endif
*((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;
}
bit32 make_sync_crc(struct mailbox *mailbox, struct index_record *record)
{
char buf[4096];
bit32 flagcrc = 0;
int flag;
/* calculate an XORed CRC32 over all the flags on the message, so no
* matter what order they are store in the header, the final value
* is the same */
if (record->system_flags & FLAG_DELETED)
flagcrc ^= crc32_cstring("\\deleted");
if (record->system_flags & FLAG_ANSWERED)
flagcrc ^= crc32_cstring("\\answered");
if (record->system_flags & FLAG_FLAGGED)
flagcrc ^= crc32_cstring("\\flagged");
if (record->system_flags & FLAG_DRAFT)
flagcrc ^= crc32_cstring("\\draft");
if (record->system_flags & FLAG_SEEN)
flagcrc ^= crc32_cstring("\\seen");
for (flag = 0; flag < MAX_USER_FLAGS; flag++) {
if (!mailbox->flagname[flag])
continue;
if (!(record->user_flags[flag/32] & (1<<(flag&31))))
continue;
/* need to compare without case being significant */
strlcpy(buf, mailbox->flagname[flag], 4096);
lcase(buf);
flagcrc ^= crc32_cstring(buf);
}
/* NOTE: this format is idential to an UPDATE COPY command except
* that the string format of the flags has been replaced with a
* checksum over the flags */
snprintf(buf, 4096, "%u " MODSEQ_FMT " %lu (%u) %lu %s",
record->uid, record->modseq, record->last_updated,
flagcrc,
record->internaldate,
message_guid_encode(&record->guid));
return crc32_cstring(buf);
}
static void mailbox_quota_dirty(struct mailbox *mailbox)
{
/* track quota use */
if (!mailbox->quota_dirty) {
mailbox->quota_dirty = 1;
mailbox->quota_previously_used = mailbox->i.quota_mailbox_used;
}
}
void mailbox_index_update_counts(struct mailbox *mailbox,
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;
mailbox_quota_dirty(mailbox);
/* update mailbox header fields */
if (record->system_flags & FLAG_ANSWERED)
mailbox->i.answered += num;
if (record->system_flags & FLAG_FLAGGED)
mailbox->i.flagged += num;
if (record->system_flags & FLAG_DELETED)
mailbox->i.deleted += num;
if (is_add) {
mailbox->i.exists++;
mailbox->i.quota_mailbox_used += record->size;
}
else {
if (mailbox->i.exists)
mailbox->i.exists--;
/* corruption prevention - check we don't go negative */
if (mailbox->i.quota_mailbox_used > record->size)
mailbox->i.quota_mailbox_used -= record->size;
else
mailbox->i.quota_mailbox_used = 0;
}
mailbox->i.sync_crc ^= make_sync_crc(mailbox, record);
mailbox_index_dirty(mailbox);
}
int mailbox_index_recalc(struct mailbox *mailbox)
{
struct index_record record;
int r = 0;
uint32_t recno;
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.sync_crc = 0;
for (recno = 1; recno <= mailbox->i.num_records; recno++) {
r = mailbox_read_index_record(mailbox, recno, &record);
if (r) return r;
mailbox_index_update_counts(mailbox, &record, 1);
}
return 0;
}
/*
* 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 - possibly test all the other suposedly
* invarient fields here too? */
if (record->uid != oldrecord.uid)
return IMAP_IOERROR;
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 */
if (!(record->system_flags & FLAG_EXPUNGED))
return IMAP_IOERROR;
}
/* 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;
}
/* 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;
assert(mailbox_index_islocked(mailbox, 1));
/* Append MUST be a higher UID than any we've yet seen */
if (record->uid <= mailbox->i.last_uid)
return IMAP_IOERROR; /* XXX - better code */
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);
offset = mailbox->i.start_offset +
(mailbox->i.num_records * 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++;
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;
}
/* need a mailbox exclusive lock, we're rewriting files */
static int mailbox_index_repack(struct mailbox *mailbox)
{
char *fname;
uint32_t newrecno = 1;
indexbuffer_t ibuf;
unsigned char *buf = ibuf.buf;
uint32_t recno;
int newindex_fd = -1, newcache_fd = -1;
struct index_record record;
size_t offset;
int newgeneration;
int r = IMAP_IOERROR;
int n;
syslog(LOG_INFO, "Repacking mailbox %s", mailbox->name);
fname = mailbox_meta_newfname(mailbox, META_INDEX);
newindex_fd = open(fname, O_RDWR|O_TRUNC|O_CREAT, 0666);
if (newindex_fd == -1) goto fail;
fname = mailbox_meta_newfname(mailbox, META_CACHE);
newcache_fd = open(fname, O_RDWR|O_TRUNC|O_CREAT, 0666);
if (newcache_fd == -1) goto fail;
/* update the generation number */
newgeneration = mailbox->i.generation_no + 1;
*((bit32 *)(buf)) = htonl(newgeneration);
n = retry_write(newcache_fd, buf, 4);
if (n != 4) goto fail;
for (recno = 1; recno <= mailbox->i.num_records; recno++) {
r = mailbox_read_index_record(mailbox, recno, &record);
if (r) goto fail;
/* 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 > mailbox->i.deletedmodseq)
mailbox->i.deletedmodseq = record.modseq;
continue;
}
/* read in the old cache record */
r = mailbox_cacherecord(mailbox, &record);
if (r) goto fail;
/* 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(newcache_fd, &record);
if (r) goto fail;
offset = mailbox->i.start_offset +
(newrecno-1) * mailbox->i.record_size;
/* write the index record out */
mailbox_index_record_to_buf(&record, buf);
n = lseek(newindex_fd, offset, SEEK_SET);
if (n == -1) {
syslog(LOG_ERR, "IOERROR: seeking index record %u for %s: %m",
recno, mailbox->name);
goto fail;
}
n = retry_write(newindex_fd, buf, INDEX_RECORD_SIZE);
if (n != INDEX_RECORD_SIZE) {
syslog(LOG_ERR, "IOERROR: writing index record %u for %s: %m",
recno, mailbox->name);
goto fail;
}
newrecno++;
}
/* update final header fields */
mailbox->i.generation_no = newgeneration;
mailbox->i.first_expunged = 0;
mailbox->i.last_repack_time = time(0);
mailbox->i.num_records = newrecno - 1;
mailbox->i.leaked_cache_records = 0;
/* we unlinked any "needs unlink" in the process */
mailbox->i.options &= ~(OPT_MAILBOX_NEEDS_REPACK|OPT_MAILBOX_NEEDS_UNLINK);
mailbox_index_header_to_buf(&mailbox->i, buf);
n = lseek(newindex_fd, 0, SEEK_SET);
if (n == -1) goto fail;
n = retry_write(newindex_fd, buf, INDEX_HEADER_SIZE);
if (n != INDEX_HEADER_SIZE) goto fail;
if (fsync(newindex_fd) || fsync(newcache_fd))
goto fail;
close(newcache_fd);
close(newindex_fd);
r = mailbox_meta_rename(mailbox, META_INDEX);
if (!r) r = mailbox_meta_rename(mailbox, META_CACHE);
return r;
fail:
if (newcache_fd != -1) close(newcache_fd);
if (newindex_fd != -1) close(newindex_fd);
return IMAP_IOERROR;
}
/*
* 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;
r = mailbox_commit(mailbox);
}
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,
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;
char generation_buf[4];
int createfnames[] = { META_INDEX, META_CACHE, META_HEADER, 0 };
struct mailboxlist *listitem;
/* 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);
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);
/* 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->header_dirty = 1;
if (!uniqueid) {
mailbox_make_uniqueid(mailbox);
} else {
mailbox->uniqueid = xstrdup(uniqueid);
}
/* write out the initial generation number to the cache file */
*((bit32 *)generation_buf) = htonl(mailbox->i.generation_no);
n = retry_write(mailbox->cache_fd, 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);
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 __attribute__((unused)),
int matchlen __attribute__((unused)),
int maycreate __attribute__((unused)),
void *rock __attribute__((unused)))
{
return CYRUSDB_DONE;
}
/*
* 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 mailbox deleted */
mailbox_index_dirty(mailbox);
mailbox->i.options |= OPT_MAILBOX_DELETED;
/* mark the quota removed */
mailbox_quota_dirty(mailbox);
mailbox->i.quota_mailbox_used = 0;
/* 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(struct mailbox *mailbox)
{
char nbuf[MAX_MAILBOX_BUFFER];
char pbuf[MAX_MAILBOX_PATH+1], mbuf[MAX_MAILBOX_PATH+1];
char *ntail, *ptail, *mtail = NULL;
char *path, *mpath;
int r;
/* make sure it really is deleted! */
assert(mailbox->i.options & OPT_MAILBOX_DELETED);
/* XXX - use explicit paths to each type of file */
/* Flush data (message file) directory */
path = mboxname_datapath(mailbox->part, mailbox->name, 0);
mailbox_delete_files(path);
strlcpy(pbuf, path, sizeof(pbuf));
ptail = pbuf + strlen(pbuf);
/* Flush metadata directory */
mpath = mboxname_metapath(mailbox->part, mailbox->name, 0, 0);
if (strcmp(path, mpath)) {
mailbox_delete_files(mpath);
strlcpy(mbuf, mpath, sizeof(mbuf));
mtail = mbuf + strlen(mbuf);
}
strlcpy(nbuf, mailbox->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, NULL);
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, NULL, NULL);
} while(r == IMAP_MAILBOX_NONEXISTENT);
return 0;
}
struct meta_file {
unsigned long metaflag;
int optional;
int nolink;
};
static struct meta_file meta_files[] = {
{ META_INDEX, 0, 1 },
{ META_CACHE, 0, 1 },
{ META_SQUAT, 1, 0 },
{ 0, 0, 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;
unsigned int flag;
char oldbuf[MAX_MAILBOX_PATH], newbuf[MAX_MAILBOX_PATH];
struct meta_file *mf;
char *path, *mpath;
struct mailbox *newmailbox = NULL;
unsigned uidvalidity;
uint32_t recno;
struct index_record record;
assert(mailbox_index_islocked(oldmailbox, 1));
if (strcmp(oldmailbox->name, newname) == 0) {
/* Just moving mailboxes between partitions */
uidvalidity = oldmailbox->i.uidvalidity;
}
else {
uidvalidity = time(0);
}
/* Create new mailbox */
r = mailbox_create(newname, newpartition,
oldmailbox->acl, (userid ? NULL : oldmailbox->uniqueid),
oldmailbox->i.options, uidvalidity, &newmailbox);
if (r) return r;
/* Check quota if necessary */
if (!ignorequota && newmailbox->quotaroot && (!oldmailbox->quotaroot ||
strcmp(oldmailbox->quotaroot, newmailbox->quotaroot))) {
struct quota q;
q.root = newmailbox->quotaroot;
r = quota_read(&q, NULL, 1);
/* check if the limit is exceeded */
if (!r && q.limit >= 0 && q.used + oldmailbox->i.quota_mailbox_used >
(uquota_t) q.limit * QUOTA_UNITS) {
r = IMAP_QUOTA_EXCEEDED;
}
/* then we abort - no space to rename */
if (r && r != IMAP_QUOTAROOT_NONEXISTENT)
goto fail;
}
/* Copy over meta files */
for (mf = meta_files; !r && mf->metaflag; mf++) {
struct stat sbuf;
strncpy(oldbuf, mailbox_meta_fname(oldmailbox, mf->metaflag), MAX_MAILBOX_PATH);
strncpy(newbuf, mailbox_meta_fname(newmailbox, mf->metaflag), MAX_MAILBOX_PATH);
unlink(newbuf); /* Make link() possible */
if (!mf->optional || stat(oldbuf, &sbuf) != -1) {
r = mailbox_copyfile(oldbuf, newbuf, mf->nolink);
if (r) goto fail;
}
}
/* Re-open index file */
r = mailbox_open_index(newmailbox);
if (r) goto fail;
r = mailbox_read_index_header(newmailbox);
if (r) goto fail;
for (recno = 1; recno <= newmailbox->i.num_records; recno++) {
r = mailbox_read_index_record(newmailbox, recno, &record);
if (r) goto fail;
if (record.system_flags & FLAG_UNLINKED)
continue;
strncpy(oldbuf, mailbox_message_fname(oldmailbox, record.uid), MAX_MAILBOX_PATH);
strncpy(newbuf, mailbox_message_fname(newmailbox, record.uid), MAX_MAILBOX_PATH);
r = mailbox_copyfile(oldbuf, newbuf, 0);
if (r) goto fail;
}
/* Copy flag names */
newmailbox->header_dirty = 1;
for (flag = 0; flag < MAX_USER_FLAGS; flag++) {
if (oldmailbox->flagname[flag]) {
newmailbox->flagname[flag] = xstrdup(oldmailbox->flagname[flag]);
}
}
r = seen_copy(userid, oldmailbox, newmailbox);
if (r) goto fail;
/* mark the "used" back to zero, so it updates the new quota! */
mailbox_quota_dirty(newmailbox);
newmailbox->quota_previously_used = 0;
/* commit the index changes last, once files are in place */
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);
return 0;
fail:
/* failure and back out */
/* XXX - per file paths, need to clean up individual filenames */
path = mboxname_datapath(newmailbox->part, newmailbox->name, 0);
mailbox_delete_files(path);
mpath = mboxname_metapath(newmailbox->part, newmailbox->name, 0, 0);
if (strcmp(path, mpath)) mailbox_delete_files(mpath);
mailbox_close(&newmailbox);
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 srcfd, destfd;
struct stat sbuf;
const char *src_base = 0;
unsigned long src_size = 0;
int n;
if (!nolink) {
if (link(from, to) == 0) return 0;
if (errno == EEXIST) {
if (unlink(to) == -1) {
syslog(LOG_ERR, "IOERROR: unlinking to recreate %s: %m", to);
return IMAP_IOERROR;
}
if (link(from, to) == 0) return 0;
}
}
destfd = open(to, O_RDWR|O_TRUNC|O_CREAT, 0666);
if (destfd == -1) {
syslog(LOG_ERR, "IOERROR: creating %s: %m", to);
return IMAP_IOERROR;
}
srcfd = open(from, O_RDONLY, 0666);
if (srcfd == -1) {
syslog(LOG_ERR, "IOERROR: opening %s: %m", from);
close(destfd);
return IMAP_IOERROR;
}
if (fstat(srcfd, &sbuf) == -1) {
syslog(LOG_ERR, "IOERROR: fstat on %s: %m", from);
close(srcfd);
close(destfd);
return IMAP_IOERROR;
}
map_refresh(srcfd, 1, &src_base, &src_size, sbuf.st_size, from, 0);
n = retry_write(destfd, src_base, src_size);
if (n == -1 || fsync(destfd)) {
map_free(&src_base, &src_size);
close(srcfd);
close(destfd);
syslog(LOG_ERR, "IOERROR: writing %s: %m", to);
return IMAP_IOERROR;
}
map_free(&src_base, &src_size);
close(srcfd);
close(destfd);
return 0;
}
/* ---------------------------------------------------------------------- */
/* RECONSTRUCT SUPPORT */
/* ---------------------------------------------------------------------- */
#define UIDGROW 300
struct found_files {
unsigned long *uids;
unsigned nalloc;
unsigned nused;
};
static int sort_uid(const void *a, const void *b)
{
return *(unsigned long *)a - *(unsigned long *)b;
}
static void init_files(struct found_files *ff)
{
ff->uids = NULL;
ff->nused = 0;
ff->nalloc = 0;
}
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)
{
if (ff->nalloc)
free(ff->uids);
init_files(ff);
}
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;
init_files(files);
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 = parseuint32(p, &p, &uid);
/* it has to have a . after the number and nothing else */
if (r || uid == 0 || *p++ != '.' || *p) {
/* check if it's a directory */
snprintf(buf, MAX_MAILBOX_PATH, "%s/%s", dirpath, dirent->d_name);
if (stat(buf, &sbuf) == -1) continue; /* ignore emepheral */
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;
}
/* 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;
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;
syslog(LOG_NOTICE, "create new mailbox %s", name);
mailbox->part = xstrdup(mbentry.partition);
mailbox->acl = xstrdup(mbentry.acl);
/* 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);
return mailbox_create(name, mbentry.partition, mbentry.acl,
NULL, options, time(0), mbptr);
}
/* 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) {
r = mboxlist_update(mailbox->name, mailbox->mbtype,
mailbox->part, acl, 0);
}
}
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_compare(&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) {
r = message_parse(fname, record);
if (r) return r;
/* it's not the same message! */
if (!message_guid_compare(&record->guid, &copy.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) {
record->system_flags |= FLAG_EXPUNGED | FLAG_UNLINKED;
mailbox->i.options |= OPT_MAILBOX_NEEDS_UNLINK;
return mailbox_rewrite_index_record(mailbox, record);
}
/* 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 available from the file */
if (!record->internaldate) {
if (did_stat || stat(fname, &sbuf) != -1)
record->internaldate = sbuf.st_mtime;
else
record->internaldate = time(NULL);
}
if (did_stat && sbuf.st_mtime != record->internaldate) {
printf("%s timestamp mismatch %u\n",
mailbox->name, record->uid);
syslog(LOG_ERR, "%s timestamp mismatch %u",
mailbox->name, record->uid);
if (make_changes) {
/* make the file timestamp correct */
struct utimbuf settime;
settime.actime = settime.modtime = record->internaldate;
if (utime(fname, &settime) == -1)
return IMAP_IOERROR;
}
}
/* 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, &copy, 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 make_changes)
{
char *fname = mailbox_message_fname(mailbox, uid);
int r = 0;
struct index_record record;
struct stat sbuf;
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));
record.internaldate = sbuf.st_mtime;
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(mailbox_message_fname(mailbox, uid));
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->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);
}
if (old->sync_crc != new->sync_crc) {
syslog(LOG_ERR, "%s: updating sync_crc %08X => %08X",
mailbox->name, old->sync_crc, new->sync_crc);
printf("%s: updating sync_crc %08X => %08X\n",
mailbox->name, old->sync_crc, new->sync_crc);
}
}
/*
* 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;
struct found_files discovered;
struct index_header old_header;
int have_file;
uint32_t recno;
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);
char buf[4];
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 */
*((bit32 *)(buf)) = htonl(mailbox->i.generation_no);
n = retry_write(mailbox->cache_fd, buf, 4);
if (n != 4) goto close;
/* ensure that next user will create the MMAPing */
mailbox->need_cache_refresh = 1;
}
r = find_files(mailbox, &files, flags);
if (r) goto close;
init_files(&discovered);
msg = 0;
for (recno = 1; recno <= mailbox->i.num_records; recno++) {
if (mailbox_read_index_record(mailbox, recno, &record)) {
printf("%s: record corrupted %u (maybe uid %u)\n",
mailbox->name, recno, record.uid);
continue;
}
/* 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++;
}
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);
if (make_changes) {
r = mailbox_commit(mailbox);
}
close:
free_files(&files);
free_files(&discovered);
mailbox_close(&mailbox);
return r;
}

File Metadata

Mime Type
text/x-c
Expires
Sat, Apr 4, 1:48 AM (1 w, 2 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18822020
Default Alt Text
mailbox.c (102 KB)

Event Timeline