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