Beispiel #1
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;
	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);
}
Beispiel #2
0
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;
}
Beispiel #3
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);
}
Beispiel #4
0
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;
}
Beispiel #5
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;
}
Beispiel #6
0
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);
	}
Beispiel #7
0
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;
}