1 /* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
2 
3 #include "lib.h"
4 #include "array.h"
5 #include "guid.h"
6 #include "llist.h"
7 #include "ioloop.h"
8 #include "str.h"
9 #include "ioloop.h"
10 #include "dict-private.h"
11 
12 struct dict_commit_callback_ctx {
13 	pool_t pool;
14 	struct dict_commit_callback_ctx *prev, *next;
15 	struct dict *dict;
16 	struct event *event;
17 	dict_transaction_commit_callback_t *callback;
18 	struct dict_op_settings_private set;
19 	struct timeout *to;
20 	void *context;
21 	struct dict_commit_result result;
22 	bool delayed_callback:1;
23 };
24 
25 struct dict_lookup_callback_ctx {
26 	struct dict *dict;
27 	struct event *event;
28 	dict_lookup_callback_t *callback;
29 	void *context;
30 };
31 
32 static ARRAY(struct dict *) dict_drivers;
33 
34 static void
35 dict_commit_async_timeout(struct dict_commit_callback_ctx *ctx);
36 
37 static struct event_category event_category_dict = {
38 	.name = "dict",
39 };
40 
dict_driver_lookup(const char * name)41 static struct dict *dict_driver_lookup(const char *name)
42 {
43 	struct dict *dict;
44 
45 	array_foreach_elem(&dict_drivers, dict) {
46 		if (strcmp(dict->name, name) == 0)
47 			return dict;
48 	}
49 	return NULL;
50 }
51 
dict_transaction_commit_async_noop_callback(const struct dict_commit_result * result ATTR_UNUSED,void * context ATTR_UNUSED)52 void dict_transaction_commit_async_noop_callback(
53 	const struct dict_commit_result *result ATTR_UNUSED,
54 	void *context ATTR_UNUSED)
55 {
56 	/* do nothing */
57 }
58 
dict_driver_register(struct dict * driver)59 void dict_driver_register(struct dict *driver)
60 {
61 	if (!array_is_created(&dict_drivers))
62 		i_array_init(&dict_drivers, 8);
63 
64 	if (dict_driver_lookup(driver->name) != NULL) {
65 		i_fatal("dict_driver_register(%s): Already registered",
66 			driver->name);
67 	}
68 	array_push_back(&dict_drivers, &driver);
69 }
70 
dict_driver_unregister(struct dict * driver)71 void dict_driver_unregister(struct dict *driver)
72 {
73 	struct dict *const *dicts;
74 	unsigned int idx = UINT_MAX;
75 
76 	array_foreach(&dict_drivers, dicts) {
77 		if (*dicts == driver) {
78 			idx = array_foreach_idx(&dict_drivers, dicts);
79 			break;
80 		}
81 	}
82 	i_assert(idx != UINT_MAX);
83 	array_delete(&dict_drivers, idx, 1);
84 
85 	if (array_count(&dict_drivers) == 0)
86 		array_free(&dict_drivers);
87 }
88 
dict_init(const char * uri,const struct dict_settings * set,struct dict ** dict_r,const char ** error_r)89 int dict_init(const char *uri, const struct dict_settings *set,
90 	      struct dict **dict_r, const char **error_r)
91 {
92 	struct dict_settings set_dup = *set;
93 	struct dict *dict;
94 	const char *p, *name, *error;
95 
96 	p = strchr(uri, ':');
97 	if (p == NULL) {
98 		*error_r = t_strdup_printf("Dictionary URI is missing ':': %s",
99 					   uri);
100 		return -1;
101 	}
102 
103 	name = t_strdup_until(uri, p);
104 	dict = dict_driver_lookup(name);
105 	if (dict == NULL) {
106 		*error_r = t_strdup_printf("Unknown dict module: %s", name);
107 		return -1;
108 	}
109 	struct event *event = event_create(set->event_parent);
110 	event_add_category(event, &event_category_dict);
111 	event_add_str(event, "driver", dict->name);
112 	event_set_append_log_prefix(event, t_strdup_printf("dict(%s): ",
113 				    dict->name));
114 	set_dup.event_parent = event;
115 	if (dict->v.init(dict, p+1, &set_dup, dict_r, &error) < 0) {
116 		*error_r = t_strdup_printf("dict %s: %s", name, error);
117 		event_unref(&event);
118 		return -1;
119 	}
120 	i_assert(*dict_r != NULL);
121 	(*dict_r)->refcount++;
122 	(*dict_r)->event = event;
123 	e_debug(event_create_passthrough(event)->set_name("dict_created")->event(),
124 		"dict created (uri=%s, base_dir=%s)", uri, set->base_dir);
125 
126 	return 0;
127 }
128 
dict_ref(struct dict * dict)129 static void dict_ref(struct dict *dict)
130 {
131 	i_assert(dict->refcount > 0);
132 
133 	dict->refcount++;
134 }
135 
dict_unref(struct dict ** _dict)136 static void dict_unref(struct dict **_dict)
137 {
138 	struct dict *dict = *_dict;
139 	*_dict = NULL;
140 	if (dict == NULL)
141 		return;
142 	struct event *event = dict->event;
143 	i_assert(dict->refcount > 0);
144 	if (--dict->refcount == 0) {
145 		dict->v.deinit(dict);
146 		e_debug(event_create_passthrough(event)->
147 			set_name("dict_destroyed")->event(), "dict destroyed");
148 		event_unref(&event);
149 	}
150 }
151 
dict_deinit(struct dict ** _dict)152 void dict_deinit(struct dict **_dict)
153 {
154 	struct dict *dict = *_dict;
155 
156 	*_dict = NULL;
157 
158 	i_assert(dict->iter_count == 0);
159 	i_assert(dict->transaction_count == 0);
160 	i_assert(dict->transactions == NULL);
161 	i_assert(dict->commits == NULL);
162 	dict_unref(&dict);
163 }
164 
dict_wait(struct dict * dict)165 void dict_wait(struct dict *dict)
166 {
167 	struct dict_commit_callback_ctx *commit, *next;
168 
169 	e_debug(dict->event, "Waiting for dict to finish pending operations");
170 	if (dict->v.wait != NULL)
171 		dict->v.wait(dict);
172 	for (commit = dict->commits; commit != NULL; commit = next) {
173 		next = commit->next;
174 		dict_commit_async_timeout(commit);
175 	}
176 }
177 
dict_switch_ioloop(struct dict * dict)178 bool dict_switch_ioloop(struct dict *dict)
179 {
180 	struct dict_commit_callback_ctx *commit;
181 	bool ret = FALSE;
182 
183 	for (commit = dict->commits; commit != NULL; commit = commit->next) {
184 		commit->to = io_loop_move_timeout(&commit->to);
185 		ret = TRUE;
186 	}
187 	if (dict->v.switch_ioloop != NULL) {
188 		if (dict->v.switch_ioloop(dict))
189 			return TRUE;
190 	}
191 	return ret;
192 }
193 
dict_key_prefix_is_valid(const char * key,const char * username)194 static bool dict_key_prefix_is_valid(const char *key, const char *username)
195 {
196 	if (str_begins(key, DICT_PATH_SHARED))
197 		return TRUE;
198 	if (str_begins(key, DICT_PATH_PRIVATE)) {
199 		i_assert(username != NULL && username[0] != '\0');
200 		return TRUE;
201 	}
202 	return FALSE;
203 
204 }
205 
dict_pre_api_callback(struct dict * dict)206 void dict_pre_api_callback(struct dict *dict)
207 {
208 	if (dict->prev_ioloop != NULL) {
209 		/* Don't let callback see that we've created our
210 		   internal ioloop in case it wants to add some ios
211 		   or timeouts. */
212 		io_loop_set_current(dict->prev_ioloop);
213 	}
214 }
215 
dict_post_api_callback(struct dict * dict)216 void dict_post_api_callback(struct dict *dict)
217 {
218 	if (dict->prev_ioloop != NULL) {
219 		io_loop_set_current(dict->ioloop);
220 		io_loop_stop(dict->ioloop);
221 	}
222 }
223 
dict_lookup_finished(struct event * event,int ret,const char * error)224 static void dict_lookup_finished(struct event *event, int ret, const char *error)
225 {
226 	i_assert(ret >= 0 || error != NULL);
227 	const char *key = event_find_field_recursive_str(event, "key");
228 	if (ret < 0)
229 		event_add_str(event, "error", error);
230 	else if (ret == 0)
231 		event_add_str(event, "key_not_found", "yes");
232 	event_set_name(event, "dict_lookup_finished");
233 	e_debug(event, "Lookup finished for '%s': %s",  key, ret > 0 ?
234 			"found" :
235 			"not found");
236 }
237 
dict_transaction_finished(struct event * event,enum dict_commit_ret ret,bool rollback,const char * error)238 static void dict_transaction_finished(struct event *event, enum dict_commit_ret ret,
239 				      bool rollback, const char *error)
240 {
241 	i_assert(ret > DICT_COMMIT_RET_FAILED || error != NULL);
242 	if (ret == DICT_COMMIT_RET_FAILED || ret == DICT_COMMIT_RET_WRITE_UNCERTAIN) {
243 		 if (ret == DICT_COMMIT_RET_WRITE_UNCERTAIN)
244 			event_add_str(event, "write_uncertain", "yes");
245 		event_add_str(event, "error", error);
246 	} else if (rollback) {
247 		event_add_str(event, "rollback", "yes");
248 	} else if (ret == 0) {
249 		event_add_str(event, "key_not_found", "yes");
250 	}
251 	event_set_name(event, "dict_transaction_finished");
252 	e_debug(event, "Dict transaction finished");
253 }
254 
255 static void
dict_lookup_callback(const struct dict_lookup_result * result,void * context)256 dict_lookup_callback(const struct dict_lookup_result *result,
257 		     void *context)
258 {
259 	struct dict_lookup_callback_ctx *ctx = context;
260 
261 	dict_pre_api_callback(ctx->dict);
262 	ctx->callback(result, ctx->context);
263 	dict_post_api_callback(ctx->dict);
264 	dict_lookup_finished(ctx->event, result->ret, result->error);
265 	event_unref(&ctx->event);
266 
267 	dict_unref(&ctx->dict);
268 	i_free(ctx);
269 }
270 
271 static void
dict_commit_async_timeout(struct dict_commit_callback_ctx * ctx)272 dict_commit_async_timeout(struct dict_commit_callback_ctx *ctx)
273 {
274 	DLLIST_REMOVE(&ctx->dict->commits, ctx);
275 	timeout_remove(&ctx->to);
276 	dict_pre_api_callback(ctx->dict);
277 	if (ctx->callback != NULL)
278 		ctx->callback(&ctx->result, ctx->context);
279 	else if (ctx->result.ret < 0)
280 		e_error(ctx->event, "Commit failed: %s", ctx->result.error);
281 	dict_post_api_callback(ctx->dict);
282 
283 	dict_transaction_finished(ctx->event, ctx->result.ret, FALSE, ctx->result.error);
284 	dict_op_settings_private_free(&ctx->set);
285 	event_unref(&ctx->event);
286 	dict_unref(&ctx->dict);
287 	pool_unref(&ctx->pool);
288 }
289 
dict_commit_callback(const struct dict_commit_result * result,void * context)290 static void dict_commit_callback(const struct dict_commit_result *result,
291 				 void *context)
292 {
293 	struct dict_commit_callback_ctx *ctx = context;
294 
295 	i_assert(result->ret >= 0 || result->error != NULL);
296 	ctx->result = *result;
297 	if (ctx->delayed_callback) {
298 		ctx->result.error = p_strdup(ctx->pool, ctx->result.error);
299 		ctx->to = timeout_add_short(0, dict_commit_async_timeout, ctx);
300 	} else {
301 		dict_commit_async_timeout(ctx);
302 	}
303 }
304 
305 static struct event *
dict_event_create(struct dict * dict,const struct dict_op_settings * set)306 dict_event_create(struct dict *dict, const struct dict_op_settings *set)
307 {
308 	struct event *event = event_create(dict->event);
309 	if (set->username != NULL)
310 		event_add_str(event, "user", set->username);
311 	return event;
312 }
313 
dict_lookup(struct dict * dict,const struct dict_op_settings * set,pool_t pool,const char * key,const char ** value_r,const char ** error_r)314 int dict_lookup(struct dict *dict, const struct dict_op_settings *set,
315 		pool_t pool, const char *key,
316 		const char **value_r, const char **error_r)
317 {
318 	struct event *event = dict_event_create(dict, set);
319 	int ret;
320 	i_assert(dict_key_prefix_is_valid(key, set->username));
321 
322 	e_debug(event, "Looking up '%s'", key);
323 	event_add_str(event, "key", key);
324 	ret = dict->v.lookup(dict, set, pool, key, value_r, error_r);
325 	dict_lookup_finished(event, ret, *error_r);
326 	event_unref(&event);
327 	return ret;
328 }
329 
330 #undef dict_lookup_async
dict_lookup_async(struct dict * dict,const struct dict_op_settings * set,const char * key,dict_lookup_callback_t * callback,void * context)331 void dict_lookup_async(struct dict *dict, const struct dict_op_settings *set,
332 		       const char *key, dict_lookup_callback_t *callback,
333 		       void *context)
334 {
335 	if (dict->v.lookup_async == NULL) {
336 		struct dict_lookup_result result;
337 
338 		i_zero(&result);
339 		/* event is going to be sent by dict_lookup */
340 		result.ret = dict_lookup(dict, set, pool_datastack_create(),
341 					 key, &result.value, &result.error);
342 		const char *const values[] = { result.value, NULL };
343 		result.values = values;
344 		callback(&result, context);
345 		return;
346 	}
347 	struct dict_lookup_callback_ctx *lctx =
348 		i_new(struct dict_lookup_callback_ctx, 1);
349 	lctx->dict = dict;
350 	dict_ref(lctx->dict);
351 	lctx->callback = callback;
352 	lctx->context = context;
353 	lctx->event = dict_event_create(dict, set);
354 	event_add_str(lctx->event, "key", key);
355 	e_debug(lctx->event, "Looking up (async) '%s'", key);
356 	dict->v.lookup_async(dict, set, key, dict_lookup_callback, lctx);
357 }
358 
359 struct dict_iterate_context *
dict_iterate_init(struct dict * dict,const struct dict_op_settings * set,const char * path,enum dict_iterate_flags flags)360 dict_iterate_init(struct dict *dict, const struct dict_op_settings *set,
361 		  const char *path, enum dict_iterate_flags flags)
362 {
363 	struct dict_iterate_context *ctx;
364 
365 	i_assert(path != NULL);
366 	i_assert(dict_key_prefix_is_valid(path, set->username));
367 
368 	if (dict->v.iterate_init == NULL) {
369 		/* not supported by backend */
370 		ctx = &dict_iter_unsupported;
371 	} else {
372 		ctx = dict->v.iterate_init(dict, set, path, flags);
373 	}
374 	/* the dict in context can differ from the dict
375 	   passed as parameter, e.g. it can be dict-fail when
376 	   iteration is not supported. */
377 	ctx->event = dict_event_create(dict, set);
378 	ctx->flags = flags;
379 	dict_op_settings_dup(set, &ctx->set);
380 
381 	event_add_str(ctx->event, "key", path);
382 	event_set_name(ctx->event, "dict_iteration_started");
383 	e_debug(ctx->event, "Iterating prefix %s", path);
384 	ctx->dict->iter_count++;
385 	return ctx;
386 }
387 
dict_iterate(struct dict_iterate_context * ctx,const char ** key_r,const char ** value_r)388 bool dict_iterate(struct dict_iterate_context *ctx,
389 		  const char **key_r, const char **value_r)
390 {
391 	const char *const *values;
392 
393 	if (!dict_iterate_values(ctx, key_r, &values))
394 		return FALSE;
395 	if ((ctx->flags & DICT_ITERATE_FLAG_NO_VALUE) == 0)
396 		*value_r = values[0];
397 	return TRUE;
398 }
399 
dict_iterate_values(struct dict_iterate_context * ctx,const char ** key_r,const char * const ** values_r)400 bool dict_iterate_values(struct dict_iterate_context *ctx,
401 			 const char **key_r, const char *const **values_r)
402 {
403 
404 	if (ctx->max_rows > 0 && ctx->row_count >= ctx->max_rows) {
405 		e_debug(ctx->event, "Maximum row count (%"PRIu64") reached",
406 			ctx->max_rows);
407 		/* row count was limited */
408 		ctx->has_more = FALSE;
409 		return FALSE;
410 	}
411 	if (!ctx->dict->v.iterate(ctx, key_r, values_r))
412 		return FALSE;
413 	if ((ctx->flags & DICT_ITERATE_FLAG_NO_VALUE) != 0) {
414 		/* always return value as NULL to be consistent across
415 		   drivers */
416 		*values_r = NULL;
417 	} else {
418 		i_assert(values_r[0] != NULL);
419 	}
420 	ctx->row_count++;
421 	return TRUE;
422 }
423 
424 #undef dict_iterate_set_async_callback
dict_iterate_set_async_callback(struct dict_iterate_context * ctx,dict_iterate_callback_t * callback,void * context)425 void dict_iterate_set_async_callback(struct dict_iterate_context *ctx,
426 				     dict_iterate_callback_t *callback,
427 				     void *context)
428 {
429 	ctx->async_callback = callback;
430 	ctx->async_context = context;
431 }
432 
dict_iterate_set_limit(struct dict_iterate_context * ctx,uint64_t max_rows)433 void dict_iterate_set_limit(struct dict_iterate_context *ctx,
434 			    uint64_t max_rows)
435 {
436 	ctx->max_rows = max_rows;
437 }
438 
dict_iterate_has_more(struct dict_iterate_context * ctx)439 bool dict_iterate_has_more(struct dict_iterate_context *ctx)
440 {
441 	return ctx->has_more;
442 }
443 
dict_iterate_deinit(struct dict_iterate_context ** _ctx,const char ** error_r)444 int dict_iterate_deinit(struct dict_iterate_context **_ctx,
445 			const char **error_r)
446 {
447 	struct dict_iterate_context *ctx = *_ctx;
448 
449 	if (ctx == NULL)
450 		return 0;
451 
452 	struct event *event = ctx->event;
453 	int ret;
454 	uint64_t rows;
455 
456 	i_assert(ctx->dict->iter_count > 0);
457 	ctx->dict->iter_count--;
458 
459 	*_ctx = NULL;
460 	rows = ctx->row_count;
461 	struct dict_op_settings_private set_copy = ctx->set;
462 	ret = ctx->dict->v.iterate_deinit(ctx, error_r);
463 	dict_op_settings_private_free(&set_copy);
464 
465 	event_add_int(event, "rows", rows);
466 	event_set_name(event, "dict_iteration_finished");
467 
468 	if (ret < 0) {
469 		event_add_str(event, "error", *error_r);
470 		e_debug(event, "Iteration finished: %s", *error_r);
471 	} else {
472 		if (rows == 0)
473 			event_add_str(event, "key_not_found", "yes");
474 		e_debug(event, "Iteration finished, got %"PRIu64" rows", rows);
475 	}
476 
477 	event_unref(&event);
478 	return ret;
479 }
480 
481 struct dict_transaction_context *
dict_transaction_begin(struct dict * dict,const struct dict_op_settings * set)482 dict_transaction_begin(struct dict *dict, const struct dict_op_settings *set)
483 {
484 	struct dict_transaction_context *ctx;
485 	guid_128_t guid;
486 	if (dict->v.transaction_init == NULL)
487 		ctx = &dict_transaction_unsupported;
488 	else
489 		ctx = dict->v.transaction_init(dict);
490 	/* the dict in context can differ from the dict
491 	   passed as parameter, e.g. it can be dict-fail when
492 	   transactions are not supported. */
493 	ctx->dict->transaction_count++;
494 	DLLIST_PREPEND(&ctx->dict->transactions, ctx);
495 	ctx->event = dict_event_create(dict, set);
496 	dict_op_settings_dup(set, &ctx->set);
497 	guid_128_generate(guid);
498 	event_add_str(ctx->event, "txid", guid_128_to_string(guid));
499 	event_set_name(ctx->event, "dict_transaction_started");
500 	e_debug(ctx->event, "Starting transaction");
501 	return ctx;
502 }
503 
dict_transaction_no_slowness_warning(struct dict_transaction_context * ctx)504 void dict_transaction_no_slowness_warning(struct dict_transaction_context *ctx)
505 {
506 	ctx->no_slowness_warning = TRUE;
507 }
508 
dict_transaction_set_timestamp(struct dict_transaction_context * ctx,const struct timespec * ts)509 void dict_transaction_set_timestamp(struct dict_transaction_context *ctx,
510 				    const struct timespec *ts)
511 {
512 	/* These asserts are mainly here to guarantee a possibility in future
513 	   to change the API to support multiple timestamps within the same
514 	   transaction, so this call would apply only to the following
515 	   changes. */
516 	i_assert(!ctx->changed);
517 	i_assert(ctx->timestamp.tv_sec == 0);
518 	i_assert(ts->tv_sec > 0);
519 
520 	ctx->timestamp = *ts;
521 	struct event_passthrough *e = event_create_passthrough(ctx->event)->
522 		set_name("dict_set_timestamp");
523 
524 	e_debug(e->event(), "Setting timestamp on transaction to (%"PRIdTIME_T", %ld)",
525 		 ts->tv_sec, ts->tv_nsec);
526 	if (ctx->dict->v.set_timestamp != NULL)
527 		ctx->dict->v.set_timestamp(ctx, ts);
528 }
529 
530 struct dict_commit_sync_result {
531 	int ret;
532 	char *error;
533 };
534 
535 static void
dict_transaction_commit_sync_callback(const struct dict_commit_result * result,void * context)536 dict_transaction_commit_sync_callback(const struct dict_commit_result *result,
537 				      void *context)
538 {
539 	struct dict_commit_sync_result *sync_result = context;
540 
541 	sync_result->ret = result->ret;
542 	sync_result->error = i_strdup(result->error);
543 }
544 
dict_transaction_commit(struct dict_transaction_context ** _ctx,const char ** error_r)545 int dict_transaction_commit(struct dict_transaction_context **_ctx,
546 			    const char **error_r)
547 {
548 	pool_t pool = pool_alloconly_create("dict_commit_callback_ctx", 64);
549 	struct dict_commit_callback_ctx *cctx =
550 		p_new(pool, struct dict_commit_callback_ctx, 1);
551 	struct dict_transaction_context *ctx = *_ctx;
552 	struct dict_commit_sync_result result;
553 
554 	*_ctx = NULL;
555 	cctx->pool = pool;
556 	i_zero(&result);
557 	i_assert(ctx->dict->transaction_count > 0);
558 	ctx->dict->transaction_count--;
559 	DLLIST_REMOVE(&ctx->dict->transactions, ctx);
560 	DLLIST_PREPEND(&ctx->dict->commits, cctx);
561 	cctx->dict = ctx->dict;
562 	dict_ref(cctx->dict);
563 	cctx->callback = dict_transaction_commit_sync_callback;
564 	cctx->context = &result;
565 	cctx->event = ctx->event;
566 	cctx->set = ctx->set;
567 
568 	ctx->dict->v.transaction_commit(ctx, FALSE, dict_commit_callback, cctx);
569 	*error_r = t_strdup(result.error);
570 	i_free(result.error);
571 	return result.ret;
572 }
573 
574 #undef dict_transaction_commit_async
dict_transaction_commit_async(struct dict_transaction_context ** _ctx,dict_transaction_commit_callback_t * callback,void * context)575 void dict_transaction_commit_async(struct dict_transaction_context **_ctx,
576 				   dict_transaction_commit_callback_t *callback,
577 				   void *context)
578 {
579 	pool_t pool = pool_alloconly_create("dict_commit_callback_ctx", 64);
580 	struct dict_commit_callback_ctx *cctx =
581 		p_new(pool, struct dict_commit_callback_ctx, 1);
582 	struct dict_transaction_context *ctx = *_ctx;
583 
584 	*_ctx = NULL;
585 	i_assert(ctx->dict->transaction_count > 0);
586 	ctx->dict->transaction_count--;
587 	DLLIST_REMOVE(&ctx->dict->transactions, ctx);
588 	DLLIST_PREPEND(&ctx->dict->commits, cctx);
589 	if (callback == NULL)
590 		callback = dict_transaction_commit_async_noop_callback;
591 	cctx->pool = pool;
592 	cctx->dict = ctx->dict;
593 	dict_ref(cctx->dict);
594 	cctx->callback = callback;
595 	cctx->context = context;
596 	cctx->event = ctx->event;
597 	cctx->set = ctx->set;
598 	cctx->delayed_callback = TRUE;
599 	ctx->dict->v.transaction_commit(ctx, TRUE, dict_commit_callback, cctx);
600 	cctx->delayed_callback = FALSE;
601 }
602 
dict_transaction_commit_async_nocallback(struct dict_transaction_context ** ctx)603 void dict_transaction_commit_async_nocallback(
604 	struct dict_transaction_context **ctx)
605 {
606 	dict_transaction_commit_async(ctx, NULL, NULL);
607 }
608 
dict_transaction_rollback(struct dict_transaction_context ** _ctx)609 void dict_transaction_rollback(struct dict_transaction_context **_ctx)
610 {
611 	struct dict_transaction_context *ctx = *_ctx;
612 
613 	if (ctx == NULL)
614 		return;
615 
616 	struct event *event = ctx->event;
617 
618 	*_ctx = NULL;
619 	i_assert(ctx->dict->transaction_count > 0);
620 	ctx->dict->transaction_count--;
621 	DLLIST_REMOVE(&ctx->dict->transactions, ctx);
622 	struct dict_op_settings_private set_copy = ctx->set;
623 	ctx->dict->v.transaction_rollback(ctx);
624 	dict_transaction_finished(event, DICT_COMMIT_RET_OK, TRUE, NULL);
625 	dict_op_settings_private_free(&set_copy);
626 	event_unref(&event);
627 }
628 
dict_set(struct dict_transaction_context * ctx,const char * key,const char * value)629 void dict_set(struct dict_transaction_context *ctx,
630 	      const char *key, const char *value)
631 {
632 	i_assert(dict_key_prefix_is_valid(key, ctx->set.username));
633 	struct event_passthrough *e = event_create_passthrough(ctx->event)->
634 		set_name("dict_set_key")->
635 		add_str("key", key);
636 
637 	e_debug(e->event(), "Setting '%s' to '%s'", key, value);
638 
639 	T_BEGIN {
640 		ctx->dict->v.set(ctx, key, value);
641 	} T_END;
642 	ctx->changed = TRUE;
643 }
644 
dict_unset(struct dict_transaction_context * ctx,const char * key)645 void dict_unset(struct dict_transaction_context *ctx,
646 		const char *key)
647 {
648 	i_assert(dict_key_prefix_is_valid(key, ctx->set.username));
649 	struct event_passthrough *e = event_create_passthrough(ctx->event)->
650 		set_name("dict_unset_key")->
651 		add_str("key", key);
652 
653 	e_debug(e->event(), "Unsetting '%s'", key);
654 
655 	T_BEGIN {
656 		ctx->dict->v.unset(ctx, key);
657 	} T_END;
658 	ctx->changed = TRUE;
659 }
660 
dict_atomic_inc(struct dict_transaction_context * ctx,const char * key,long long diff)661 void dict_atomic_inc(struct dict_transaction_context *ctx,
662 		     const char *key, long long diff)
663 {
664 	i_assert(dict_key_prefix_is_valid(key, ctx->set.username));
665 	struct event_passthrough *e = event_create_passthrough(ctx->event)->
666 		set_name("dict_increment_key")->
667 		add_str("key", key);
668 
669 	e_debug(e->event(), "Incrementing '%s' with %lld", key, diff);
670 
671 	if (diff != 0) T_BEGIN {
672 		ctx->dict->v.atomic_inc(ctx, key, diff);
673 		ctx->changed = TRUE;
674 	} T_END;
675 }
676 
dict_escape_string(const char * str)677 const char *dict_escape_string(const char *str)
678 {
679 	const char *p;
680 	string_t *ret;
681 
682 	/* see if we need to escape it */
683 	for (p = str; *p != '\0'; p++) {
684 		if (*p == '/' || *p == '\\')
685 			break;
686 	}
687 
688 	if (*p == '\0')
689 		return str;
690 
691 	/* escape */
692 	ret = t_str_new((size_t) (p - str) + 128);
693 	str_append_data(ret, str, (size_t) (p - str));
694 
695 	for (; *p != '\0'; p++) {
696 		switch (*p) {
697 		case '/':
698 			str_append_c(ret, '\\');
699 			str_append_c(ret, '|');
700 			break;
701 		case '\\':
702 			str_append_c(ret, '\\');
703 			str_append_c(ret, '\\');
704 			break;
705 		default:
706 			str_append_c(ret, *p);
707 			break;
708 		}
709 	}
710 	return str_c(ret);
711 }
712 
dict_unescape_string(const char * str)713 const char *dict_unescape_string(const char *str)
714 {
715 	const char *p;
716 	string_t *ret;
717 
718 	/* see if we need to unescape it */
719 	for (p = str; *p != '\0'; p++) {
720 		if (*p == '\\')
721 			break;
722 	}
723 
724 	if (*p == '\0')
725 		return str;
726 
727 	/* unescape */
728 	ret = t_str_new((size_t) (p - str) + strlen(p) + 1);
729 	str_append_data(ret, str, (size_t) (p - str));
730 
731 	for (; *p != '\0'; p++) {
732 		if (*p != '\\')
733 			str_append_c(ret, *p);
734 		else {
735 			if (*++p == '|')
736 				str_append_c(ret, '/');
737 			else if (*p == '\0')
738 				break;
739 			else
740 				str_append_c(ret, *p);
741 		}
742 	}
743 	return str_c(ret);
744 }
745 
dict_op_settings_dup(const struct dict_op_settings * source,struct dict_op_settings_private * dest_r)746 void dict_op_settings_dup(const struct dict_op_settings *source,
747 			  struct dict_op_settings_private *dest_r)
748 {
749 	i_zero(dest_r);
750 	dest_r->username = i_strdup(source->username);
751 	dest_r->home_dir = i_strdup(source->home_dir);
752 }
753 
dict_op_settings_private_free(struct dict_op_settings_private * set)754 void dict_op_settings_private_free(struct dict_op_settings_private *set)
755 {
756 	i_free(set->username);
757 	i_free(set->home_dir);
758 }
759