Coverage Report

Created: 2026-09-03 07:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib-index/mail-index.c
Line
Count
Source
1
/* Copyright (c) Dovecot authors, see top-level COPYING file */
2
3
#include "lib.h"
4
#include "ioloop.h"
5
#include "array.h"
6
#include "buffer.h"
7
#include "eacces-error.h"
8
#include "hash.h"
9
#include "str-sanitize.h"
10
#include "mmap-util.h"
11
#include "nfs-workarounds.h"
12
#include "read-full.h"
13
#include "write-full.h"
14
#include "mail-index-alloc-cache.h"
15
#include "mail-index-private.h"
16
#include "mail-index-view-private.h"
17
#include "mail-index-sync-private.h"
18
#include "mail-index-modseq.h"
19
#include "mail-transaction-log-private.h"
20
#include "mail-transaction-log-view-private.h"
21
#include "mail-cache.h"
22
23
#include <stdio.h>
24
#include <time.h>
25
#include <sys/stat.h>
26
#include <ctype.h>
27
28
struct mail_index_module_register mail_index_module_register = { 0 };
29
30
struct event_category event_category_mail_index = {
31
  .name = "mail-index",
32
};
33
34
static void mail_index_close_nonopened(struct mail_index *index);
35
36
static const struct mail_index_optimization_settings default_optimization_set = {
37
  .index = {
38
    .rewrite_min_log_bytes = 8 * 1024,
39
    .rewrite_max_log_bytes = 128 * 1024,
40
  },
41
  .log = {
42
    .min_size = 32 * 1024,
43
    .max_size = 1024 * 1024,
44
    .min_age_secs = 5 * 60,
45
    .log2_max_age_secs = 3600 * 24 * 2,
46
  },
47
  .cache = {
48
    .unaccessed_field_drop_secs = 3600 * 24 * 30,
49
    .record_max_size = 64 * 1024,
50
    .max_size = 1024 * 1024 * 1024,
51
    .purge_min_size = 32 * 1024,
52
    .purge_delete_percentage = 20,
53
    .purge_continued_percentage = 200,
54
    .purge_header_continue_count = 4,
55
  },
56
};
57
58
struct mail_index *mail_index_alloc(struct event *parent_event,
59
            const char *dir, const char *prefix)
60
0
{
61
0
  struct mail_index *index;
62
63
0
  index = i_new(struct mail_index, 1);
64
0
  index->dir = i_strdup(dir);
65
0
  index->prefix = i_strdup(prefix);
66
0
  index->fd = -1;
67
0
  index->event = event_create(parent_event);
68
0
  event_add_category(index->event, &event_category_mail_index);
69
70
0
  index->extension_pool =
71
0
    pool_alloconly_create(MEMPOOL_GROWING"index extension", 1024);
72
0
  p_array_init(&index->extensions, index->extension_pool, 5);
73
0
  i_array_init(&index->module_contexts,
74
0
         I_MIN(5, mail_index_module_register.id));
75
76
0
  index->set.mode = 0600;
77
0
  index->set.gid = (gid_t)-1;
78
0
  index->set.lock_method = FILE_LOCK_METHOD_FCNTL;
79
0
  index->set.max_lock_timeout_secs = UINT_MAX;
80
0
  index->optimization_set = default_optimization_set;
81
82
0
  index->keywords_ext_id =
83
0
    mail_index_ext_register(index, MAIL_INDEX_EXT_KEYWORDS,
84
0
          128, 2, 1);
85
0
  index->keywords_pool = pool_alloconly_create("keywords", 512);
86
0
  i_array_init(&index->keywords, 16);
87
0
  hash_table_create(&index->keywords_hash, index->keywords_pool, 0,
88
0
        strcase_hash, strcasecmp);
89
0
  index->log = mail_transaction_log_alloc(index);
90
0
  mail_index_modseq_init(index);
91
0
  return index;
92
0
}
93
94
void mail_index_free(struct mail_index **_index)
95
0
{
96
0
  struct mail_index *index = *_index;
97
98
0
  *_index = NULL;
99
100
0
  i_assert(index->open_count == 0);
101
102
0
  mail_transaction_log_free(&index->log);
103
0
  hash_table_destroy(&index->keywords_hash);
104
0
  pool_unref(&index->extension_pool);
105
0
  pool_unref(&index->keywords_pool);
106
107
0
  array_free(&index->keywords);
108
0
  array_free(&index->module_contexts);
109
110
0
  event_unref(&index->event);
111
0
  i_free(index->set.cache_dir);
112
0
  i_free(index->set.ext_hdr_init_data);
113
0
  i_free(index->set.gid_origin);
114
0
  i_free(index->last_error.text);
115
0
  i_free(index->dir);
116
0
  i_free(index->prefix);
117
0
  i_free(index->need_recreate);
118
0
  i_free(index);
119
0
}
120
121
void mail_index_set_cache_dir(struct mail_index *index, const char *dir)
122
0
{
123
0
  i_free(index->set.cache_dir);
124
0
  index->set.cache_dir = i_strdup(dir);
125
0
}
126
127
void mail_index_set_fsync_mode(struct mail_index *index,
128
             enum fsync_mode mode,
129
             enum mail_index_fsync_mask mask)
130
0
{
131
0
  index->set.fsync_mode = mode;
132
0
  index->set.fsync_mask = mask;
133
0
}
134
135
bool mail_index_use_existing_permissions(struct mail_index *index)
136
0
{
137
0
  struct stat st;
138
139
0
  if (MAIL_INDEX_IS_IN_MEMORY(index))
140
0
    return FALSE;
141
142
0
  if (stat(index->dir, &st) < 0) {
143
0
    if (errno != ENOENT)
144
0
      e_error(index->event, "stat(%s) failed: %m", index->dir);
145
0
    return FALSE;
146
0
  }
147
148
0
  index->set.mode = st.st_mode & 0666;
149
0
  if (S_ISDIR(st.st_mode) && (st.st_mode & S_ISGID) != 0) {
150
    /* directory's GID is used automatically for new files */
151
0
    index->set.gid = (gid_t)-1;
152
0
  } else if ((st.st_mode & 0070) >> 3 == (st.st_mode & 0007)) {
153
    /* group has same permissions as world, so don't bother
154
       changing it */
155
0
    index->set.gid = (gid_t)-1;
156
0
  } else if (getegid() == st.st_gid) {
157
    /* using our own gid, no need to change it */
158
0
    index->set.gid = (gid_t)-1;
159
0
  } else {
160
0
    index->set.gid = st.st_gid;
161
0
  }
162
163
0
  i_free(index->set.gid_origin);
164
0
  if (index->set.gid != (gid_t)-1)
165
0
    index->set.gid_origin = i_strdup("preserved existing GID");
166
0
  return TRUE;
167
0
}
168
169
void mail_index_set_permissions(struct mail_index *index,
170
        mode_t mode, gid_t gid, const char *gid_origin)
171
0
{
172
0
  index->set.mode = mode & 0666;
173
0
  index->set.gid = gid;
174
175
0
  i_free(index->set.gid_origin);
176
0
  index->set.gid_origin = i_strdup(gid_origin);
177
0
}
178
179
void mail_index_set_lock_method(struct mail_index *index,
180
        enum file_lock_method lock_method,
181
        unsigned int max_timeout_secs)
182
0
{
183
0
  index->set.lock_method = lock_method;
184
0
  index->set.max_lock_timeout_secs = max_timeout_secs;
185
0
}
186
187
void mail_index_set_optimization_settings(struct mail_index *index,
188
  const struct mail_index_optimization_settings *set)
189
0
{
190
0
  struct mail_index_optimization_settings *dest =
191
0
    &index->optimization_set;
192
193
  /* index */
194
0
  if (set->index.rewrite_min_log_bytes != 0)
195
0
    dest->index.rewrite_min_log_bytes = set->index.rewrite_min_log_bytes;
196
0
  if (set->index.rewrite_max_log_bytes != 0)
197
0
    dest->index.rewrite_max_log_bytes = set->index.rewrite_max_log_bytes;
198
199
  /* log */
200
0
  if (set->log.min_size != 0)
201
0
    dest->log.min_size = set->log.min_size;
202
0
  if (set->log.max_size != 0)
203
0
    dest->log.max_size = set->log.max_size;
204
0
  if (set->log.min_age_secs != 0)
205
0
    dest->log.min_age_secs = set->log.min_age_secs;
206
0
  if (set->log.log2_max_age_secs != 0)
207
0
    dest->log.log2_max_age_secs = set->log.log2_max_age_secs;
208
209
  /* cache */
210
0
  if (set->cache.unaccessed_field_drop_secs != 0)
211
0
    dest->cache.unaccessed_field_drop_secs =
212
0
      set->cache.unaccessed_field_drop_secs;
213
0
  if (set->cache.max_size != 0)
214
0
    dest->cache.max_size = set->cache.max_size;
215
0
  if (set->cache.purge_min_size != 0)
216
0
    dest->cache.purge_min_size = set->cache.purge_min_size;
217
0
  if (set->cache.purge_delete_percentage != 0)
218
0
    dest->cache.purge_delete_percentage =
219
0
      set->cache.purge_delete_percentage;
220
0
  if (set->cache.purge_continued_percentage != 0)
221
0
    dest->cache.purge_continued_percentage =
222
0
      set->cache.purge_continued_percentage;
223
0
  if (set->cache.purge_header_continue_count != 0)
224
0
    dest->cache.purge_header_continue_count =
225
0
      set->cache.purge_header_continue_count;
226
0
  if (set->cache.record_max_size != 0)
227
0
    dest->cache.record_max_size = set->cache.record_max_size;
228
229
0
  dest->cache.max_header_name_length = set->cache.max_header_name_length;
230
0
  dest->cache.max_headers_count = set->cache.max_headers_count;
231
0
}
232
233
void mail_index_set_ext_init_data(struct mail_index *index, uint32_t ext_id,
234
          const void *data, size_t size)
235
0
{
236
0
  const struct mail_index_registered_ext *rext;
237
238
0
  i_assert(index->set.ext_hdr_init_data == NULL ||
239
0
     index->set.ext_hdr_init_id == ext_id);
240
0
  i_assert(size > 0);
241
242
0
  rext = array_idx(&index->extensions, ext_id);
243
0
  i_assert(rext->hdr_size == size);
244
245
0
  index->set.ext_hdr_init_id = ext_id;
246
0
  i_free(index->set.ext_hdr_init_data);
247
0
  index->set.ext_hdr_init_data = i_malloc(size);
248
0
  memcpy(index->set.ext_hdr_init_data, data, size);
249
0
}
250
251
bool mail_index_ext_name_is_valid(const char *name)
252
0
{
253
0
  size_t i;
254
255
0
  for (i = 0; name[i] != '\0'; i++) {
256
0
    if (!i_isalnum(name[i]) && name[i] != '-' && name[i] != '_' &&
257
0
        name[i] != ' ')
258
0
      return FALSE;
259
260
0
  }
261
0
  return i == 0 || i < MAIL_INDEX_EXT_NAME_MAX_LENGTH;
262
0
}
263
264
uint32_t mail_index_ext_register(struct mail_index *index, const char *name,
265
         uint32_t default_hdr_size,
266
         uint16_t default_record_size,
267
         uint16_t default_record_align)
268
0
{
269
0
  struct mail_index_registered_ext rext;
270
0
  uint32_t ext_id;
271
272
0
  if (!mail_index_ext_name_is_valid(name))
273
0
    i_panic("mail_index_ext_register(%s): Invalid name", name);
274
275
0
  if (default_record_size != 0 && default_record_align == 0) {
276
0
    i_panic("mail_index_ext_register(%s): "
277
0
      "Invalid record alignment", name);
278
0
  }
279
280
0
  if (mail_index_ext_lookup(index, name, &ext_id))
281
0
    return ext_id;
282
283
0
  i_zero(&rext);
284
0
  rext.name = p_strdup(index->extension_pool, name);
285
0
  rext.index_idx = array_count(&index->extensions);
286
0
  rext.hdr_size = default_hdr_size;
287
0
  rext.record_size = default_record_size;
288
0
  rext.record_align = default_record_align;
289
290
0
  array_push_back(&index->extensions, &rext);
291
0
  return rext.index_idx;
292
0
}
293
294
void mail_index_ext_register_resize_defaults(struct mail_index *index,
295
               uint32_t ext_id,
296
               uint32_t default_hdr_size,
297
               uint16_t default_record_size,
298
               uint16_t default_record_align)
299
0
{
300
0
  struct mail_index_registered_ext *rext;
301
302
0
  rext = array_idx_modifiable(&index->extensions, ext_id);
303
0
  rext->hdr_size = default_hdr_size;
304
0
  rext->record_size = default_record_size;
305
0
  rext->record_align = default_record_align;
306
0
}
307
308
bool mail_index_ext_lookup(struct mail_index *index, const char *name,
309
         uint32_t *ext_id_r)
310
0
{
311
0
  const struct mail_index_registered_ext *extensions;
312
0
  unsigned int i, count;
313
314
0
  extensions = array_get(&index->extensions, &count);
315
0
  for (i = 0; i < count; i++) {
316
0
    if (strcmp(extensions[i].name, name) == 0) {
317
0
      *ext_id_r = i;
318
0
      return TRUE;
319
0
    }
320
0
  }
321
322
0
  *ext_id_r = (uint32_t)-1;
323
0
  return FALSE;
324
0
}
325
326
void mail_index_register_expunge_handler(struct mail_index *index,
327
           uint32_t ext_id,
328
           mail_index_expunge_handler_t *cb)
329
0
{
330
0
  struct mail_index_registered_ext *rext;
331
332
0
  rext = array_idx_modifiable(&index->extensions, ext_id);
333
0
  i_assert(rext->expunge_handler == NULL || rext->expunge_handler == cb);
334
335
0
  rext->expunge_handler = cb;
336
0
}
337
338
void mail_index_unregister_expunge_handler(struct mail_index *index,
339
             uint32_t ext_id)
340
0
{
341
0
  struct mail_index_registered_ext *rext;
342
343
0
  rext = array_idx_modifiable(&index->extensions, ext_id);
344
0
  i_assert(rext->expunge_handler != NULL);
345
346
0
  rext->expunge_handler = NULL;
347
0
}
348
349
bool mail_index_keyword_lookup(struct mail_index *index,
350
             const char *keyword, unsigned int *idx_r)
351
0
{
352
0
  char *key;
353
0
  void *value;
354
355
  /* keywords_hash keeps a name => index mapping of keywords.
356
     Keywords are never removed from it, so the index values are valid
357
     for the lifetime of the mail_index. */
358
0
  if (hash_table_lookup_full(index->keywords_hash, keyword,
359
0
           &key, &value)) {
360
0
    *idx_r = POINTER_CAST_TO(value, unsigned int);
361
0
    return TRUE;
362
0
  }
363
364
0
  *idx_r = UINT_MAX;
365
0
  return FALSE;
366
0
}
367
368
void mail_index_keyword_lookup_or_create(struct mail_index *index,
369
           const char *keyword,
370
           unsigned int *idx_r)
371
0
{
372
0
  char *keyword_dup;
373
374
0
  i_assert(*keyword != '\0');
375
376
0
  if (mail_index_keyword_lookup(index, keyword, idx_r))
377
0
    return;
378
379
0
  keyword = keyword_dup = p_strdup(index->keywords_pool, keyword);
380
0
  *idx_r = array_count(&index->keywords);
381
382
0
  hash_table_insert(index->keywords_hash, keyword_dup,
383
0
        POINTER_CAST(*idx_r));
384
0
  array_push_back(&index->keywords, &keyword);
385
386
  /* keep the array NULL-terminated, but the NULL itself invisible */
387
0
  array_append_zero(&index->keywords);
388
0
  array_pop_back(&index->keywords);
389
0
}
390
391
const ARRAY_TYPE(keywords) *mail_index_get_keywords(struct mail_index *index)
392
0
{
393
0
  return &index->keywords;
394
0
}
395
396
struct mail_keywords *
397
mail_index_keywords_create(struct mail_index *index,
398
         const char *const keywords[])
399
0
{
400
0
  struct mail_keywords *k;
401
0
  unsigned int src, dest, i, count;
402
403
0
  count = str_array_length(keywords);
404
0
  if (count == 0) {
405
0
    k = i_new(struct mail_keywords, 1);
406
0
    k->index = index;
407
0
    k->refcount = 1;
408
0
    return k;
409
0
  }
410
411
  /* @UNSAFE */
412
0
  k = i_malloc(MALLOC_ADD(sizeof(struct mail_keywords),
413
0
        MALLOC_MULTIPLY(sizeof(k->idx[0]), count)));
414
0
  k->index = index;
415
0
  k->refcount = 1;
416
417
  /* look up the keywords from index. they're never removed from there
418
     so we can permanently store indexes to them. */
419
0
  for (src = dest = 0; src < count; src++) {
420
0
    mail_index_keyword_lookup_or_create(index, keywords[src],
421
0
                &k->idx[dest]);
422
    /* ignore if this is a duplicate */
423
0
    for (i = 0; i < src; i++) {
424
0
      if (k->idx[i] == k->idx[dest])
425
0
        break;
426
0
    }
427
0
    if (i == src)
428
0
      dest++;
429
0
  }
430
0
  k->count = dest;
431
0
  return k;
432
0
}
433
434
struct mail_keywords *
435
mail_index_keywords_create_from_indexes(struct mail_index *index,
436
          const ARRAY_TYPE(keyword_indexes)
437
            *keyword_indexes)
438
0
{
439
0
  struct mail_keywords *k;
440
0
  const unsigned int *indexes;
441
0
  unsigned int src, dest, i, count;
442
443
0
  indexes = array_get(keyword_indexes, &count);
444
0
  if (count == 0) {
445
0
    k = i_new(struct mail_keywords, 1);
446
0
    k->index = index;
447
0
    k->refcount = 1;
448
0
    return k;
449
0
  }
450
451
  /* @UNSAFE */
452
0
  k = i_malloc(MALLOC_ADD(sizeof(struct mail_keywords),
453
0
        MALLOC_MULTIPLY(sizeof(k->idx[0]), count)));
454
0
  k->index = index;
455
0
  k->refcount = 1;
456
457
  /* copy but skip duplicates */
458
0
  for (src = dest = 0; src < count; src++) {
459
0
    for (i = 0; i < src; i++) {
460
0
      if (k->idx[i] == indexes[src])
461
0
        break;
462
0
    }
463
0
    if (i == src)
464
0
      k->idx[dest++] = indexes[src];
465
0
  }
466
0
  k->count = dest;
467
0
  return k;
468
0
}
469
470
void mail_index_keywords_ref(struct mail_keywords *keywords)
471
0
{
472
0
  keywords->refcount++;
473
0
}
474
475
void mail_index_keywords_unref(struct mail_keywords **_keywords)
476
0
{
477
0
  struct mail_keywords *keywords = *_keywords;
478
479
0
  i_assert(keywords->refcount > 0);
480
481
0
  *_keywords = NULL;
482
0
  if (--keywords->refcount == 0)
483
0
    i_free(keywords);
484
0
}
485
486
int mail_index_try_open_only(struct mail_index *index)
487
0
{
488
0
  i_assert(index->fd == -1);
489
0
  i_assert(!MAIL_INDEX_IS_IN_MEMORY(index));
490
491
  /* Note that our caller must close index->fd by itself. */
492
0
  if (index->readonly)
493
0
    errno = EACCES;
494
0
  else {
495
0
    index->fd = nfs_safe_open(index->filepath, O_RDWR);
496
0
    index->readonly = FALSE;
497
0
  }
498
499
0
  if (index->fd == -1 && ENOACCESS(errno)) {
500
0
    index->fd = open(index->filepath, O_RDONLY);
501
0
    index->readonly = TRUE;
502
0
  }
503
504
0
  if (index->fd == -1) {
505
0
    if (errno != ENOENT) {
506
0
      mail_index_set_syscall_error(index, "open()");
507
0
      return -1;
508
0
    }
509
510
    /* have to create it */
511
0
    return 0;
512
0
  }
513
0
  return 1;
514
0
}
515
516
static int
517
mail_index_try_open(struct mail_index *index)
518
0
{
519
0
  int ret;
520
521
0
  i_assert(index->fd == -1);
522
523
0
  if (MAIL_INDEX_IS_IN_MEMORY(index))
524
0
    return 0;
525
526
0
  ret = mail_index_map(index, MAIL_INDEX_SYNC_HANDLER_HEAD);
527
0
  if (ret == 0 && !index->readonly) {
528
    /* it's corrupted - recreate it */
529
0
    if (index->fd != -1) {
530
0
      if (close(index->fd) < 0)
531
0
        mail_index_set_syscall_error(index, "close()");
532
0
      index->fd = -1;
533
0
    }
534
0
  }
535
0
  return ret;
536
0
}
537
538
int mail_index_create_tmp_file(struct mail_index *index,
539
             const char *path_prefix, const char **path_r)
540
0
{
541
0
  mode_t old_mask;
542
0
  const char *path;
543
0
  int fd;
544
545
0
  i_assert(!MAIL_INDEX_IS_IN_MEMORY(index));
546
547
0
  path = *path_r = t_strconcat(path_prefix, ".tmp", NULL);
548
0
  old_mask = umask(0);
549
0
  fd = open(path, O_RDWR|O_CREAT | O_EXCL | O_NOFOLLOW, index->set.mode);
550
0
  umask(old_mask);
551
0
  if (fd == -1 && errno == EEXIST) {
552
    /* stale temp file. unlink and recreate rather than overwriting,
553
       just to make sure locking problems won't cause corruption */
554
0
    if (i_unlink(path) < 0)
555
0
      return -1;
556
0
    old_mask = umask(0);
557
0
    fd = open(path, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW,
558
0
        index->set.mode);
559
0
    umask(old_mask);
560
0
  }
561
0
  if (fd == -1) {
562
0
    mail_index_file_set_syscall_error(index, path, "creat()");
563
0
    return -1;
564
0
  }
565
566
0
  mail_index_fchown(index, fd, path);
567
0
  return fd;
568
0
}
569
570
static const char *mail_index_get_cache_path(struct mail_index *index)
571
0
{
572
0
  const char *dir;
573
574
0
  if (index->set.cache_dir != NULL)
575
0
    dir = index->set.cache_dir;
576
0
  else if (index->dir != NULL)
577
0
    dir = index->dir;
578
0
  else
579
0
    return NULL;
580
0
  return t_strconcat(dir, "/", index->prefix,
581
0
         MAIL_CACHE_FILE_SUFFIX, NULL);
582
0
}
583
584
static int mail_index_open_files(struct mail_index *index,
585
         enum mail_index_open_flags flags)
586
0
{
587
0
  int ret;
588
589
0
  ret = mail_transaction_log_open(index->log);
590
0
  if (ret == 0) {
591
0
    if ((flags & MAIL_INDEX_OPEN_FLAG_CREATE) == 0)
592
0
      return 0;
593
594
    /* if dovecot.index exists, read it first so that we can get
595
       the correct indexid and log sequence */
596
0
    (void)mail_index_try_open(index);
597
598
0
    if (index->indexid == 0) {
599
      /* Create a new indexid for us. If we're opening index
600
         into memory, index->map doesn't exist yet. */
601
0
      index->indexid = ioloop_time32;
602
0
      index->initial_create = TRUE;
603
0
      if (index->map != NULL)
604
0
        index->map->hdr.indexid = index->indexid;
605
0
    }
606
607
0
    ret = mail_transaction_log_create(index->log, FALSE);
608
0
    if (index->map != NULL) {
609
      /* log creation could have changed it if someone else
610
         just created it. */
611
0
      index->map->hdr.indexid = index->indexid;
612
0
    }
613
0
    index->initial_create = FALSE;
614
0
    index->initial_created = TRUE;
615
0
  }
616
0
  if (ret >= 0) {
617
0
    ret = index->map != NULL ? 1 : mail_index_try_open(index);
618
0
    if (ret == 0 && !index->readonly) {
619
      /* corrupted */
620
0
      mail_transaction_log_close(index->log);
621
0
      ret = mail_transaction_log_create(index->log, TRUE);
622
0
      if (ret == 0) {
623
0
        if (index->map != NULL)
624
0
          mail_index_unmap(&index->map);
625
0
        index->map = mail_index_map_alloc(index);
626
0
      }
627
0
    }
628
0
  }
629
0
  if (ret < 0) {
630
    /* open/create failed, fallback to in-memory indexes */
631
0
    if ((flags & MAIL_INDEX_OPEN_FLAG_CREATE) == 0)
632
0
      return -1;
633
634
0
    if (mail_index_move_to_memory(index) < 0)
635
0
      return -1;
636
0
  }
637
638
0
  if (index->cache == NULL) {
639
0
    const char *path = mail_index_get_cache_path(index);
640
0
    index->cache = mail_cache_open_or_create_path(index, path);
641
0
  }
642
0
  return 1;
643
0
}
644
645
static int
646
mail_index_open_opened(struct mail_index *index,
647
           enum mail_index_open_flags flags)
648
0
{
649
0
  int ret;
650
651
0
  i_assert(index->map != NULL);
652
653
0
  if ((index->map->hdr.flags & MAIL_INDEX_HDR_FLAG_CORRUPTED) != 0) {
654
    /* index was marked corrupted. we'll probably need to
655
       recreate the files. */
656
0
    mail_index_unmap(&index->map);
657
0
    mail_index_close_file(index);
658
0
    mail_transaction_log_close(index->log);
659
0
    if ((ret = mail_index_open_files(index, flags)) <= 0)
660
0
      return ret;
661
0
  }
662
663
0
  index->open_count++;
664
0
  return 1;
665
0
}
666
667
int mail_index_open(struct mail_index *index, enum mail_index_open_flags flags)
668
0
{
669
0
  int ret;
670
671
0
  if (index->open_count > 0) {
672
0
    if ((ret = mail_index_open_opened(index, flags)) <= 0) {
673
      /* doesn't exist and create flag not used */
674
0
    }
675
0
    return ret;
676
0
  }
677
678
0
  index->filepath = MAIL_INDEX_IS_IN_MEMORY(index) ?
679
0
    i_strdup("(in-memory index)") :
680
0
    i_strconcat(index->dir, "/", index->prefix, NULL);
681
682
0
  mail_index_reset_error(index);
683
0
  index->readonly = FALSE;
684
0
  index->log_sync_locked = FALSE;
685
0
  index->flags = flags;
686
0
  index->readonly = (flags & MAIL_INDEX_OPEN_FLAG_READONLY) != 0;
687
0
  if ((flags & MAIL_INDEX_OPEN_FLAG_DEBUG) != 0)
688
0
    event_set_forced_debug(index->event, TRUE);
689
0
  else
690
0
    event_unset_forced_debug(index->event);
691
692
0
  if ((flags & MAIL_INDEX_OPEN_FLAG_NFS_FLUSH) != 0 &&
693
0
      index->set.fsync_mode != FSYNC_MODE_ALWAYS)
694
0
    i_fatal("nfs flush requires mail_fsync=always");
695
0
  if ((flags & MAIL_INDEX_OPEN_FLAG_NFS_FLUSH) != 0 &&
696
0
      (flags & MAIL_INDEX_OPEN_FLAG_MMAP_DISABLE) == 0)
697
0
    i_fatal("nfs flush requires mmap_disable=yes");
698
699
  /* NOTE: increase open_count only after mail_index_open_files().
700
     it's used elsewhere to check if we're doing an initial opening
701
     of the index files */
702
0
  if ((ret = mail_index_open_files(index, flags)) <= 0) {
703
    /* doesn't exist and create flag not used */
704
0
    mail_index_close_nonopened(index);
705
0
    return ret;
706
0
  }
707
708
0
  index->open_count++;
709
710
0
  if (index->log->head == NULL) {
711
0
    mail_index_close(index);
712
0
    mail_index_set_error(index, "Index is corrupted "
713
0
              "(log->view->head == NULL)");
714
0
    return -1;
715
0
  }
716
717
0
  i_assert(index->map != NULL);
718
0
  mail_index_alloc_cache_index_opened(index);
719
0
  return 1;
720
0
}
721
722
int mail_index_open_or_create(struct mail_index *index,
723
            enum mail_index_open_flags flags)
724
0
{
725
0
  int ret;
726
727
0
  flags |= MAIL_INDEX_OPEN_FLAG_CREATE;
728
0
  ret = mail_index_open(index, flags);
729
0
  i_assert(ret != 0);
730
0
  return ret < 0 ? -1 : (index->initial_created ? 1 : 0);
731
0
}
732
733
void mail_index_close_file(struct mail_index *index)
734
0
{
735
0
  if (index->fd != -1) {
736
0
    if (close(index->fd) < 0)
737
0
      mail_index_set_syscall_error(index, "close()");
738
0
    index->fd = -1;
739
0
  }
740
0
}
741
742
static void mail_index_close_nonopened(struct mail_index *index)
743
0
{
744
0
  i_assert(!index->syncing);
745
746
0
  if (index->views != NULL) {
747
0
    i_panic("Leaked view for index %s: Opened in %s:%u",
748
0
      index->filepath, index->views->source_filename,
749
0
      index->views->source_linenum);
750
0
  }
751
0
  i_assert(index->views == NULL);
752
753
0
  mail_transaction_log_finish_pending_rotation(index->log);
754
755
0
  if (index->map != NULL)
756
0
    mail_index_unmap(&index->map);
757
758
0
  mail_index_close_file(index);
759
0
  mail_transaction_log_close(index->log);
760
0
  if (index->cache != NULL)
761
0
    mail_cache_free(&index->cache);
762
763
0
  i_free_and_null(index->filepath);
764
765
0
  index->indexid = 0;
766
0
}
767
768
void mail_index_close(struct mail_index *index)
769
0
{
770
0
  i_assert(index->open_count > 0);
771
772
0
  mail_index_alloc_cache_index_closing(index);
773
0
  if (--index->open_count == 0)
774
0
    mail_index_close_nonopened(index);
775
0
}
776
777
int mail_index_unlink(struct mail_index *index)
778
0
{
779
0
  const char *path;
780
0
  int last_errno = 0;
781
782
0
  if (MAIL_INDEX_IS_IN_MEMORY(index) || index->readonly)
783
0
    return 0;
784
785
  /* main index */
786
0
  if (unlink(index->filepath) < 0 && errno != ENOENT)
787
0
    last_errno = errno;
788
789
  /* logs */
790
0
  path = t_strconcat(index->filepath, MAIL_TRANSACTION_LOG_SUFFIX, NULL);
791
0
  if (unlink(path) < 0 && errno != ENOENT)
792
0
    last_errno = errno;
793
794
0
  path = t_strconcat(index->filepath,
795
0
         MAIL_TRANSACTION_LOG_SUFFIX".2", NULL);
796
0
  if (unlink(path) < 0 && errno != ENOENT)
797
0
    last_errno = errno;
798
799
  /* cache */
800
0
  path = t_strconcat(index->filepath, MAIL_CACHE_FILE_SUFFIX, NULL);
801
0
  if (unlink(path) < 0 && errno != ENOENT)
802
0
    last_errno = errno;
803
804
0
  if (last_errno == 0)
805
0
    return 0;
806
0
  else {
807
0
    errno = last_errno;
808
0
    return -1;
809
0
  }
810
0
}
811
812
int mail_index_reopen_if_changed(struct mail_index *index, bool *reopened_r,
813
         const char **reason_r)
814
0
{
815
0
  struct stat st1, st2;
816
0
  int ret;
817
818
0
  *reopened_r = FALSE;
819
820
0
  if (MAIL_INDEX_IS_IN_MEMORY(index)) {
821
0
    *reason_r = "in-memory index";
822
0
    return 0;
823
0
  }
824
825
0
  if (index->fd == -1)
826
0
    goto final;
827
828
0
  if ((index->flags & MAIL_INDEX_OPEN_FLAG_NFS_FLUSH) != 0)
829
0
    nfs_flush_file_handle_cache(index->filepath);
830
0
  if (nfs_safe_stat(index->filepath, &st2) < 0) {
831
0
    if (errno == ENOENT) {
832
0
      *reason_r = "index not found via stat()";
833
0
      return 0;
834
0
    }
835
0
    mail_index_set_syscall_error(index, "stat()");
836
0
    return -1;
837
0
  }
838
839
0
  if (fstat(index->fd, &st1) < 0) {
840
0
    if (!ESTALE_FSTAT(errno)) {
841
0
      mail_index_set_syscall_error(index, "fstat()");
842
0
      return -1;
843
0
    }
844
    /* deleted/recreated, reopen */
845
0
    *reason_r = "index is stale";
846
0
  } else if (st1.st_ino == st2.st_ino &&
847
0
       CMP_DEV_T(st1.st_dev, st2.st_dev)) {
848
    /* the same file */
849
0
    *reason_r = "index unchanged";
850
0
    return 1;
851
0
  } else {
852
0
    *reason_r = "index inode changed";
853
0
  }
854
855
  /* new file, new locks. the old fd can keep its locks, they don't
856
     matter anymore as no-one's going to modify the file. */
857
0
  mail_index_close_file(index);
858
859
0
final:
860
0
  if ((ret = mail_index_try_open_only(index)) == 0)
861
0
    *reason_r = "index not found via open()";
862
0
  else if (ret > 0) {
863
0
    *reason_r = "index opened";
864
0
    *reopened_r = TRUE;
865
0
  }
866
0
  return ret;
867
0
}
868
869
int mail_index_refresh(struct mail_index *index)
870
0
{
871
0
  int ret;
872
873
0
  ret = mail_index_map(index, MAIL_INDEX_SYNC_HANDLER_HEAD);
874
0
  return ret <= 0 ? -1 : 0;
875
0
}
876
877
struct mail_cache *mail_index_get_cache(struct mail_index *index)
878
0
{
879
0
  return index->cache;
880
0
}
881
882
static void ATTR_FORMAT(2,0)
883
mail_index_set_verror(struct mail_index *index, const char *fmt, va_list args)
884
0
{
885
0
  i_free(index->last_error.text);
886
887
0
  if (fmt == NULL)
888
0
    index->last_error.text = NULL;
889
0
  else {
890
0
    index->last_error.text = i_strdup_vprintf(fmt, args);
891
0
    e_error(index->event, "%s", index->last_error.text);
892
0
  }
893
0
}
894
895
void mail_index_set_error(struct mail_index *index, const char *fmt, ...)
896
0
{
897
0
  va_list args;
898
0
  va_start(args, fmt);
899
0
  mail_index_set_verror(index, fmt, args);
900
0
  va_end(args);
901
0
}
902
903
void mail_index_set_error_code(struct mail_index *index,
904
             enum mail_index_error_code code,
905
             const char *fmt, ...)
906
0
{
907
0
  va_list args;
908
0
  va_start(args, fmt);
909
0
  mail_index_set_verror(index, fmt, args);
910
0
  index->last_error.code = code;
911
0
  va_end(args);
912
0
}
913
914
void mail_index_set_error_nolog(struct mail_index *index, const char *str)
915
0
{
916
0
  i_assert(str != NULL);
917
918
0
  char *old_error = index->last_error.text;
919
0
  index->last_error.text = i_strdup(str);
920
0
  i_free(old_error);
921
0
}
922
923
bool mail_index_is_in_memory(struct mail_index *index)
924
0
{
925
0
  return MAIL_INDEX_IS_IN_MEMORY(index);
926
0
}
927
928
static void mail_index_set_as_in_memory(struct mail_index *index)
929
0
{
930
0
  i_free_and_null(index->dir);
931
932
0
  i_free(index->filepath);
933
0
  index->filepath = i_strdup("(in-memory index)");
934
0
}
935
936
int mail_index_move_to_memory(struct mail_index *index)
937
0
{
938
0
  struct mail_index_map *map;
939
940
0
  if (MAIL_INDEX_IS_IN_MEMORY(index))
941
0
    return index->map == NULL ? -1 : 0;
942
943
0
  if ((index->flags & MAIL_INDEX_OPEN_FLAG_NEVER_IN_MEMORY) != 0)
944
0
    return -1;
945
946
0
  if (index->map == NULL) {
947
    /* index was never even opened. just mark it as being in
948
       memory and let the caller re-open the index. */
949
0
    i_assert(index->fd == -1);
950
0
    mail_index_set_as_in_memory(index);
951
0
    return -1;
952
0
  }
953
954
  /* move index map to memory */
955
0
  if (!MAIL_INDEX_MAP_IS_IN_MEMORY(index->map)) {
956
0
    map = mail_index_map_clone(index->map);
957
0
    mail_index_unmap(&index->map);
958
0
    index->map = map;
959
0
  }
960
961
0
  if (index->log != NULL) {
962
    /* move transaction log to memory */
963
0
    if (mail_transaction_log_move_to_memory(index->log) < 0)
964
0
      return -1;
965
0
  }
966
967
0
  if (index->fd != -1) {
968
0
    if (close(index->fd) < 0)
969
0
      mail_index_set_syscall_error(index, "close()");
970
0
    index->fd = -1;
971
0
  }
972
0
  mail_index_set_as_in_memory(index);
973
0
  return 0;
974
0
}
975
976
void mail_index_mark_corrupted(struct mail_index *index)
977
0
{
978
0
  index->indexid = 0;
979
980
0
  index->map->hdr.flags |= MAIL_INDEX_HDR_FLAG_CORRUPTED;
981
0
  if (!index->readonly) {
982
0
    if (unlink(index->filepath) < 0 &&
983
0
        errno != ENOENT && errno != ESTALE)
984
0
      mail_index_set_syscall_error(index, "unlink()");
985
0
    (void)mail_transaction_log_unlink(index->log);
986
0
  }
987
0
}
988
989
bool mail_index_is_deleted(struct mail_index *index)
990
0
{
991
0
  return index->index_delete_requested || index->index_deleted;
992
0
}
993
994
int mail_index_get_modification_time(struct mail_index *index, time_t *mtime_r)
995
0
{
996
0
  struct stat st;
997
0
  const char *path;
998
999
0
  *mtime_r = 0;
1000
0
  if (MAIL_INDEX_IS_IN_MEMORY(index)) {
1001
    /* this function doesn't make sense for in-memory indexes */
1002
0
    return 0;
1003
0
  }
1004
1005
  /* index may not be open, so index->filepath may be NULL */
1006
0
  path = t_strconcat(index->dir, "/", index->prefix,
1007
0
         MAIL_TRANSACTION_LOG_SUFFIX, NULL);
1008
0
  if (stat(path, &st) < 0) {
1009
0
    if (errno == ENOENT) {
1010
      /* .log is always supposed to exist - don't bother
1011
         trying to stat(dovecot.index) */
1012
0
      return 0;
1013
0
    }
1014
0
    mail_index_file_set_syscall_error(index, path, "stat()");
1015
0
    return -1;
1016
0
  }
1017
0
  *mtime_r = st.st_mtime;
1018
0
  return 0;
1019
0
}
1020
1021
void mail_index_fchown(struct mail_index *index, int fd, const char *path)
1022
0
{
1023
0
  mode_t mode;
1024
1025
0
  if (index->set.gid == (gid_t)-1) {
1026
    /* no gid changing */
1027
0
    return;
1028
0
  } else if (fchown(fd, (uid_t)-1, index->set.gid) == 0) {
1029
    /* success */
1030
0
    return;
1031
0
  } if ((index->set.mode & 0060) >> 3 == (index->set.mode & 0006)) {
1032
    /* group and world permissions are the same, so group doesn't
1033
       really matter. ignore silently. */
1034
0
    return;
1035
0
  }
1036
0
  if (!ENOACCESS(errno))
1037
0
    mail_index_file_set_syscall_error(index, path, "fchown()");
1038
0
  else {
1039
0
    mail_index_set_error(index, "%s",
1040
0
      eperm_error_get_chgrp("fchown", path, index->set.gid,
1041
0
                index->set.gid_origin));
1042
0
  }
1043
1044
  /* continue, but change permissions so that only the common
1045
     subset of group and world is used. this makes sure no one
1046
     gets any extra permissions. */
1047
0
  mode = ((index->set.mode & 0060) >> 3) & (index->set.mode & 0006);
1048
0
  mode |= (mode << 3) | (index->set.mode & 0600);
1049
0
  if (fchmod(fd, mode) < 0)
1050
0
    mail_index_file_set_syscall_error(index, path, "fchmod()");
1051
0
}
1052
1053
int mail_index_lock_sync(struct mail_index *index, const char *lock_reason)
1054
0
{
1055
0
  uint32_t file_seq;
1056
0
  uoff_t file_offset;
1057
1058
0
  return mail_transaction_log_sync_lock(index->log, lock_reason,
1059
0
                &file_seq, &file_offset);
1060
0
}
1061
1062
void mail_index_unlock(struct mail_index *index, const char *long_lock_reason)
1063
0
{
1064
0
  mail_transaction_log_sync_unlock(index->log, long_lock_reason);
1065
0
}
1066
1067
bool mail_index_is_locked(struct mail_index *index)
1068
0
{
1069
0
  return index->log_sync_locked;
1070
0
}
1071
1072
void mail_index_set_syscall_error(struct mail_index *index,
1073
          const char *function)
1074
0
{
1075
0
  mail_index_file_set_syscall_error(index, index->filepath, function);
1076
0
}
1077
1078
void mail_index_file_set_syscall_error(struct mail_index *index,
1079
               const char *filepath,
1080
               const char *function)
1081
0
{
1082
0
  const char *errstr;
1083
1084
0
  i_assert(filepath != NULL);
1085
0
  i_assert(function != NULL);
1086
1087
0
  if (errno == ENOENT) {
1088
0
    struct stat st;
1089
0
    int old_errno = errno;
1090
0
    i_assert(index->log->filepath != NULL);
1091
0
    if (nfs_safe_stat(index->log->filepath, &st) < 0 &&
1092
0
        errno == ENOENT) {
1093
      /* the index log has gone away */
1094
0
      index->index_deleted = TRUE;
1095
0
      errno = old_errno;
1096
0
      return;
1097
0
    }
1098
0
    errno = old_errno;
1099
0
  }
1100
1101
0
  if (ENOSPACE(errno)) {
1102
0
    index->last_error.code = MAIL_INDEX_ERROR_CODE_NO_SPACE;
1103
0
    if ((index->flags & MAIL_INDEX_OPEN_FLAG_NEVER_IN_MEMORY) == 0)
1104
0
      return;
1105
0
  }
1106
1107
0
  if (ENOACCESS(errno)) {
1108
0
    function = t_strcut(function, '(');
1109
0
    if (strcmp(function, "creat") == 0 ||
1110
0
        str_begins_with(function, "file_dotlock_"))
1111
0
      errstr = eacces_error_get_creating(function, filepath);
1112
0
    else
1113
0
      errstr = eacces_error_get(function, filepath);
1114
1115
0
    mail_index_set_error_code(index, MAIL_INDEX_ERROR_CODE_NO_ACCESS,
1116
0
            "%s", errstr);
1117
0
  } else {
1118
0
    const char *suffix = errno != EFBIG ? "" :
1119
0
      " (process was started with ulimit -f limit)";
1120
0
    mail_index_set_error(index, "%s failed with file %s: "
1121
0
             "%m%s", function, filepath, suffix);
1122
0
  }
1123
0
}
1124
1125
const char *mail_index_get_last_error(struct mail_index *index,
1126
              enum mail_index_error_code *code_r)
1127
0
{
1128
0
  if (code_r != NULL)
1129
0
    *code_r = index->last_error.code;
1130
0
  return index->last_error.text;
1131
0
}
1132
1133
void mail_index_reset_error(struct mail_index *index)
1134
0
{
1135
0
  i_free(index->last_error.text);
1136
0
  i_zero(&index->last_error);
1137
0
}