/src/dovecot/src/lib-storage/index/dbox-multi/mdbox-purge.c
Line | Count | Source |
1 | | /* Copyright (c) Dovecot authors, see top-level COPYING file */ |
2 | | |
3 | | #include "lib.h" |
4 | | #include "array.h" |
5 | | #include "istream.h" |
6 | | #include "ostream.h" |
7 | | #include "str.h" |
8 | | #include "hash.h" |
9 | | #include "dbox-attachment.h" |
10 | | #include "mdbox-storage.h" |
11 | | #include "mdbox-storage-rebuild.h" |
12 | | #include "mdbox-file.h" |
13 | | #include "mdbox-map.h" |
14 | | #include "mdbox-sync.h" |
15 | | |
16 | | #include <dirent.h> |
17 | | |
18 | | /* |
19 | | Altmoving works like: |
20 | | |
21 | | 1. Message's DBOX_INDEX_FLAG_ALT flag is changed. This is caught by mdbox |
22 | | code and map UID's alt-refcount is updated. It won't be written to disk. |
23 | | 2. mdbox_purge() is called, which checks if map UID's refcount equals |
24 | | to its alt-refcount. If it does, it's moved to alt storage. Moving to |
25 | | primary storage is done if _ALT flag was removed from any message. |
26 | | */ |
27 | | |
28 | | enum mdbox_msg_action { |
29 | | MDBOX_MSG_ACTION_MOVE_TO_ALT = 1, |
30 | | MDBOX_MSG_ACTION_MOVE_FROM_ALT |
31 | | }; |
32 | | |
33 | | struct mdbox_purge_context { |
34 | | pool_t pool; |
35 | | struct mdbox_storage *storage; |
36 | | |
37 | | uint32_t lowest_primary_file_id; |
38 | | /* list of file_ids that exist in primary storage. this list is looked |
39 | | up while there is no locking, so it may not be accurate anymore by |
40 | | the time it's used. */ |
41 | | ARRAY_TYPE(seq_range) primary_file_ids; |
42 | | /* list of file_ids that we need to purge */ |
43 | | ARRAY_TYPE(seq_range) purge_file_ids; |
44 | | |
45 | | /* uint32_t map_uid => enum mdbox_msg_action action */ |
46 | | HASH_TABLE(void *, void *) altmoves; |
47 | | bool have_altmoves; |
48 | | |
49 | | struct mdbox_map_atomic_context *atomic; |
50 | | struct mdbox_map_append_context *append_ctx; |
51 | | }; |
52 | | |
53 | | static int mdbox_map_file_msg_offset_cmp(const struct mdbox_map_file_msg *m1, |
54 | | const struct mdbox_map_file_msg *m2) |
55 | 0 | { |
56 | 0 | if (m1->offset < m2->offset) |
57 | 0 | return -1; |
58 | 0 | else if (m1->offset > m2->offset) |
59 | 0 | return 1; |
60 | 0 | else |
61 | 0 | return 0; |
62 | 0 | } |
63 | | |
64 | | static int |
65 | | mdbox_file_read_metadata_hdr(struct dbox_file *file, |
66 | | struct dbox_metadata_header *meta_hdr_r) |
67 | 0 | { |
68 | 0 | const unsigned char *data; |
69 | 0 | size_t size; |
70 | 0 | int ret; |
71 | |
|
72 | 0 | ret = i_stream_read_bytes(file->input, &data, &size, |
73 | 0 | sizeof(*meta_hdr_r)); |
74 | 0 | if (ret <= 0) { |
75 | 0 | i_assert(ret == -1); |
76 | 0 | if (file->input->stream_errno == 0) { |
77 | 0 | dbox_file_set_corrupted(file, "missing metadata"); |
78 | 0 | return 0; |
79 | 0 | } |
80 | 0 | mail_storage_set_critical(&file->storage->storage, |
81 | 0 | "read(%s) failed: %s", file->cur_path, |
82 | 0 | i_stream_get_error(file->input)); |
83 | 0 | return -1; |
84 | 0 | } |
85 | | |
86 | 0 | memcpy(meta_hdr_r, data, sizeof(*meta_hdr_r)); |
87 | 0 | if (memcmp(meta_hdr_r->magic_post, DBOX_MAGIC_POST, |
88 | 0 | sizeof(meta_hdr_r->magic_post)) != 0) { |
89 | 0 | dbox_file_set_corrupted(file, "invalid metadata magic"); |
90 | 0 | return 0; |
91 | 0 | } |
92 | 0 | i_stream_skip(file->input, sizeof(*meta_hdr_r)); |
93 | 0 | return 1; |
94 | 0 | } |
95 | | |
96 | | static int |
97 | | mdbox_file_metadata_copy(struct dbox_file *file, struct ostream *output) |
98 | 0 | { |
99 | 0 | struct dbox_metadata_header meta_hdr; |
100 | 0 | const char *line; |
101 | 0 | size_t buf_size; |
102 | 0 | int ret; |
103 | |
|
104 | 0 | if ((ret = mdbox_file_read_metadata_hdr(file, &meta_hdr)) <= 0) |
105 | 0 | return ret; |
106 | | |
107 | 0 | o_stream_nsend(output, &meta_hdr, sizeof(meta_hdr)); |
108 | 0 | buf_size = i_stream_get_max_buffer_size(file->input); |
109 | | /* use unlimited line length for metadata */ |
110 | 0 | i_stream_set_max_buffer_size(file->input, SIZE_MAX); |
111 | 0 | while ((line = i_stream_read_next_line(file->input)) != NULL) { |
112 | 0 | if (*line == '\0') { |
113 | | /* end of metadata */ |
114 | 0 | break; |
115 | 0 | } |
116 | 0 | o_stream_nsend_str(output, line); |
117 | 0 | o_stream_nsend(output, "\n", 1); |
118 | 0 | } |
119 | 0 | i_stream_set_max_buffer_size(file->input, buf_size); |
120 | |
|
121 | 0 | if (line == NULL) { |
122 | 0 | dbox_file_set_corrupted(file, "missing end-of-metadata line"); |
123 | 0 | return 0; |
124 | 0 | } |
125 | 0 | o_stream_nsend(output, "\n", 1); |
126 | 0 | return 1; |
127 | 0 | } |
128 | | |
129 | | static int |
130 | | mdbox_metadata_get_extrefs(struct dbox_file *file, pool_t ext_refs_pool, |
131 | | ARRAY_TYPE(mail_attachment_extref) *extrefs) |
132 | 0 | { |
133 | 0 | struct event *event = file->storage->storage.event; |
134 | 0 | struct dbox_metadata_header meta_hdr; |
135 | 0 | const char *line; |
136 | 0 | size_t buf_size; |
137 | 0 | int ret; |
138 | | |
139 | | /* skip and ignore the header */ |
140 | 0 | if ((ret = mdbox_file_read_metadata_hdr(file, &meta_hdr)) <= 0) |
141 | 0 | return ret; |
142 | | |
143 | 0 | buf_size = i_stream_get_max_buffer_size(file->input); |
144 | | /* use unlimited line length for metadata */ |
145 | 0 | i_stream_set_max_buffer_size(file->input, SIZE_MAX); |
146 | 0 | while ((line = i_stream_read_next_line(file->input)) != NULL) { |
147 | 0 | if (*line == '\0') { |
148 | | /* end of metadata */ |
149 | 0 | break; |
150 | 0 | } |
151 | 0 | if (*line == DBOX_METADATA_EXT_REF) T_BEGIN { |
152 | 0 | if (!index_attachment_parse_extrefs(line+1, ext_refs_pool, |
153 | 0 | extrefs)) { |
154 | 0 | e_warning(event, "%s: Ignoring corrupted extref: %s", |
155 | 0 | file->cur_path, line); |
156 | 0 | } |
157 | 0 | } T_END; |
158 | 0 | } |
159 | 0 | i_stream_set_max_buffer_size(file->input, buf_size); |
160 | |
|
161 | 0 | if (line == NULL) { |
162 | 0 | dbox_file_set_corrupted(file, "missing end-of-metadata line"); |
163 | 0 | return 0; |
164 | 0 | } |
165 | 0 | return 1; |
166 | 0 | } |
167 | | |
168 | | static bool |
169 | | mdbox_purge_want_altpath(struct mdbox_purge_context *ctx, |
170 | | struct dbox_file *file, uint32_t map_uid) |
171 | 0 | { |
172 | 0 | enum mdbox_msg_action action; |
173 | 0 | void *value; |
174 | |
|
175 | 0 | if (dbox_file_is_in_alt(file)) |
176 | 0 | return TRUE; |
177 | | |
178 | 0 | if (!ctx->have_altmoves) |
179 | 0 | return FALSE; |
180 | | |
181 | 0 | value = hash_table_lookup(ctx->altmoves, POINTER_CAST(map_uid)); |
182 | 0 | action = POINTER_CAST_TO(value, enum mdbox_msg_action); |
183 | 0 | return action == MDBOX_MSG_ACTION_MOVE_TO_ALT; |
184 | 0 | } |
185 | | |
186 | | static int |
187 | | mdbox_purge_save_msg(struct mdbox_purge_context *ctx, struct dbox_file *file, |
188 | | const struct mdbox_map_file_msg *msg) |
189 | 0 | { |
190 | 0 | struct dbox_file_append_context *out_file_append; |
191 | 0 | struct istream *input; |
192 | 0 | struct ostream *output; |
193 | 0 | enum mdbox_map_append_flags append_flags; |
194 | 0 | uoff_t msg_size; |
195 | 0 | int ret; |
196 | |
|
197 | 0 | if (ctx->append_ctx == NULL) |
198 | 0 | ctx->append_ctx = mdbox_map_append_begin(ctx->atomic); |
199 | |
|
200 | 0 | append_flags = !mdbox_purge_want_altpath(ctx, file, msg->map_uid) ? 0 : |
201 | 0 | DBOX_MAP_APPEND_FLAG_ALT; |
202 | 0 | msg_size = file->msg_header_size + file->cur_physical_size; |
203 | 0 | if (mdbox_map_append_next(ctx->append_ctx, file->cur_physical_size, |
204 | 0 | append_flags, &out_file_append, &output) < 0) |
205 | 0 | return -1; |
206 | | |
207 | 0 | i_assert(file != out_file_append->file); |
208 | | |
209 | 0 | input = i_stream_create_limit(file->input, msg_size); |
210 | 0 | o_stream_nsend_istream(output, input); |
211 | 0 | if (o_stream_flush(output) < 0) { |
212 | 0 | mail_storage_set_critical(&file->storage->storage, |
213 | 0 | "write(%s) failed: %s", |
214 | 0 | out_file_append->file->cur_path, |
215 | 0 | o_stream_get_error(output)); |
216 | 0 | ret = -1; |
217 | 0 | } else if (input->v_offset != msg_size) { |
218 | 0 | i_assert(input->v_offset < msg_size); |
219 | 0 | i_assert(i_stream_read_eof(file->input)); |
220 | | |
221 | 0 | dbox_file_set_corrupted(file, "truncated message at EOF"); |
222 | 0 | ret = 0; |
223 | 0 | } else { |
224 | 0 | ret = 1; |
225 | 0 | } |
226 | 0 | i_stream_unref(&input); |
227 | |
|
228 | 0 | if (ret > 0) { |
229 | | /* copy metadata */ |
230 | 0 | if ((ret = mdbox_file_metadata_copy(file, output)) <= 0) |
231 | 0 | return ret; |
232 | | |
233 | 0 | mdbox_map_append_finish(ctx->append_ctx); |
234 | 0 | } |
235 | 0 | return ret; |
236 | 0 | } |
237 | | |
238 | | static int |
239 | | mdbox_file_purge_check_refcounts(struct mdbox_purge_context *ctx, |
240 | | const ARRAY_TYPE(mdbox_map_file_msg) *msgs_arr) |
241 | 0 | { |
242 | 0 | struct mdbox_map *map = ctx->storage->map; |
243 | 0 | struct mdbox_map_mail_index_record rec; |
244 | 0 | uint16_t refcount; |
245 | 0 | const struct mdbox_map_file_msg *msgs; |
246 | 0 | unsigned int i, count; |
247 | 0 | int ret; |
248 | |
|
249 | 0 | if (mdbox_map_atomic_lock(ctx->atomic, "purging check") < 0) |
250 | 0 | return -1; |
251 | | |
252 | 0 | msgs = array_get(msgs_arr, &count); |
253 | 0 | for (i = 0; i < count; i++) { |
254 | 0 | if (msgs[i].refcount != 0) |
255 | 0 | continue; |
256 | | |
257 | 0 | ret = mdbox_map_lookup_full(map, msgs[i].map_uid, &rec, |
258 | 0 | &refcount); |
259 | 0 | if (ret <= 0) { |
260 | 0 | if (ret < 0) |
261 | 0 | return -1; |
262 | 0 | mdbox_map_set_corrupted(map, |
263 | 0 | "Purging unexpectedly lost map_uid=%u", |
264 | 0 | msgs[i].map_uid); |
265 | 0 | return -1; |
266 | 0 | } |
267 | 0 | if (refcount > 0) |
268 | 0 | return 0; |
269 | 0 | } |
270 | 0 | return 1; |
271 | 0 | } |
272 | | |
273 | | static int |
274 | | mdbox_purge_attachments(struct mdbox_purge_context *ctx, |
275 | | const ARRAY_TYPE(mail_attachment_extref) *extrefs_arr) |
276 | 0 | { |
277 | 0 | struct dbox_storage *storage = &ctx->storage->storage; |
278 | 0 | const struct mail_attachment_extref *extref; |
279 | 0 | int ret = 0; |
280 | |
|
281 | 0 | array_foreach(extrefs_arr, extref) { |
282 | 0 | if (index_attachment_delete(&storage->storage, |
283 | 0 | storage->attachment_fs, |
284 | 0 | extref->path) < 0) |
285 | 0 | ret = -1; |
286 | 0 | } |
287 | 0 | return ret; |
288 | 0 | } |
289 | | |
290 | | static int |
291 | | mdbox_file_purge(struct mdbox_purge_context *ctx, struct dbox_file *file, |
292 | | uint32_t file_id) |
293 | 0 | { |
294 | 0 | struct mdbox_storage *dstorage = (struct mdbox_storage *)file->storage; |
295 | 0 | struct stat st; |
296 | 0 | ARRAY_TYPE(mdbox_map_file_msg) msgs_arr; |
297 | 0 | const struct mdbox_map_file_msg *msgs; |
298 | 0 | ARRAY_TYPE(seq_range) expunged_map_uids; |
299 | 0 | ARRAY_TYPE(uint32_t) copied_map_uids; |
300 | 0 | ARRAY_TYPE(mail_attachment_extref) ext_refs; |
301 | 0 | pool_t ext_refs_pool; |
302 | 0 | unsigned int i, count; |
303 | 0 | uoff_t offset; |
304 | 0 | int ret; |
305 | |
|
306 | 0 | i_assert(ctx->atomic == NULL); |
307 | 0 | i_assert(ctx->append_ctx == NULL); |
308 | | |
309 | 0 | if ((ret = dbox_file_try_lock(file)) <= 0) |
310 | 0 | return ret; |
311 | | |
312 | | /* make sure the file still exists. another process may have already |
313 | | deleted it. */ |
314 | 0 | if (stat(file->cur_path, &st) < 0) { |
315 | 0 | dbox_file_unlock(file); |
316 | 0 | if (errno == ENOENT) |
317 | 0 | return 0; |
318 | | |
319 | 0 | mail_storage_set_critical(&file->storage->storage, |
320 | 0 | "stat(%s) failed: %m", file->cur_path); |
321 | 0 | return -1; |
322 | 0 | } |
323 | | |
324 | | /* get list of map UIDs that exist in this file (again has to be done |
325 | | after locking) */ |
326 | 0 | i_array_init(&msgs_arr, 128); |
327 | 0 | if (mdbox_map_get_file_msgs(dstorage->map, file_id, |
328 | 0 | &msgs_arr) < 0) { |
329 | 0 | array_free(&msgs_arr); |
330 | 0 | dbox_file_unlock(file); |
331 | 0 | return -1; |
332 | 0 | } |
333 | | /* sort messages by their offset */ |
334 | 0 | array_sort(&msgs_arr, mdbox_map_file_msg_offset_cmp); |
335 | |
|
336 | 0 | ext_refs_pool = pool_alloconly_create("mdbox purge ext refs", 1024); |
337 | 0 | ctx->atomic = mdbox_map_atomic_begin(ctx->storage->map); |
338 | 0 | msgs = array_get(&msgs_arr, &count); |
339 | 0 | i_array_init(&ext_refs, 32); |
340 | 0 | i_array_init(&copied_map_uids, I_MIN(count, 1)); |
341 | 0 | i_array_init(&expunged_map_uids, I_MIN(count, 1)); |
342 | 0 | offset = file->file_header_size; |
343 | 0 | for (i = 0; i < count; i++) { |
344 | 0 | if ((ret = dbox_file_seek(file, offset)) <= 0) |
345 | 0 | break; |
346 | | |
347 | 0 | if (msgs[i].offset != offset) { |
348 | | /* map doesn't match file's actual contents */ |
349 | 0 | dbox_file_set_corrupted(file, |
350 | 0 | "purging found mismatched offsets " |
351 | 0 | "(%"PRIuUOFF_T" vs %u, %u/%u)", |
352 | 0 | offset, msgs[i].offset, i, count); |
353 | 0 | ret = 0; |
354 | 0 | break; |
355 | 0 | } |
356 | | |
357 | 0 | if (msgs[i].refcount == 0) { |
358 | | /* skip over expunged message */ |
359 | 0 | i_stream_seek(file->input, offset + |
360 | 0 | file->msg_header_size + |
361 | 0 | file->cur_physical_size); |
362 | | /* skip metadata */ |
363 | 0 | ret = mdbox_metadata_get_extrefs(file, ext_refs_pool, |
364 | 0 | &ext_refs); |
365 | 0 | if (ret <= 0) |
366 | 0 | break; |
367 | 0 | seq_range_array_add(&expunged_map_uids, |
368 | 0 | msgs[i].map_uid); |
369 | 0 | } else { |
370 | | /* non-expunged message. write it to output file. */ |
371 | 0 | i_stream_seek(file->input, offset); |
372 | 0 | ret = mdbox_purge_save_msg(ctx, file, &msgs[i]); |
373 | 0 | if (ret <= 0) |
374 | 0 | break; |
375 | 0 | array_push_back(&copied_map_uids, &msgs[i].map_uid); |
376 | 0 | } |
377 | 0 | offset = file->input->v_offset; |
378 | 0 | } |
379 | 0 | if (offset != (uoff_t)st.st_size && ret > 0) { |
380 | | /* file has more messages than what map tells us */ |
381 | 0 | dbox_file_set_corrupted(file, |
382 | 0 | "more messages available than in map " |
383 | 0 | "(%"PRIuUOFF_T" < %"PRIuUOFF_T")", offset, st.st_size); |
384 | 0 | ret = 0; |
385 | 0 | } |
386 | 0 | if (ret > 0 && ctx->append_ctx != NULL) { |
387 | | /* flush writes before locking the map */ |
388 | 0 | if (mdbox_map_append_flush(ctx->append_ctx) < 0) |
389 | 0 | ret = -1; |
390 | 0 | } |
391 | |
|
392 | 0 | if (ret <= 0) |
393 | 0 | ret = -1; |
394 | 0 | else { |
395 | | /* it's possible that one of the messages we purged was |
396 | | just copied to another mailbox. the only way to prevent that |
397 | | would be to keep map locked during the purge, but that could |
398 | | keep it locked for too long. instead we'll check here if |
399 | | there are such copies, and if there are cancel this file's |
400 | | purge. */ |
401 | 0 | ret = mdbox_file_purge_check_refcounts(ctx, &msgs_arr); |
402 | 0 | } |
403 | 0 | array_free(&msgs_arr); msgs = NULL; |
404 | |
|
405 | 0 | if (ret <= 0) { |
406 | | /* failed */ |
407 | 0 | } else if (ctx->append_ctx == NULL) { |
408 | | /* everything purged from this file */ |
409 | 0 | ret = 1; |
410 | 0 | } else { |
411 | | /* assign new file_id + offset to moved messages */ |
412 | 0 | if (mdbox_map_append_move(ctx->append_ctx, &copied_map_uids, |
413 | 0 | &expunged_map_uids) < 0 || |
414 | 0 | mdbox_map_append_commit(ctx->append_ctx) < 0) |
415 | 0 | ret = -1; |
416 | 0 | else |
417 | 0 | ret = 1; |
418 | 0 | } |
419 | 0 | if (ctx->append_ctx != NULL) |
420 | 0 | mdbox_map_append_free(&ctx->append_ctx); |
421 | 0 | (void)mdbox_map_atomic_finish(&ctx->atomic); |
422 | | |
423 | | /* unlink only after unlocking map, so readers don't see it |
424 | | temporarily vanished */ |
425 | 0 | if (ret > 0) { |
426 | 0 | (void)dbox_file_unlink(file); |
427 | 0 | if (mdbox_map_remove_file_id(ctx->storage->map, file_id) < 0) |
428 | 0 | ret = -1; |
429 | 0 | } else { |
430 | 0 | dbox_file_unlock(file); |
431 | 0 | } |
432 | 0 | array_free(&copied_map_uids); |
433 | 0 | array_free(&expunged_map_uids); |
434 | |
|
435 | 0 | (void)mdbox_purge_attachments(ctx, &ext_refs); |
436 | 0 | array_free(&ext_refs); |
437 | 0 | pool_unref(&ext_refs_pool); |
438 | 0 | return ret; |
439 | 0 | } |
440 | | |
441 | | void mdbox_purge_alt_flag_change(struct mail *mail, bool move_to_alt) |
442 | 0 | { |
443 | 0 | struct mdbox_mailbox *mbox = MDBOX_MAILBOX(mail->box); |
444 | 0 | ARRAY_TYPE(uint32_t) *dest; |
445 | 0 | uint32_t map_uid; |
446 | | |
447 | | /* we'll assume here that alt flag won't be changed multiple times |
448 | | for the same mail. it shouldn't happen with current code, and |
449 | | checking for it would just slow down the code. |
450 | | |
451 | | so the way it works currently is just that map_uids are added to |
452 | | an array, which is later sorted and processed further. note that |
453 | | it's still possible that the same map_uid exists in the array |
454 | | multiple times. */ |
455 | 0 | if (mdbox_mail_lookup(mbox, mbox->box.view, mail->seq, &map_uid) < 0) |
456 | 0 | return; |
457 | | |
458 | 0 | dest = move_to_alt ? &mbox->storage->move_to_alt_map_uids : |
459 | 0 | &mbox->storage->move_from_alt_map_uids; |
460 | |
|
461 | 0 | if (!array_is_created(dest)) |
462 | 0 | i_array_init(dest, 256); |
463 | 0 | array_push_back(dest, &map_uid); |
464 | 0 | } |
465 | | |
466 | | static struct mdbox_purge_context * |
467 | | mdbox_purge_alloc(struct mdbox_storage *storage) |
468 | 0 | { |
469 | 0 | struct mdbox_purge_context *ctx; |
470 | 0 | pool_t pool; |
471 | |
|
472 | 0 | pool = pool_alloconly_create("mdbox purge context", 1024*32); |
473 | 0 | ctx = p_new(pool, struct mdbox_purge_context, 1); |
474 | 0 | ctx->pool = pool; |
475 | 0 | ctx->storage = storage; |
476 | 0 | ctx->lowest_primary_file_id = (uint32_t)-1; |
477 | 0 | i_array_init(&ctx->primary_file_ids, 64); |
478 | 0 | i_array_init(&ctx->purge_file_ids, 64); |
479 | 0 | hash_table_create_direct(&ctx->altmoves, pool, 0); |
480 | 0 | return ctx; |
481 | 0 | } |
482 | | |
483 | | static void mdbox_purge_free(struct mdbox_purge_context **_ctx) |
484 | 0 | { |
485 | 0 | struct mdbox_purge_context *ctx = *_ctx; |
486 | |
|
487 | 0 | *_ctx = NULL; |
488 | |
|
489 | 0 | hash_table_destroy(&ctx->altmoves); |
490 | 0 | array_free(&ctx->primary_file_ids); |
491 | 0 | array_free(&ctx->purge_file_ids); |
492 | 0 | pool_unref(&ctx->pool); |
493 | 0 | } |
494 | | |
495 | | static int mdbox_purge_get_primary_files(struct mdbox_purge_context *ctx) |
496 | 0 | { |
497 | 0 | struct mdbox_storage *dstorage = ctx->storage; |
498 | 0 | struct mail_storage *storage = &dstorage->storage.storage; |
499 | 0 | DIR *dir; |
500 | 0 | struct dirent *d; |
501 | 0 | string_t *path; |
502 | 0 | const char *suffix; |
503 | 0 | unsigned int file_id; |
504 | 0 | size_t dir_len; |
505 | 0 | int ret = 0; |
506 | |
|
507 | 0 | if (!array_is_created(&dstorage->move_to_alt_map_uids) && |
508 | 0 | !array_is_created(&dstorage->move_from_alt_map_uids)) { |
509 | | /* we don't need to do alt moving, don't bother getting list |
510 | | of primary files */ |
511 | 0 | return 0; |
512 | 0 | } |
513 | | |
514 | 0 | dir = opendir(dstorage->storage_dir); |
515 | 0 | if (dir == NULL) { |
516 | 0 | if (errno == ENOENT) { |
517 | | /* no storage directory at all yet */ |
518 | 0 | return 0; |
519 | 0 | } |
520 | 0 | mail_storage_set_critical(storage, |
521 | 0 | "opendir(%s) failed: %m", dstorage->storage_dir); |
522 | 0 | return -1; |
523 | 0 | } |
524 | | |
525 | 0 | path = t_str_new(256); |
526 | 0 | str_append(path, dstorage->storage_dir); |
527 | 0 | str_append_c(path, '/'); |
528 | 0 | dir_len = str_len(path); |
529 | |
|
530 | 0 | for (errno = 0; (d = readdir(dir)) != NULL; errno = 0) { |
531 | 0 | if (!str_begins(d->d_name, MDBOX_MAIL_FILE_PREFIX, &suffix)) |
532 | 0 | continue; |
533 | 0 | if (str_to_uint32(suffix, &file_id) < 0) |
534 | 0 | continue; |
535 | | |
536 | 0 | str_truncate(path, dir_len); |
537 | 0 | str_append(path, d->d_name); |
538 | 0 | seq_range_array_add(&ctx->primary_file_ids, file_id); |
539 | 0 | } |
540 | 0 | if (array_count(&ctx->primary_file_ids) > 0) { |
541 | 0 | const struct seq_range *range = |
542 | 0 | array_front(&ctx->primary_file_ids); |
543 | 0 | ctx->lowest_primary_file_id = range[0].seq1; |
544 | 0 | } |
545 | 0 | if (errno != 0) { |
546 | 0 | mail_storage_set_critical(storage, |
547 | 0 | "readdir(%s) failed: %m", dstorage->storage_dir); |
548 | 0 | ret = -1; |
549 | 0 | } |
550 | 0 | if (closedir(dir) < 0) { |
551 | 0 | mail_storage_set_critical(storage, |
552 | 0 | "closedir(%s) failed: %m", dstorage->storage_dir); |
553 | 0 | ret = -1; |
554 | 0 | } |
555 | 0 | return ret; |
556 | 0 | } |
557 | | |
558 | | static int uint32_t_cmp(const uint32_t *u1, const uint32_t *u2) |
559 | 0 | { |
560 | 0 | if (*u1 < *u2) |
561 | 0 | return -1; |
562 | 0 | if (*u1 > *u2) |
563 | 0 | return 1; |
564 | 0 | return 0; |
565 | 0 | } |
566 | | |
567 | | static int mdbox_altmove_add_files(struct mdbox_purge_context *ctx) |
568 | 0 | { |
569 | 0 | struct mdbox_storage *dstorage = ctx->storage; |
570 | 0 | const uint32_t *map_uids; |
571 | 0 | unsigned int i, count, alt_refcount = 0; |
572 | 0 | struct mdbox_map_mail_index_record cur_rec; |
573 | 0 | enum mdbox_msg_action action; |
574 | 0 | uint32_t cur_map_uid; |
575 | 0 | uint16_t cur_refcount = 0; |
576 | 0 | uoff_t offset; |
577 | 0 | int ret = 0; |
578 | | |
579 | | /* first add move-to-alt actions */ |
580 | 0 | if (array_is_created(&dstorage->move_to_alt_map_uids)) { |
581 | 0 | array_sort(&dstorage->move_to_alt_map_uids, uint32_t_cmp); |
582 | 0 | map_uids = array_get(&dstorage->move_to_alt_map_uids, &count); |
583 | 0 | } else { |
584 | 0 | map_uids = NULL; |
585 | 0 | count = 0; |
586 | 0 | } |
587 | 0 | cur_map_uid = 0; |
588 | 0 | for (i = 0; i < count; i++) { |
589 | 0 | if (cur_map_uid != map_uids[i]) { |
590 | 0 | cur_map_uid = map_uids[i]; |
591 | 0 | if (mdbox_map_lookup_full(dstorage->map, cur_map_uid, |
592 | 0 | &cur_rec, &cur_refcount) < 0) { |
593 | 0 | cur_refcount = (uint16_t)-1; |
594 | 0 | ret = -1; |
595 | 0 | } |
596 | 0 | alt_refcount = 1; |
597 | 0 | } else { |
598 | 0 | alt_refcount++; |
599 | 0 | } |
600 | |
|
601 | 0 | if (alt_refcount == cur_refcount && |
602 | 0 | seq_range_exists(&ctx->primary_file_ids, cur_rec.file_id)) { |
603 | | /* all instances marked as moved to alt storage */ |
604 | 0 | action = MDBOX_MSG_ACTION_MOVE_TO_ALT; |
605 | 0 | hash_table_insert(ctx->altmoves, |
606 | 0 | POINTER_CAST(cur_map_uid), |
607 | 0 | POINTER_CAST(action)); |
608 | 0 | seq_range_array_add(&ctx->purge_file_ids, |
609 | 0 | cur_rec.file_id); |
610 | 0 | } |
611 | 0 | } |
612 | | |
613 | | /* next add move-from-alt actions. they override move-to-alt actions |
614 | | in case there happen to be any conflicts (shouldn't). only a single |
615 | | move-from-alt record is needed to do the move. */ |
616 | 0 | if (array_is_created(&dstorage->move_from_alt_map_uids)) |
617 | 0 | map_uids = array_get(&dstorage->move_from_alt_map_uids, &count); |
618 | 0 | else { |
619 | 0 | map_uids = NULL; |
620 | 0 | count = 0; |
621 | 0 | } |
622 | 0 | cur_map_uid = 0; |
623 | 0 | for (i = 0; i < count; i++) { |
624 | 0 | if (cur_map_uid == map_uids[i]) |
625 | 0 | continue; |
626 | 0 | cur_map_uid = map_uids[i]; |
627 | |
|
628 | 0 | if (mdbox_map_lookup(dstorage->map, cur_map_uid, |
629 | 0 | &cur_rec.file_id, &offset) < 0) { |
630 | 0 | ret = -1; |
631 | 0 | continue; |
632 | 0 | } |
633 | 0 | if (seq_range_exists(&ctx->primary_file_ids, cur_rec.file_id)) { |
634 | | /* already in primary storage */ |
635 | 0 | continue; |
636 | 0 | } |
637 | | |
638 | 0 | action = MDBOX_MSG_ACTION_MOVE_FROM_ALT; |
639 | 0 | hash_table_update(ctx->altmoves, POINTER_CAST(cur_map_uid), |
640 | 0 | POINTER_CAST(action)); |
641 | 0 | seq_range_array_add(&ctx->purge_file_ids, cur_rec.file_id); |
642 | 0 | } |
643 | 0 | ctx->have_altmoves = hash_table_count(ctx->altmoves) > 0; |
644 | 0 | return ret; |
645 | 0 | } |
646 | | |
647 | | int mdbox_purge(struct mail_storage *_storage) |
648 | 0 | { |
649 | 0 | struct mdbox_storage *storage = (struct mdbox_storage *)_storage; |
650 | 0 | struct mdbox_purge_context *ctx; |
651 | 0 | struct dbox_file *file; |
652 | 0 | struct seq_range_iter iter; |
653 | 0 | unsigned int i = 0; |
654 | 0 | uint32_t file_id; |
655 | 0 | bool deleted; |
656 | 0 | int ret; |
657 | |
|
658 | 0 | ctx = mdbox_purge_alloc(storage); |
659 | 0 | ret = mdbox_map_get_zero_ref_files(storage->map, &ctx->purge_file_ids); |
660 | 0 | if (storage->alt_storage_dir != NULL) { |
661 | 0 | if (mdbox_purge_get_primary_files(ctx) < 0) |
662 | 0 | ret = -1; |
663 | 0 | else { |
664 | | /* add files that can be altmoved */ |
665 | 0 | if (mdbox_altmove_add_files(ctx) < 0) |
666 | 0 | ret = -1; |
667 | 0 | } |
668 | 0 | } |
669 | |
|
670 | 0 | seq_range_array_iter_init(&iter, &ctx->purge_file_ids); i = 0; |
671 | 0 | while (ret == 0 && |
672 | 0 | seq_range_array_iter_nth(&iter, i++, &file_id)) T_BEGIN { |
673 | 0 | file = mdbox_file_init(storage, file_id); |
674 | 0 | if (dbox_file_open(file, &deleted) > 0 && !deleted) { |
675 | 0 | if (mdbox_file_purge(ctx, file, file_id) < 0) |
676 | 0 | ret = -1; |
677 | 0 | } else { |
678 | 0 | if (mdbox_map_remove_file_id(storage->map, file_id) < 0) |
679 | 0 | ret = -1; |
680 | 0 | } |
681 | 0 | dbox_file_unref(&file); |
682 | 0 | } T_END; |
683 | 0 | mdbox_purge_free(&ctx); |
684 | |
|
685 | 0 | if (storage->corrupted_reason != NULL) { |
686 | | /* purging found corrupted files */ |
687 | | (void)mdbox_storage_rebuild(storage, NULL, |
688 | 0 | MDBOX_REBUILD_REASON_CORRUPTED); |
689 | 0 | ret = -1; |
690 | 0 | } |
691 | 0 | return ret; |
692 | 0 | } |