1 /* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */
2 
3 #include "lib.h"
4 #include "array.h"
5 #include "ioloop.h"
6 #include "mail-index-private.h"
7 #include "mail-index-modseq.h"
8 #include "mail-index-transaction-private.h"
9 
mail_transaction_expunge_guid_cmp(const struct mail_transaction_expunge_guid * e1,const struct mail_transaction_expunge_guid * e2)10 int mail_transaction_expunge_guid_cmp(const struct mail_transaction_expunge_guid *e1,
11 				      const struct mail_transaction_expunge_guid *e2)
12 {
13 	if (e1->uid < e2->uid)
14 		return -1;
15 	else if (e1->uid > e2->uid)
16 		return 1;
17 	else
18 		return 0;
19 }
20 
mail_index_transaction_sort_expunges(struct mail_index_transaction * t)21 void mail_index_transaction_sort_expunges(struct mail_index_transaction *t)
22 {
23 	if (!t->expunges_nonsorted)
24 		return;
25 
26 	array_sort(&t->expunges, mail_transaction_expunge_guid_cmp);
27 	t->expunges_nonsorted = FALSE;
28 }
29 
30 static void
ext_reset_update_atomic(struct mail_index_transaction * t,uint32_t ext_id,uint32_t expected_reset_id)31 ext_reset_update_atomic(struct mail_index_transaction *t,
32 			uint32_t ext_id, uint32_t expected_reset_id)
33 {
34 	const struct mail_index_ext *map_ext;
35 	struct mail_transaction_ext_reset *reset;
36 	uint32_t idx, reset_id;
37 
38 	if (!mail_index_map_get_ext_idx(t->view->index->map, ext_id, &idx)) {
39 		/* new extension */
40 		reset_id = 1;
41 	} else {
42 		map_ext = array_idx(&t->view->index->map->extensions, idx);
43 		reset_id = map_ext->reset_id + 1;
44 	}
45 	if (reset_id != expected_reset_id) {
46 		/* ignore this extension update */
47 		mail_index_ext_set_reset_id(t, ext_id, 0);
48 		return;
49 	}
50 
51 	if (reset_id == 0)
52 		reset_id++;
53 
54 	array_idx_set(&t->ext_reset_ids, ext_id, &reset_id);
55 
56 	/* reseting existing data is optional */
57 	if (array_is_created(&t->ext_resets)) {
58 		reset = array_idx_modifiable(&t->ext_resets, ext_id);
59 		if (reset->new_reset_id == (uint32_t)-1)
60 			reset->new_reset_id = reset_id;
61 	}
62 }
63 
64 static void
transaction_update_atomic_reset_ids(struct mail_index_transaction * t)65 transaction_update_atomic_reset_ids(struct mail_index_transaction *t)
66 {
67 	const uint32_t *expected_reset_ids;
68 	unsigned int ext_id, count;
69 
70 	if (!array_is_created(&t->ext_reset_atomic))
71 		return;
72 
73 	expected_reset_ids = array_get(&t->ext_reset_atomic, &count);
74 	for (ext_id = 0; ext_id < count; ext_id++) {
75 		if (expected_reset_ids[ext_id] != 0) {
76 			ext_reset_update_atomic(t, ext_id,
77 						expected_reset_ids[ext_id]);
78 		}
79 	}
80 }
81 
82 static unsigned int
mail_transaction_drop_range(struct mail_index_transaction * t,struct mail_index_flag_update update,unsigned int update_idx,ARRAY_TYPE (seq_range)* keeps)83 mail_transaction_drop_range(struct mail_index_transaction *t,
84 			    struct mail_index_flag_update update,
85 			    unsigned int update_idx,
86 			    ARRAY_TYPE(seq_range) *keeps)
87 {
88 	const struct seq_range *keep_range;
89 	unsigned int i, keep_count;
90 
91 	keep_range = array_get(keeps, &keep_count);
92 	if (keep_count == 1 &&
93 	    update.uid1 == keep_range[0].seq1 &&
94 	    update.uid2 == keep_range[0].seq2) {
95 		/* everything is kept */
96 		return update_idx + 1;
97 	}
98 
99 	array_delete(&t->updates, update_idx, 1);
100 
101 	/* add back all the updates we want to keep */
102 	for (i = 0; i < keep_count; i++, update_idx++) {
103 		update.uid1 = keep_range[i].seq1;
104 		update.uid2 = keep_range[i].seq2;
105 		array_insert(&t->updates, update_idx, &update, 1);
106 	}
107 	return update_idx;
108 }
109 
110 static void
mail_index_transaction_finish_flag_updates(struct mail_index_transaction * t)111 mail_index_transaction_finish_flag_updates(struct mail_index_transaction *t)
112 {
113 	const struct mail_index_flag_update *updates, *u;
114 	const struct mail_index_record *rec;
115 	unsigned int i, count;
116 	ARRAY_TYPE(seq_range) keeps;
117 	uint32_t seq;
118 
119 	if (!t->drop_unnecessary_flag_updates || !array_is_created(&t->updates))
120 		return;
121 
122 	t_array_init(&keeps, 64);
123 	updates = array_get(&t->updates, &count);
124 	for (i = 0; i < count; ) {
125 		/* first get the list of changes to drop */
126 		u = &updates[i];
127 		array_clear(&keeps);
128 		for (seq = u->uid1; seq <= u->uid2; seq++) {
129 			rec = mail_index_lookup(t->view, seq);
130 			if ((rec->flags & u->add_flags) != u->add_flags ||
131 			    (rec->flags & u->remove_flags) != 0) {
132 				/* keep this change */
133 				seq_range_array_add(&keeps, seq);
134 			}
135 		}
136 		i = mail_transaction_drop_range(t, updates[i], i, &keeps);
137 		updates = array_get(&t->updates, &count);
138 	}
139 
140 	if (array_count(&t->updates) == 0)
141 		array_free(&t->updates);
142 }
143 
144 static void
mail_index_transaction_check_conflicts(struct mail_index_transaction * t)145 mail_index_transaction_check_conflicts(struct mail_index_transaction *t)
146 {
147 	uint32_t seq;
148 	bool ret1, ret2;
149 
150 	i_assert(t->max_modseq != 0);
151 	i_assert(t->conflict_seqs != NULL);
152 
153 	if (t->max_modseq == mail_index_modseq_get_highest(t->view)) {
154 		/* no conflicts possible */
155 		return;
156 	}
157 	if (t->min_flagupdate_seq == 0) {
158 		/* no flag updates */
159 		return;
160 	}
161 
162 	for (seq = t->min_flagupdate_seq; seq <= t->max_flagupdate_seq; seq++) {
163 		if (mail_index_modseq_lookup(t->view, seq) > t->max_modseq) {
164 			ret1 = mail_index_cancel_flag_updates(t, seq);
165 			ret2 = mail_index_cancel_keyword_updates(t, seq);
166 			if (ret1 || ret2) {
167 				seq_range_array_add_with_init(t->conflict_seqs,
168 							      16, seq);
169 			}
170 		}
171 	}
172 	mail_index_transaction_set_log_updates(t);
173 }
174 
175 static uint32_t
mail_index_transaction_get_uid(struct mail_index_transaction * t,uint32_t seq)176 mail_index_transaction_get_uid(struct mail_index_transaction *t, uint32_t seq)
177 {
178 	const struct mail_index_record *rec;
179 
180 	i_assert(seq > 0);
181 
182 	if (seq >= t->first_new_seq)
183 		rec = mail_index_transaction_lookup(t, seq);
184 	else {
185 		i_assert(seq <= t->view->map->hdr.messages_count);
186 		rec = MAIL_INDEX_REC_AT_SEQ(t->view->map, seq);
187 	}
188 	i_assert(rec->uid != 0);
189 	return rec->uid;
190 }
191 
192 static void
mail_index_convert_to_uids(struct mail_index_transaction * t,ARRAY_TYPE (seq_array)* array)193 mail_index_convert_to_uids(struct mail_index_transaction *t,
194 			   ARRAY_TYPE(seq_array) *array)
195 {
196 	uint32_t *seq;
197 	unsigned int i, count;
198 
199 	if (!array_is_created(array))
200 		return;
201 
202 	count = array_count(array);
203 	for (i = 0; i < count; i++) {
204 		seq = array_idx_modifiable(array, i);
205 		*seq = mail_index_transaction_get_uid(t, *seq);
206 	}
207 }
208 
209 static uint32_t
get_nonexpunged_uid2(struct mail_index_transaction * t,uint32_t uid1,uint32_t seq1)210 get_nonexpunged_uid2(struct mail_index_transaction *t,
211 		     uint32_t uid1, uint32_t seq1)
212 {
213 	seq1++;
214 
215 	while (mail_index_transaction_get_uid(t, seq1) == uid1 + 1) {
216 		seq1++;
217 		uid1++;
218 	}
219 	return uid1;
220 }
221 
mail_index_transaction_seq_range_to_uid(struct mail_index_transaction * t,ARRAY_TYPE (seq_range)* array)222 void mail_index_transaction_seq_range_to_uid(struct mail_index_transaction *t,
223 					     ARRAY_TYPE(seq_range) *array)
224 {
225 	struct seq_range *range, *new_range;
226 	unsigned int i, count;
227 	uint32_t uid1, uid2, prev_uid = 0;
228 
229 	if (!array_is_created(array))
230 		return;
231 
232 	count = array_count(array);
233 	for (i = 0; i < count; i++) {
234 		range = array_idx_modifiable(array, i);
235 
236 		uid1 = mail_index_transaction_get_uid(t, range->seq1);
237 		uid2 = mail_index_transaction_get_uid(t, range->seq2);
238 		i_assert(uid1 > prev_uid);
239 		if (uid2 - uid1 == range->seq2 - range->seq1) {
240 			/* simple conversion */
241 			range->seq1 = uid1;
242 			range->seq2 = uid2;
243 			prev_uid = uid2;
244 		} else {
245 			/* remove expunged UIDs */
246 			new_range = array_insert_space(array, i);
247 			range = array_idx_modifiable(array, i + 1);
248 			count++;
249 
250 			memcpy(new_range, range, array->arr.element_size);
251 			new_range->seq1 = uid1;
252 			new_range->seq2 = get_nonexpunged_uid2(t, uid1,
253 							       range->seq1);
254 			i_assert(new_range->seq2 < uid2);
255 
256 			/* continue the range without the inserted seqs */
257 			range->seq1 += new_range->seq2 - new_range->seq1 + 1;
258 			prev_uid = new_range->seq2;
259 		}
260 	}
261 }
262 
keyword_updates_convert_to_uids(struct mail_index_transaction * t)263 static void keyword_updates_convert_to_uids(struct mail_index_transaction *t)
264 {
265         struct mail_index_transaction_keyword_update *update;
266 
267 	if (!array_is_created(&t->keyword_updates))
268 		return;
269 
270 	array_foreach_modifiable(&t->keyword_updates, update) {
271 		mail_index_transaction_seq_range_to_uid(t, &update->add_seq);
272 		mail_index_transaction_seq_range_to_uid(t, &update->remove_seq);
273 	}
274 }
275 
expunges_convert_to_uids(struct mail_index_transaction * t)276 static void expunges_convert_to_uids(struct mail_index_transaction *t)
277 {
278 	struct mail_transaction_expunge_guid *expunges;
279 	unsigned int src, dest, count;
280 
281 	if (!array_is_created(&t->expunges))
282 		return;
283 
284 	mail_index_transaction_sort_expunges(t);
285 
286 	expunges = array_get_modifiable(&t->expunges, &count);
287 	if (count == 0)
288 		return;
289 
290 	/* convert uids and drop duplicates */
291 	expunges[0].uid = mail_index_transaction_get_uid(t, expunges[0].uid);
292 	for (src = dest = 1; src < count; src++) {
293 		expunges[dest].uid =
294 			mail_index_transaction_get_uid(t, expunges[src].uid);
295 		if (expunges[dest-1].uid != expunges[dest].uid) {
296 			if (dest != src) {
297 				memcpy(expunges[dest].guid_128, expunges[src].guid_128,
298 				       sizeof(expunges[dest].guid_128));
299 			}
300 			dest++;
301 		}
302 	}
303 	array_delete(&t->expunges, dest, count-dest);
304 }
305 
306 static void
mail_index_transaction_convert_to_uids(struct mail_index_transaction * t)307 mail_index_transaction_convert_to_uids(struct mail_index_transaction *t)
308 {
309 	ARRAY_TYPE(seq_array) *update;
310 
311 	if (array_is_created(&t->ext_rec_updates)) {
312 		array_foreach_modifiable(&t->ext_rec_updates, update)
313 			mail_index_convert_to_uids(t, update);
314 	}
315 	if (array_is_created(&t->ext_rec_atomics)) {
316 		array_foreach_modifiable(&t->ext_rec_atomics, update)
317 			mail_index_convert_to_uids(t, update);
318 	}
319 
320         keyword_updates_convert_to_uids(t);
321 	expunges_convert_to_uids(t);
322 	mail_index_convert_to_uids(t, (void *)&t->modseq_updates);
323 	mail_index_transaction_seq_range_to_uid(t, (void *)&t->updates);
324 }
325 
mail_index_transaction_finish_so_far(struct mail_index_transaction * t)326 void mail_index_transaction_finish_so_far(struct mail_index_transaction *t)
327 {
328 	if (array_is_created(&t->appends))
329 		mail_index_transaction_sort_appends(t);
330 	mail_index_transaction_finish_flag_updates(t);
331 	if (t->max_modseq != 0)
332 		mail_index_transaction_check_conflicts(t);
333 }
334 
mail_index_transaction_finish(struct mail_index_transaction * t)335 void mail_index_transaction_finish(struct mail_index_transaction *t)
336 {
337 	mail_index_transaction_finish_so_far(t);
338 
339 	if (array_is_created(&t->appends))
340 		mail_index_update_day_headers(t, ioloop_time);
341 	if (array_is_created(&t->ext_reset_atomic))
342 		transaction_update_atomic_reset_ids(t);
343 	/* finally convert all sequences to UIDs before we write them,
344 	   but after we've checked and removed conflicts */
345 	mail_index_transaction_convert_to_uids(t);
346 
347 	/* and kind of ugly way to update highest modseq */
348 	if (t->min_highest_modseq != 0)
349 		mail_index_update_modseq(t, 0, t->min_highest_modseq);
350 }
351