mail-modifylog.c revision 7f0113f4efa1e4e020d64f714d1d765963005fd5
c636315472e4f87313af7be30b7fbcad4b8ca8a4Stephen Gallagher/* Copyright (C) 2002 Timo Sirainen */
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher/* Maximum size for modify log (isn't exact) */
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher#define MODIFYLOG_FILE_POSITION(log, ptr) \
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher ((int) ((char *) (ptr) - (char *) (log)->mmap_base))
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagherstatic int file_lock(int fd, int wait_lock, int lock_type)
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher if (fcntl(fd, wait_lock ? F_SETLKW : F_SETLK, &fl) == -1) {
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher/* Returns 1 = ok, 0 = failed to get the lock, -1 = error */
be1ef1c62ad13612be5e1f879476c24452a5d6d0Stephen Gallagherstatic int mail_modifylog_try_lock(MailModifyLog *log, int lock_type)
4dd615c01357b8715711aad6820ba9595d3ad377Stephen Gallagher ret = file_lock(log->fd, FALSE, lock_type);
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher index_set_error(log->index, "fcntl() failed with file %s: %m",
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagherstatic int mail_modifylog_wait_lock(MailModifyLog *log)
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher if (file_lock(log->fd, TRUE, F_RDLCK) < 1) {
c737e1444fb186e349e59bfa9dac4995b720b4b1Jan Zeleny index_set_error(log->index, "fcntl() failed with file %s: %m",
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher/* returns 1 = yes, 0 = no, -1 = error */
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagherstatic int mail_modifylog_have_other_users(MailModifyLog *log)
2827b0d03f7b6bafa504d22a5d7ca39cbda048b3Pavel Březina /* try grabbing exclusive lock */
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher /* revert back to shared lock */
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher switch (mail_modifylog_try_lock(log, F_RDLCK)) {
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher /* shouldn't happen */
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher index_set_error(log->index, "fcntl(F_WRLCK -> F_RDLCK) "
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher /* fall through */
fe60346714a73ac3987f786731389320633dd245Pavel Březina unsigned int extra;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher if (!log->dirty_mmap && log->mmaped_id == log->header->sync_id)
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher (void)munmap(log->mmap_base, log->mmap_length);
bf5a808fa92007c325c3996e79694badfab201d4Stephen Gallagher log->mmap_base = mmap_rw_file(log->fd, &log->mmap_length);
bf5a808fa92007c325c3996e79694badfab201d4Stephen Gallagher "modify log: mmap() failed with file %s: %m",
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher if (log->mmap_length < sizeof(ModifyLogHeader)) {
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher /* FIXME: we could do better.. */
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher extra = (log->mmap_length - sizeof(ModifyLogHeader)) %
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher /* partial write or corrupted -
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher truncate the file to valid length */
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher (void)ftruncate(log->fd, (off_t)log->mmap_length);
3b08dec5ee634f83ee18e1753d5ffe0ac5e3c458Jakub Hrozekstatic MailModifyLog *mail_modifylog_new(MailIndex *index)
3b1df539835367cb81cd5ff0f9959947d5642e55Stephen Gallagherstatic void mail_modifylog_close(MailModifyLog *log)
667db40da4db362d7ca0a1f7f1c4ba40fb71795aJakub Hrozekstatic int mail_modifylog_init_fd(MailModifyLog *log, int fd,
667db40da4db362d7ca0a1f7f1c4ba40fb71795aJakub Hrozek const char *path)
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher /* write header */
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher if (write_full(fd, &hdr, sizeof(hdr)) < 0) {
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher index_set_error(log->index, "write() failed for modify "
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher index_set_error(log->index, "ftruncate() failed for modify "
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagherstatic int mail_modifylog_open_and_init_file(MailModifyLog *log,
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher index_set_error(log->index, "Error opening modify log "
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher index_set_error(log->index, "Error locking modify log "
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher if (ret == 1 && mail_modifylog_init_fd(log, fd, path)) {
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagherint mail_modifylog_create(MailIndex *index)
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher i_assert(index->lock_type == MAIL_LOCK_EXCLUSIVE);
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher path = t_strconcat(log->index->filepath, ".log", NULL);
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher if (!mail_modifylog_open_and_init_file(log, path) ||
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher /* fatal failure */
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher/* Returns 1 = ok, 0 = full, -1 = error */
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagherstatic int mail_modifylog_open_and_verify(MailModifyLog *log, const char *path)
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher index_set_error(log->index, "Can't open modify log "
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher if (read(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) {
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher index_set_error(log->index, "read() failed when for modify "
2e6087c6cc903d5164b9a1d5e3d791fd046001d9Jakub Hrozek if (ret != -1 && hdr.indexid != log->index->indexid) {
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher index_set_error(log->index, "IndexID mismatch for modify log "
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher /* we have to rebuild it, make sure it's deleted. */
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher if (ret != -1 && hdr.sync_id == SYNC_ID_FULL) {
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagherstatic int mail_modifylog_find_or_create(MailModifyLog *log)
3b08dec5ee634f83ee18e1753d5ffe0ac5e3c458Jakub Hrozek for (i = 0; i < 2; i++) {
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher /* first try <index>.log */
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher path1 = t_strconcat(log->index->filepath, ".log", NULL);
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher path2 = t_strconcat(log->index->filepath, ".log.2", NULL);
72e60fd4eabcfbcdbfe01e8c38b94052bc6c2067Jakub Hrozek ret = mail_modifylog_open_and_verify(log, path1);
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher /* then <index>.log.2 */
5a70b84cb66fb8c7a3fce0e3f2e4b61e0b2ea9d4Simo Sorce if (mail_modifylog_open_and_verify(log, path2) == 1)
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher if (mail_modifylog_open_and_init_file(log, path1))
ef39c0adcb61b16f9edc7beb4cdc8f3b0d5a8f15Stephen Gallagher if (mail_modifylog_open_and_init_file(log, path2))
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher /* maybe the file was just switched, check the logs again */
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher index_set_error(log->index, "We could neither use nor create "
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher "the modify log for index %s", log->index->filepath);
1f1e6cbc59868f06dee3ab4b3df660fcb77ce1c8Jakub Hrozekint mail_modifylog_open_or_create(MailIndex *index)
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher if (!mail_modifylog_find_or_create(log) ||
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher /* fatal failure */
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallaghervoid mail_modifylog_free(MailModifyLog *log)
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagherint mail_modifylog_sync_file(MailModifyLog *log)
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher if (msync(log->mmap_base, log->mmap_length, MS_SYNC) == -1) {
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher index_set_error(log->index, "msync() failed for %s: %m",
3b08dec5ee634f83ee18e1753d5ffe0ac5e3c458Jakub Hrozek index_set_error(log->index, "fsync() failed for %s: %m",
fe2091327ff44f80d6681c261494e4432404e9baStephen Gallagherstatic int mail_modifylog_append(MailModifyLog *log, ModifyLogRecord *rec,
87c07559af5cfcd2752295ef7c425bd3205f426fStephen Gallagher i_assert(log->index->lock_type == MAIL_LOCK_EXCLUSIVE);
fe2091327ff44f80d6681c261494e4432404e9baStephen Gallagher switch (mail_modifylog_have_other_users(log)) {
c2352a73f52f600d95966ebe0b0819649ba923faStephen Gallagher /* we're the only one having this log open,
4c1bf6607060cea867fccf667063c028dfd51e96Stephen Gallagher no need for modify log. */
fe2091327ff44f80d6681c261494e4432404e9baStephen Gallagher index_set_error(log->index, "lseek() failed with file %s: %m",
fe2091327ff44f80d6681c261494e4432404e9baStephen Gallagher if (write_full(log->fd, rec, sizeof(ModifyLogRecord)) < 0) {
fe2091327ff44f80d6681c261494e4432404e9baStephen Gallagher index_set_error(log->index, "Error appending to file %s: %m",
87c07559af5cfcd2752295ef7c425bd3205f426fStephen Gallagher log->synced_position += sizeof(ModifyLogRecord);
3f98cdc011bb4e8cd22c088f288b0bcdb6452492Jakub Hrozekint mail_modifylog_add_expunge(MailModifyLog *log, unsigned int seq,
b32159300fea63222d8dd9200ed634087704ea74Stephen Gallagher /* expunges must not be added when log isn't synced */
b32159300fea63222d8dd9200ed634087704ea74Stephen Gallagher i_assert(external_change || log->synced_id == log->header->sync_id);
4dd615c01357b8715711aad6820ba9595d3ad377Stephen Gallagher return mail_modifylog_append(log, &rec, external_change);
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagherint mail_modifylog_add_flags(MailModifyLog *log, unsigned int seq,
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher return mail_modifylog_append(log, &rec, external_change);
fe2091327ff44f80d6681c261494e4432404e9baStephen GallagherModifyLogRecord *mail_modifylog_get_nonsynced(MailModifyLog *log,
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher unsigned int *count)
ef39c0adcb61b16f9edc7beb4cdc8f3b0d5a8f15Stephen Gallagher i_assert(log->index->lock_type != MAIL_LOCK_UNLOCK);
b32159300fea63222d8dd9200ed634087704ea74Stephen Gallagher i_assert(log->synced_position <= log->mmap_length);
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher i_assert(log->synced_position >= sizeof(ModifyLogHeader));
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher rec = (ModifyLogRecord *) ((char *) log->mmap_base +
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher end_rec = (ModifyLogRecord *) ((char *) log->mmap_base +
fe2091327ff44f80d6681c261494e4432404e9baStephen Gallagherstatic int mail_modifylog_switch_file(MailModifyLog *log)
2827b0d03f7b6bafa504d22a5d7ca39cbda048b3Pavel Březinastatic void mail_modifylog_try_switch_file(MailModifyLog *log)
2827b0d03f7b6bafa504d22a5d7ca39cbda048b3Pavel Březina if (mail_modifylog_open_and_init_file(log, path)) {
2827b0d03f7b6bafa504d22a5d7ca39cbda048b3Pavel Březina /* FIXME: we want to update the _old_ file's header.
9e80079370ff3b943832adc3c5ef430e64be0a0cJakub Hrozek and this changes the new one. and it's already closed the
9e80079370ff3b943832adc3c5ef430e64be0a0cJakub Hrozek old one and mmap() is invalid, and we crash here.. */
9e80079370ff3b943832adc3c5ef430e64be0a0cJakub Hrozekint mail_modifylog_mark_synced(MailModifyLog *log)
9e80079370ff3b943832adc3c5ef430e64be0a0cJakub Hrozek i_assert(log->index->lock_type != MAIL_LOCK_UNLOCK);
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher if (log->header->sync_id == SYNC_ID_FULL) {
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher /* log file is full, switch to next one */
fe2091327ff44f80d6681c261494e4432404e9baStephen Gallagher if (log->synced_id == log->header->sync_id) {
fe2091327ff44f80d6681c261494e4432404e9baStephen Gallagher /* we are already synced */
52e0894fd65bff4715c88330eb62b28e1635228fStephen Gallagher if (log->mmap_length > MAX_MODIFYLOG_SIZE) {
530ba03ecabb472f17d5d1ab546aec9390492de1Jakub Hrozek /* if the other file isn't locked, switch to it */
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagherstatic int compare_uint(const void *p1, const void *p2)
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher return *u1 < *u2 ? -1 : *u1 > *u2 ? 1 : 0;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagherconst unsigned int *
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallaghermail_modifylog_seq_get_expunges(MailModifyLog *log,
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher unsigned int last_pos_seq, before, max_records, *arr, *expunges;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher i_assert(log->index->lock_type != MAIL_LOCK_UNLOCK);
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher /* find the first expunged message that affects our range */
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher rec = (ModifyLogRecord *) ((char *) log->mmap_base +
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher end_rec = (ModifyLogRecord *) ((char *) log->mmap_base +
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher if (rec->type == RECORD_TYPE_EXPUNGE && rec->seq <= last_seq)
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher /* none found */
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher expunges = t_malloc(sizeof(unsigned int));
fe2091327ff44f80d6681c261494e4432404e9baStephen Gallagher /* allocate memory for the returned array. the file size - synced
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher position should be quite near the amount of memory we need, unless
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher there's lots of FLAGS_CHANGED records which is why there's the
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher second check to make sure it's not unneededly large. */
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher max_records = (log->mmap_length - MODIFYLOG_FILE_POSITION(log, rec)) /
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher if (max_records > last_seq - first_seq + 1)
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher expunges = arr = t_malloc((max_records+1) * sizeof(unsigned int));
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher /* last_pos_seq is updated all the time to contain the last_seq
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher comparable to current record's seq. number */
fe2091327ff44f80d6681c261494e4432404e9baStephen Gallagher /* before our range */
f1828234a850dd28465425248a83a993f262918fPavel Březina /* within our range */
f1828234a850dd28465425248a83a993f262918fPavel Březina /* log contains more data than it should
f1828234a850dd28465425248a83a993f262918fPavel Březina have - must be corrupted. */
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina "Modify log %s is corrupted",
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher /* sort the UID array, not including the terminating 0 */
21f28bdbab10881b9fb0b890dfa15af429326606Sumit Bose qsort(expunges, (unsigned int) (arr - expunges), sizeof(unsigned int),
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagherconst unsigned int *
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallaghermail_modifylog_uid_get_expunges(MailModifyLog *log,
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher /* pretty much copy&pasted from sequence code above ..
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher kind of annoying */
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher unsigned int before, max_records, *arr, *expunges;
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher i_assert(log->index->lock_type != MAIL_LOCK_UNLOCK);
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher /* find the first expunged message that affects our range */
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher rec = (ModifyLogRecord *) ((char *) log->mmap_base +
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher end_rec = (ModifyLogRecord *) ((char *) log->mmap_base +
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher if (rec->type == RECORD_TYPE_EXPUNGE && rec->uid <= last_uid)
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher /* none found */
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher expunges = t_malloc(sizeof(unsigned int));
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher /* allocate memory for the returned array. the file size - synced
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher position should be quite near the amount of memory we need, unless
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher there's lots of FLAGS_CHANGED records which is why there's the
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher second check to make sure it's not unneededly large. */
5843ad321944a028f6dee7e1fd4f9381c4953d07Sumit Bose max_records = (log->mmap_length - MODIFYLOG_FILE_POSITION(log, rec)) /
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher if (max_records > last_uid - first_uid + 1)
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher expunges = arr = t_malloc((max_records+1) * sizeof(unsigned int));
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher rec->uid >= first_uid && rec->uid <= last_uid) {
c8b8901b05da9e31dba320f305ec20301e928cfbSumit Bose /* within our range */
fe2091327ff44f80d6681c261494e4432404e9baStephen Gallagher /* log contains more data than it should
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher have - must be corrupted. */
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher "Modify log %s is corrupted",
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher /* sort the UID array, not including the terminating 0 */
551aa6c36797ed720487f5974dcadabf19e6ff9fStephen Gallagher qsort(expunges, (unsigned int) (arr - expunges), sizeof(unsigned int),