Coverage Report

Created: 2026-08-31 07:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib-index/mail-transaction-log-file.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 "ioloop.h"
6
#include "file-dotlock.h"
7
#include "nfs-workarounds.h"
8
#include "read-full.h"
9
#include "write-full.h"
10
#include "mmap-util.h"
11
#include "mail-index-private.h"
12
#include "mail-index-modseq.h"
13
#include "mail-transaction-log-private.h"
14
15
0
#define LOG_PREFETCH IO_BLOCK_SIZE
16
0
#define MEMORY_LOG_NAME "(in-memory transaction log file)"
17
0
#define LOG_NEW_DOTLOCK_SUFFIX ".newlock"
18
19
static int
20
mail_transaction_log_file_sync(struct mail_transaction_log_file *file,
21
             bool *retry_r, const char **reason_r);
22
23
static void
24
log_file_set_syscall_error(struct mail_transaction_log_file *file,
25
         const char *function)
26
0
{
27
0
  mail_index_file_set_syscall_error(file->log->index,
28
0
            file->filepath, function);
29
0
}
30
31
static void
32
mail_transaction_log_mark_corrupted(struct mail_transaction_log_file *file)
33
0
{
34
0
  unsigned int offset =
35
0
    offsetof(struct mail_transaction_log_header, indexid);
36
0
  int flags;
37
38
0
  if (MAIL_TRANSACTION_LOG_FILE_IN_MEMORY(file) ||
39
0
      file->log->index->readonly)
40
0
    return;
41
42
  /* indexid=0 marks the log file as corrupted. we opened the file with
43
     O_APPEND, and now we need to drop it for pwrite() to work (at least
44
     in Linux) */
45
0
  flags = fcntl(file->fd, F_GETFL, 0);
46
0
  if (flags < 0) {
47
0
    mail_index_file_set_syscall_error(file->log->index,
48
0
      file->filepath, "fcntl(F_GETFL)");
49
0
    return;
50
0
  }
51
0
  if (fcntl(file->fd, F_SETFL, flags & ~O_APPEND) < 0) {
52
0
    mail_index_file_set_syscall_error(file->log->index,
53
0
      file->filepath, "fcntl(F_SETFL)");
54
0
    return;
55
0
  }
56
0
  if (pwrite_full(file->fd, &file->hdr.indexid,
57
0
      sizeof(file->hdr.indexid), offset) < 0) {
58
0
    mail_index_file_set_syscall_error(file->log->index,
59
0
              file->filepath, "pwrite()");
60
0
  }
61
0
}
62
63
void mail_transaction_log_file_set_need_rotate(
64
  struct mail_transaction_log_file *file, const char *fmt, ...)
65
0
{
66
0
  va_list va;
67
68
0
  if (file->need_rotate != NULL)
69
0
    return;
70
71
0
  va_start(va, fmt);
72
0
  file->need_rotate = i_strdup_vprintf(fmt, va);
73
0
  va_end(va);
74
0
}
75
76
void mail_transaction_log_file_set_garbage_at_eof(
77
  struct mail_transaction_log_file *file, const char *fmt, ...)
78
0
{
79
0
  va_list va;
80
81
0
  if (file->garbage_at_eof) {
82
    /* Already known - don't log it again */
83
0
    return;
84
0
  }
85
0
  file->garbage_at_eof = TRUE;
86
87
0
  va_start(va, fmt);
88
0
  T_BEGIN {
89
0
    const char *reason = t_strdup_vprintf(fmt, va);
90
91
0
    mail_transaction_log_file_set_need_rotate(file, "%s", reason);
92
0
    e_debug(file->log->index->event,
93
0
      "Transaction log %s has garbage at EOF: %s",
94
0
      file->filepath, reason);
95
0
  } T_END;
96
0
  va_end(va);
97
0
}
98
99
void
100
mail_transaction_log_file_set_corrupted(struct mail_transaction_log_file *file,
101
          const char *fmt, ...)
102
0
{
103
0
  va_list va;
104
105
0
  file->corrupted = TRUE;
106
0
  file->hdr.indexid = 0;
107
0
  mail_transaction_log_mark_corrupted(file);
108
109
0
  va_start(va, fmt);
110
0
  T_BEGIN {
111
0
    mail_index_set_error(file->log->index,
112
0
      "Corrupted transaction log file %s seq %u: %s "
113
0
      "(sync_offset=%"PRIuUOFF_T")",
114
0
      file->filepath, file->hdr.file_seq,
115
0
      t_strdup_vprintf(fmt, va), file->sync_offset);
116
0
  } T_END;
117
0
  va_end(va);
118
0
}
119
120
struct mail_transaction_log_file *
121
mail_transaction_log_file_alloc(struct mail_transaction_log *log,
122
        const char *path)
123
0
{
124
0
  struct mail_transaction_log_file *file;
125
126
0
  file = i_new(struct mail_transaction_log_file, 1);
127
0
  file->log = log;
128
0
  file->filepath = i_strdup(path);
129
0
  file->fd = -1;
130
0
  return file;
131
0
}
132
133
void mail_transaction_log_file_free(struct mail_transaction_log_file **_file)
134
0
{
135
0
  struct mail_transaction_log_file *file = *_file;
136
0
  struct mail_transaction_log_file **p;
137
0
  int old_errno = errno;
138
139
0
  *_file = NULL;
140
141
0
  i_assert(!file->locked);
142
0
  i_assert(file->refcount == 0);
143
144
0
  for (p = &file->log->files; *p != NULL; p = &(*p)->next) {
145
0
    if (*p == file) {
146
0
      *p = file->next;
147
0
      break;
148
0
    }
149
0
  }
150
151
0
  if (file == file->log->head)
152
0
    file->log->head = NULL;
153
154
0
  buffer_free(&file->buffer);
155
156
0
  if (file->mmap_base != NULL) {
157
0
    if (munmap(file->mmap_base, file->mmap_size) < 0)
158
0
      log_file_set_syscall_error(file, "munmap()");
159
0
  }
160
161
0
  if (file->fd != -1) {
162
0
    if (close(file->fd) < 0)
163
0
      log_file_set_syscall_error(file, "close()");
164
0
  }
165
166
0
  i_free(file->filepath);
167
0
  i_free(file->need_rotate);
168
0
        i_free(file);
169
170
0
        errno = old_errno;
171
0
}
172
173
static void
174
mail_transaction_log_file_skip_to_head(struct mail_transaction_log_file *file)
175
0
{
176
0
  struct mail_transaction_log *log = file->log;
177
0
  struct mail_index_map *map = log->index->map;
178
0
  const struct mail_index_modseq_header *modseq_hdr;
179
0
  uoff_t head_offset;
180
181
0
  if (map == NULL || file->hdr.file_seq != map->hdr.log_file_seq ||
182
0
      map->hdr.log_file_head_offset == 0)
183
0
    return;
184
185
  /* we can get a valid log offset from index file. initialize
186
     sync_offset from it so we don't have to read the whole log
187
     file from beginning. */
188
0
  head_offset = map->hdr.log_file_head_offset;
189
190
0
  modseq_hdr = mail_index_map_get_modseq_header(map);
191
0
  if (head_offset < file->hdr.hdr_size) {
192
0
    mail_index_set_error(log->index,
193
0
             "%s: log_file_head_offset too small",
194
0
             log->index->filepath);
195
0
    file->sync_offset = file->hdr.hdr_size;
196
0
    file->sync_highest_modseq = file->hdr.initial_modseq;
197
0
  } else if (modseq_hdr == NULL && file->hdr.initial_modseq == 0) {
198
    /* modseqs not used yet */
199
0
    file->sync_offset = head_offset;
200
0
    file->sync_highest_modseq = 0;
201
0
  } else if (modseq_hdr == NULL ||
202
0
       modseq_hdr->log_seq != file->hdr.file_seq) {
203
    /* highest_modseq not synced, start from beginning */
204
0
    file->sync_offset = file->hdr.hdr_size;
205
0
    file->sync_highest_modseq = file->hdr.initial_modseq;
206
0
  } else if (modseq_hdr->log_offset > head_offset) {
207
0
    mail_index_set_error(log->index,
208
0
             "%s: modseq_hdr.log_offset too large",
209
0
             log->index->filepath);
210
0
    file->sync_offset = file->hdr.hdr_size;
211
0
    file->sync_highest_modseq = file->hdr.initial_modseq;
212
0
  } else {
213
    /* start from where we last stopped tracking modseqs */
214
0
    file->sync_offset = modseq_hdr->log_offset;
215
0
    file->sync_highest_modseq = modseq_hdr->highest_modseq;
216
0
  }
217
0
  if (file->hdr.file_seq == log->index->map->hdr.log_file_seq) {
218
0
    file->last_read_hdr_tail_offset =
219
0
      log->index->map->hdr.log_file_tail_offset;
220
0
  }
221
0
  if (file->last_read_hdr_tail_offset > file->max_tail_offset)
222
0
    file->max_tail_offset = file->last_read_hdr_tail_offset;
223
0
}
224
225
static void
226
mail_transaction_log_file_add_to_list(struct mail_transaction_log_file *file)
227
0
{
228
0
  struct mail_transaction_log_file **p;
229
0
  const char *reason;
230
0
  bool retry;
231
232
0
  file->sync_offset = file->hdr.hdr_size;
233
0
  file->sync_highest_modseq = file->hdr.initial_modseq;
234
0
  mail_transaction_log_file_skip_to_head(file);
235
236
  /* insert it to correct position */
237
0
  for (p = &file->log->files; *p != NULL; p = &(*p)->next) {
238
0
    if ((*p)->hdr.file_seq > file->hdr.file_seq)
239
0
      break;
240
0
    i_assert((*p)->hdr.file_seq < file->hdr.file_seq);
241
0
  }
242
243
0
  file->next = *p;
244
0
  *p = file;
245
246
0
  if (file->buffer != NULL) {
247
    /* if we read any unfinished data, make sure the buffer gets
248
       truncated. */
249
0
    (void)mail_transaction_log_file_sync(file, &retry, &reason);
250
0
    buffer_set_used_size(file->buffer,
251
0
             file->sync_offset - file->buffer_offset);
252
0
  }
253
0
}
254
255
static int
256
mail_transaction_log_init_hdr(struct mail_transaction_log *log,
257
            struct mail_transaction_log_header *hdr)
258
0
{
259
0
  struct mail_index *index = log->index;
260
0
  struct mail_transaction_log_file *file;
261
262
0
  i_assert(index->indexid != 0);
263
264
0
  i_zero(hdr);
265
0
  hdr->major_version = MAIL_TRANSACTION_LOG_MAJOR_VERSION;
266
0
  hdr->minor_version = MAIL_TRANSACTION_LOG_MINOR_VERSION;
267
0
  hdr->hdr_size = sizeof(struct mail_transaction_log_header);
268
0
  hdr->indexid = log->index->indexid;
269
0
  hdr->create_stamp = ioloop_time32;
270
0
#ifndef WORDS_BIGENDIAN
271
0
  hdr->compat_flags |= MAIL_INDEX_COMPAT_LITTLE_ENDIAN;
272
0
#endif
273
274
0
  if (index->fd != -1) {
275
    /* not creating index - make sure we have latest header */
276
0
    if (!index->mapping) {
277
0
      if (mail_index_map(index,
278
0
             MAIL_INDEX_SYNC_HANDLER_HEAD) <= 0)
279
0
        return -1;
280
0
    } else {
281
      /* if we got here from mapping, the .log file is
282
         corrupted. use whatever values we got from index
283
         file */
284
0
    }
285
0
  }
286
0
  if (index->map != NULL) {
287
0
    hdr->prev_file_seq = index->map->hdr.log_file_seq;
288
0
    hdr->prev_file_offset = index->map->hdr.log_file_head_offset;
289
0
    hdr->file_seq = index->map->hdr.log_file_seq + 1;
290
0
    hdr->initial_modseq =
291
0
      mail_index_map_modseq_get_highest(index->map);
292
0
  } else {
293
0
    hdr->file_seq = 1;
294
0
  }
295
0
  if (hdr->initial_modseq == 0) {
296
    /* modseq tracking in log files is required for many reasons
297
       nowadays, even if per-message modseqs aren't enabled in
298
       dovecot.index. */
299
0
    hdr->initial_modseq = 1;
300
0
  }
301
302
0
  if (log->head != NULL) {
303
    /* make sure the sequence always increases to avoid crashes
304
       later. this catches the buggy case where two processes
305
       happen to replace the same log file. */
306
0
    for (file = log->head->next; file != NULL; file = file->next) {
307
0
      if (hdr->file_seq <= file->hdr.file_seq)
308
0
        hdr->file_seq = file->hdr.file_seq + 1;
309
0
    }
310
311
0
    if (hdr->file_seq <= log->head->hdr.file_seq) {
312
      /* make sure the sequence grows */
313
0
      hdr->file_seq = log->head->hdr.file_seq+1;
314
0
    }
315
0
    if (hdr->initial_modseq < log->head->sync_highest_modseq) {
316
      /* this should be always up-to-date */
317
0
      hdr->initial_modseq = log->head->sync_highest_modseq;
318
0
    }
319
0
  }
320
0
  return 0;
321
0
}
322
323
struct mail_transaction_log_file *
324
mail_transaction_log_file_alloc_in_memory(struct mail_transaction_log *log)
325
0
{
326
0
  struct mail_transaction_log_file *file;
327
328
0
  file = mail_transaction_log_file_alloc(log, MEMORY_LOG_NAME);
329
0
  if (mail_transaction_log_init_hdr(log, &file->hdr) < 0) {
330
0
    i_free(file);
331
0
    return NULL;
332
0
  }
333
334
0
  file->buffer = buffer_create_dynamic(default_pool, 4096);
335
0
  file->buffer_offset = sizeof(file->hdr);
336
337
0
  mail_transaction_log_file_add_to_list(file);
338
0
  return file;
339
0
}
340
341
static int
342
mail_transaction_log_file_dotlock(struct mail_transaction_log_file *file)
343
0
{
344
0
  struct dotlock_settings dotlock_set;
345
0
  int ret;
346
347
0
  if (file->log->dotlock_refcount > 0)
348
0
    ret = 1;
349
0
  else {
350
0
    i_assert(file->log->dotlock_refcount == 0);
351
0
    mail_transaction_log_get_dotlock_set(file->log, &dotlock_set);
352
0
    ret = file_dotlock_create(&dotlock_set, file->filepath, 0,
353
0
            &file->log->dotlock);
354
0
  }
355
0
  if (ret > 0) {
356
0
    file->log->dotlock_refcount++;
357
0
    file->locked = TRUE;
358
0
    file->lock_create_time = time(NULL);
359
0
    return 0;
360
0
  }
361
0
  if (ret < 0) {
362
0
    log_file_set_syscall_error(file, "file_dotlock_create()");
363
0
    return -1;
364
0
  }
365
366
0
  mail_index_set_error(file->log->index,
367
0
           "Timeout (%us) while waiting for "
368
0
           "dotlock for transaction log file %s",
369
0
           dotlock_set.timeout, file->filepath);
370
0
  return -1;
371
0
}
372
373
static int
374
mail_transaction_log_file_undotlock(struct mail_transaction_log_file *file)
375
0
{
376
0
  int ret;
377
378
0
  i_assert(file->log->dotlock_refcount >= 0);
379
0
  if (--file->log->dotlock_refcount > 0)
380
0
    return 0;
381
382
0
  ret = file_dotlock_delete(&file->log->dotlock);
383
0
  if (ret < 0) {
384
0
    log_file_set_syscall_error(file, "file_dotlock_delete()");
385
0
    return -1;
386
0
  }
387
388
0
  if (ret == 0) {
389
0
    mail_index_set_error(file->log->index,
390
0
      "Dotlock was lost for transaction log file %s",
391
0
      file->filepath);
392
0
    return -1;
393
0
  }
394
0
  return 0;
395
0
}
396
397
int mail_transaction_log_file_lock(struct mail_transaction_log_file *file)
398
0
{
399
0
  struct mail_index *index = file->log->index;
400
0
  unsigned int lock_timeout_secs;
401
0
  int ret;
402
403
0
  if (file->locked)
404
0
    return 0;
405
406
0
  if (MAIL_TRANSACTION_LOG_FILE_IN_MEMORY(file)) {
407
0
    file->locked = TRUE;
408
0
    return 0;
409
0
  }
410
411
0
  if (index->readonly) {
412
0
    mail_index_set_error_code(
413
0
      index, MAIL_INDEX_ERROR_CODE_NO_ACCESS,
414
0
      "Index is read-only, can't write-lock %s",
415
0
      file->filepath);
416
0
    return -1;
417
0
  }
418
419
0
  if (index->set.lock_method == FILE_LOCK_METHOD_DOTLOCK)
420
0
    return mail_transaction_log_file_dotlock(file);
421
422
0
  i_assert(file->file_lock == NULL);
423
0
  lock_timeout_secs = I_MIN(MAIL_TRANSACTION_LOG_LOCK_TIMEOUT,
424
0
          index->set.max_lock_timeout_secs);
425
0
  ret = mail_index_lock_fd(index, file->filepath, file->fd,
426
0
         F_WRLCK, lock_timeout_secs,
427
0
         &file->file_lock);
428
0
  if (ret > 0) {
429
0
    file->locked = TRUE;
430
0
    file->lock_create_time = time(NULL);
431
0
    return 0;
432
0
  }
433
0
  if (ret < 0) {
434
0
    log_file_set_syscall_error(file, "mail_index_wait_lock_fd()");
435
0
    return -1;
436
0
  }
437
438
0
  mail_index_set_error(index,
439
0
    "Timeout (%us) while waiting for lock for "
440
0
    "transaction log file %s%s",
441
0
    lock_timeout_secs, file->filepath,
442
0
    file_lock_find(file->fd, index->set.lock_method, F_WRLCK));
443
0
  return -1;
444
0
}
445
446
void mail_transaction_log_file_unlock(struct mail_transaction_log_file *file,
447
              const char *lock_reason)
448
0
{
449
0
  unsigned int lock_time;
450
451
0
  if (!file->locked)
452
0
    return;
453
454
0
  file->locked = FALSE;
455
0
  file->locked_sync_offset_updated = FALSE;
456
457
0
  if (MAIL_TRANSACTION_LOG_FILE_IN_MEMORY(file))
458
0
    return;
459
460
0
  lock_time = time(NULL) - file->lock_create_time;
461
0
  if (lock_time >= MAIL_TRANSACTION_LOG_LOCK_WARN_SECS && lock_reason != NULL) {
462
0
    e_warning(file->log->index->event,
463
0
        "Transaction log file %s was locked for %u seconds (%s)",
464
0
        file->filepath, lock_time, lock_reason);
465
0
  }
466
467
0
  if (file->log->index->set.lock_method == FILE_LOCK_METHOD_DOTLOCK) {
468
0
    (void)mail_transaction_log_file_undotlock(file);
469
0
    return;
470
0
  }
471
472
0
  file_unlock(&file->file_lock);
473
0
}
474
475
static ssize_t
476
mail_transaction_log_file_read_header(struct mail_transaction_log_file *file)
477
0
{
478
0
  void *dest;
479
0
  size_t pos, dest_size;
480
0
  ssize_t ret;
481
482
0
  i_assert(file->buffer == NULL && file->mmap_base == NULL);
483
484
0
  i_zero(&file->hdr);
485
0
  if (file->last_size < mmap_get_page_size() && file->last_size > 0) {
486
    /* just read the entire transaction log to memory.
487
       note that if some of the data hasn't been fully committed
488
       yet (hdr.size=0), the buffer must be truncated later */
489
0
    file->buffer = buffer_create_dynamic(default_pool, 4096);
490
0
    file->buffer_offset = 0;
491
0
    dest_size = file->last_size;
492
0
    dest = buffer_append_space_unsafe(file->buffer, dest_size);
493
0
  } else {
494
    /* read only the header */
495
0
    dest = &file->hdr;
496
0
    dest_size = sizeof(file->hdr);
497
0
  }
498
499
  /* it's not necessarily an error to read less than wanted header size,
500
     since older versions of the log format used smaller headers. */
501
0
        pos = 0;
502
0
  do {
503
0
    ret = pread(file->fd, PTR_OFFSET(dest, pos),
504
0
          dest_size - pos, pos);
505
0
    if (ret > 0)
506
0
      pos += ret;
507
0
  } while (ret > 0 && pos < dest_size);
508
509
0
  if (file->buffer != NULL) {
510
0
    buffer_set_used_size(file->buffer, pos);
511
0
    memcpy(&file->hdr, file->buffer->data,
512
0
           I_MIN(pos, sizeof(file->hdr)));
513
0
  }
514
515
0
  return ret < 0 ? -1 : (ssize_t)pos;
516
0
}
517
518
static int
519
mail_transaction_log_file_fail_dupe(struct mail_transaction_log_file *file)
520
0
{
521
0
  int ret;
522
523
  /* mark the old file corrupted. we can't safely remove
524
     it from the list however, so return failure. */
525
0
  file->hdr.indexid = 0;
526
0
  if (strcmp(file->filepath, file->log->head->filepath) != 0) {
527
    /* only mark .2 corrupted, just to make sure we don't lose any
528
       changes from .log in case we're somehow wrong */
529
0
    mail_transaction_log_mark_corrupted(file);
530
0
    ret = 0;
531
0
  } else {
532
0
    ret = -1;
533
0
  }
534
0
  if (!file->corrupted) {
535
0
    file->corrupted = TRUE;
536
0
    mail_index_set_error(file->log->index,
537
0
             "Transaction log %s: "
538
0
             "duplicate transaction log sequence (%u)",
539
0
             file->filepath, file->hdr.file_seq);
540
0
  }
541
0
  return ret;
542
0
}
543
544
static int
545
mail_transaction_log_file_read_hdr(struct mail_transaction_log_file *file,
546
           bool ignore_estale)
547
0
{
548
0
        struct mail_transaction_log_file *f;
549
0
  int ret;
550
551
0
  i_assert(!MAIL_TRANSACTION_LOG_FILE_IN_MEMORY(file));
552
553
0
  if (file->corrupted)
554
0
    return 0;
555
556
0
  ret = mail_transaction_log_file_read_header(file);
557
0
  if (ret < 0) {
558
0
                if (errno != ESTALE || !ignore_estale)
559
0
      log_file_set_syscall_error(file, "pread()");
560
0
    return -1;
561
0
  }
562
0
  if (file->hdr.major_version != MAIL_TRANSACTION_LOG_MAJOR_VERSION) {
563
    /* incompatible version - fix silently */
564
0
    return 0;
565
0
  }
566
0
  if (ret < MAIL_TRANSACTION_LOG_HEADER_MIN_SIZE) {
567
0
    mail_transaction_log_file_set_corrupted(file,
568
0
      "unexpected end of file while reading header");
569
0
    return 0;
570
0
  }
571
572
0
  const unsigned int hdr_version =
573
0
    MAIL_TRANSACTION_LOG_HDR_VERSION(&file->hdr);
574
0
  if (MAIL_TRANSACTION_LOG_VERSION_HAVE(hdr_version, COMPAT_FLAGS)) {
575
    /* we have compatibility flags */
576
0
    enum mail_index_header_compat_flags compat_flags = 0;
577
578
0
#ifndef WORDS_BIGENDIAN
579
0
    compat_flags |= MAIL_INDEX_COMPAT_LITTLE_ENDIAN;
580
0
#endif
581
0
    if (file->hdr.compat_flags != compat_flags) {
582
      /* architecture change */
583
0
      mail_index_set_error(file->log->index,
584
0
               "Rebuilding index file %s: "
585
0
               "CPU architecture changed",
586
0
               file->log->index->filepath);
587
0
      return 0;
588
0
    }
589
0
  }
590
0
  if (file->hdr.hdr_size < MAIL_TRANSACTION_LOG_HEADER_MIN_SIZE) {
591
0
    mail_transaction_log_file_set_corrupted(file,
592
0
      "Header size too small");
593
0
    return 0;
594
0
  }
595
0
  if (file->hdr.hdr_size < sizeof(file->hdr)) {
596
    /* @UNSAFE: smaller than we expected - zero out the fields we
597
       shouldn't have filled */
598
0
    memset(PTR_OFFSET(&file->hdr, file->hdr.hdr_size), 0,
599
0
           sizeof(file->hdr) - file->hdr.hdr_size);
600
0
  }
601
602
0
  if (file->hdr.indexid == 0) {
603
    /* corrupted */
604
0
    file->corrupted = TRUE;
605
0
    mail_index_set_error(file->log->index,
606
0
      "Transaction log file %s: marked corrupted",
607
0
      file->filepath);
608
0
    return 0;
609
0
  }
610
0
  if (file->hdr.indexid != file->log->index->indexid) {
611
0
    if (file->log->index->indexid == 0 ||
612
0
        file->log->index->initial_create)
613
0
      ;
614
0
    else if (strcmp(file->filepath, file->log->filepath2) == 0) {
615
      /* .log.2 has a different indexid. This is rather
616
         unlikely situation to notice. We'll handle it by
617
         deleting the .log.2 so a permanently wrong indexid
618
         gets fixed automatically. Since .log.2 doesn't
619
         contain anything critical, it's not so bad even if
620
         the deletion wasn't really necessary. */
621
0
      mail_transaction_log_file_set_corrupted(file,
622
0
        "indexid changed: %u -> %u - deleting",
623
0
        file->log->index->indexid, file->hdr.indexid);
624
0
      return 0;
625
0
    } else {
626
      /* Index was just rebuilt, possibly because the whole
627
         mailbox was recreated under us. Handle it the same
628
         as if the mailbox directory had been deleted. */
629
0
      e_debug(file->log->index->event,
630
0
        "Transaction log file %s indexid changed: %u -> %u",
631
0
        file->filepath,
632
0
        file->log->index->indexid, file->hdr.indexid);
633
0
      file->log->index->index_deleted = TRUE;
634
0
      errno = ENOENT;
635
0
      return -1;
636
0
    }
637
638
    /* creating index file. since transaction log is created
639
       first, use the indexid in it to create the main index
640
       to avoid races. */
641
0
    file->log->index->indexid = file->hdr.indexid;
642
0
  }
643
644
  /* make sure we already don't have a file with the same sequence
645
     opened. it shouldn't happen unless the old log file was
646
     corrupted. */
647
0
  for (f = file->log->files; f != NULL; f = f->next) {
648
0
    if (f->hdr.file_seq == file->hdr.file_seq) {
649
0
      if (strcmp(f->filepath, f->log->head->filepath) != 0) {
650
        /* old "f" is the .log.2 */
651
0
        return mail_transaction_log_file_fail_dupe(f);
652
0
      } else {
653
        /* new "file" is probably the .log.2 */
654
0
        return mail_transaction_log_file_fail_dupe(file);
655
0
      }
656
0
    }
657
0
  }
658
659
0
  file->sync_highest_modseq = file->hdr.initial_modseq;
660
0
  return 1;
661
0
}
662
663
static int
664
mail_transaction_log_file_stat(struct mail_transaction_log_file *file,
665
             bool ignore_estale)
666
0
{
667
0
  struct stat st;
668
669
0
  if (fstat(file->fd, &st) < 0) {
670
0
                if (!ESTALE_FSTAT(errno) || !ignore_estale)
671
0
      log_file_set_syscall_error(file, "fstat()");
672
0
    return -1;
673
0
  }
674
675
0
  file->st_dev = st.st_dev;
676
0
  file->st_ino = st.st_ino;
677
0
  file->last_mtime = st.st_mtime;
678
0
  file->last_size = st.st_size;
679
0
  return 0;
680
0
}
681
682
static bool
683
mail_transaction_log_file_is_dupe(struct mail_transaction_log_file *file)
684
0
{
685
0
  struct mail_transaction_log_file *tmp;
686
687
0
  for (tmp = file->log->files; tmp != NULL; tmp = tmp->next) {
688
0
    if (tmp->st_ino == file->st_ino &&
689
0
        CMP_DEV_T(tmp->st_dev, file->st_dev))
690
0
      return TRUE;
691
0
  }
692
0
  return FALSE;
693
0
}
694
695
static void log_write_ext_hdr_init_data(struct mail_index *index, buffer_t *buf)
696
0
{
697
0
  const struct mail_index_registered_ext *rext;
698
0
  struct mail_transaction_header *hdr;
699
0
  struct mail_transaction_ext_intro *intro;
700
0
  struct mail_transaction_ext_hdr_update *ext_hdr;
701
0
  unsigned int hdr_offset;
702
703
0
  rext = array_idx(&index->extensions, index->set.ext_hdr_init_id);
704
705
  /* introduce the extension */
706
0
  hdr_offset = buf->used;
707
0
  hdr = buffer_append_space_unsafe(buf, sizeof(*hdr));
708
0
  hdr->type = MAIL_TRANSACTION_EXT_INTRO;
709
710
0
  intro = buffer_append_space_unsafe(buf, sizeof(*intro));
711
0
  intro->ext_id = (uint32_t)-1;
712
0
  intro->hdr_size = rext->hdr_size;
713
0
  intro->record_size = rext->record_size;
714
0
  intro->record_align = rext->record_align;
715
0
  intro->name_size = strlen(rext->name);
716
0
  buffer_append(buf, rext->name, intro->name_size);
717
0
  if (buf->used % 4 != 0)
718
0
    buffer_append_zero(buf, 4 - buf->used % 4);
719
720
0
  hdr = buffer_get_space_unsafe(buf, hdr_offset, sizeof(*hdr));
721
0
  hdr->size = mail_index_uint32_to_offset(buf->used - hdr_offset);
722
723
  /* add the extension header data */
724
0
  hdr_offset = buf->used;
725
0
  hdr = buffer_append_space_unsafe(buf, sizeof(*hdr));
726
0
  hdr->type = MAIL_TRANSACTION_EXT_HDR_UPDATE;
727
728
0
  ext_hdr = buffer_append_space_unsafe(buf, sizeof(*ext_hdr));
729
0
  ext_hdr->size = rext->hdr_size;
730
0
  buffer_append(buf, index->set.ext_hdr_init_data, rext->hdr_size);
731
732
0
  hdr = buffer_get_space_unsafe(buf, hdr_offset, sizeof(*hdr));
733
0
  hdr->size = mail_index_uint32_to_offset(buf->used - hdr_offset);
734
0
}
735
736
static int
737
mail_transaction_log_file_create2(struct mail_transaction_log_file *file,
738
          int new_fd, bool reset,
739
          struct dotlock **dotlock)
740
0
{
741
0
  struct mail_index *index = file->log->index;
742
0
  struct stat st;
743
0
  const char *path2;
744
0
  buffer_t *writebuf;
745
0
  int fd, ret;
746
0
  bool rename_existing, need_lock;
747
748
0
  need_lock = file->log->head != NULL && file->log->head->locked;
749
750
0
  if (fcntl(new_fd, F_SETFL, O_APPEND) < 0) {
751
0
    log_file_set_syscall_error(file, "fcntl(O_APPEND)");
752
0
    return -1;
753
0
  }
754
755
0
  if ((index->flags & MAIL_INDEX_OPEN_FLAG_NFS_FLUSH) != 0) {
756
    /* although we check also mtime and file size below, it's done
757
       only to fix broken log files. we don't bother flushing
758
       attribute cache just for that. */
759
0
    nfs_flush_file_handle_cache(file->filepath);
760
0
  }
761
762
  /* log creation is locked now - see if someone already created it.
763
     note that if we're rotating, we need to keep the log locked until
764
     the file has been rewritten. and because fcntl() locks are stupid,
765
     if we go and open()+close() the file and we had it already opened,
766
     its locks are lost. so we use stat() to check if the file has been
767
     recreated, although it almost never is. */
768
0
  if (reset)
769
0
    rename_existing = FALSE;
770
0
  else if (nfs_safe_stat(file->filepath, &st) < 0) {
771
0
    if (errno != ENOENT) {
772
0
      log_file_set_syscall_error(file, "stat()");
773
0
      return -1;
774
0
    }
775
0
    rename_existing = FALSE;
776
0
  } else if (st.st_ino == file->st_ino &&
777
0
       CMP_DEV_T(st.st_dev, file->st_dev) &&
778
       /* inode/dev checks are enough when we're rotating the file,
779
          but not when we're replacing a broken log file */
780
0
       st.st_mtime == file->last_mtime &&
781
0
       (uoff_t)st.st_size == file->last_size) {
782
    /* no-one else recreated the file */
783
0
    rename_existing = TRUE;
784
0
  } else {
785
    /* recreated. use the file if its header is ok */
786
0
    fd = nfs_safe_open(file->filepath, O_RDWR | O_APPEND);
787
0
    if (fd == -1) {
788
0
      if (errno != ENOENT) {
789
0
        log_file_set_syscall_error(file, "open()");
790
0
        return -1;
791
0
      }
792
0
    } else {
793
0
      file->fd = fd;
794
0
      file->last_size = 0;
795
0
      if (mail_transaction_log_file_read_hdr(file,
796
0
                     FALSE) > 0 &&
797
0
          mail_transaction_log_file_stat(file, FALSE) == 0) {
798
        /* yes, it was ok */
799
0
        file_dotlock_delete(dotlock);
800
0
        mail_transaction_log_file_add_to_list(file);
801
0
        return 0;
802
0
      }
803
0
      file->fd = -1;
804
0
      if (close(fd) < 0)
805
0
        log_file_set_syscall_error(file, "close()");
806
0
    }
807
0
    rename_existing = FALSE;
808
0
  }
809
810
0
  if (index->fd == -1 && !rename_existing) {
811
    /* creating the initial index */
812
0
    reset = TRUE;
813
0
  }
814
815
0
  if (mail_transaction_log_init_hdr(file->log, &file->hdr) < 0)
816
0
    return -1;
817
818
0
  if (reset) {
819
    /* don't reset modseqs. if we're resetting due to rebuilding
820
       indexes we'll probably want to keep uidvalidity and in such
821
       cases we really don't want to shrink modseqs. */
822
0
    file->hdr.prev_file_seq = 0;
823
0
    file->hdr.prev_file_offset = 0;
824
0
  }
825
826
0
  writebuf = t_buffer_create(128);
827
0
  buffer_append(writebuf, &file->hdr, sizeof(file->hdr));
828
829
0
  if (index->set.ext_hdr_init_data != NULL && reset)
830
0
    log_write_ext_hdr_init_data(index, writebuf);
831
0
  if (write_full(new_fd, writebuf->data, writebuf->used) < 0) {
832
0
    log_file_set_syscall_error(file, "write_full()");
833
0
    return -1;
834
0
  }
835
836
0
  if (file->log->index->set.fsync_mode == FSYNC_MODE_ALWAYS) {
837
    /* the header isn't important, so don't bother calling
838
       fdatasync() unless it's required */
839
0
    if (fdatasync(new_fd) < 0) {
840
0
      log_file_set_syscall_error(file, "fdatasync()");
841
0
      return -1;
842
0
    }
843
0
  }
844
845
0
  file->fd = new_fd;
846
0
  ret = mail_transaction_log_file_stat(file, FALSE);
847
848
0
  if (need_lock && ret == 0) {
849
    /* we'll need to preserve the lock */
850
0
    if (mail_transaction_log_file_lock(file) < 0)
851
0
      ret = -1;
852
0
  }
853
854
  /* if we return -1 the dotlock deletion code closes the fd */
855
0
  file->fd = -1;
856
0
  if (ret < 0)
857
0
    return -1;
858
859
  /* keep two log files */
860
0
  if (rename_existing) {
861
    /* rename() would be nice and easy way to do this, except then
862
       there's a race condition between the rename and
863
       file_dotlock_replace(). during that time the log file
864
       doesn't exist, which could cause problems. */
865
0
    path2 = t_strconcat(file->filepath, ".2", NULL);
866
0
    if (i_unlink_if_exists(path2) < 0) {
867
      /* try to link() anyway */
868
0
    }
869
0
    if (nfs_safe_link(file->filepath, path2, FALSE) < 0 &&
870
0
        errno != ENOENT && errno != EEXIST) {
871
0
                        mail_index_set_error(index, "link(%s, %s) failed: %m",
872
0
               file->filepath, path2);
873
      /* ignore the error. we don't care that much about the
874
         second log file and we're going to overwrite this
875
         first one. */
876
0
    }
877
    /* NOTE: here's a race condition where both .log and .log.2
878
       point to the same file. our reading code should ignore that
879
       though by comparing the inodes. */
880
0
  }
881
882
0
  if (file_dotlock_replace(dotlock,
883
0
         DOTLOCK_REPLACE_FLAG_DONT_CLOSE_FD) <= 0) {
884
    /* need to unlock to avoid assert-crash in
885
       mail_transaction_log_file_free() */
886
0
    mail_transaction_log_file_unlock(file, "creation failed");
887
0
    return -1;
888
0
  }
889
890
  /* success */
891
0
  file->fd = new_fd;
892
0
  mail_transaction_log_file_add_to_list(file);
893
894
0
  i_assert(!need_lock || file->locked);
895
0
  return 1;
896
0
}
897
898
int mail_transaction_log_file_create(struct mail_transaction_log_file *file,
899
             bool reset)
900
0
{
901
0
  struct mail_index *index = file->log->index;
902
0
  struct dotlock_settings new_dotlock_set;
903
0
  struct dotlock *dotlock;
904
0
  mode_t old_mask;
905
0
  int fd, ret;
906
907
0
  i_assert(!MAIL_INDEX_IS_IN_MEMORY(index));
908
909
0
  if (file->log->index->readonly) {
910
0
    mail_index_set_error(index,
911
0
      "Can't create log file %s: Index is read-only",
912
0
      file->filepath);
913
0
    return -1;
914
0
  }
915
916
0
  if (index->indexid == 0) {
917
0
    mail_index_set_error(index,
918
0
      "Can't create log file %s: Index is marked corrupted",
919
0
      file->filepath);
920
0
    return -1;
921
0
  }
922
923
0
  mail_transaction_log_get_dotlock_set(file->log, &new_dotlock_set);
924
0
  new_dotlock_set.lock_suffix = LOG_NEW_DOTLOCK_SUFFIX;
925
926
  /* With dotlocking we might already have path.lock created, so this
927
     filename has to be different. */
928
0
  old_mask = umask(index->set.mode ^ 0666);
929
0
  fd = file_dotlock_open(&new_dotlock_set, file->filepath, 0, &dotlock);
930
0
  umask(old_mask);
931
932
0
  if (fd == -1) {
933
0
    log_file_set_syscall_error(file, "file_dotlock_open()");
934
0
    return -1;
935
0
  }
936
0
  mail_index_fchown(index, fd, file_dotlock_get_lock_path(dotlock));
937
938
        /* either fd gets used or the dotlock gets deleted and returned fd
939
           is for the existing file */
940
0
  ret = mail_transaction_log_file_create2(file, fd, reset, &dotlock);
941
0
  if (ret < 0) {
942
0
    if (dotlock != NULL)
943
0
      file_dotlock_delete(&dotlock);
944
0
    return -1;
945
0
  }
946
0
  return ret;
947
0
}
948
949
int mail_transaction_log_file_open(struct mail_transaction_log_file *file,
950
           const char **reason_r)
951
0
{
952
0
  struct mail_index *index = file->log->index;
953
0
        unsigned int i;
954
0
  bool ignore_estale;
955
0
  int ret;
956
957
0
        for (i = 0;; i++) {
958
0
    if (!index->readonly) {
959
0
      file->fd = nfs_safe_open(file->filepath,
960
0
             O_RDWR | O_APPEND);
961
0
    } else {
962
0
      file->fd = nfs_safe_open(file->filepath, O_RDONLY);
963
0
    }
964
0
    if (file->fd == -1 && ENOACCESS(errno)) {
965
0
      file->fd = nfs_safe_open(file->filepath, O_RDONLY);
966
0
      index->readonly = TRUE;
967
0
    }
968
0
    if (file->fd == -1) {
969
0
      if (errno == ENOENT) {
970
0
        *reason_r = "File doesn't exist";
971
0
        return 0;
972
0
      }
973
974
0
      log_file_set_syscall_error(file, "open()");
975
0
      *reason_r = t_strdup_printf("open() failed: %m");
976
0
      return -1;
977
0
                }
978
979
0
    ignore_estale = i < MAIL_INDEX_ESTALE_RETRY_COUNT;
980
0
    if (mail_transaction_log_file_stat(file, ignore_estale) < 0)
981
0
      ret = -1;
982
0
    else if (mail_transaction_log_file_is_dupe(file)) {
983
      /* probably our already opened .log file has been
984
         renamed to .log.2 and we're trying to reopen it.
985
         also possible that hit a race condition where .log
986
         and .log.2 are linked. */
987
0
      *reason_r = "File is already open";
988
0
      return 0;
989
0
    } else {
990
0
      ret = mail_transaction_log_file_read_hdr(file,
991
0
                 ignore_estale);
992
0
    }
993
0
    if (ret > 0) {
994
      /* success */
995
0
      break;
996
0
    }
997
998
0
    if (ret == 0) {
999
      /* corrupted */
1000
0
      if (index->readonly) {
1001
        /* don't delete */
1002
0
      } else {
1003
0
        i_unlink_if_exists(file->filepath);
1004
0
      }
1005
0
      *reason_r = "File is corrupted";
1006
0
      return 0;
1007
0
    }
1008
0
    if (errno != ESTALE ||
1009
0
        i == MAIL_INDEX_ESTALE_RETRY_COUNT) {
1010
      /* syscall error */
1011
0
      *reason_r = t_strdup_printf("fstat() failed: %m");
1012
0
      return -1;
1013
0
    }
1014
1015
    /* ESTALE - try again */
1016
0
    buffer_free(&file->buffer);
1017
0
        }
1018
1019
0
  mail_transaction_log_file_add_to_list(file);
1020
0
  return 1;
1021
0
}
1022
1023
static int
1024
log_file_track_mailbox_sync_offset_hdr(struct mail_transaction_log_file *file,
1025
               const void *data, unsigned int trans_size,
1026
               const char **error_r)
1027
0
{
1028
0
  const struct mail_transaction_header_update *u = data;
1029
0
  const struct mail_index_header *ihdr;
1030
0
  const unsigned int size = trans_size - sizeof(struct mail_transaction_header);
1031
0
  const unsigned int offset_pos =
1032
0
    offsetof(struct mail_index_header, log_file_tail_offset);
1033
0
  const unsigned int offset_size = sizeof(ihdr->log_file_tail_offset);
1034
0
  uint32_t tail_offset;
1035
1036
0
  i_assert(offset_size == sizeof(tail_offset));
1037
1038
0
  if (size < sizeof(*u) || size < sizeof(*u) + u->size) {
1039
0
    *error_r = "header update extends beyond record size";
1040
0
    mail_transaction_log_file_set_corrupted(file, "%s", *error_r);
1041
0
    return -1;
1042
0
  }
1043
1044
0
  if (u->offset <= offset_pos &&
1045
0
      u->offset + u->size >= offset_pos + offset_size) {
1046
0
    memcpy(&tail_offset,
1047
0
           CONST_PTR_OFFSET(u + 1, offset_pos - u->offset),
1048
0
           sizeof(tail_offset));
1049
1050
0
    if (tail_offset < file->last_read_hdr_tail_offset) {
1051
      /* ignore shrinking tail offsets */
1052
0
      return 1;
1053
0
    } else if (tail_offset > file->sync_offset + trans_size) {
1054
0
      mail_transaction_log_file_set_corrupted(file,
1055
0
        "log_file_tail_offset %u goes past sync offset %"PRIuUOFF_T,
1056
0
        tail_offset, file->sync_offset + trans_size);
1057
0
    } else {
1058
0
      file->last_read_hdr_tail_offset = tail_offset;
1059
0
      if (tail_offset > file->max_tail_offset)
1060
0
        file->max_tail_offset = tail_offset;
1061
0
      return 1;
1062
0
    }
1063
0
  }
1064
0
  return 0;
1065
0
}
1066
1067
static bool
1068
flag_updates_have_non_internal(const struct mail_transaction_flag_update *u,
1069
             unsigned int count, unsigned int version)
1070
0
{
1071
  /* Hide internal flags from modseqs if the log file's version
1072
     is new enough. This allows upgrading without the modseqs suddenly
1073
     shrinking. */
1074
0
  if (!MAIL_TRANSACTION_LOG_VERSION_HAVE(version, HIDE_INTERNAL_MODSEQS))
1075
0
    return TRUE;
1076
1077
0
  for (unsigned int i = 0; i < count; i++) {
1078
0
    if (!MAIL_TRANSACTION_FLAG_UPDATE_IS_INTERNAL(&u[i]))
1079
0
      return TRUE;
1080
0
  }
1081
0
  return FALSE;
1082
0
}
1083
1084
void mail_transaction_update_modseq(const struct mail_transaction_header *hdr,
1085
            const void *data, uint64_t *cur_modseq,
1086
            unsigned int version)
1087
0
{
1088
0
  uint32_t trans_size;
1089
1090
0
  trans_size = mail_index_offset_to_uint32(hdr->size);
1091
0
  i_assert(trans_size != 0);
1092
1093
0
  if (*cur_modseq != 0) {
1094
    /* tracking modseqs */
1095
0
  } else if ((hdr->type & MAIL_TRANSACTION_TYPE_MASK) ==
1096
0
       MAIL_TRANSACTION_EXT_INTRO) {
1097
    /* modseqs not tracked yet. see if this is a modseq
1098
       extension introduction. */
1099
0
    const struct mail_transaction_ext_intro *intro = data;
1100
0
    const unsigned int modseq_ext_len =
1101
0
      strlen(MAIL_INDEX_MODSEQ_EXT_NAME);
1102
1103
0
    if (intro->name_size == modseq_ext_len &&
1104
0
        memcmp(intro + 1, MAIL_INDEX_MODSEQ_EXT_NAME,
1105
0
         modseq_ext_len) == 0) {
1106
      /* modseq tracking started */
1107
0
      *cur_modseq += 1;
1108
0
    }
1109
0
    return;
1110
0
  } else {
1111
    /* not tracking modseqs */
1112
0
    return;
1113
0
  }
1114
1115
0
  switch (hdr->type & MAIL_TRANSACTION_TYPE_MASK) {
1116
0
  case MAIL_TRANSACTION_EXPUNGE | MAIL_TRANSACTION_EXPUNGE_PROT:
1117
0
  case MAIL_TRANSACTION_EXPUNGE_GUID | MAIL_TRANSACTION_EXPUNGE_PROT:
1118
0
    if ((hdr->type & MAIL_TRANSACTION_EXTERNAL) == 0) {
1119
      /* ignore expunge requests */
1120
0
      break;
1121
0
    }
1122
    /* fall through */
1123
0
  case MAIL_TRANSACTION_APPEND:
1124
0
  case MAIL_TRANSACTION_KEYWORD_UPDATE:
1125
0
  case MAIL_TRANSACTION_KEYWORD_RESET:
1126
0
  case MAIL_TRANSACTION_ATTRIBUTE_UPDATE:
1127
    /* these changes increase modseq */
1128
0
    *cur_modseq += 1;
1129
0
    break;
1130
0
  case MAIL_TRANSACTION_FLAG_UPDATE: {
1131
0
    const struct mail_transaction_flag_update *rec = data;
1132
0
    unsigned int count;
1133
1134
0
    count = (trans_size - sizeof(*hdr)) / sizeof(*rec);
1135
0
    if (flag_updates_have_non_internal(rec, count, version))
1136
0
      *cur_modseq += 1;
1137
0
    break;
1138
0
  }
1139
0
  case MAIL_TRANSACTION_MODSEQ_UPDATE: {
1140
0
    const struct mail_transaction_modseq_update *rec, *end;
1141
1142
0
    end = CONST_PTR_OFFSET(data, trans_size - sizeof(*hdr));
1143
0
    for (rec = data; rec < end; rec++) {
1144
0
      uint64_t modseq = ((uint64_t)rec->modseq_high32 << 32) |
1145
0
        rec->modseq_low32;
1146
0
      if (*cur_modseq < modseq)
1147
0
        *cur_modseq = modseq;
1148
0
    }
1149
0
  }
1150
0
  }
1151
0
}
1152
1153
static int
1154
log_file_track_sync(struct mail_transaction_log_file *file,
1155
        const struct mail_transaction_header *hdr,
1156
        unsigned int trans_size, const char **error_r)
1157
0
{
1158
0
  const void *data = hdr + 1;
1159
0
  int ret;
1160
1161
0
  mail_transaction_update_modseq(hdr, hdr + 1, &file->sync_highest_modseq,
1162
0
    MAIL_TRANSACTION_LOG_HDR_VERSION(&file->hdr));
1163
0
  if ((hdr->type & MAIL_TRANSACTION_EXTERNAL) == 0)
1164
0
    return 1;
1165
1166
  /* external transactions: */
1167
0
  switch (hdr->type & MAIL_TRANSACTION_TYPE_MASK) {
1168
0
  case MAIL_TRANSACTION_HEADER_UPDATE:
1169
    /* see if this updates mailbox_sync_offset */
1170
0
    ret = log_file_track_mailbox_sync_offset_hdr(file, data,
1171
0
                   trans_size, error_r);
1172
0
    if (ret != 0)
1173
0
      return ret < 0 ? -1 : 1;
1174
0
    break;
1175
0
  case MAIL_TRANSACTION_INDEX_DELETED:
1176
0
    if (file->sync_offset < file->index_undeleted_offset ||
1177
0
        file->hdr.file_seq < file->log->index->index_delete_changed_file_seq)
1178
0
      break;
1179
0
    file->log->index->index_deleted = TRUE;
1180
0
    file->log->index->index_delete_requested = FALSE;
1181
0
    file->log->index->index_delete_changed_file_seq = file->hdr.file_seq;
1182
0
    file->index_deleted_offset = file->sync_offset + trans_size;
1183
0
    break;
1184
0
  case MAIL_TRANSACTION_INDEX_UNDELETED:
1185
0
    if (file->sync_offset < file->index_deleted_offset ||
1186
0
        file->hdr.file_seq < file->log->index->index_delete_changed_file_seq)
1187
0
      break;
1188
0
    file->log->index->index_deleted = FALSE;
1189
0
    file->log->index->index_delete_requested = FALSE;
1190
0
    file->log->index->index_delete_changed_file_seq = file->hdr.file_seq;
1191
0
    file->index_undeleted_offset = file->sync_offset + trans_size;
1192
0
    break;
1193
0
  case MAIL_TRANSACTION_BOUNDARY: {
1194
0
    const struct mail_transaction_boundary *boundary =
1195
0
      (const void *)(hdr + 1);
1196
0
    size_t wanted_buffer_size;
1197
1198
0
    wanted_buffer_size = file->sync_offset - file->buffer_offset +
1199
0
      boundary->size;
1200
0
    if (wanted_buffer_size > file->buffer->used) {
1201
      /* the full transaction hasn't been written yet */
1202
0
      return 0;
1203
0
    }
1204
0
    break;
1205
0
  }
1206
0
  }
1207
1208
0
  if (file->max_tail_offset == file->sync_offset) {
1209
    /* external transactions aren't synced to mailbox. we can
1210
       update mailbox sync offset to skip this transaction to
1211
       avoid re-reading it at the next sync. */
1212
0
    file->max_tail_offset += trans_size;
1213
0
  }
1214
0
  return 1;
1215
0
}
1216
1217
/* Returns TRUE if the index has already been synced past the given offset in
1218
   this log file, i.e. the log has lost data that was already used. */
1219
static bool
1220
log_file_is_index_synced_past(const struct mail_transaction_log_file *file,
1221
            uoff_t offset)
1222
0
{
1223
0
  const struct mail_index_map *map = file->log->index->map;
1224
1225
0
  if (map == NULL || map->hdr.log_file_seq != file->hdr.file_seq)
1226
0
    return FALSE;
1227
0
  return map->hdr.log_file_head_offset > offset;
1228
0
}
1229
1230
static int
1231
mail_transaction_log_file_sync(struct mail_transaction_log_file *file,
1232
             bool *retry_r, const char **reason_r)
1233
0
{
1234
0
        const struct mail_transaction_header *hdr;
1235
0
  const void *data;
1236
0
  struct stat st;
1237
0
  size_t size, avail;
1238
0
  uint32_t trans_size = 0;
1239
0
  int ret;
1240
1241
0
  i_assert(file->sync_offset >= file->buffer_offset);
1242
1243
0
  *retry_r = FALSE;
1244
1245
0
  data = buffer_get_data(file->buffer, &size);
1246
0
  if (file->buffer_offset + size < file->sync_offset) {
1247
0
    *reason_r = t_strdup_printf(
1248
0
      "log file shrank (%"PRIuUOFF_T" < %"PRIuUOFF_T")",
1249
0
      file->buffer_offset + (uoff_t)size, file->sync_offset);
1250
0
    mail_transaction_log_file_set_corrupted(file, "%s", *reason_r);
1251
    /* fix the sync_offset to avoid crashes later on */
1252
0
    file->sync_offset = file->buffer_offset + size;
1253
0
    return 0;
1254
0
  }
1255
0
  while (file->sync_offset - file->buffer_offset + sizeof(*hdr) <= size) {
1256
0
    hdr = CONST_PTR_OFFSET(data, file->sync_offset -
1257
0
               file->buffer_offset);
1258
0
    trans_size = mail_index_offset_to_uint32(hdr->size);
1259
0
    if (trans_size == 0) {
1260
      /* unfinished or corrupted */
1261
0
      break;
1262
0
    }
1263
0
    if (trans_size < sizeof(*hdr)) {
1264
0
      *reason_r = t_strdup_printf(
1265
0
        "hdr.size too small (%u)", trans_size);
1266
0
      mail_transaction_log_file_set_corrupted(file, "%s", *reason_r);
1267
0
      return 0;
1268
0
    }
1269
1270
0
    if (file->sync_offset - file->buffer_offset + trans_size > size)
1271
0
      break;
1272
1273
    /* transaction has been fully written */
1274
0
    if ((ret = log_file_track_sync(file, hdr, trans_size, reason_r)) <= 0) {
1275
0
      if (ret < 0)
1276
0
        return 0;
1277
0
      break;
1278
0
    }
1279
1280
0
    file->sync_offset += trans_size;
1281
0
  }
1282
1283
0
  if (file->mmap_base != NULL && !file->locked) {
1284
    /* Now that all the mmaped pages have page faulted, check if
1285
       the file had changed while doing that. Only after the last
1286
       page has faulted, the size returned by fstat() can be
1287
       trusted. Otherwise it might point to a page boundary while
1288
       the next page is still being written.
1289
1290
       Without this check we might see partial transactions,
1291
       sometimes causing "Extension record updated without intro
1292
       prefix" errors. */
1293
0
    if (fstat(file->fd, &st) < 0) {
1294
0
      log_file_set_syscall_error(file, "fstat()");
1295
0
      *reason_r = t_strdup_printf("fstat() failed: %m");
1296
0
      return -1;
1297
0
    }
1298
0
    if ((uoff_t)st.st_size != file->last_size) {
1299
0
      file->last_size = st.st_size;
1300
0
      *retry_r = TRUE;
1301
0
      *reason_r = "File size changed - retrying";
1302
0
      return 0;
1303
0
    }
1304
0
  }
1305
1306
0
  avail = file->sync_offset - file->buffer_offset;
1307
0
  if (avail != size) {
1308
    /* There's more data than we could sync at the moment. If the
1309
       last record's size wasn't valid, we can't know if it will
1310
       be updated unless we've locked the log. */
1311
0
    if (file->locked &&
1312
0
        !log_file_is_index_synced_past(file, file->sync_offset)) {
1313
      /* A previous write to the log wasn't fully written.
1314
         The log isn't corrupted - the partially written
1315
         transaction is simply ignored - but it can't be
1316
         appended to anymore. */
1317
0
      mail_transaction_log_file_set_garbage_at_eof(file,
1318
0
        "Partially written transaction at "
1319
0
        "offset %"PRIuUOFF_T, file->sync_offset);
1320
0
    } else if (file->locked) {
1321
      /* The index has already been synced past this point,
1322
         so the log has really lost data that was used. */
1323
0
      *reason_r = "Unexpected garbage at EOF";
1324
0
      mail_transaction_log_file_set_corrupted(file, "%s",
1325
0
                *reason_r);
1326
0
      return 0;
1327
0
    } else {
1328
      /* The size field will be updated soon */
1329
0
      mail_index_flush_read_cache(file->log->index,
1330
0
                file->filepath,
1331
0
                file->fd, file->locked);
1332
0
    }
1333
0
  }
1334
1335
0
  if (file->next != NULL &&
1336
0
      file->hdr.file_seq == file->next->hdr.prev_file_seq &&
1337
0
      file->next->hdr.prev_file_offset != file->sync_offset) {
1338
0
    *reason_r = t_strdup_printf(
1339
0
      "Invalid transaction log size "
1340
0
      "(%"PRIuUOFF_T" vs %u): %s", file->sync_offset,
1341
0
      file->log->head->hdr.prev_file_offset, file->filepath);
1342
0
    mail_transaction_log_file_set_corrupted(file, "%s", *reason_r);
1343
0
    return 0;
1344
0
  }
1345
1346
0
  return 1;
1347
0
}
1348
1349
static int
1350
mail_transaction_log_file_insert_read(struct mail_transaction_log_file *file,
1351
              uoff_t offset, const char **reason_r)
1352
0
{
1353
0
  void *data;
1354
0
  size_t size;
1355
0
  ssize_t ret;
1356
1357
0
  size = file->buffer_offset - offset;
1358
0
  buffer_copy(file->buffer, size, file->buffer, 0, SIZE_MAX);
1359
1360
0
  data = buffer_get_space_unsafe(file->buffer, 0, size);
1361
0
  ret = pread_full(file->fd, data, size, offset);
1362
0
  if (ret > 0) {
1363
    /* success */
1364
0
    file->buffer_offset -= size;
1365
0
    return 1;
1366
0
  }
1367
1368
  /* failure. don't leave ourself to inconsistent state */
1369
0
  buffer_copy(file->buffer, 0, file->buffer, size, SIZE_MAX);
1370
0
  buffer_set_used_size(file->buffer, file->buffer->used - size);
1371
1372
0
  if (ret == 0) {
1373
0
    *reason_r = "file shrank unexpectedly";
1374
0
    mail_transaction_log_file_set_corrupted(file, "%s", *reason_r);
1375
0
    return 0;
1376
0
  } else if (errno == ESTALE) {
1377
    /* log file was deleted in NFS server, fail silently */
1378
0
    *reason_r = t_strdup_printf("read() failed: %m");
1379
0
    return 0;
1380
0
  } else {
1381
0
    log_file_set_syscall_error(file, "pread()");
1382
0
    *reason_r = t_strdup_printf("read() failed: %m");
1383
0
    return -1;
1384
0
  }
1385
0
}
1386
1387
static int
1388
mail_transaction_log_file_read_more(struct mail_transaction_log_file *file,
1389
            const char **reason_r)
1390
0
{
1391
0
  void *data;
1392
0
  size_t size;
1393
0
  uint32_t read_offset;
1394
0
  ssize_t ret;
1395
1396
0
  read_offset = file->buffer_offset + file->buffer->used;
1397
1398
0
  do {
1399
0
    data = buffer_append_space_unsafe(file->buffer, LOG_PREFETCH);
1400
0
    ret = pread(file->fd, data, LOG_PREFETCH, read_offset);
1401
0
    if (ret > 0)
1402
0
      read_offset += ret;
1403
1404
0
    size = read_offset - file->buffer_offset;
1405
0
    buffer_set_used_size(file->buffer, size);
1406
0
  } while (ret > 0 || (ret < 0 && errno == EINTR));
1407
1408
0
  file->last_size = read_offset;
1409
1410
0
  if (ret < 0) {
1411
0
    *reason_r = t_strdup_printf("pread() failed: %m");
1412
0
    if (errno == ESTALE) {
1413
      /* log file was deleted in NFS server, fail silently */
1414
0
      return 0;
1415
0
    }
1416
0
    log_file_set_syscall_error(file, "pread()");
1417
0
    return -1;
1418
0
  }
1419
0
  return 1;
1420
0
}
1421
1422
static bool
1423
mail_transaction_log_file_need_nfs_flush(struct mail_transaction_log_file *file)
1424
0
{
1425
0
  const struct mail_index_header *hdr = &file->log->index->map->hdr;
1426
0
  uoff_t max_offset = file->last_size;
1427
1428
0
  if (file->next != NULL &&
1429
0
      file->hdr.file_seq == file->next->hdr.prev_file_seq &&
1430
0
      file->next->hdr.prev_file_offset != max_offset) {
1431
    /* we already have a newer log file which says that we haven't
1432
       synced the entire file. */
1433
0
    return TRUE;
1434
0
  }
1435
1436
0
  if (file->hdr.file_seq == hdr->log_file_seq &&
1437
0
      max_offset < hdr->log_file_head_offset)
1438
0
    return TRUE;
1439
1440
0
  return FALSE;
1441
0
}
1442
1443
static int
1444
mail_transaction_log_file_read(struct mail_transaction_log_file *file,
1445
             uoff_t start_offset, bool nfs_flush,
1446
             const char **reason_r)
1447
0
{
1448
0
  bool retry;
1449
0
  int ret;
1450
1451
0
  i_assert(file->mmap_base == NULL);
1452
1453
  /* NFS: if file isn't locked, we're optimistic that we can read enough
1454
     data without flushing attribute cache. if after reading we notice
1455
     that we really should have read more, flush the cache and try again.
1456
     if file is locked, the attribute cache was already flushed when
1457
     refreshing the log. */
1458
0
  if (nfs_flush &&
1459
0
      (file->log->index->flags & MAIL_INDEX_OPEN_FLAG_NFS_FLUSH) != 0) {
1460
0
    if (!file->locked)
1461
0
      nfs_flush_attr_cache_unlocked(file->filepath);
1462
0
    else
1463
0
      nfs_flush_attr_cache_fd_locked(file->filepath, file->fd);
1464
0
  }
1465
1466
0
  if (file->buffer != NULL && file->buffer_offset > start_offset) {
1467
    /* we have to insert missing data to beginning of buffer */
1468
0
    ret = mail_transaction_log_file_insert_read(file, start_offset, reason_r);
1469
0
    if (ret <= 0)
1470
0
      return ret;
1471
0
  }
1472
1473
0
  if (file->buffer == NULL) {
1474
0
    file->buffer =
1475
0
      buffer_create_dynamic(default_pool, LOG_PREFETCH);
1476
0
    file->buffer_offset = start_offset;
1477
0
  }
1478
1479
0
  if ((ret = mail_transaction_log_file_read_more(file, reason_r)) <= 0)
1480
0
    ;
1481
0
  else if (!nfs_flush &&
1482
0
     (file->log->index->flags & MAIL_INDEX_OPEN_FLAG_NFS_FLUSH) != 0 &&
1483
0
     mail_transaction_log_file_need_nfs_flush(file)) {
1484
    /* we didn't read enough data. flush and try again. */
1485
0
    return mail_transaction_log_file_read(file, start_offset, TRUE, reason_r);
1486
0
  } else if ((ret = mail_transaction_log_file_sync(file, &retry, reason_r)) == 0) {
1487
0
    i_assert(!retry); /* retry happens only with mmap */
1488
0
  }
1489
0
  i_assert(file->sync_offset >= file->buffer_offset);
1490
0
  buffer_set_used_size(file->buffer,
1491
0
           file->sync_offset - file->buffer_offset);
1492
0
  return ret;
1493
0
}
1494
1495
static bool
1496
log_file_map_check_offsets(struct mail_transaction_log_file *file,
1497
         uoff_t start_offset, uoff_t end_offset,
1498
         const char **reason_r)
1499
0
{
1500
0
  struct stat st, st2;
1501
1502
0
  if (start_offset > file->sync_offset) {
1503
    /* broken start offset */
1504
0
    if (MAIL_TRANSACTION_LOG_FILE_IN_MEMORY(file)) {
1505
0
      *reason_r = t_strdup_printf(
1506
0
        "%s: start_offset (%"PRIuUOFF_T") > "
1507
0
        "current sync_offset (%"PRIuUOFF_T")",
1508
0
        file->filepath, start_offset, file->sync_offset);
1509
0
      return FALSE;
1510
0
    }
1511
1512
0
    if (fstat(file->fd, &st) < 0) {
1513
0
      log_file_set_syscall_error(file, "fstat()");
1514
0
      st.st_size = -1;
1515
0
    }
1516
0
    *reason_r = t_strdup_printf(
1517
0
      "%s: start_offset (%"PRIuUOFF_T") > "
1518
0
      "current sync_offset (%"PRIuUOFF_T"), file size=%"PRIuUOFF_T,
1519
0
      file->filepath, start_offset, file->sync_offset,
1520
0
      st.st_size);
1521
0
    if (stat(file->filepath, &st2) == 0) {
1522
0
      if (st.st_ino != st2.st_ino) {
1523
0
        *reason_r = t_strdup_printf(
1524
0
          "%s, file unexpectedly replaced", *reason_r);
1525
0
      }
1526
0
    } else if (errno == ENOENT) {
1527
0
      *reason_r = t_strdup_printf(
1528
0
        "%s, file unexpectedly deleted", *reason_r);
1529
0
    } else {
1530
0
      log_file_set_syscall_error(file, "stat()");
1531
0
    }
1532
0
    return FALSE;
1533
0
  }
1534
0
  if (end_offset != UOFF_T_MAX && end_offset > file->sync_offset) {
1535
0
    *reason_r = t_strdup_printf(
1536
0
      "%s: end_offset (%"PRIuUOFF_T") > "
1537
0
      "current sync_offset (%"PRIuUOFF_T")",
1538
0
      file->filepath, start_offset, file->sync_offset);
1539
0
    return FALSE;
1540
0
  }
1541
1542
0
  return TRUE;
1543
0
}
1544
1545
static int
1546
mail_transaction_log_file_mmap(struct mail_transaction_log_file *file,
1547
             const char **reason_r)
1548
0
{
1549
  /* we may have switched to mmaping */
1550
0
  buffer_free(&file->buffer);
1551
1552
0
  file->mmap_size = file->last_size;
1553
0
  file->mmap_base = mmap(NULL, file->mmap_size, PROT_READ, MAP_SHARED,
1554
0
             file->fd, 0);
1555
0
  if (file->mmap_base == MAP_FAILED) {
1556
0
    file->mmap_base = NULL;
1557
0
    if (ioloop_time != file->last_mmap_error_time) {
1558
0
      file->last_mmap_error_time = ioloop_time;
1559
0
      log_file_set_syscall_error(file, t_strdup_printf(
1560
0
        "mmap(size=%zu)", file->mmap_size));
1561
0
    }
1562
0
    *reason_r = t_strdup_printf("mmap(size=%zu) failed: %m",
1563
0
              file->mmap_size);
1564
0
    file->mmap_size = 0;
1565
0
    return -1;
1566
0
  }
1567
1568
0
  if (file->mmap_size > mmap_get_page_size()) {
1569
0
    errno = posix_madvise(file->mmap_base, file->mmap_size,
1570
0
          POSIX_MADV_SEQUENTIAL);
1571
0
    if (errno != 0)
1572
0
      log_file_set_syscall_error(file, "posix_madvise()");
1573
0
  }
1574
1575
0
  buffer_create_from_const_data(&file->mmap_buffer,
1576
0
              file->mmap_base, file->mmap_size);
1577
0
  file->buffer = &file->mmap_buffer;
1578
0
  file->buffer_offset = 0;
1579
0
  return 0;
1580
0
}
1581
1582
static void
1583
mail_transaction_log_file_munmap(struct mail_transaction_log_file *file)
1584
0
{
1585
0
  if (file->mmap_base == NULL)
1586
0
    return;
1587
1588
0
  i_assert(file->buffer != NULL);
1589
0
  if (munmap(file->mmap_base, file->mmap_size) < 0)
1590
0
    log_file_set_syscall_error(file, "munmap()");
1591
0
  file->mmap_base = NULL;
1592
0
  file->mmap_size = 0;
1593
0
  buffer_free(&file->buffer);
1594
0
}
1595
1596
static int
1597
mail_transaction_log_file_map_mmap(struct mail_transaction_log_file *file,
1598
           uoff_t start_offset, const char **reason_r)
1599
0
{
1600
0
  struct stat st;
1601
0
  bool retry;
1602
0
  int ret;
1603
1604
  /* we are going to mmap() this file, but it's not necessarily
1605
     mmaped currently. */
1606
0
  i_assert(file->buffer_offset == 0 || file->mmap_base == NULL);
1607
0
  i_assert(file->mmap_size == 0 || file->mmap_base != NULL);
1608
1609
0
  if (fstat(file->fd, &st) < 0) {
1610
0
    log_file_set_syscall_error(file, "fstat()");
1611
0
    *reason_r = t_strdup_printf("fstat() failed: %m");
1612
0
    return -1;
1613
0
  }
1614
0
  file->last_size = st.st_size;
1615
1616
0
  if ((uoff_t)st.st_size < file->sync_offset) {
1617
0
    *reason_r = t_strdup_printf(
1618
0
      "file size shrank (%"PRIuUOFF_T" < %"PRIuUOFF_T")",
1619
0
      (uoff_t)st.st_size, file->sync_offset);
1620
0
    mail_transaction_log_file_set_corrupted(file, "%s", *reason_r);
1621
0
    return 0;
1622
0
  }
1623
1624
0
  if (file->buffer != NULL && file->buffer_offset <= start_offset &&
1625
0
      (uoff_t)st.st_size == file->buffer_offset + file->buffer->used) {
1626
    /* we already have the whole file mapped */
1627
0
    if ((ret = mail_transaction_log_file_sync(file, &retry, reason_r)) != 0 ||
1628
0
        !retry)
1629
0
      return ret;
1630
    /* size changed, re-mmap */
1631
0
  }
1632
1633
0
  do {
1634
0
    mail_transaction_log_file_munmap(file);
1635
1636
0
    if (file->last_size - start_offset < mmap_get_page_size()) {
1637
      /* just reading the file is probably faster */
1638
0
      return mail_transaction_log_file_read(file,
1639
0
                    start_offset,
1640
0
                    FALSE, reason_r);
1641
0
    }
1642
1643
0
    if (mail_transaction_log_file_mmap(file, reason_r) < 0)
1644
0
      return -1;
1645
0
    ret = mail_transaction_log_file_sync(file, &retry, reason_r);
1646
0
  } while (retry);
1647
1648
0
  return ret;
1649
0
}
1650
1651
int mail_transaction_log_file_map(struct mail_transaction_log_file *file,
1652
          uoff_t start_offset, uoff_t end_offset,
1653
          const char **reason_r)
1654
0
{
1655
0
  uoff_t map_start_offset = start_offset;
1656
0
  size_t size;
1657
0
  int ret;
1658
1659
0
  if (file->hdr.indexid == 0) {
1660
    /* corrupted */
1661
0
    *reason_r = "corrupted, indexid=0";
1662
0
    return 0;
1663
0
  }
1664
1665
0
  i_assert(start_offset >= file->hdr.hdr_size);
1666
0
  i_assert(start_offset <= end_offset);
1667
0
  i_assert(file->buffer == NULL || file->mmap_base != NULL ||
1668
0
     file->sync_offset >= file->buffer_offset + file->buffer->used);
1669
1670
0
  if (file->locked_sync_offset_updated && file == file->log->head &&
1671
0
      end_offset == UOFF_T_MAX) {
1672
    /* we're not interested of going further than sync_offset */
1673
0
    if (!log_file_map_check_offsets(file, start_offset,
1674
0
            end_offset, reason_r))
1675
0
      return 0;
1676
0
    i_assert(start_offset <= file->sync_offset);
1677
0
    end_offset = file->sync_offset;
1678
0
  }
1679
1680
0
  if (file->buffer != NULL && file->buffer_offset <= start_offset) {
1681
    /* see if we already have it */
1682
0
    size = file->buffer->used;
1683
0
    if (file->buffer_offset + size >= end_offset)
1684
0
      return 1;
1685
0
  }
1686
1687
0
  if (file->locked) {
1688
    /* set this only when we've synced to end of file while locked
1689
       (either end_offset=UOFF_T_MAX or we had to read anyway) */
1690
0
    file->locked_sync_offset_updated = TRUE;
1691
0
  }
1692
1693
0
  if (MAIL_TRANSACTION_LOG_FILE_IN_MEMORY(file)) {
1694
0
    if (start_offset < file->buffer_offset || file->buffer == NULL) {
1695
      /* we had moved the log to memory but failed to read
1696
         the beginning of the log file */
1697
0
      *reason_r = "Beginning of the log isn't available";
1698
0
      return 0;
1699
0
    }
1700
0
    return log_file_map_check_offsets(file, start_offset,
1701
0
              end_offset, reason_r) ? 1 : 0;
1702
0
  }
1703
1704
0
  if (start_offset > file->sync_offset)
1705
0
    mail_transaction_log_file_skip_to_head(file);
1706
0
  if (start_offset > file->sync_offset) {
1707
    /* although we could just skip over the unwanted data, we have
1708
       to sync everything so that modseqs are calculated
1709
       correctly */
1710
0
    map_start_offset = file->sync_offset;
1711
0
  }
1712
1713
0
  if ((file->log->index->flags & MAIL_INDEX_OPEN_FLAG_MMAP_DISABLE) == 0)
1714
0
    ret = mail_transaction_log_file_map_mmap(file, map_start_offset, reason_r);
1715
0
  else {
1716
0
    mail_transaction_log_file_munmap(file);
1717
0
    ret = mail_transaction_log_file_read(file, map_start_offset, FALSE, reason_r);
1718
0
  }
1719
1720
0
  i_assert(file->buffer == NULL || file->mmap_base != NULL ||
1721
0
     file->sync_offset >= file->buffer_offset + file->buffer->used);
1722
0
  if (ret <= 0)
1723
0
    return ret;
1724
1725
0
  i_assert(file->buffer != NULL);
1726
0
  return log_file_map_check_offsets(file, start_offset, end_offset,
1727
0
            reason_r) ? 1 : 0;
1728
0
}
1729
1730
int mail_transaction_log_file_move_to_memory(struct mail_transaction_log_file *file)
1731
0
{
1732
0
  const char *error;
1733
0
  buffer_t *buf;
1734
0
  int ret = 0;
1735
1736
0
  if (MAIL_TRANSACTION_LOG_FILE_IN_MEMORY(file))
1737
0
    return 0;
1738
1739
0
  if (file->mmap_base != NULL) {
1740
    /* just copy to memory */
1741
0
    i_assert(file->buffer_offset == 0);
1742
1743
0
    buf = buffer_create_dynamic(default_pool, file->mmap_size);
1744
0
    buffer_append(buf, file->mmap_base, file->mmap_size);
1745
0
    buffer_free(&file->buffer);
1746
0
    file->buffer = buf;
1747
1748
    /* and lose the mmap */
1749
0
    if (munmap(file->mmap_base, file->mmap_size) < 0)
1750
0
      log_file_set_syscall_error(file, "munmap()");
1751
0
    file->mmap_base = NULL;
1752
0
  } else if (file->buffer_offset != 0) {
1753
    /* we don't have the full log in the memory. read it. */
1754
0
    ret = mail_transaction_log_file_read(file, 0, FALSE, &error);
1755
0
    if (ret <= 0) {
1756
0
      mail_index_set_error(file->log->index,
1757
0
        "%s: Failed to read into memory: %s", file->filepath, error);
1758
0
    }
1759
0
  }
1760
0
  file->last_size = 0;
1761
1762
0
  if (close(file->fd) < 0)
1763
0
    log_file_set_syscall_error(file, "close()");
1764
0
  file->fd = -1;
1765
1766
0
  i_free(file->filepath);
1767
0
  file->filepath = i_strdup(file->log->filepath);
1768
0
  return ret < 0 ? -1 : 0;
1769
0
}