Mercurial > dovecot > original-hg > dovecot-1.2
annotate src/lib-dict/dict-client.c @ 4385:2c4a39afea07 HEAD
Fixes
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Fri, 16 Jun 2006 14:42:59 +0300 |
parents | 51a1d0fbbc94 |
children | b5d4c1e9a492 |
rev | line source |
---|---|
3793 | 1 /* Copyright (C) 2005 Timo Sirainen */ |
2 | |
3 #include "lib.h" | |
4 #include "str.h" | |
5 #include "network.h" | |
6 #include "istream.h" | |
7 #include "ostream.h" | |
8 #include "dict-private.h" | |
9 #include "dict-client.h" | |
10 | |
11 #include <unistd.h> | |
12 #include <fcntl.h> | |
13 | |
14 struct client_dict { | |
15 struct dict dict; | |
16 | |
17 pool_t pool; | |
18 int fd; | |
19 const char *uri; | |
20 const char *username; | |
21 const char *path; | |
22 | |
23 struct istream *input; | |
24 struct ostream *output; | |
25 | |
26 unsigned int connect_counter; | |
27 unsigned int transaction_id_counter; | |
28 | |
29 unsigned int in_iteration:1; | |
30 unsigned int handshaked:1; | |
31 }; | |
32 | |
33 struct client_dict_iterate_context { | |
34 struct dict_iterate_context ctx; | |
35 | |
36 pool_t pool; | |
3863
55df57c028d4
Added "bool" type and changed all ints that were used as booleans to bool.
Timo Sirainen <tss@iki.fi>
parents:
3858
diff
changeset
|
37 bool failed; |
3793 | 38 }; |
39 | |
40 struct client_dict_transaction_context { | |
41 struct dict_transaction_context ctx; | |
42 | |
43 unsigned int id; | |
44 unsigned int connect_counter; | |
45 | |
3863
55df57c028d4
Added "bool" type and changed all ints that were used as booleans to bool.
Timo Sirainen <tss@iki.fi>
parents:
3858
diff
changeset
|
46 bool failed; |
3793 | 47 }; |
48 | |
49 static int client_dict_connect(struct client_dict *dict); | |
50 static void client_dict_disconnect(struct client_dict *dict); | |
51 | |
52 const char *dict_client_escape(const char *src) | |
53 { | |
54 const char *p; | |
55 string_t *dest; | |
56 | |
57 /* first do a quick lookup to see if there's anything to escape. | |
58 probably not. */ | |
59 for (p = src; *p != '\0'; p++) { | |
60 if (*p == '\t' || *p == '\n' || *p == '\001') | |
61 break; | |
62 } | |
63 | |
64 if (*p == '\0') | |
65 return src; | |
66 | |
67 dest = t_str_new(256); | |
68 str_append_n(dest, src, p - src); | |
69 | |
3858 | 70 for (; *p != '\0'; p++) { |
3793 | 71 switch (*p) { |
72 case '\t': | |
73 str_append_c(dest, '\001'); | |
74 str_append_c(dest, 't'); | |
75 break; | |
76 case '\n': | |
77 str_append_c(dest, '\001'); | |
78 str_append_c(dest, 'n'); | |
79 break; | |
80 case '\001': | |
81 str_append_c(dest, '\001'); | |
82 str_append_c(dest, '1'); | |
83 break; | |
84 default: | |
85 str_append_c(dest, *p); | |
86 break; | |
87 } | |
88 } | |
89 return str_c(dest); | |
90 } | |
91 | |
92 const char *dict_client_unescape(const char *src) | |
93 { | |
94 const char *p; | |
95 string_t *dest; | |
96 | |
97 /* first do a quick lookup to see if there's anything to unescape. | |
98 probably not. */ | |
99 for (p = src; *p != '\0'; p++) { | |
100 if (*p == '\001') | |
101 break; | |
102 } | |
103 | |
104 if (*p == '\0') | |
105 return src; | |
106 | |
107 dest = t_str_new(256); | |
108 str_append_n(dest, src, p - src); | |
109 for (; *p != '\0'; p++) { | |
110 if (*p != '\001') | |
111 str_append_c(dest, *p); | |
112 else if (p[1] != '\0') { | |
113 p++; | |
114 switch (*p) { | |
115 case '1': | |
116 str_append_c(dest, '\001'); | |
117 break; | |
118 case 't': | |
119 str_append_c(dest, '\t'); | |
120 break; | |
121 case 'n': | |
122 str_append_c(dest, '\n'); | |
123 break; | |
124 } | |
125 } | |
126 } | |
127 return str_c(dest); | |
128 } | |
129 | |
130 static int client_dict_send_query(struct client_dict *dict, const char *query) | |
131 { | |
4368 | 132 if (dict->output == NULL) { |
133 /* not connected currently */ | |
134 if (client_dict_connect(dict) < 0) | |
135 return -1; | |
136 } | |
137 | |
3793 | 138 if (o_stream_send_str(dict->output, query) < 0 || |
139 o_stream_flush(dict->output) < 0) { | |
140 /* Send failed */ | |
141 if (!dict->handshaked) { | |
142 /* we're trying to send hello, don't try to reconnect */ | |
143 return -1; | |
144 } | |
145 | |
146 /* Reconnect and try again. */ | |
147 client_dict_disconnect(dict); | |
148 if (client_dict_connect(dict) < 0) | |
149 return -1; | |
150 | |
151 if (o_stream_send_str(dict->output, query) < 0 || | |
152 o_stream_flush(dict->output) < 0) { | |
153 i_error("write(%s) failed: %m", dict->path); | |
154 return -1; | |
155 } | |
156 } | |
157 return 0; | |
158 } | |
159 | |
4368 | 160 static int |
161 client_dict_send_transaction_query(struct client_dict_transaction_context *ctx, | |
162 const char *query) | |
163 { | |
164 struct client_dict *dict = (struct client_dict *)ctx->ctx.dict; | |
165 | |
4385 | 166 if (ctx->connect_counter != dict->connect_counter || ctx->failed) |
167 return -1; | |
168 | |
169 if (dict->output == NULL) { | |
170 /* not connected currently */ | |
171 if (client_dict_connect(dict) < 0) | |
172 return -1; | |
173 } | |
174 | |
4368 | 175 if (o_stream_send_str(dict->output, query) < 0 || |
176 o_stream_flush(dict->output) < 0) { | |
177 /* Send failed. Our transactions have died, so don't even try | |
178 to re-send the command */ | |
179 ctx->failed = TRUE; | |
180 client_dict_disconnect(dict); | |
181 return -1; | |
182 } | |
183 return 0; | |
184 } | |
185 | |
3793 | 186 static char *client_dict_read_line(struct client_dict *dict) |
187 { | |
188 char *line; | |
189 int ret; | |
190 | |
191 line = NULL; | |
192 while ((ret = i_stream_read(dict->input)) > 0) { | |
193 line = i_stream_next_line(dict->input); | |
194 if (line != NULL) | |
195 return line; | |
196 } | |
197 | |
198 i_error("read(%s) failed: %m", dict->path); | |
199 return NULL; | |
200 } | |
201 | |
202 static int client_dict_connect(struct client_dict *dict) | |
203 { | |
204 const char *query; | |
205 | |
206 i_assert(dict->fd == -1); | |
207 | |
208 dict->fd = net_connect_unix(dict->path); | |
209 if (dict->fd == -1) { | |
210 i_error("net_connect_unix(%s) failed: %m", dict->path); | |
211 return -1; | |
212 } | |
213 | |
214 /* Dictionary lookups are blocking */ | |
215 net_set_nonblock(dict->fd, FALSE); | |
216 | |
217 dict->input = i_stream_create_file(dict->fd, default_pool, | |
218 (size_t)-1, FALSE); | |
219 dict->output = o_stream_create_file(dict->fd, default_pool, | |
220 4096, FALSE); | |
221 dict->transaction_id_counter = 0; | |
222 | |
223 t_push(); | |
3852 | 224 query = t_strdup_printf("%c%u\t%u\t%s\t%s\n", DICT_PROTOCOL_CMD_HELLO, |
3793 | 225 DICT_CLIENT_PROTOCOL_MAJOR_VERSION, |
226 DICT_CLIENT_PROTOCOL_MINOR_VERSION, | |
227 dict->username, dict->uri); | |
228 if (client_dict_send_query(dict, query) < 0) { | |
229 client_dict_disconnect(dict); | |
4367
763401b5b344
t_pop() calls were missing from some error handling paths
Timo Sirainen <tss@iki.fi>
parents:
4070
diff
changeset
|
230 t_pop(); |
3793 | 231 return -1; |
232 } | |
233 t_pop(); | |
234 | |
235 dict->handshaked = TRUE; | |
236 return 0; | |
237 } | |
238 | |
239 static void client_dict_disconnect(struct client_dict *dict) | |
240 { | |
241 dict->connect_counter++; | |
242 dict->handshaked = FALSE; | |
243 | |
3879
928229f8b3e6
deinit, unref, destroy, close, free, etc. functions now take a pointer to
Timo Sirainen <tss@iki.fi>
parents:
3863
diff
changeset
|
244 if (dict->input != NULL) |
4070
71b8faa84ec6
Added i_stream_destroy() and o_stream_destroy() and used them instead of
Timo Sirainen <tss@iki.fi>
parents:
3990
diff
changeset
|
245 i_stream_destroy(&dict->input); |
3879
928229f8b3e6
deinit, unref, destroy, close, free, etc. functions now take a pointer to
Timo Sirainen <tss@iki.fi>
parents:
3863
diff
changeset
|
246 if (dict->output != NULL) |
4070
71b8faa84ec6
Added i_stream_destroy() and o_stream_destroy() and used them instead of
Timo Sirainen <tss@iki.fi>
parents:
3990
diff
changeset
|
247 o_stream_destroy(&dict->output); |
3793 | 248 |
249 if (dict->fd != -1) { | |
250 if (close(dict->fd) < 0) | |
251 i_error("close(%s) failed: %m", dict->path); | |
252 dict->fd = -1; | |
253 } | |
254 } | |
255 | |
3967
6fabe878c46d
Dictionary takes now a username parameter, which is used for private
Timo Sirainen <tss@iki.fi>
parents:
3879
diff
changeset
|
256 static struct dict *client_dict_init(struct dict *dict_class, const char *uri, |
6fabe878c46d
Dictionary takes now a username parameter, which is used for private
Timo Sirainen <tss@iki.fi>
parents:
3879
diff
changeset
|
257 const char *username) |
3793 | 258 { |
259 struct client_dict *dict; | |
3967
6fabe878c46d
Dictionary takes now a username parameter, which is used for private
Timo Sirainen <tss@iki.fi>
parents:
3879
diff
changeset
|
260 const char *dest_uri; |
3793 | 261 pool_t pool; |
262 | |
3967
6fabe878c46d
Dictionary takes now a username parameter, which is used for private
Timo Sirainen <tss@iki.fi>
parents:
3879
diff
changeset
|
263 /* uri = [<path>] ":" <uri> */ |
3793 | 264 dest_uri = strchr(uri, ':'); |
265 if (dest_uri == NULL) { | |
266 i_error("dict-client: Invalid URI: %s", uri); | |
267 return NULL; | |
268 } | |
269 | |
270 pool = pool_alloconly_create("client dict", 1024); | |
271 dict = p_new(pool, struct client_dict, 1); | |
272 dict->pool = pool; | |
273 dict->dict = *dict_class; | |
3967
6fabe878c46d
Dictionary takes now a username parameter, which is used for private
Timo Sirainen <tss@iki.fi>
parents:
3879
diff
changeset
|
274 dict->username = p_strdup(pool, username); |
3793 | 275 |
276 dict->fd = -1; | |
277 | |
3967
6fabe878c46d
Dictionary takes now a username parameter, which is used for private
Timo Sirainen <tss@iki.fi>
parents:
3879
diff
changeset
|
278 if (*uri != ':') { |
3793 | 279 /* path given */ |
3967
6fabe878c46d
Dictionary takes now a username parameter, which is used for private
Timo Sirainen <tss@iki.fi>
parents:
3879
diff
changeset
|
280 dict->path = p_strdup_until(pool, uri, dest_uri); |
3793 | 281 } else { |
282 dict->path = DEFAULT_DICT_SERVER_SOCKET_PATH; | |
283 } | |
284 dict->uri = p_strdup(pool, dest_uri + 1); | |
285 | |
286 (void)client_dict_connect(dict); | |
287 return &dict->dict; | |
288 } | |
289 | |
290 static void client_dict_deinit(struct dict *_dict) | |
291 { | |
292 struct client_dict *dict = (struct client_dict *)_dict; | |
293 | |
294 client_dict_disconnect(dict); | |
295 pool_unref(dict->pool); | |
296 } | |
297 | |
298 static int client_dict_lookup(struct dict *_dict, pool_t pool, | |
299 const char *key, const char **value_r) | |
300 { | |
301 struct client_dict *dict = (struct client_dict *)_dict; | |
302 const char *line; | |
303 | |
304 if (dict->fd == -1) | |
305 return -1; | |
306 | |
307 t_push(); | |
308 line = t_strdup_printf("%c%s\n", DICT_PROTOCOL_CMD_LOOKUP, | |
309 dict_client_escape(key)); | |
310 if (client_dict_send_query(dict, line) < 0) { | |
311 t_pop(); | |
312 return -1; | |
313 } | |
314 t_pop(); | |
315 | |
316 /* read reply */ | |
317 line = client_dict_read_line(dict); | |
318 if (line == NULL) | |
319 return -1; | |
320 | |
321 if (*line == DICT_PROTOCOL_REPLY_OK) { | |
322 *value_r = p_strdup(pool, dict_client_unescape(line + 1)); | |
323 return 1; | |
324 } else { | |
325 *value_r = NULL; | |
326 return *line == DICT_PROTOCOL_REPLY_NOTFOUND ? 0 : -1; | |
327 } | |
328 } | |
329 | |
330 static struct dict_iterate_context * | |
3863
55df57c028d4
Added "bool" type and changed all ints that were used as booleans to bool.
Timo Sirainen <tss@iki.fi>
parents:
3858
diff
changeset
|
331 client_dict_iterate_init(struct dict *_dict, const char *path, bool recurse) |
3793 | 332 { |
333 struct client_dict *dict = (struct client_dict *)_dict; | |
334 struct client_dict_iterate_context *ctx; | |
335 const char *query; | |
336 | |
337 if (dict->in_iteration) | |
338 i_panic("dict-client: Only one iteration supported"); | |
339 dict->in_iteration = TRUE; | |
340 | |
341 ctx = i_new(struct client_dict_iterate_context, 1); | |
342 ctx->ctx.dict = _dict; | |
343 ctx->pool = pool_alloconly_create("client dict iteration", 512); | |
344 | |
345 t_push(); | |
346 query = t_strdup_printf("%c%d\t%s\n", DICT_PROTOCOL_CMD_ITERATE, | |
347 recurse, dict_client_escape(path)); | |
348 if (client_dict_send_query(dict, query) < 0) | |
349 ctx->failed = TRUE; | |
350 t_pop(); | |
351 | |
352 return &ctx->ctx; | |
353 } | |
354 | |
355 static int client_dict_iterate(struct dict_iterate_context *_ctx, | |
356 const char **key_r, const char **value_r) | |
357 { | |
358 struct client_dict_iterate_context *ctx = | |
359 (struct client_dict_iterate_context *)_ctx; | |
360 struct client_dict *dict = (struct client_dict *)_ctx->dict; | |
361 char *line, *value; | |
362 | |
363 if (ctx->failed) | |
364 return -1; | |
365 | |
366 /* read next reply */ | |
367 line = client_dict_read_line(dict); | |
368 if (line == NULL) | |
369 return -1; | |
370 | |
371 if (*line == '\0') { | |
372 /* end of iteration */ | |
373 return 0; | |
374 } | |
375 | |
376 /* line contains key \t value */ | |
377 p_clear(ctx->pool); | |
378 | |
379 value = strchr(line, '\t'); | |
380 if (value == NULL) { | |
381 /* broken protocol */ | |
382 i_error("dict client (%s) sent broken reply", dict->path); | |
383 return -1; | |
384 } | |
385 *value++ = '\0'; | |
386 | |
387 *key_r = p_strdup(ctx->pool, dict_client_unescape(line)); | |
388 *value_r = p_strdup(ctx->pool, dict_client_unescape(value)); | |
389 return 1; | |
390 } | |
391 | |
392 static void client_dict_iterate_deinit(struct dict_iterate_context *_ctx) | |
393 { | |
394 struct client_dict *dict = (struct client_dict *)_ctx->dict; | |
395 struct client_dict_iterate_context *ctx = | |
396 (struct client_dict_iterate_context *)_ctx; | |
397 | |
398 pool_unref(ctx->pool); | |
399 i_free(ctx); | |
400 dict->in_iteration = TRUE; | |
401 } | |
402 | |
403 static struct dict_transaction_context * | |
404 client_dict_transaction_init(struct dict *_dict) | |
405 { | |
406 struct client_dict *dict = (struct client_dict *)_dict; | |
407 struct client_dict_transaction_context *ctx; | |
4368 | 408 const char *query; |
3793 | 409 |
410 ctx = i_new(struct client_dict_transaction_context, 1); | |
411 ctx->ctx.dict = _dict; | |
412 ctx->id = ++dict->transaction_id_counter; | |
4368 | 413 |
414 t_push(); | |
415 query = t_strdup_printf("%c%u\n", DICT_PROTOCOL_CMD_BEGIN, ctx->id); | |
416 if (client_dict_send_query(dict, query) < 0) | |
417 ctx->failed = TRUE; | |
418 else | |
419 ctx->connect_counter = dict->connect_counter; | |
420 t_pop(); | |
421 | |
3793 | 422 return &ctx->ctx; |
423 } | |
424 | |
425 static int client_dict_transaction_commit(struct dict_transaction_context *_ctx) | |
426 { | |
427 struct client_dict_transaction_context *ctx = | |
428 (struct client_dict_transaction_context *)_ctx; | |
429 struct client_dict *dict = (struct client_dict *)_ctx->dict; | |
430 const char *query, *line; | |
431 int ret = ctx->failed ? -1 : 0; | |
432 | |
4385 | 433 t_push(); |
434 query = t_strdup_printf("%c%u\n", !ctx->failed ? | |
435 DICT_PROTOCOL_CMD_COMMIT : | |
436 DICT_PROTOCOL_CMD_ROLLBACK, ctx->id); | |
437 if (client_dict_send_transaction_query(ctx, query) < 0) | |
3793 | 438 ret = -1; |
4385 | 439 else if (ret == 0) { |
440 /* read reply */ | |
441 line = client_dict_read_line(dict); | |
442 if (line == NULL || *line != DICT_PROTOCOL_REPLY_OK) | |
3793 | 443 ret = -1; |
4385 | 444 } |
3793 | 445 |
4385 | 446 t_pop(); |
3793 | 447 i_free(ctx); |
448 | |
449 return ret; | |
450 } | |
451 | |
452 static void | |
453 client_dict_transaction_rollback(struct dict_transaction_context *_ctx) | |
454 { | |
455 struct client_dict_transaction_context *ctx = | |
456 (struct client_dict_transaction_context *)_ctx; | |
457 const char *query; | |
458 | |
4385 | 459 t_push(); |
460 query = t_strdup_printf("%c%u\n", DICT_PROTOCOL_CMD_ROLLBACK, ctx->id); | |
461 (void)client_dict_send_transaction_query(ctx, query); | |
462 t_pop(); | |
463 | |
3793 | 464 i_free(ctx); |
465 } | |
466 | |
467 static void client_dict_set(struct dict_transaction_context *_ctx, | |
468 const char *key, const char *value) | |
469 { | |
470 struct client_dict_transaction_context *ctx = | |
471 (struct client_dict_transaction_context *)_ctx; | |
472 const char *query; | |
473 | |
474 t_push(); | |
3990
e2e6919c6c4d
LF wasn't sent at the end of all commands.
Timo Sirainen <tss@iki.fi>
parents:
3967
diff
changeset
|
475 query = t_strdup_printf("%c%u\t%s\t%s\n", |
e2e6919c6c4d
LF wasn't sent at the end of all commands.
Timo Sirainen <tss@iki.fi>
parents:
3967
diff
changeset
|
476 DICT_PROTOCOL_CMD_SET, ctx->id, |
3793 | 477 dict_client_escape(key), |
478 dict_client_escape(value)); | |
4368 | 479 (void)client_dict_send_transaction_query(ctx, query); |
3793 | 480 t_pop(); |
481 } | |
482 | |
483 static void client_dict_atomic_inc(struct dict_transaction_context *_ctx, | |
3990
e2e6919c6c4d
LF wasn't sent at the end of all commands.
Timo Sirainen <tss@iki.fi>
parents:
3967
diff
changeset
|
484 const char *key, long long diff) |
3793 | 485 { |
486 struct client_dict_transaction_context *ctx = | |
487 (struct client_dict_transaction_context *)_ctx; | |
488 const char *query; | |
489 | |
490 t_push(); | |
3990
e2e6919c6c4d
LF wasn't sent at the end of all commands.
Timo Sirainen <tss@iki.fi>
parents:
3967
diff
changeset
|
491 query = t_strdup_printf("%c%u\t%s\t%lld\n", |
e2e6919c6c4d
LF wasn't sent at the end of all commands.
Timo Sirainen <tss@iki.fi>
parents:
3967
diff
changeset
|
492 DICT_PROTOCOL_CMD_ATOMIC_INC, |
3793 | 493 ctx->id, dict_client_escape(key), diff); |
4368 | 494 (void)client_dict_send_transaction_query(ctx, query); |
3793 | 495 t_pop(); |
496 } | |
497 | |
498 static struct dict client_dict = { | |
499 MEMBER(name) "proxy", | |
500 | |
501 { | |
502 client_dict_init, | |
503 client_dict_deinit, | |
504 client_dict_lookup, | |
505 client_dict_iterate_init, | |
506 client_dict_iterate, | |
507 client_dict_iterate_deinit, | |
508 client_dict_transaction_init, | |
509 client_dict_transaction_commit, | |
510 client_dict_transaction_rollback, | |
511 client_dict_set, | |
512 client_dict_atomic_inc | |
513 } | |
514 }; | |
515 | |
516 void dict_client_register(void) | |
517 { | |
518 dict_class_register(&client_dict); | |
519 } | |
520 | |
521 void dict_client_unregister(void) | |
522 { | |
523 dict_class_unregister(&client_dict); | |
524 } |