static void maildir_mail_remove_sizes_from_filename(struct mail *mail, enum mail_fetch_field field) { struct maildir_mailbox *mbox = (struct maildir_mailbox *)mail->box; enum maildir_uidlist_rec_flag flags; const char *fname; uoff_t size; char wrong_key; if (mbox->storage->set->maildir_broken_filename_sizes) { /* never try to fix sizes in maildir filenames */ return; } if (maildir_sync_lookup(mbox, mail->uid, &flags, &fname) <= 0) return; if (strchr(fname, MAILDIR_EXTRA_SEP) == NULL) return; if (field == MAIL_FETCH_VIRTUAL_SIZE && maildir_filename_get_size(fname, MAILDIR_EXTRA_VIRTUAL_SIZE, &size)) { wrong_key = 'W'; } else if (field == MAIL_FETCH_PHYSICAL_SIZE && maildir_filename_get_size(fname, MAILDIR_EXTRA_FILE_SIZE, &size)) { wrong_key = 'S'; } else { /* the broken size isn't in filename */ return; } (void)maildir_file_do(mbox, mail->uid, do_fix_size, &wrong_key); }
static int maildir_mail_get_physical_size(struct mail *_mail, uoff_t *size_r) { struct index_mail *mail = (struct index_mail *)_mail; struct maildir_mailbox *mbox = (struct maildir_mailbox *)_mail->box; struct index_mail_data *data = &mail->data; struct stat st; const char *path; int ret; if (maildir_uidlist_is_read(mbox->uidlist) || (_mail->box->flags & MAILBOX_FLAG_POP3_SESSION) != 0) { /* try to get the size from uidlist (see virtual size above) */ if (maildir_quick_size_lookup(mail, FALSE, &data->physical_size) < 0) return -1; } if (data->physical_size == (uoff_t)-1) { if (index_mail_get_physical_size(_mail, size_r) == 0) { i_assert(mail->data.physical_size != (uoff_t)-1); maildir_handle_size_caching(mail, TRUE, FALSE); return 0; } if (maildir_quick_size_lookup(mail, FALSE, &data->physical_size) < 0) return -1; } if (data->physical_size != (uoff_t)-1) { data->dont_cache_fetch_fields |= MAIL_FETCH_PHYSICAL_SIZE; *size_r = data->physical_size; return 0; } if (!_mail->saving) { ret = maildir_file_do(mbox, _mail->uid, do_stat, &st); if (ret <= 0) { if (ret == 0) mail_set_expunged(_mail); return -1; } } else { /* saved mail which hasn't been committed yet */ path = maildir_save_file_get_path(_mail->transaction, _mail->seq); if (stat(path, &st) < 0) { mail_storage_set_critical(_mail->box->storage, "stat(%s) failed: %m", path); return -1; } } data->physical_size = st.st_size; maildir_handle_size_caching(mail, FALSE, FALSE); *size_r = st.st_size; return 0; }
static void maildir_mail_remove_sizes_from_filename(struct mail *mail, enum mail_fetch_field field) { struct maildir_mailbox *mbox = (struct maildir_mailbox *)mail->box; struct mail_private *pmail = (struct mail_private *)mail; enum maildir_uidlist_rec_flag flags; const char *fname; uoff_t size; struct maildir_size_fix_ctx ctx; if (mbox->storage->set->maildir_broken_filename_sizes) { /* never try to fix sizes in maildir filenames */ return; } if (maildir_sync_lookup(mbox, mail->uid, &flags, &fname) <= 0) return; if (strchr(fname, MAILDIR_EXTRA_SEP) == NULL) return; memset(&ctx, 0, sizeof(ctx)); ctx.physical_size = (uoff_t)-1; if (field == MAIL_FETCH_VIRTUAL_SIZE && maildir_filename_get_size(fname, MAILDIR_EXTRA_VIRTUAL_SIZE, &size)) { ctx.wrong_key = 'W'; } else if (field == MAIL_FETCH_PHYSICAL_SIZE && maildir_filename_get_size(fname, MAILDIR_EXTRA_FILE_SIZE, &size)) { ctx.wrong_key = 'S'; } else { /* the broken size isn't in filename */ return; } if (pmail->v.istream_opened != NULL) { /* the mail could be e.g. compressed. get the physical size the slow way by actually reading the mail. */ struct istream *input; const struct stat *stp; if (mail_get_stream(mail, NULL, NULL, &input) < 0) return; if (i_stream_stat(input, TRUE, &stp) < 0) return; ctx.physical_size = stp->st_size; } (void)maildir_file_do(mbox, mail->uid, do_fix_size, &ctx); }
static int maildir_mail_stat(struct mail *mail, struct stat *st_r) { struct maildir_mailbox *mbox = (struct maildir_mailbox *)mail->box; struct index_mail *imail = (struct index_mail *)mail; const char *path; int fd, ret; if (mail->lookup_abort == MAIL_LOOKUP_ABORT_NOT_IN_CACHE) { mail_set_aborted(mail); return -1; } if (imail->data.access_part != 0 && imail->data.stream == NULL) { /* we're going to open the mail anyway */ struct istream *input; (void)mail_get_stream(mail, NULL, NULL, &input); } if (imail->data.stream != NULL && (fd = i_stream_get_fd(imail->data.stream)) != -1) { mail->transaction->stats.fstat_lookup_count++; if (fstat(fd, st_r) < 0) { mail_storage_set_critical(mail->box->storage, "fstat(%s) failed: %m", i_stream_get_name(imail->data.stream)); return -1; } } else if (!mail->saving) { mail->transaction->stats.stat_lookup_count++; ret = maildir_file_do(mbox, mail->uid, do_stat, st_r); if (ret <= 0) { if (ret == 0) mail_set_expunged(mail); return -1; } } else { mail->transaction->stats.stat_lookup_count++; path = maildir_save_file_get_path(mail->transaction, mail->seq); if (stat(path, st_r) < 0) { mail_storage_set_critical(mail->box->storage, "stat(%s) failed: %m", path); return -1; } } return 0; }
static struct istream * maildir_open_mail(struct maildir_mailbox *mbox, struct mail *mail, bool *deleted_r) { struct istream *input; const char *path; struct maildir_open_context ctx; *deleted_r = FALSE; ctx.fd = -1; ctx.path = NULL; mail->transaction->stats.open_lookup_count++; if (!mail->saving) { if (maildir_file_do(mbox, mail->uid, do_open, &ctx) < 0) return NULL; } else { path = maildir_save_file_get_path(mail->transaction, mail->seq); if (do_open(mbox, path, &ctx) <= 0) return NULL; } if (ctx.fd == -1) { *deleted_r = TRUE; return NULL; } input = i_stream_create_fd_autoclose(&ctx.fd, 0); if (input->stream_errno == EISDIR) { i_stream_destroy(&input); if (maildir_lose_unexpected_dir(&mbox->storage->storage, ctx.path) >= 0) *deleted_r = TRUE; } else { i_stream_set_name(input, ctx.path); index_mail_set_read_buffer_size(mail, input); } i_free(ctx.path); return input; }
int maildir_sync_index(struct maildir_index_sync_context *ctx, bool partial) { struct maildir_mailbox *mbox = ctx->mbox; struct mail_index_view *view = ctx->view; struct mail_index_view *view2; struct maildir_uidlist_iter_ctx *iter; struct mail_index_transaction *trans = ctx->trans; const struct mail_index_header *hdr; struct mail_index_header empty_hdr; const struct mail_index_record *rec; uint32_t seq, seq2, uid, prev_uid; enum maildir_uidlist_rec_flag uflags; const char *filename; uint32_t uid_validity, next_uid, hdr_next_uid, first_recent_uid; uint32_t first_uid; unsigned int changes = 0; int ret = 0; time_t time_before_sync; guid_128_t expunged_guid_128; enum mail_flags private_flags_mask; bool expunged, full_rescan = FALSE; i_assert(!mbox->syncing_commit); first_uid = 1; hdr = mail_index_get_header(view); uid_validity = maildir_uidlist_get_uid_validity(mbox->uidlist); if (uid_validity != hdr->uid_validity && uid_validity != 0 && hdr->uid_validity != 0) { /* uidvalidity changed and index isn't being synced for the first time, reset the index so we can add all messages as new */ i_warning("Maildir %s: UIDVALIDITY changed (%u -> %u)", mailbox_get_path(&ctx->mbox->box), hdr->uid_validity, uid_validity); mail_index_reset(trans); mailbox_recent_flags_reset(&mbox->box); first_uid = hdr->messages_count + 1; memset(&empty_hdr, 0, sizeof(empty_hdr)); empty_hdr.next_uid = 1; hdr = &empty_hdr; } hdr_next_uid = hdr->next_uid; ctx->mbox->box.tmp_sync_view = view; private_flags_mask = mailbox_get_private_flags_mask(&mbox->box); time_before_sync = time(NULL); mbox->syncing_commit = TRUE; seq = prev_uid = 0; first_recent_uid = I_MAX(hdr->first_recent_uid, 1); i_array_init(&ctx->keywords, MAILDIR_MAX_KEYWORDS); i_array_init(&ctx->idx_keywords, MAILDIR_MAX_KEYWORDS); iter = maildir_uidlist_iter_init(mbox->uidlist); while (maildir_uidlist_iter_next(iter, &uid, &uflags, &filename)) { maildir_filename_flags_get(ctx->keywords_sync_ctx, filename, &ctx->flags, &ctx->keywords); i_assert(uid > prev_uid); prev_uid = uid; /* the private flags are kept only in indexes. don't use them at all even for newly seen mails */ ctx->flags &= ~private_flags_mask; again: seq++; ctx->uid = uid; if (seq > hdr->messages_count) { if (uid < hdr_next_uid) { if (maildir_handle_uid_insertion(ctx, uflags, filename, uid) < 0) ret = -1; seq--; continue; } /* Trust uidlist recent flags only for newly added messages. When saving/copying messages with flags they're stored to cur/ and uidlist treats them as non-recent. */ if ((uflags & MAILDIR_UIDLIST_REC_FLAG_RECENT) == 0) { if (uid >= first_recent_uid) first_recent_uid = uid + 1; } hdr_next_uid = uid + 1; mail_index_append(trans, uid, &seq); mail_index_update_flags(trans, seq, MODIFY_REPLACE, ctx->flags); if (array_count(&ctx->keywords) > 0) { struct mail_keywords *kw; kw = mail_index_keywords_create_from_indexes( mbox->box.index, &ctx->keywords); mail_index_update_keywords(trans, seq, MODIFY_REPLACE, kw); mail_index_keywords_unref(&kw); } continue; } rec = mail_index_lookup(view, seq); if (uid > rec->uid) { /* already expunged (no point in showing guid in the expunge record anymore) */ mail_index_expunge(ctx->trans, seq); goto again; } if (uid < rec->uid) { if (maildir_handle_uid_insertion(ctx, uflags, filename, uid) < 0) ret = -1; seq--; continue; } index_sync_changes_read(ctx->sync_changes, ctx->uid, &expunged, expunged_guid_128); if (expunged) { if (!maildir_expunge_is_valid_guid(ctx, ctx->uid, filename, expunged_guid_128)) continue; if (maildir_file_do(mbox, ctx->uid, maildir_expunge, ctx) >= 0) { /* successful expunge */ mail_index_expunge(ctx->trans, seq); } if ((++changes % MAILDIR_SLOW_MOVE_COUNT) == 0) maildir_sync_notify(ctx->maildir_sync_ctx); continue; } /* the private flags are stored only in indexes, keep them */ ctx->flags |= rec->flags & private_flags_mask; if (index_sync_changes_have(ctx->sync_changes)) { /* apply flag changes to maildir */ if (maildir_file_do(mbox, ctx->uid, maildir_sync_flags, ctx) < 0) ctx->flags |= MAIL_INDEX_MAIL_FLAG_DIRTY; if ((++changes % MAILDIR_SLOW_MOVE_COUNT) == 0) maildir_sync_notify(ctx->maildir_sync_ctx); } if ((uflags & MAILDIR_UIDLIST_REC_FLAG_NONSYNCED) != 0) { /* partial syncing */ if ((uflags & MAILDIR_UIDLIST_REC_FLAG_NEW_DIR) != 0) { /* we last saw this mail in new/, but it's not there anymore. possibly expunged, make sure. */ full_rescan = TRUE; } continue; } if ((rec->flags & MAIL_INDEX_MAIL_FLAG_DIRTY) != 0) { /* we haven't been able to update maildir with this record's flag changes. don't sync them. */ continue; } if (ctx->flags != (rec->flags & MAIL_FLAGS_NONRECENT)) { mail_index_update_flags(trans, seq, MODIFY_REPLACE, ctx->flags); } maildir_sync_mail_keywords(ctx, seq); } maildir_uidlist_iter_deinit(&iter); if (!partial) { /* expunge the rest */ for (seq++; seq <= hdr->messages_count; seq++) mail_index_expunge(ctx->trans, seq); } /* add \Recent flags. use updated view so it contains newly appended messages. */ view2 = mail_index_transaction_open_updated_view(trans); if (mail_index_lookup_seq_range(view2, first_recent_uid, (uint32_t)-1, &seq, &seq2) && seq2 >= first_uid) { if (seq < first_uid) { /* UIDVALIDITY changed, skip over the old messages */ seq = first_uid; } mailbox_recent_flags_set_seqs(&mbox->box, view2, seq, seq2); } mail_index_view_close(&view2); if (ctx->uidlist_sync_ctx != NULL) { if (maildir_uidlist_sync_deinit(&ctx->uidlist_sync_ctx, TRUE) < 0) ret = -1; } if (mbox->box.v.sync_notify != NULL) mbox->box.v.sync_notify(&mbox->box, 0, 0); ctx->mbox->box.tmp_sync_view = NULL; /* check cur/ mtime later. if we came here from saving messages they could still be moved to cur/ directory. */ ctx->update_maildir_hdr_cur = TRUE; mbox->maildir_hdr.cur_check_time = time_before_sync; if (uid_validity == 0) { uid_validity = hdr->uid_validity != 0 ? hdr->uid_validity : maildir_get_uidvalidity_next(mbox->box.list); maildir_uidlist_set_uid_validity(mbox->uidlist, uid_validity); } maildir_uidlist_set_next_uid(mbox->uidlist, hdr_next_uid, FALSE); if (uid_validity != hdr->uid_validity) { mail_index_update_header(trans, offsetof(struct mail_index_header, uid_validity), &uid_validity, sizeof(uid_validity), TRUE); }
static int maildir_copy_hardlink(struct mail_save_context *ctx, struct mail *mail) { struct maildir_mailbox *dest_mbox = (struct maildir_mailbox *)ctx->transaction->box; struct maildir_mailbox *src_mbox; struct maildir_filename *mf; struct hardlink_ctx do_ctx; const char *path, *guid, *dest_fname; uoff_t vsize, size; enum mail_lookup_abort old_abort; if (strcmp(mail->box->storage->name, MAILDIR_STORAGE_NAME) == 0) src_mbox = (struct maildir_mailbox *)mail->box; else if (strcmp(mail->box->storage->name, "raw") == 0) { /* lda uses raw format */ src_mbox = NULL; } else { /* Can't hard link files from the source storage */ return 0; } /* hard link to tmp/ with a newly generated filename and later when we have uidlist locked, move it to new/cur. */ dest_fname = maildir_filename_generate(); memset(&do_ctx, 0, sizeof(do_ctx)); do_ctx.dest_path = t_strdup_printf("%s/tmp/%s", mailbox_get_path(&dest_mbox->box), dest_fname); if (src_mbox != NULL) { /* maildir */ if (maildir_file_do(src_mbox, mail->uid, do_hardlink, &do_ctx) < 0) return -1; } else { /* raw / lda */ if (mail_get_special(mail, MAIL_FETCH_UIDL_FILE_NAME, &path) < 0 || *path == '\0') return 0; if (do_hardlink(dest_mbox, path, &do_ctx) < 0) return -1; } if (!do_ctx.success) { /* couldn't copy with hardlinking, fallback to copying */ return 0; } /* hardlinked to tmp/, treat as normal copied mail */ mf = maildir_save_add(ctx, dest_fname, mail); if (mail_get_special(mail, MAIL_FETCH_GUID, &guid) == 0) { if (*guid != '\0') maildir_save_set_dest_basename(ctx, mf, guid); } /* remember size/vsize if possible */ old_abort = mail->lookup_abort; mail->lookup_abort = MAIL_LOOKUP_ABORT_READ_MAIL; if (mail_get_physical_size(mail, &size) < 0) size = (uoff_t)-1; if (mail_get_virtual_size(mail, &vsize) < 0) vsize = (uoff_t)-1; maildir_save_set_sizes(mf, size, vsize); mail->lookup_abort = old_abort; return 1; }