1 /* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */
2
3 #include "lib.h"
4 #include "ioloop.h"
5 #include "array.h"
6 #include "hook-build.h"
7 #include "bsearch-insert-pos.h"
8 #include "llist.h"
9 #include "mail-index-private.h"
10 #include "mail-transaction-log-private.h"
11 #include "mail-index-transaction-private.h"
12
13 static ARRAY(hook_mail_index_transaction_created_t *)
14 hook_mail_index_transaction_created;
15
mail_index_transaction_hook_register(hook_mail_index_transaction_created_t * hook)16 void mail_index_transaction_hook_register(hook_mail_index_transaction_created_t *hook)
17 {
18 if (!array_is_created(&hook_mail_index_transaction_created))
19 i_array_init(&hook_mail_index_transaction_created, 8);
20 array_push_back(&hook_mail_index_transaction_created, &hook);
21 }
22
mail_index_transaction_hook_unregister(hook_mail_index_transaction_created_t * hook)23 void mail_index_transaction_hook_unregister(hook_mail_index_transaction_created_t *hook)
24 {
25 unsigned int idx;
26 bool found = FALSE;
27
28 i_assert(array_is_created(&hook_mail_index_transaction_created));
29 for(idx = 0; idx < array_count(&hook_mail_index_transaction_created); idx++) {
30 hook_mail_index_transaction_created_t *arr_hook =
31 array_idx_elem(&hook_mail_index_transaction_created, idx);
32 if (arr_hook == hook) {
33 array_delete(&hook_mail_index_transaction_created, idx, 1);
34 found = TRUE;
35 break;
36 }
37 }
38 i_assert(found == TRUE);
39 if (array_count(&hook_mail_index_transaction_created) == 0)
40 array_free(&hook_mail_index_transaction_created);
41 }
42
43
44 struct mail_index_view *
mail_index_transaction_get_view(struct mail_index_transaction * t)45 mail_index_transaction_get_view(struct mail_index_transaction *t)
46 {
47 return t->view;
48 }
49
mail_index_transaction_is_expunged(struct mail_index_transaction * t,uint32_t seq)50 bool mail_index_transaction_is_expunged(struct mail_index_transaction *t,
51 uint32_t seq)
52 {
53 struct mail_transaction_expunge_guid key;
54
55 if (!array_is_created(&t->expunges))
56 return FALSE;
57
58 if (t->expunges_nonsorted)
59 mail_index_transaction_sort_expunges(t);
60
61 key.uid = seq;
62 return array_bsearch(&t->expunges, &key,
63 mail_transaction_expunge_guid_cmp) != NULL;
64 }
65
mail_index_transaction_ref(struct mail_index_transaction * t)66 void mail_index_transaction_ref(struct mail_index_transaction *t)
67 {
68 t->refcount++;
69 }
70
mail_index_transaction_unref(struct mail_index_transaction ** _t)71 void mail_index_transaction_unref(struct mail_index_transaction **_t)
72 {
73 struct mail_index_transaction *t = *_t;
74
75 *_t = NULL;
76 if (--t->refcount > 0)
77 return;
78
79 mail_index_transaction_reset_v(t);
80
81 DLLIST_REMOVE(&t->view->transactions_list, t);
82 array_free(&t->module_contexts);
83 if (t->latest_view != NULL)
84 mail_index_view_close(&t->latest_view);
85 mail_index_view_close(&t->view);
86 i_free(t);
87 }
88
mail_index_transaction_get_next_uid(struct mail_index_transaction * t)89 uint32_t mail_index_transaction_get_next_uid(struct mail_index_transaction *t)
90 {
91 const struct mail_index_header *head_hdr, *hdr;
92 unsigned int offset;
93 uint32_t next_uid;
94
95 head_hdr = &t->view->index->map->hdr;
96 hdr = &t->view->map->hdr;
97 next_uid = t->reset || head_hdr->uid_validity != hdr->uid_validity ?
98 1 : hdr->next_uid;
99 if (array_is_created(&t->appends) && t->highest_append_uid != 0) {
100 /* get next_uid from appends if they have UIDs. it's possible
101 that some appends have too low UIDs, they'll be caught
102 later. */
103 if (next_uid <= t->highest_append_uid)
104 next_uid = t->highest_append_uid + 1;
105 }
106
107 /* see if it's been updated in pre/post header changes */
108 offset = offsetof(struct mail_index_header, next_uid);
109 if (t->post_hdr_mask[offset] != 0) {
110 hdr = (const void *)t->post_hdr_change;
111 if (hdr->next_uid > next_uid)
112 next_uid = hdr->next_uid;
113 }
114 if (t->pre_hdr_mask[offset] != 0) {
115 hdr = (const void *)t->pre_hdr_change;
116 if (hdr->next_uid > next_uid)
117 next_uid = hdr->next_uid;
118 }
119 return next_uid;
120 }
121
mail_index_transaction_lookup_latest_keywords(struct mail_index_transaction * t,uint32_t seq,ARRAY_TYPE (keyword_indexes)* keywords)122 void mail_index_transaction_lookup_latest_keywords(struct mail_index_transaction *t,
123 uint32_t seq,
124 ARRAY_TYPE(keyword_indexes) *keywords)
125 {
126 uint32_t uid, latest_seq;
127
128 /* seq points to the transaction's primary view */
129 mail_index_lookup_uid(t->view, seq, &uid);
130
131 /* get the latest keywords from the updated index, or fallback to the
132 primary view if the message is already expunged */
133 if (t->latest_view == NULL) {
134 mail_index_refresh(t->view->index);
135 t->latest_view = mail_index_view_open(t->view->index);
136 }
137 if (mail_index_lookup_seq(t->latest_view, uid, &latest_seq))
138 mail_index_lookup_keywords(t->latest_view, latest_seq, keywords);
139 else
140 mail_index_lookup_keywords(t->view, seq, keywords);
141 }
142
143 static int
mail_transaction_log_file_refresh(struct mail_index_transaction * t,struct mail_transaction_log_append_ctx * ctx)144 mail_transaction_log_file_refresh(struct mail_index_transaction *t,
145 struct mail_transaction_log_append_ctx *ctx)
146 {
147 struct mail_transaction_log_file *file;
148
149 if (t->reset) {
150 /* Reset the whole index, preserving only indexid. Begin by
151 rotating the log. We don't care if we skip some non-synced
152 transactions. */
153 if (mail_transaction_log_rotate(t->view->index->log, TRUE) < 0)
154 return -1;
155
156 if (!MAIL_INDEX_TRANSACTION_HAS_CHANGES(t)) {
157 /* we only wanted to reset */
158 return 0;
159 }
160 }
161 file = t->view->index->log->head;
162
163 /* make sure we have everything mapped */
164 if (mail_index_map(t->view->index, MAIL_INDEX_SYNC_HANDLER_HEAD) <= 0)
165 return -1;
166
167 i_assert(file->sync_offset >= file->buffer_offset);
168 ctx->new_highest_modseq = file->sync_highest_modseq;
169 return 1;
170 }
171
172 static int
mail_index_transaction_commit_real(struct mail_index_transaction * t,uoff_t * commit_size_r,enum mail_index_transaction_change * changes_r)173 mail_index_transaction_commit_real(struct mail_index_transaction *t,
174 uoff_t *commit_size_r,
175 enum mail_index_transaction_change *changes_r)
176 {
177 struct mail_transaction_log *log = t->view->index->log;
178 struct mail_transaction_log_append_ctx *ctx;
179 enum mail_transaction_type trans_flags = 0;
180 uint32_t log_seq1, log_seq2;
181 uoff_t log_offset1, log_offset2;
182 int ret;
183
184 *changes_r = 0;
185
186 if ((t->flags & MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL) != 0)
187 trans_flags |= MAIL_TRANSACTION_EXTERNAL;
188 if ((t->flags & MAIL_INDEX_TRANSACTION_FLAG_SYNC) != 0)
189 trans_flags |= MAIL_TRANSACTION_SYNC;
190
191 if (mail_transaction_log_append_begin(log->index, trans_flags, &ctx) < 0)
192 return -1;
193 ret = mail_transaction_log_file_refresh(t, ctx);
194 if (ret > 0) T_BEGIN {
195 mail_index_transaction_finish(t);
196 mail_index_transaction_export(t, ctx, changes_r);
197 } T_END;
198
199 mail_transaction_log_get_head(log, &log_seq1, &log_offset1);
200 if (mail_transaction_log_append_commit(&ctx) < 0 || ret < 0)
201 return -1;
202 mail_transaction_log_get_head(log, &log_seq2, &log_offset2);
203 i_assert(log_seq1 == log_seq2);
204
205 if (t->reset) {
206 /* get rid of the old index. it might just confuse readers,
207 especially if it's broken. */
208 i_unlink_if_exists(log->index->filepath);
209 }
210
211 *commit_size_r = log_offset2 - log_offset1;
212
213 if ((t->flags & MAIL_INDEX_TRANSACTION_FLAG_HIDE) != 0 &&
214 log_offset1 != log_offset2) {
215 /* mark the area covered by this transaction hidden */
216 mail_index_view_add_hidden_transaction(t->view, log_seq1,
217 log_offset1, log_offset2 - log_offset1);
218 }
219 return 0;
220 }
221
mail_index_transaction_commit_v(struct mail_index_transaction * t,struct mail_index_transaction_commit_result * result_r)222 static int mail_index_transaction_commit_v(struct mail_index_transaction *t,
223 struct mail_index_transaction_commit_result *result_r)
224 {
225 struct mail_index *index = t->view->index;
226 bool changed;
227 int ret;
228
229 i_assert(t->first_new_seq >
230 mail_index_view_get_messages_count(t->view));
231
232 changed = MAIL_INDEX_TRANSACTION_HAS_CHANGES(t) || t->reset;
233 ret = !changed ? 0 :
234 mail_index_transaction_commit_real(t, &result_r->commit_size,
235 &result_r->changes_mask);
236 mail_transaction_log_get_head(index->log, &result_r->log_file_seq,
237 &result_r->log_file_offset);
238
239 if (ret == 0 && !index->syncing && changed) {
240 /* if we're committing a normal transaction, we want to
241 have those changes in the index mapping immediately. this
242 is especially important when committing cache offset
243 updates.
244
245 however if we're syncing the index now, the mapping must
246 be done later as MAIL_INDEX_SYNC_HANDLER_FILE so that
247 expunge handlers get run for the newly expunged messages
248 (and sync handlers that require HANDLER_FILE as well). */
249 index->sync_commit_result = result_r;
250 mail_index_refresh(index);
251 index->sync_commit_result = NULL;
252 }
253
254 mail_index_transaction_unref(&t);
255 return ret;
256 }
257
mail_index_transaction_rollback_v(struct mail_index_transaction * t)258 static void mail_index_transaction_rollback_v(struct mail_index_transaction *t)
259 {
260 mail_index_transaction_unref(&t);
261 }
262
mail_index_transaction_commit(struct mail_index_transaction ** t)263 int mail_index_transaction_commit(struct mail_index_transaction **t)
264 {
265 struct mail_index_transaction_commit_result result;
266
267 return mail_index_transaction_commit_full(t, &result);
268 }
269
mail_index_transaction_commit_full(struct mail_index_transaction ** _t,struct mail_index_transaction_commit_result * result_r)270 int mail_index_transaction_commit_full(struct mail_index_transaction **_t,
271 struct mail_index_transaction_commit_result *result_r)
272 {
273 struct mail_index_transaction *t = *_t;
274 struct mail_index *index = t->view->index;
275 bool index_undeleted = t->index_undeleted;
276
277 if (mail_index_view_is_inconsistent(t->view)) {
278 mail_index_set_error_nolog(index, "View is inconsistent");
279 mail_index_transaction_rollback(_t);
280 return -1;
281 }
282 if (!index_undeleted && !t->commit_deleted_index) {
283 if (t->view->index->index_deleted ||
284 (t->view->index->index_delete_requested &&
285 !t->view->index->syncing)) {
286 /* no further changes allowed */
287 mail_index_set_error_nolog(index, "Index is marked deleted");
288 mail_index_transaction_rollback(_t);
289 return -1;
290 }
291 }
292
293 *_t = NULL;
294 i_zero(result_r);
295 if (t->v.commit(t, result_r) < 0)
296 return -1;
297
298 if (index_undeleted) {
299 index->index_deleted = FALSE;
300 index->index_delete_requested = FALSE;
301 }
302 return 0;
303 }
304
mail_index_transaction_rollback(struct mail_index_transaction ** _t)305 void mail_index_transaction_rollback(struct mail_index_transaction **_t)
306 {
307 struct mail_index_transaction *t = *_t;
308
309 *_t = NULL;
310 t->v.rollback(t);
311 }
312
313 static struct mail_index_transaction_vfuncs trans_vfuncs = {
314 mail_index_transaction_reset_v,
315 mail_index_transaction_commit_v,
316 mail_index_transaction_rollback_v
317 };
318
319 struct mail_index_transaction *
mail_index_transaction_begin(struct mail_index_view * view,enum mail_index_transaction_flags flags)320 mail_index_transaction_begin(struct mail_index_view *view,
321 enum mail_index_transaction_flags flags)
322 {
323 struct mail_index_transaction *t;
324
325 /* don't allow syncing view while there's ongoing transactions */
326 mail_index_view_ref(view);
327
328 t = i_new(struct mail_index_transaction, 1);
329 t->refcount = 1;
330 t->v = trans_vfuncs;
331 t->view = view;
332 t->flags = flags;
333
334 if (view->syncing) {
335 /* transaction view cannot work if new records are being added
336 in two places. make sure it doesn't happen. */
337 t->no_appends = TRUE;
338 t->first_new_seq = (uint32_t)-1;
339 } else {
340 t->first_new_seq =
341 mail_index_view_get_messages_count(t->view) + 1;
342 }
343
344 i_array_init(&t->module_contexts,
345 I_MIN(5, mail_index_module_register.id));
346 DLLIST_PREPEND(&view->transactions_list, t);
347
348 if (array_is_created(&hook_mail_index_transaction_created)) {
349 struct hook_build_context *ctx =
350 hook_build_init((void *)&t->v, sizeof(t->v));
351 hook_mail_index_transaction_created_t *callback;
352 array_foreach_elem(&hook_mail_index_transaction_created, callback) {
353 callback(t);
354 hook_build_update(ctx, t->vlast);
355 }
356 t->vlast = NULL;
357 hook_build_deinit(&ctx);
358 }
359 return t;
360 }
361