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