1 /* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */
2
3 #include "lib.h"
4 #include "nfs-workarounds.h"
5 #include "read-full.h"
6 #include "write-full.h"
7 #include "ostream.h"
8 #include "mail-index-private.h"
9 #include "mail-transaction-log-private.h"
10
11 #include <stdio.h>
12
13 #define MAIL_INDEX_MIN_UPDATE_SIZE 1024
14 /* if we're updating >= count-n messages, recreate the index */
15 #define MAIL_INDEX_MAX_OVERWRITE_NEG_SEQ_COUNT 10
16
mail_index_create_backup(struct mail_index * index)17 static int mail_index_create_backup(struct mail_index *index)
18 {
19 const char *backup_path, *tmp_backup_path;
20 int ret;
21
22 if (index->fd != -1) {
23 /* we very much want to avoid creating a backup file that
24 hasn't been written to disk yet */
25 if (fdatasync(index->fd) < 0) {
26 mail_index_set_error(index, "fdatasync(%s) failed: %m",
27 index->filepath);
28 return -1;
29 }
30 }
31
32 backup_path = t_strconcat(index->filepath, ".backup", NULL);
33 tmp_backup_path = t_strconcat(backup_path, ".tmp", NULL);
34 ret = link(index->filepath, tmp_backup_path);
35 if (ret < 0 && errno == EEXIST) {
36 if (unlink(tmp_backup_path) < 0 && errno != ENOENT) {
37 mail_index_set_error(index, "unlink(%s) failed: %m",
38 tmp_backup_path);
39 return -1;
40 }
41 ret = link(index->filepath, tmp_backup_path);
42 }
43 if (ret < 0) {
44 if (errno == ENOENT) {
45 /* no dovecot.index file, ignore */
46 return 0;
47 }
48 mail_index_set_error(index, "link(%s, %s) failed: %m",
49 index->filepath, tmp_backup_path);
50 return -1;
51 }
52
53 if (rename(tmp_backup_path, backup_path) < 0) {
54 mail_index_set_error(index, "rename(%s, %s) failed: %m",
55 tmp_backup_path, backup_path);
56 return -1;
57 }
58 return 0;
59 }
60
mail_index_recreate(struct mail_index * index)61 static int mail_index_recreate(struct mail_index *index)
62 {
63 struct mail_index_map *map = index->map;
64 struct ostream *output;
65 unsigned int base_size;
66 const char *path;
67 int ret = 0, fd;
68
69 i_assert(!MAIL_INDEX_IS_IN_MEMORY(index));
70 i_assert(map->hdr.indexid == index->indexid);
71 i_assert((map->hdr.flags & MAIL_INDEX_HDR_FLAG_CORRUPTED) == 0);
72 i_assert(index->indexid != 0);
73
74 fd = mail_index_create_tmp_file(index, index->filepath, &path);
75 if (fd == -1)
76 return -1;
77
78 output = o_stream_create_fd_file(fd, 0, FALSE);
79 o_stream_cork(output);
80
81 base_size = I_MIN(map->hdr.base_header_size, sizeof(map->hdr));
82 o_stream_nsend(output, &map->hdr, base_size);
83 o_stream_nsend(output, MAIL_INDEX_MAP_HDR_OFFSET(map, base_size),
84 map->hdr.header_size - base_size);
85 o_stream_nsend(output, map->rec_map->records,
86 map->rec_map->records_count * map->hdr.record_size);
87 if (o_stream_finish(output) < 0) {
88 mail_index_file_set_syscall_error(index, path, "write()");
89 ret = -1;
90 }
91 o_stream_destroy(&output);
92
93 if (ret == 0 && index->set.fsync_mode != FSYNC_MODE_NEVER) {
94 if (fdatasync(fd) < 0) {
95 mail_index_file_set_syscall_error(index, path,
96 "fdatasync()");
97 ret = -1;
98 }
99 }
100
101 if (close(fd) < 0) {
102 mail_index_file_set_syscall_error(index, path, "close()");
103 ret = -1;
104 }
105
106 if ((index->flags & MAIL_INDEX_OPEN_FLAG_KEEP_BACKUPS) != 0)
107 (void)mail_index_create_backup(index);
108
109 if (ret == 0 && rename(path, index->filepath) < 0) {
110 mail_index_set_error(index, "rename(%s, %s) failed: %m",
111 path, index->filepath);
112 ret = -1;
113 }
114
115 if (ret < 0)
116 i_unlink(path);
117 return ret;
118 }
119
mail_index_should_recreate(struct mail_index * index)120 static bool mail_index_should_recreate(struct mail_index *index)
121 {
122 struct stat st1, st2;
123
124 if (nfs_safe_stat(index->filepath, &st1) < 0) {
125 if (errno != ENOENT) {
126 mail_index_set_syscall_error(index, "stat()");
127 return FALSE;
128 } else if (index->fd == -1) {
129 /* main index hasn't been created yet */
130 return TRUE;
131 } else {
132 /* mailbox was just deleted? don't log an error */
133 return FALSE;
134 }
135 }
136 if (index->fd == -1) {
137 /* main index was just created by another process */
138 return FALSE;
139 }
140 if (fstat(index->fd, &st2) < 0) {
141 if (!ESTALE_FSTAT(errno))
142 mail_index_set_syscall_error(index, "fstat()");
143 return FALSE;
144 }
145 if (st1.st_ino != st2.st_ino ||
146 !CMP_DEV_T(st1.st_dev, st2.st_dev)) {
147 /* Index has already been recreated since we last read it.
148 We can't trust our decisions about whether to recreate it. */
149 return FALSE;
150 }
151 return TRUE;
152 }
153
mail_index_write(struct mail_index * index,bool want_rotate,const char * reason)154 void mail_index_write(struct mail_index *index, bool want_rotate,
155 const char *reason)
156 {
157 struct mail_index_header *hdr = &index->map->hdr;
158 bool rotated = FALSE;
159
160 i_assert(index->log_sync_locked);
161
162 if (index->readonly)
163 return;
164
165 /* rotate the .log before writing index, so the index will point to
166 the latest log. Note that it's the caller's responsibility to make
167 sure that the .log can be safely rotated (i.e. everything has been
168 synced). */
169 if (want_rotate) {
170 if (mail_transaction_log_rotate(index->log, FALSE) == 0) {
171 struct mail_transaction_log_file *file =
172 index->log->head;
173 /* Log rotation refreshes the index, which may cause the
174 map to change. Because we're locked, it's not
175 supposed to happen and will likely lead to an
176 assert-crash below, but we still need to make sure
177 we're using the latest map to do the checks. */
178 hdr = &index->map->hdr;
179 i_assert(file->hdr.prev_file_seq == hdr->log_file_seq);
180 i_assert(file->hdr.prev_file_offset == hdr->log_file_head_offset);
181 hdr->log_file_seq = file->hdr.file_seq;
182 hdr->log_file_head_offset =
183 hdr->log_file_tail_offset = file->hdr.hdr_size;
184 /* Assume .log.2 was created successfully. If it
185 wasn't, it just causes an extra stat() and gets
186 fixed later on. */
187 hdr->log2_rotate_time = ioloop_time;
188 rotated = TRUE;
189 }
190 }
191
192 if (MAIL_INDEX_IS_IN_MEMORY(index))
193 ;
194 else if (!rotated && !mail_index_should_recreate(index)) {
195 /* make sure we don't keep getting back in here */
196 index->reopen_main_index = TRUE;
197 } else {
198 if (mail_index_recreate(index) < 0) {
199 (void)mail_index_move_to_memory(index);
200 return;
201 }
202 event_set_name(index->event, "mail_index_recreated");
203 e_debug(index->event, "Recreated %s (file_seq=%u) because: %s",
204 index->filepath, hdr->log_file_seq, reason);
205 }
206
207 index->main_index_hdr_log_file_seq = hdr->log_file_seq;
208 index->main_index_hdr_log_file_tail_offset = hdr->log_file_tail_offset;
209 }
210