Coverage Report

Created: 2026-08-08 06:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}