mdbox-purge.c revision 8776322310b57a11c52cfb1822f35cf18b095525
c25356d5978632df6203437e1953bcb29e0c736fTimo Sirainen/* Copyright (c) 2007-2014 Dovecot authors, see the included COPYING file */
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen Altmoving works like:
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen 1. Message's DBOX_INDEX_FLAG_ALT flag is changed. This is caught by mdbox
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen code and map UID's alt-refcount is updated. It won't be written to disk.
fdc557286bc9f92c5f3bb49096ff6e2bcec0ea79Timo Sirainen 2. mdbox_purge() is called, which checks if map UID's refcount equals
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen to its alt-refcount. If it does, it's moved to alt storage. Moving to
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen primary storage is done if _ALT flag was removed from any message.
1d2b188f0eedc3cab6e27ceac5425a037f38042eTimo Sirainen /* list of file_ids that exist in primary storage. this list is looked
1d2b188f0eedc3cab6e27ceac5425a037f38042eTimo Sirainen up while there is no locking, so it may not be accurate anymore by
c0a87e5f3316a57e6f915882fa1951d0fbb74a61Timo Sirainen the time it's used. */
3e564425db51f3921ce4de11859777135fdedd15Timo Sirainen /* list of file_ids that we need to purge */
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen /* uint32_t map_uid => enum mdbox_msg_action action */
563273bdac80393af63b9520cbf4d24cc0efd028Timo Sirainenstatic int mdbox_map_file_msg_offset_cmp(const struct mdbox_map_file_msg *m1,
fdc557286bc9f92c5f3bb49096ff6e2bcec0ea79Timo Sirainenmdbox_file_read_metadata_hdr(struct dbox_file *file,
fdc557286bc9f92c5f3bb49096ff6e2bcec0ea79Timo Sirainen const unsigned char *data;
fdc557286bc9f92c5f3bb49096ff6e2bcec0ea79Timo Sirainen ret = i_stream_read_data(file->input, &data, &size,
420040a5930a2b497e79ff0b5f59ba4b764a5b39Timo Sirainen dbox_file_set_corrupted(file, "missing metadata");
563273bdac80393af63b9520cbf4d24cc0efd028Timo Sirainen mail_storage_set_critical(&file->storage->storage,
f1743785713e7632459d623d5df2108f4b93accbTimo Sirainen memcpy(meta_hdr_r, data, sizeof(*meta_hdr_r));
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen if (memcmp(meta_hdr_r->magic_post, DBOX_MAGIC_POST,
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen dbox_file_set_corrupted(file, "invalid metadata magic");
8d630c15a8ed6f85553467c3a231a273defca5f6Timo Sirainen i_stream_skip(file->input, sizeof(*meta_hdr_r));
fdc557286bc9f92c5f3bb49096ff6e2bcec0ea79Timo Sirainenmdbox_file_metadata_copy(struct dbox_file *file, struct ostream *output)
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen if ((ret = mdbox_file_read_metadata_hdr(file, &meta_hdr)) <= 0)
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainen o_stream_nsend(output, &meta_hdr, sizeof(meta_hdr));
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen buf_size = i_stream_get_max_buffer_size(file->input);
a2f250a332dfc1e6cd4ffd196c621eb9dbf7b8a1Timo Sirainen /* use unlimited line length for metadata */
cff1f182205e674285cf3ff446a0dcf7afea277dTimo Sirainen i_stream_set_max_buffer_size(file->input, (size_t)-1);
cff1f182205e674285cf3ff446a0dcf7afea277dTimo Sirainen while ((line = i_stream_read_next_line(file->input)) != NULL) {
1d2b188f0eedc3cab6e27ceac5425a037f38042eTimo Sirainen /* end of metadata */
61b0637759146621cbb7edcbd0b03a71cfd66dfeTimo Sirainen i_stream_set_max_buffer_size(file->input, buf_size);
17ad2164c747cedbf81dae1893063e71a3df0356Timo Sirainen dbox_file_set_corrupted(file, "missing end-of-metadata line");
d3442384ca53d4b18a493db7dd0b000f470419cfTimo Sirainenmdbox_metadata_get_extrefs(struct dbox_file *file, pool_t ext_refs_pool,
d3442384ca53d4b18a493db7dd0b000f470419cfTimo Sirainen /* skip and ignore the header */
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen if ((ret = mdbox_file_read_metadata_hdr(file, &meta_hdr)) <= 0)
c979eeda1f46483d9c963e265786b701d7683d77Timo Sirainen buf_size = i_stream_get_max_buffer_size(file->input);
c979eeda1f46483d9c963e265786b701d7683d77Timo Sirainen /* use unlimited line length for metadata */
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen i_stream_set_max_buffer_size(file->input, (size_t)-1);
2584e86cc2d8c31ba30a4109cf4ba09d1e37e28aTimo Sirainen while ((line = i_stream_read_next_line(file->input)) != NULL) {
4b41116563110d00330896a568eff1078c382827Timo Sirainen /* end of metadata */
5137d2d80255938a0f5fb8f3c1a21b34cf11ada3Timo Sirainen if (!index_attachment_parse_extrefs(line+1, ext_refs_pool,
f81f4bc282cd1944cec187bae89c0701a416ed2aTimo Sirainen i_warning("%s: Ignoring corrupted extref: %s",
b2c1349cf07410aefab0f5b17153af9e5cfcf48fTimo Sirainen i_stream_set_max_buffer_size(file->input, buf_size);
dbe64f3893616a4005c8946be75d2dc8f6823d72Timo Sirainen dbox_file_set_corrupted(file, "missing end-of-metadata line");
fdc557286bc9f92c5f3bb49096ff6e2bcec0ea79Timo Sirainenmdbox_purge_want_altpath(struct mdbox_purge_context *ctx,
e3aeeb634245e80d4f643f8d2eea11d6b72336d8Timo Sirainen value = hash_table_lookup(ctx->altmoves, POINTER_CAST(map_uid));
e3aeeb634245e80d4f643f8d2eea11d6b72336d8Timo Sirainen action = POINTER_CAST_TO(value, enum mdbox_msg_action);
e3aeeb634245e80d4f643f8d2eea11d6b72336d8Timo Sirainen return action == MDBOX_MSG_ACTION_MOVE_TO_ALT;
bd4d0a1a7c0626452b8d82f37e3ec07267ac9896Timo Sirainenmdbox_purge_save_msg(struct mdbox_purge_context *ctx, struct dbox_file *file,
1460ef7a18c53216ddb4a94bb62fba96076aae8eTimo Sirainen struct dbox_file_append_context *out_file_append;
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen ctx->append_ctx = mdbox_map_append_begin(ctx->atomic);
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen append_flags = !mdbox_purge_want_altpath(ctx, file, msg->map_uid) ? 0 :
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen msg_size = file->msg_header_size + file->cur_physical_size;
ca98892a6b8a30ffc1fe26fcf02c7d59e3204e7eTimo Sirainen if (mdbox_map_append_next(ctx->append_ctx, file->cur_physical_size,
df00412606a00714a6e85383fa87fbdc7cc1fb5bTimo Sirainen input = i_stream_create_limit(file->input, msg_size);
df00412606a00714a6e85383fa87fbdc7cc1fb5bTimo Sirainen mail_storage_set_critical(&file->storage->storage,
fdc557286bc9f92c5f3bb49096ff6e2bcec0ea79Timo Sirainen mail_storage_set_critical(&file->storage->storage,
fdc557286bc9f92c5f3bb49096ff6e2bcec0ea79Timo Sirainen "write(%s) failed: %m",
ca98892a6b8a30ffc1fe26fcf02c7d59e3204e7eTimo Sirainen dbox_file_set_corrupted(file, "truncated message at EOF");
ca98d6a1bbe73499da758a36bfab2963375c8d06Timo Sirainen /* copy metadata */
ca98d6a1bbe73499da758a36bfab2963375c8d06Timo Sirainen if ((ret = mdbox_file_metadata_copy(file, output)) <= 0)
f81f4bc282cd1944cec187bae89c0701a416ed2aTimo Sirainenmdbox_file_purge_check_refcounts(struct mdbox_purge_context *ctx,
f81f4bc282cd1944cec187bae89c0701a416ed2aTimo Sirainen const ARRAY_TYPE(mdbox_map_file_msg) *msgs_arr)
fdc557286bc9f92c5f3bb49096ff6e2bcec0ea79Timo Sirainen unsigned int i, count;
4bbd396aa6198c84f3f7763b6e8a63a26e97e141Timo Sirainen for (i = 0; i < count; i++) {
3f91e60401495a4046c73992fabaa5e77200a451Timo Sirainen ret = mdbox_map_lookup_full(map, msgs[i].map_uid, &rec,
eb64c3586d854cddd693f0b811d897399076a441Timo Sirainen "Purging unexpectedly lost map_uid=%u",
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainenmdbox_purge_attachments(struct mdbox_purge_context *ctx,
aa247243412a49f9bdebf7255e131dc6ece4ed46Timo Sirainen const ARRAY_TYPE(mail_attachment_extref) *extrefs_arr)
fdc557286bc9f92c5f3bb49096ff6e2bcec0ea79Timo Sirainen struct dbox_storage *storage = &ctx->storage->storage;
a2f250a332dfc1e6cd4ffd196c621eb9dbf7b8a1Timo Sirainen if (index_attachment_delete(&storage->storage,
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainenmdbox_file_purge(struct mdbox_purge_context *ctx, struct dbox_file *file,
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainen struct mdbox_storage *dstorage = (struct mdbox_storage *)file->storage;
83bb013a99f0936995f9c7a1077822662d8fefdbTimo Sirainen unsigned int i, count;
225e82df5dd1e765f4e52b80c954558f00e5a7dfTimo Sirainen /* make sure the file still exists. another process may have already
73b50eecfc31750a312e2f940023f522eb07178cTimo Sirainen deleted it. */
fdc557286bc9f92c5f3bb49096ff6e2bcec0ea79Timo Sirainen mail_storage_set_critical(&file->storage->storage,
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen /* get list of map UIDs that exist in this file (again has to be done
fdc557286bc9f92c5f3bb49096ff6e2bcec0ea79Timo Sirainen after locking) */
539977f9257bd8985be5a8093658da266ae9cd19Timo Sirainen if (mdbox_map_get_file_msgs(dstorage->map, file_id,
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen /* sort messages by their offset */
b0a901f1dbe9e05ac1c92a0974af6bce0274f31aTimo Sirainen array_sort(&msgs_arr, mdbox_map_file_msg_offset_cmp);
b0a901f1dbe9e05ac1c92a0974af6bce0274f31aTimo Sirainen ext_refs_pool = pool_alloconly_create("mdbox purge ext refs", 1024);
1eb17e61d3d38372674aa0c55caedb0185a985f5Timo Sirainen ctx->atomic = mdbox_map_atomic_begin(ctx->storage->map);
b0a901f1dbe9e05ac1c92a0974af6bce0274f31aTimo Sirainen i_array_init(&copied_map_uids, I_MIN(count, 1));
b0a901f1dbe9e05ac1c92a0974af6bce0274f31aTimo Sirainen i_array_init(&expunged_map_uids, I_MIN(count, 1));
b0a901f1dbe9e05ac1c92a0974af6bce0274f31aTimo Sirainen for (i = 0; i < count; i++) {
b0a901f1dbe9e05ac1c92a0974af6bce0274f31aTimo Sirainen if ((ret = dbox_file_seek(file, offset)) <= 0)
b0a901f1dbe9e05ac1c92a0974af6bce0274f31aTimo Sirainen /* map doesn't match file's actual contents */
fdc557286bc9f92c5f3bb49096ff6e2bcec0ea79Timo Sirainen "purging found mismatched offsets "
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen /* skip over expunged message */
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen /* skip metadata */
194603b35061fea1ee8d171a7104b6985c610966Timo Sirainen ret = mdbox_metadata_get_extrefs(file, ext_refs_pool,
cd83124e5d070a016c590bb0b1096d7828c7b6adTimo Sirainen /* non-expunged message. write it to output file. */
cd83124e5d070a016c590bb0b1096d7828c7b6adTimo Sirainen ret = mdbox_purge_save_msg(ctx, file, &msgs[i]);
c04f9a724a7b3cc649485a61b0a540868d25d71bTimo Sirainen array_append(&copied_map_uids, &msgs[i].map_uid, 1);
eb0816090cf5a549280ad783b9aa6fec199d36baTimo Sirainen if (offset != (uoff_t)st.st_size && ret > 0) {
eb0816090cf5a549280ad783b9aa6fec199d36baTimo Sirainen /* file has more messages than what map tells us */
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen "more messages available than in map "
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen "(%"PRIuUOFF_T" < %"PRIuUOFF_T")", offset, st.st_size);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen /* flush writes before locking the map */
fdc557286bc9f92c5f3bb49096ff6e2bcec0ea79Timo Sirainen if (mdbox_map_append_flush(ctx->append_ctx) < 0)
3dd0679b6f24be0287cc42d7a60bbf59cdf8b637Timo Sirainen /* it's possible that one of the messages we purged was
3dd0679b6f24be0287cc42d7a60bbf59cdf8b637Timo Sirainen just copied to another mailbox. the only way to prevent that
3dd0679b6f24be0287cc42d7a60bbf59cdf8b637Timo Sirainen would be to keep map locked during the purge, but that could
3dd0679b6f24be0287cc42d7a60bbf59cdf8b637Timo Sirainen keep it locked for too long. instead we'll check here if
3dd0679b6f24be0287cc42d7a60bbf59cdf8b637Timo Sirainen there are such copies, and if there are cancel this file's
3dd0679b6f24be0287cc42d7a60bbf59cdf8b637Timo Sirainen ret = mdbox_file_purge_check_refcounts(ctx, &msgs_arr);
4321f6c969e7b8f6b243ff5bb6b8d297921676f6Timo Sirainen /* everything purged from this file */
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen /* assign new file_id + offset to moved messages */
a423d985ba7261661475811c22b21b80ec765a71Timo Sirainen if (mdbox_map_append_move(ctx->append_ctx, &copied_map_uids,
0cce885512b836ce021260a58e7b4f099b36d0f1Timo Sirainen /* unlink only after unlocking map, so readers don't see it
0cce885512b836ce021260a58e7b4f099b36d0f1Timo Sirainen temporarily vanished */
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen if (mdbox_map_remove_file_id(ctx->storage->map, file_id) < 0)
a423d985ba7261661475811c22b21b80ec765a71Timo Sirainen (void)mdbox_purge_attachments(ctx, &ext_refs);
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainenvoid mdbox_purge_alt_flag_change(struct mail *mail, bool move_to_alt)
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen struct mdbox_mailbox *mbox = (struct mdbox_mailbox *)mail->box;
a4f09749814b93e8ad3ec8a0dc18885b874d6f8cTimo Sirainen /* we'll assume here that alt flag won't be changed multiple times
a4f09749814b93e8ad3ec8a0dc18885b874d6f8cTimo Sirainen for the same mail. it shouldn't happen with current code, and
a4f09749814b93e8ad3ec8a0dc18885b874d6f8cTimo Sirainen checking for it would just slow down the code.
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen so the way it works currently is just that map_uids are added to
d6badc27cd6e8d3398877b6766cb0aaeef3a7800Timo Sirainen an array, which is later sorted and processed further. note that
d6badc27cd6e8d3398877b6766cb0aaeef3a7800Timo Sirainen it's still possible that the same map_uid exists in the array
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen multiple times. */
d6badc27cd6e8d3398877b6766cb0aaeef3a7800Timo Sirainen if (mdbox_mail_lookup(mbox, mbox->box.view, mail->seq, &map_uid) < 0)
8e371a3ce32bd64288786855b8ce0cb63f19f7d1Timo Sirainen dest = move_to_alt ? &mbox->storage->move_to_alt_map_uids :
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainenmdbox_purge_alloc(struct mdbox_storage *storage)
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen pool = pool_alloconly_create("mdbox purge context", 1024*32);
43d32cbe60fdaef2699d99f1ca259053e9350411Timo Sirainen ctx = p_new(pool, struct mdbox_purge_context, 1);
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen hash_table_create_direct(&ctx->altmoves, pool, 0);
910fa4e4204a73d3d24c03f3059dd24e727ca057Timo Sirainenstatic void mdbox_purge_free(struct mdbox_purge_context **_ctx)
3c493c276f599d9b9cd10764876d648003046954Timo Sirainenstatic int mdbox_purge_get_primary_files(struct mdbox_purge_context *ctx)
036626b19f14bef582f96e556913ae91b1d67881Timo Sirainen struct mdbox_storage *dstorage = ctx->storage;
036626b19f14bef582f96e556913ae91b1d67881Timo Sirainen struct mail_storage *storage = &dstorage->storage.storage;
int ret = 0;
&file_id) < 0)
if (errno != 0) {
return ret;
int ret = 0;
count = 0;
cur_map_uid = 0;
for (i = 0; i < count; i++) {
alt_refcount++;
count = 0;
cur_map_uid = 0;
for (i = 0; i < count; i++) {
return ret;
bool deleted;
int ret;
while (ret == 0 &&
} T_END;
return ret;