/* upgrade_index.c -- Mailbox upgrade 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.199 2010/01/06 17:01:36 murch Exp $
*/
#include <config.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.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 "assert.h"
#include "crc32.h"
#include "exitcodes.h"
#include "global.h"
#include "imap_err.h"
#include "cyr_lock.h"
#include "mailbox.h"
#include "message.h"
#include "map.h"
#include "retry.h"
#include "seen.h"
#include "util.h"
#include "sequence.h"
#include "xmalloc.h"
struct expunge_data {
uint32_t uid;
const char *base;
};
static int sort_expunge(const void *a, const void *b)
{
struct expunge_data *ea = (struct expunge_data *)a;
struct expunge_data *eb = (struct expunge_data *)b;
return ea->uid - eb->uid;
}
static void upgrade_index_record(struct mailbox *mailbox,
const char *buf,
struct index_record *record,
int record_size)
{
char recordbuf[INDEX_RECORD_SIZE];
struct utimbuf settime;
const char *fname;
assert(record_size <= INDEX_RECORD_SIZE);
memset(recordbuf, 0, INDEX_RECORD_SIZE);
memcpy(recordbuf, buf, record_size);
/* do the initial parse. Ignore the result, crc32 will mismatch
* for sure */
mailbox_buf_to_index_record(recordbuf, record);
fname = mailbox_message_fname(mailbox, record->uid);
if (message_parse(fname, record)) {
/* failed to create, don't try to write */
record->crec.len = 0;
/* and the record is expunged too! */
record->system_flags |= FLAG_EXPUNGED | FLAG_UNLINKED;
syslog(LOG_ERR, "IOERROR: FATAL - failed to parse "
"file %s for upgrade, expunging", fname);
return;
}
/* update the mtime to match the internaldate */
settime.actime = settime.modtime = record->internaldate;
utime(fname, &settime);
}
/*
* Upgrade an index/expunge file for 'mailbox'
*/
int upgrade_index(struct mailbox *mailbox)
{
uint32_t recno, erecno;
uint32_t uid, euid;
unsigned long oldmapnum;
unsigned long oldnum_records;
unsigned long expunge_num = 0;
uint32_t oldminor_version, oldstart_offset, oldrecord_size;
indexbuffer_t headerbuf;
const char *bufp = NULL;
char *hbuf = (char *)headerbuf.buf;
const char *fname;
const char *datadirname;
struct stat sbuf;
struct seqset *seq = NULL;
struct index_record record;
int expunge_fd = -1;
const char *expunge_base = NULL;
unsigned long expunge_len = 0; /* mapped size */
unsigned long emapnum;
bit32 eversion, eoffset, expungerecord_size;
struct expunge_data *expunge_data = NULL;
struct mailbox_repack *repack = NULL;
int r;
if (mailbox->index_size < OFFSET_NUM_RECORDS)
return IMAP_MAILBOX_BADFORMAT;
oldminor_version = ntohl(*((bit32 *)(mailbox->index_base+OFFSET_MINOR_VERSION)));
oldstart_offset = ntohl(*((bit32 *)(mailbox->index_base+OFFSET_START_OFFSET)));
oldrecord_size = ntohl(*((bit32 *)(mailbox->index_base+OFFSET_RECORD_SIZE)));
oldnum_records = ntohl(*((bit32 *)(mailbox->index_base+OFFSET_NUM_RECORDS)));
/* bogus data at the start of the index file? */
if (!oldstart_offset || !oldrecord_size)
return IMAP_MAILBOX_BADFORMAT;
oldmapnum = (mailbox->index_size - oldstart_offset) / oldrecord_size;
if (oldmapnum < oldnum_records) {
syslog(LOG_ERR, "upgrade: %s map doesn't fit, shrinking index %lu to %lu",
mailbox->name, oldnum_records, oldmapnum);
oldnum_records = oldmapnum;
}
/* check if someone else already upgraded the index! */
if (oldminor_version == MAILBOX_MINOR_VERSION)
goto done;
/* check that the data directory exists. If not, it may be that
* something isn't correctly mounted. We don't want to wipe out
* all the index records due to IOERRORs just because the admin
* made a temporary mistake */
datadirname = mailbox_message_fname(mailbox, 0);
if (stat(datadirname, &sbuf)) {
syslog(LOG_ERR, "IOERROR: unable to find data directory %s "
"for mailbox %s, refusing to upgrade",
datadirname, mailbox->name);
return IMAP_IOERROR;
}
/* Copy existing header so we can upgrade it */
memset(hbuf, 0, INDEX_HEADER_SIZE);
if (oldstart_offset > INDEX_HEADER_SIZE)
memcpy(hbuf, mailbox->index_base, INDEX_HEADER_SIZE);
else
memcpy(hbuf, mailbox->index_base, oldstart_offset);
/* QUOTA_MAILBOX_USED64 added with minor version 6 */
if (oldminor_version < 6) {
/* upgrade quota to 64-bits (bump existing fields) */
memmove(hbuf+OFFSET_QUOTA_MAILBOX_USED, hbuf+OFFSET_QUOTA_MAILBOX_USED64,
INDEX_HEADER_SIZE - OFFSET_QUOTA_MAILBOX_USED);
/* zero the unused 32-bits */
*((bit32 *)(hbuf+OFFSET_QUOTA_MAILBOX_USED64)) = htonl(0);
}
/* ignore the result - we EXPECT a CRC32 mismatch */
mailbox_buf_to_index_header(hbuf, &mailbox->i);
/* HIGHESTMODSEQ[_64] added with minor version 8 */
if (oldminor_version < 8)
mailbox->i.highestmodseq = 1;
/* new version fields */
mailbox->i.minor_version = MAILBOX_MINOR_VERSION;
mailbox->i.start_offset = INDEX_HEADER_SIZE;
mailbox->i.record_size = INDEX_RECORD_SIZE;
/* upgrade other fields as necessary
*
* minor version wasn't updated religiously in the early days,
* so we need to use the old offset instead */
if (oldstart_offset < OFFSET_POP3_LAST_LOGIN)
mailbox->i.pop3_last_login = 0;
if (oldstart_offset < OFFSET_UIDVALIDITY)
mailbox->i.uidvalidity = 1;
if (oldstart_offset < OFFSET_MAILBOX_OPTIONS)
mailbox->i.options = config_getint(IMAPOPT_MAILBOX_DEFAULT_OPTIONS);
if (oldminor_version < 12) {
struct seendata sd;
char *owner_userid;
/* remove the CONDSTORE option - it's implicit now */
mailbox->i.options &= ~OPT_IMAP_CONDSTORE;
if (mailbox->i.options & OPT_IMAP_SHAREDSEEN)
owner_userid = "anyone";
else
owner_userid = mboxname_to_userid(mailbox->name);
r = mailbox_read_header(mailbox, NULL);
if (r) goto fail;
/* NEW HEADER FIELDS */
/* we'll set this if there are expunged records */
mailbox->i.first_expunged = 0;
/* we can't know about deletions before the current modseq */
mailbox->i.deletedmodseq = mailbox->i.highestmodseq;
/* bootstrap CRC matching */
mailbox->i.header_file_crc = mailbox->header_file_crc;
/* set up seen tracking for user inside the mailbox */
if (!owner_userid) {
r = IMAP_MAILBOX_NONEXISTENT;
} else {
struct seen *seendb = NULL;
r = seen_open(owner_userid, SEEN_SILENT, &seendb);
if (!r) r = seen_read(seendb, mailbox->uniqueid, &sd);
seen_close(&seendb);
}
if (r) { /* no seen data? */
mailbox->i.recentuid = 0;
mailbox->i.recenttime = 0;
}
else {
mailbox->i.recentuid = sd.lastuid;
mailbox->i.recenttime = sd.lastchange;
seq = seqset_parse(sd.seenuids, NULL, sd.lastuid);
seen_freedata(&sd);
}
/* check for expunge */
fname = mailbox_meta_fname(mailbox, META_EXPUNGE);
expunge_fd = open(fname, O_RDWR, 0666);
if (expunge_fd == -1) goto no_expunge;
r = fstat(expunge_fd, &sbuf);
if (r == -1) goto no_expunge;
if (sbuf.st_size < INDEX_HEADER_SIZE) goto no_expunge;
map_refresh(expunge_fd, 1, &expunge_base,
&expunge_len, sbuf.st_size, "expunge",
mailbox->name);
/* use the expunge file's header information just in case
* versions are skewed for some reason */
eversion = ntohl(*((bit32 *)(expunge_base+OFFSET_MINOR_VERSION)));
eoffset = ntohl(*((bit32 *)(expunge_base+OFFSET_START_OFFSET)));
expungerecord_size = ntohl(*((bit32 *)(expunge_base+OFFSET_RECORD_SIZE)));
/* bogus data at the start of the expunge file? */
if (!eoffset || !expungerecord_size)
goto no_expunge;
expunge_num = ntohl(*((bit32 *)(expunge_base+OFFSET_NUM_RECORDS)));
emapnum = (sbuf.st_size - eoffset) / expungerecord_size;
if (emapnum < expunge_num) {
syslog(LOG_ERR, "IOERROR: %s map doesn't fit, shrinking expunge %lu to %lu",
mailbox->name, expunge_num, emapnum);
expunge_num = emapnum;
}
/* make sure there's space for them */
expunge_data = xmalloc(expunge_num * sizeof(struct expunge_data));
/* find the start offset for each record, and the UID */
for (erecno = 1; erecno <= expunge_num; erecno++) {
bufp = expunge_base + eoffset + (erecno-1)*expungerecord_size;
expunge_data[erecno-1].uid = ntohl(*((bit32 *)(bufp+OFFSET_UID)));
expunge_data[erecno-1].base = bufp;
}
/* expunge files were not sorted. So sort them now for easier
* interleaving */
qsort(expunge_data, expunge_num,
sizeof(struct expunge_data), &sort_expunge);
}
no_expunge:
mailbox_repack_setup(mailbox, &repack);
/* Write the rest of new index */
recno = 1;
erecno = 1;
while (recno <= oldnum_records || erecno <= expunge_num) {
/* read the uid */
if (recno <= oldnum_records) {
bufp = mailbox->index_base + oldstart_offset + (recno-1)*oldrecord_size;
uid = ntohl(*((bit32 *)(bufp+OFFSET_UID)));
}
else {
uid = UINT32_MAX;
}
if (erecno <= expunge_num) {
euid = expunge_data[erecno-1].uid;
}
else {
euid = UINT32_MAX;
}
/* case: index UID is first, or the same */
if (uid <= euid) {
upgrade_index_record(mailbox, bufp, &record, oldrecord_size);
recno++;
if (uid == euid) /* duplicate in both, skip expunged */
erecno++;
}
else {
upgrade_index_record(mailbox, expunge_data[erecno-1].base,
&record, expungerecord_size);
record.system_flags |= FLAG_EXPUNGED;
erecno++;
}
if (oldminor_version < 12 && seqset_ismember(seq, record.uid))
record.system_flags |= FLAG_SEEN;
r = mailbox_repack_add(repack, &record);
if (r) goto fail;
}
r = mailbox_repack_commit(&repack);
if (r) goto fail;
/* don't need this file any more! */
unlink(mailbox_meta_fname(mailbox, META_EXPUNGE));
/* XXX - remove seen record */
syslog(LOG_INFO, "Index upgrade: %s (%d -> %d)", mailbox->name,
oldminor_version, MAILBOX_MINOR_VERSION);
done:
if (expunge_fd != -1) close(expunge_fd);
if (expunge_base) map_free(&expunge_base, &expunge_len);
seqset_free(seq);
free(expunge_data);
/* it's definitely changed! */
r = mailbox_open_index(mailbox);
if (r) return r;
return 0;
fail:
if (expunge_fd != -1) close(expunge_fd);
if (expunge_base) map_free(&expunge_base, &expunge_len);
seqset_free(seq);
free(expunge_data);
mailbox_repack_abort(&repack);
syslog(LOG_ERR, "Index upgrade failed: %s", mailbox->name);
return IMAP_IOERROR;
}