Mercurial > dovecot > original-hg > dovecot-1.2
annotate src/lib-dict/dict-client.c @ 4070:71b8faa84ec6 HEAD
Added i_stream_destroy() and o_stream_destroy() and used them instead of
*_stream_unref() where possible. Fixes at least one problem with io_remove()
being called after socket was closed, which caused problems with epoll.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Sun, 26 Feb 2006 12:04:59 +0200 |
parents | e2e6919c6c4d |
children | 763401b5b344 |
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 { | |
132 if (o_stream_send_str(dict->output, query) < 0 || | |
133 o_stream_flush(dict->output) < 0) { | |
134 /* Send failed */ | |
135 if (!dict->handshaked) { | |
136 /* we're trying to send hello, don't try to reconnect */ | |
137 return -1; | |
138 } | |
139 | |
140 /* Reconnect and try again. */ | |
141 client_dict_disconnect(dict); | |
142 if (client_dict_connect(dict) < 0) | |
143 return -1; | |
144 | |
145 if (o_stream_send_str(dict->output, query) < 0 || | |
146 o_stream_flush(dict->output) < 0) { | |
147 i_error("write(%s) failed: %m", dict->path); | |
148 return -1; | |
149 } | |
150 } | |
151 return 0; | |
152 } | |
153 | |
154 static char *client_dict_read_line(struct client_dict *dict) | |
155 { | |
156 char *line; | |
157 int ret; | |
158 | |
159 line = NULL; | |
160 while ((ret = i_stream_read(dict->input)) > 0) { | |
161 line = i_stream_next_line(dict->input); | |
162 if (line != NULL) | |
163 return line; | |
164 } | |
165 | |
166 i_error("read(%s) failed: %m", dict->path); | |
167 return NULL; | |
168 } | |
169 | |
170 static int client_dict_connect(struct client_dict *dict) | |
171 { | |
172 const char *query; | |
173 | |
174 i_assert(dict->fd == -1); | |
175 | |
176 dict->fd = net_connect_unix(dict->path); | |
177 if (dict->fd == -1) { | |
178 i_error("net_connect_unix(%s) failed: %m", dict->path); | |
179 return -1; | |
180 } | |
181 | |
182 /* Dictionary lookups are blocking */ | |
183 net_set_nonblock(dict->fd, FALSE); | |
184 | |
185 dict->input = i_stream_create_file(dict->fd, default_pool, | |
186 (size_t)-1, FALSE); | |
187 dict->output = o_stream_create_file(dict->fd, default_pool, | |
188 4096, FALSE); | |
189 dict->transaction_id_counter = 0; | |
190 | |
191 t_push(); | |
3852 | 192 query = t_strdup_printf("%c%u\t%u\t%s\t%s\n", DICT_PROTOCOL_CMD_HELLO, |
3793 | 193 DICT_CLIENT_PROTOCOL_MAJOR_VERSION, |
194 DICT_CLIENT_PROTOCOL_MINOR_VERSION, | |
195 dict->username, dict->uri); | |
196 if (client_dict_send_query(dict, query) < 0) { | |
197 client_dict_disconnect(dict); | |
198 return -1; | |
199 } | |
200 t_pop(); | |
201 | |
202 dict->handshaked = TRUE; | |
203 return 0; | |
204 } | |
205 | |
206 static void client_dict_disconnect(struct client_dict *dict) | |
207 { | |
208 dict->connect_counter++; | |
209 dict->handshaked = FALSE; | |
210 | |
3879
928229f8b3e6
deinit, unref, destroy, close, free, etc. functions now take a pointer to
Timo Sirainen <tss@iki.fi>
parents:
3863
diff
changeset
|
211 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
|
212 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
|
213 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
|
214 o_stream_destroy(&dict->output); |
3793 | 215 |
216 if (dict->fd != -1) { | |
217 if (close(dict->fd) < 0) | |
218 i_error("close(%s) failed: %m", dict->path); | |
219 dict->fd = -1; | |
220 } | |
221 } | |
222 | |
3967
6fabe878c46d
Dictionary takes now a username parameter, which is used for private
Timo Sirainen <tss@iki.fi>
parents:
3879
diff
changeset
|
223 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
|
224 const char *username) |
3793 | 225 { |
226 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
|
227 const char *dest_uri; |
3793 | 228 pool_t pool; |
229 | |
3967
6fabe878c46d
Dictionary takes now a username parameter, which is used for private
Timo Sirainen <tss@iki.fi>
parents:
3879
diff
changeset
|
230 /* uri = [<path>] ":" <uri> */ |
3793 | 231 dest_uri = strchr(uri, ':'); |
232 if (dest_uri == NULL) { | |
233 i_error("dict-client: Invalid URI: %s", uri); | |
234 return NULL; | |
235 } | |
236 | |
237 pool = pool_alloconly_create("client dict", 1024); | |
238 dict = p_new(pool, struct client_dict, 1); | |
239 dict->pool = pool; | |
240 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
|
241 dict->username = p_strdup(pool, username); |
3793 | 242 |
243 dict->fd = -1; | |
244 | |
3967
6fabe878c46d
Dictionary takes now a username parameter, which is used for private
Timo Sirainen <tss@iki.fi>
parents:
3879
diff
changeset
|
245 if (*uri != ':') { |
3793 | 246 /* path given */ |
3967
6fabe878c46d
Dictionary takes now a username parameter, which is used for private
Timo Sirainen <tss@iki.fi>
parents:
3879
diff
changeset
|
247 dict->path = p_strdup_until(pool, uri, dest_uri); |
3793 | 248 } else { |
249 dict->path = DEFAULT_DICT_SERVER_SOCKET_PATH; | |
250 } | |
251 dict->uri = p_strdup(pool, dest_uri + 1); | |
252 | |
253 (void)client_dict_connect(dict); | |
254 return &dict->dict; | |
255 } | |
256 | |
257 static void client_dict_deinit(struct dict *_dict) | |
258 { | |
259 struct client_dict *dict = (struct client_dict *)_dict; | |
260 | |
261 client_dict_disconnect(dict); | |
262 pool_unref(dict->pool); | |
263 } | |
264 | |
265 static int client_dict_lookup(struct dict *_dict, pool_t pool, | |
266 const char *key, const char **value_r) | |
267 { | |
268 struct client_dict *dict = (struct client_dict *)_dict; | |
269 const char *line; | |
270 | |
271 if (dict->fd == -1) | |
272 return -1; | |
273 | |
274 t_push(); | |
275 line = t_strdup_printf("%c%s\n", DICT_PROTOCOL_CMD_LOOKUP, | |
276 dict_client_escape(key)); | |
277 if (client_dict_send_query(dict, line) < 0) { | |
278 t_pop(); | |
279 return -1; | |
280 } | |
281 t_pop(); | |
282 | |
283 /* read reply */ | |
284 line = client_dict_read_line(dict); | |
285 if (line == NULL) | |
286 return -1; | |
287 | |
288 if (*line == DICT_PROTOCOL_REPLY_OK) { | |
289 *value_r = p_strdup(pool, dict_client_unescape(line + 1)); | |
290 return 1; | |
291 } else { | |
292 *value_r = NULL; | |
293 return *line == DICT_PROTOCOL_REPLY_NOTFOUND ? 0 : -1; | |
294 } | |
295 } | |
296 | |
297 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
|
298 client_dict_iterate_init(struct dict *_dict, const char *path, bool recurse) |
3793 | 299 { |
300 struct client_dict *dict = (struct client_dict *)_dict; | |
301 struct client_dict_iterate_context *ctx; | |
302 const char *query; | |
303 | |
304 if (dict->in_iteration) | |
305 i_panic("dict-client: Only one iteration supported"); | |
306 dict->in_iteration = TRUE; | |
307 | |
308 ctx = i_new(struct client_dict_iterate_context, 1); | |
309 ctx->ctx.dict = _dict; | |
310 ctx->pool = pool_alloconly_create("client dict iteration", 512); | |
311 | |
312 t_push(); | |
313 query = t_strdup_printf("%c%d\t%s\n", DICT_PROTOCOL_CMD_ITERATE, | |
314 recurse, dict_client_escape(path)); | |
315 if (client_dict_send_query(dict, query) < 0) | |
316 ctx->failed = TRUE; | |
317 t_pop(); | |
318 | |
319 return &ctx->ctx; | |
320 } | |
321 | |
322 static int client_dict_iterate(struct dict_iterate_context *_ctx, | |
323 const char **key_r, const char **value_r) | |
324 { | |
325 struct client_dict_iterate_context *ctx = | |
326 (struct client_dict_iterate_context *)_ctx; | |
327 struct client_dict *dict = (struct client_dict *)_ctx->dict; | |
328 char *line, *value; | |
329 | |
330 if (ctx->failed) | |
331 return -1; | |
332 | |
333 /* read next reply */ | |
334 line = client_dict_read_line(dict); | |
335 if (line == NULL) | |
336 return -1; | |
337 | |
338 if (*line == '\0') { | |
339 /* end of iteration */ | |
340 return 0; | |
341 } | |
342 | |
343 /* line contains key \t value */ | |
344 p_clear(ctx->pool); | |
345 | |
346 value = strchr(line, '\t'); | |
347 if (value == NULL) { | |
348 /* broken protocol */ | |
349 i_error("dict client (%s) sent broken reply", dict->path); | |
350 return -1; | |
351 } | |
352 *value++ = '\0'; | |
353 | |
354 *key_r = p_strdup(ctx->pool, dict_client_unescape(line)); | |
355 *value_r = p_strdup(ctx->pool, dict_client_unescape(value)); | |
356 return 1; | |
357 } | |
358 | |
359 static void client_dict_iterate_deinit(struct dict_iterate_context *_ctx) | |
360 { | |
361 struct client_dict *dict = (struct client_dict *)_ctx->dict; | |
362 struct client_dict_iterate_context *ctx = | |
363 (struct client_dict_iterate_context *)_ctx; | |
364 | |
365 pool_unref(ctx->pool); | |
366 i_free(ctx); | |
367 dict->in_iteration = TRUE; | |
368 } | |
369 | |
370 static struct dict_transaction_context * | |
371 client_dict_transaction_init(struct dict *_dict) | |
372 { | |
373 struct client_dict *dict = (struct client_dict *)_dict; | |
374 struct client_dict_transaction_context *ctx; | |
375 | |
376 ctx = i_new(struct client_dict_transaction_context, 1); | |
377 ctx->ctx.dict = _dict; | |
378 ctx->id = ++dict->transaction_id_counter; | |
379 ctx->connect_counter = dict->connect_counter; | |
380 return &ctx->ctx; | |
381 } | |
382 | |
383 static int client_dict_transaction_commit(struct dict_transaction_context *_ctx) | |
384 { | |
385 struct client_dict_transaction_context *ctx = | |
386 (struct client_dict_transaction_context *)_ctx; | |
387 struct client_dict *dict = (struct client_dict *)_ctx->dict; | |
388 const char *query, *line; | |
389 int ret = ctx->failed ? -1 : 0; | |
390 | |
391 if (ctx->connect_counter != dict->connect_counter) | |
392 ret = -1; | |
393 else { | |
394 t_push(); | |
3990
e2e6919c6c4d
LF wasn't sent at the end of all commands.
Timo Sirainen <tss@iki.fi>
parents:
3967
diff
changeset
|
395 query = t_strdup_printf("%c%u\n", !ctx->failed ? |
3793 | 396 DICT_PROTOCOL_CMD_COMMIT : |
397 DICT_PROTOCOL_CMD_ROLLBACK, | |
398 ctx->id); | |
399 if (client_dict_send_query(dict, query) < 0) | |
400 ret = -1; | |
401 else if (ret == 0) { | |
402 /* read reply */ | |
403 line = client_dict_read_line(dict); | |
404 if (line == NULL) | |
405 return -1; | |
406 | |
407 if (*line != DICT_PROTOCOL_REPLY_OK) | |
408 ret = -1; | |
409 } | |
410 | |
411 t_pop(); | |
412 } | |
413 i_free(ctx); | |
414 | |
415 return ret; | |
416 } | |
417 | |
418 static void | |
419 client_dict_transaction_rollback(struct dict_transaction_context *_ctx) | |
420 { | |
421 struct client_dict_transaction_context *ctx = | |
422 (struct client_dict_transaction_context *)_ctx; | |
423 struct client_dict *dict = (struct client_dict *)_ctx->dict; | |
424 const char *query; | |
425 | |
426 if (ctx->connect_counter == dict->connect_counter) { | |
427 t_push(); | |
3990
e2e6919c6c4d
LF wasn't sent at the end of all commands.
Timo Sirainen <tss@iki.fi>
parents:
3967
diff
changeset
|
428 query = t_strdup_printf("%c%u\n", DICT_PROTOCOL_CMD_ROLLBACK, |
3793 | 429 ctx->id); |
430 (void)client_dict_send_query(dict, query); | |
431 t_pop(); | |
432 } | |
433 i_free(ctx); | |
434 } | |
435 | |
436 static void client_dict_set(struct dict_transaction_context *_ctx, | |
437 const char *key, const char *value) | |
438 { | |
439 struct client_dict_transaction_context *ctx = | |
440 (struct client_dict_transaction_context *)_ctx; | |
441 struct client_dict *dict = (struct client_dict *)_ctx->dict; | |
442 const char *query; | |
443 | |
444 if (ctx->connect_counter != dict->connect_counter) | |
445 return; | |
446 | |
447 t_push(); | |
3990
e2e6919c6c4d
LF wasn't sent at the end of all commands.
Timo Sirainen <tss@iki.fi>
parents:
3967
diff
changeset
|
448 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
|
449 DICT_PROTOCOL_CMD_SET, ctx->id, |
3793 | 450 dict_client_escape(key), |
451 dict_client_escape(value)); | |
452 if (client_dict_send_query(dict, query) < 0) | |
453 ctx->failed = TRUE; | |
454 t_pop(); | |
455 } | |
456 | |
457 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
|
458 const char *key, long long diff) |
3793 | 459 { |
460 struct client_dict_transaction_context *ctx = | |
461 (struct client_dict_transaction_context *)_ctx; | |
462 struct client_dict *dict = (struct client_dict *)_ctx->dict; | |
463 const char *query; | |
464 | |
465 if (ctx->connect_counter != dict->connect_counter) | |
466 return; | |
467 | |
468 t_push(); | |
3990
e2e6919c6c4d
LF wasn't sent at the end of all commands.
Timo Sirainen <tss@iki.fi>
parents:
3967
diff
changeset
|
469 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
|
470 DICT_PROTOCOL_CMD_ATOMIC_INC, |
3793 | 471 ctx->id, dict_client_escape(key), diff); |
472 if (client_dict_send_query(dict, query) < 0) | |
473 ctx->failed = TRUE; | |
474 t_pop(); | |
475 } | |
476 | |
477 static struct dict client_dict = { | |
478 MEMBER(name) "proxy", | |
479 | |
480 { | |
481 client_dict_init, | |
482 client_dict_deinit, | |
483 client_dict_lookup, | |
484 client_dict_iterate_init, | |
485 client_dict_iterate, | |
486 client_dict_iterate_deinit, | |
487 client_dict_transaction_init, | |
488 client_dict_transaction_commit, | |
489 client_dict_transaction_rollback, | |
490 client_dict_set, | |
491 client_dict_atomic_inc | |
492 } | |
493 }; | |
494 | |
495 void dict_client_register(void) | |
496 { | |
497 dict_class_register(&client_dict); | |
498 } | |
499 | |
500 void dict_client_unregister(void) | |
501 { | |
502 dict_class_unregister(&client_dict); | |
503 } |