Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com> |
3 | | * Copyright (c) 2010-2014, Pieter Noordhuis <pcnoordhuis at gmail dot com> |
4 | | * Copyright (c) 2015, Matt Stancliff <matt at genges dot com>, |
5 | | * Jan-Erik Rediger <janerik at fnordig dot com> |
6 | | * |
7 | | * All rights reserved. |
8 | | * |
9 | | * Redistribution and use in source and binary forms, with or without |
10 | | * modification, are permitted provided that the following conditions are met: |
11 | | * |
12 | | * * Redistributions of source code must retain the above copyright notice, |
13 | | * this list of conditions and the following disclaimer. |
14 | | * * Redistributions in binary form must reproduce the above copyright |
15 | | * notice, this list of conditions and the following disclaimer in the |
16 | | * documentation and/or other materials provided with the distribution. |
17 | | * * Neither the name of Redis nor the names of its contributors may be used |
18 | | * to endorse or promote products derived from this software without |
19 | | * specific prior written permission. |
20 | | * |
21 | | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
22 | | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
23 | | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
24 | | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
25 | | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
26 | | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
27 | | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
28 | | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
29 | | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
30 | | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
31 | | * POSSIBILITY OF SUCH DAMAGE. |
32 | | */ |
33 | | |
34 | | #include "fmacros.h" |
35 | | #include <string.h> |
36 | | #include <stdlib.h> |
37 | | #include <assert.h> |
38 | | #include <errno.h> |
39 | | #include <ctype.h> |
40 | | |
41 | | #include "hiredis.h" |
42 | | #include "net.h" |
43 | | #include "sds.h" |
44 | | #include "async.h" |
45 | | #include "win32.h" |
46 | | |
47 | | extern int redisContextUpdateConnectTimeout(redisContext *c, const struct timeval *timeout); |
48 | | extern int redisContextUpdateCommandTimeout(redisContext *c, const struct timeval *timeout); |
49 | | |
50 | | static redisContextFuncs redisContextDefaultFuncs = { |
51 | | .close = redisNetClose, |
52 | | .free_privctx = NULL, |
53 | | .async_read = redisAsyncRead, |
54 | | .async_write = redisAsyncWrite, |
55 | | .read = redisNetRead, |
56 | | .write = redisNetWrite |
57 | | }; |
58 | | |
59 | | static redisReply *createReplyObject(int type); |
60 | | static void *createStringObject(const redisReadTask *task, char *str, size_t len); |
61 | | static void *createArrayObject(const redisReadTask *task, size_t elements); |
62 | | static void *createIntegerObject(const redisReadTask *task, long long value); |
63 | | static void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len); |
64 | | static void *createNilObject(const redisReadTask *task); |
65 | | static void *createBoolObject(const redisReadTask *task, int bval); |
66 | | |
67 | | /* Default set of functions to build the reply. Keep in mind that such a |
68 | | * function returning NULL is interpreted as OOM. */ |
69 | | static redisReplyObjectFunctions defaultFunctions = { |
70 | | createStringObject, |
71 | | createArrayObject, |
72 | | createIntegerObject, |
73 | | createDoubleObject, |
74 | | createNilObject, |
75 | | createBoolObject, |
76 | | freeReplyObject |
77 | | }; |
78 | | |
79 | | /* Create a reply object */ |
80 | 0 | static redisReply *createReplyObject(int type) { |
81 | 0 | redisReply *r = hi_calloc(1,sizeof(*r)); |
82 | |
|
83 | 0 | if (r == NULL) |
84 | 0 | return NULL; |
85 | | |
86 | 0 | r->type = type; |
87 | 0 | return r; |
88 | 0 | } |
89 | | |
90 | | /* Free a reply object */ |
91 | 0 | void freeReplyObject(void *reply) { |
92 | 0 | redisReply *r = reply; |
93 | 0 | size_t j; |
94 | |
|
95 | 0 | if (r == NULL) |
96 | 0 | return; |
97 | | |
98 | 0 | switch(r->type) { |
99 | 0 | case REDIS_REPLY_INTEGER: |
100 | 0 | case REDIS_REPLY_NIL: |
101 | 0 | case REDIS_REPLY_BOOL: |
102 | 0 | break; /* Nothing to free */ |
103 | 0 | case REDIS_REPLY_ARRAY: |
104 | 0 | case REDIS_REPLY_MAP: |
105 | 0 | case REDIS_REPLY_ATTR: |
106 | 0 | case REDIS_REPLY_SET: |
107 | 0 | case REDIS_REPLY_PUSH: |
108 | 0 | if (r->element != NULL) { |
109 | 0 | for (j = 0; j < r->elements; j++) |
110 | 0 | freeReplyObject(r->element[j]); |
111 | 0 | hi_free(r->element); |
112 | 0 | } |
113 | 0 | break; |
114 | 0 | case REDIS_REPLY_ERROR: |
115 | 0 | case REDIS_REPLY_STATUS: |
116 | 0 | case REDIS_REPLY_STRING: |
117 | 0 | case REDIS_REPLY_DOUBLE: |
118 | 0 | case REDIS_REPLY_VERB: |
119 | 0 | case REDIS_REPLY_BIGNUM: |
120 | 0 | hi_free(r->str); |
121 | 0 | break; |
122 | 0 | } |
123 | 0 | hi_free(r); |
124 | 0 | } |
125 | | |
126 | 0 | static void *createStringObject(const redisReadTask *task, char *str, size_t len) { |
127 | 0 | redisReply *r, *parent; |
128 | 0 | char *buf; |
129 | |
|
130 | 0 | r = createReplyObject(task->type); |
131 | 0 | if (r == NULL) |
132 | 0 | return NULL; |
133 | | |
134 | 0 | assert(task->type == REDIS_REPLY_ERROR || |
135 | 0 | task->type == REDIS_REPLY_STATUS || |
136 | 0 | task->type == REDIS_REPLY_STRING || |
137 | 0 | task->type == REDIS_REPLY_VERB || |
138 | 0 | task->type == REDIS_REPLY_BIGNUM); |
139 | | |
140 | | /* Copy string value */ |
141 | 0 | if (task->type == REDIS_REPLY_VERB) { |
142 | 0 | buf = hi_malloc(len-4+1); /* Skip 4 bytes of verbatim type header. */ |
143 | 0 | if (buf == NULL) goto oom; |
144 | | |
145 | 0 | memcpy(r->vtype,str,3); |
146 | 0 | r->vtype[3] = '\0'; |
147 | 0 | memcpy(buf,str+4,len-4); |
148 | 0 | buf[len-4] = '\0'; |
149 | 0 | r->len = len - 4; |
150 | 0 | } else { |
151 | 0 | buf = hi_malloc(len+1); |
152 | 0 | if (buf == NULL) goto oom; |
153 | | |
154 | 0 | memcpy(buf,str,len); |
155 | 0 | buf[len] = '\0'; |
156 | 0 | r->len = len; |
157 | 0 | } |
158 | 0 | r->str = buf; |
159 | |
|
160 | 0 | if (task->parent) { |
161 | 0 | parent = task->parent->obj; |
162 | 0 | assert(parent->type == REDIS_REPLY_ARRAY || |
163 | 0 | parent->type == REDIS_REPLY_MAP || |
164 | 0 | parent->type == REDIS_REPLY_ATTR || |
165 | 0 | parent->type == REDIS_REPLY_SET || |
166 | 0 | parent->type == REDIS_REPLY_PUSH); |
167 | 0 | parent->element[task->idx] = r; |
168 | 0 | } |
169 | 0 | return r; |
170 | | |
171 | 0 | oom: |
172 | 0 | freeReplyObject(r); |
173 | 0 | return NULL; |
174 | 0 | } |
175 | | |
176 | 0 | static void *createArrayObject(const redisReadTask *task, size_t elements) { |
177 | 0 | redisReply *r, *parent; |
178 | |
|
179 | 0 | r = createReplyObject(task->type); |
180 | 0 | if (r == NULL) |
181 | 0 | return NULL; |
182 | | |
183 | 0 | if (elements > 0) { |
184 | 0 | r->element = hi_calloc(elements,sizeof(redisReply*)); |
185 | 0 | if (r->element == NULL) { |
186 | 0 | freeReplyObject(r); |
187 | 0 | return NULL; |
188 | 0 | } |
189 | 0 | } |
190 | | |
191 | 0 | r->elements = elements; |
192 | |
|
193 | 0 | if (task->parent) { |
194 | 0 | parent = task->parent->obj; |
195 | 0 | assert(parent->type == REDIS_REPLY_ARRAY || |
196 | 0 | parent->type == REDIS_REPLY_MAP || |
197 | 0 | parent->type == REDIS_REPLY_ATTR || |
198 | 0 | parent->type == REDIS_REPLY_SET || |
199 | 0 | parent->type == REDIS_REPLY_PUSH); |
200 | 0 | parent->element[task->idx] = r; |
201 | 0 | } |
202 | 0 | return r; |
203 | 0 | } |
204 | | |
205 | 0 | static void *createIntegerObject(const redisReadTask *task, long long value) { |
206 | 0 | redisReply *r, *parent; |
207 | |
|
208 | 0 | r = createReplyObject(REDIS_REPLY_INTEGER); |
209 | 0 | if (r == NULL) |
210 | 0 | return NULL; |
211 | | |
212 | 0 | r->integer = value; |
213 | |
|
214 | 0 | if (task->parent) { |
215 | 0 | parent = task->parent->obj; |
216 | 0 | assert(parent->type == REDIS_REPLY_ARRAY || |
217 | 0 | parent->type == REDIS_REPLY_MAP || |
218 | 0 | parent->type == REDIS_REPLY_ATTR || |
219 | 0 | parent->type == REDIS_REPLY_SET || |
220 | 0 | parent->type == REDIS_REPLY_PUSH); |
221 | 0 | parent->element[task->idx] = r; |
222 | 0 | } |
223 | 0 | return r; |
224 | 0 | } |
225 | | |
226 | 0 | static void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len) { |
227 | 0 | redisReply *r, *parent; |
228 | |
|
229 | 0 | if (len == SIZE_MAX) // Prevents hi_malloc(0) if len equals to SIZE_MAX |
230 | 0 | return NULL; |
231 | | |
232 | 0 | r = createReplyObject(REDIS_REPLY_DOUBLE); |
233 | 0 | if (r == NULL) |
234 | 0 | return NULL; |
235 | | |
236 | 0 | r->dval = value; |
237 | 0 | r->str = hi_malloc(len+1); |
238 | 0 | if (r->str == NULL) { |
239 | 0 | freeReplyObject(r); |
240 | 0 | return NULL; |
241 | 0 | } |
242 | | |
243 | | /* The double reply also has the original protocol string representing a |
244 | | * double as a null terminated string. This way the caller does not need |
245 | | * to format back for string conversion, especially since Redis does efforts |
246 | | * to make the string more human readable avoiding the calssical double |
247 | | * decimal string conversion artifacts. */ |
248 | 0 | memcpy(r->str, str, len); |
249 | 0 | r->str[len] = '\0'; |
250 | 0 | r->len = len; |
251 | |
|
252 | 0 | if (task->parent) { |
253 | 0 | parent = task->parent->obj; |
254 | 0 | assert(parent->type == REDIS_REPLY_ARRAY || |
255 | 0 | parent->type == REDIS_REPLY_MAP || |
256 | 0 | parent->type == REDIS_REPLY_ATTR || |
257 | 0 | parent->type == REDIS_REPLY_SET || |
258 | 0 | parent->type == REDIS_REPLY_PUSH); |
259 | 0 | parent->element[task->idx] = r; |
260 | 0 | } |
261 | 0 | return r; |
262 | 0 | } |
263 | | |
264 | 0 | static void *createNilObject(const redisReadTask *task) { |
265 | 0 | redisReply *r, *parent; |
266 | |
|
267 | 0 | r = createReplyObject(REDIS_REPLY_NIL); |
268 | 0 | if (r == NULL) |
269 | 0 | return NULL; |
270 | | |
271 | 0 | if (task->parent) { |
272 | 0 | parent = task->parent->obj; |
273 | 0 | assert(parent->type == REDIS_REPLY_ARRAY || |
274 | 0 | parent->type == REDIS_REPLY_MAP || |
275 | 0 | parent->type == REDIS_REPLY_ATTR || |
276 | 0 | parent->type == REDIS_REPLY_SET || |
277 | 0 | parent->type == REDIS_REPLY_PUSH); |
278 | 0 | parent->element[task->idx] = r; |
279 | 0 | } |
280 | 0 | return r; |
281 | 0 | } |
282 | | |
283 | 0 | static void *createBoolObject(const redisReadTask *task, int bval) { |
284 | 0 | redisReply *r, *parent; |
285 | |
|
286 | 0 | r = createReplyObject(REDIS_REPLY_BOOL); |
287 | 0 | if (r == NULL) |
288 | 0 | return NULL; |
289 | | |
290 | 0 | r->integer = bval != 0; |
291 | |
|
292 | 0 | if (task->parent) { |
293 | 0 | parent = task->parent->obj; |
294 | 0 | assert(parent->type == REDIS_REPLY_ARRAY || |
295 | 0 | parent->type == REDIS_REPLY_MAP || |
296 | 0 | parent->type == REDIS_REPLY_ATTR || |
297 | 0 | parent->type == REDIS_REPLY_SET || |
298 | 0 | parent->type == REDIS_REPLY_PUSH); |
299 | 0 | parent->element[task->idx] = r; |
300 | 0 | } |
301 | 0 | return r; |
302 | 0 | } |
303 | | |
304 | | /* Return the number of digits of 'v' when converted to string in radix 10. |
305 | | * Implementation borrowed from link in redis/src/util.c:string2ll(). */ |
306 | 3 | static uint32_t countDigits(uint64_t v) { |
307 | 3 | uint32_t result = 1; |
308 | 3 | for (;;) { |
309 | 3 | if (v < 10) return result; |
310 | 0 | if (v < 100) return result + 1; |
311 | 0 | if (v < 1000) return result + 2; |
312 | 0 | if (v < 10000) return result + 3; |
313 | 0 | v /= 10000U; |
314 | 0 | result += 4; |
315 | 0 | } |
316 | 3 | } |
317 | | |
318 | | /* Helper that calculates the bulk length given a certain string length. */ |
319 | 1 | static size_t bulklen(size_t len) { |
320 | 1 | return 1+countDigits(len)+2+len+2; |
321 | 1 | } |
322 | | |
323 | 2 | int redisvFormatCommand(char **target, const char *format, va_list ap) { |
324 | 2 | const char *c = format; |
325 | 2 | char *cmd = NULL; /* final command */ |
326 | 2 | int pos; /* position in final command */ |
327 | 2 | sds curarg, newarg; /* current argument */ |
328 | 2 | int touched = 0; /* was the current argument touched? */ |
329 | 2 | char **curargv = NULL, **newargv = NULL; |
330 | 2 | int argc = 0; |
331 | 2 | int totlen = 0; |
332 | 2 | int error_type = 0; /* 0 = no error; -1 = memory error; -2 = format error */ |
333 | 2 | int j; |
334 | | |
335 | | /* Abort if there is not target to set */ |
336 | 2 | if (target == NULL) |
337 | 0 | return -1; |
338 | | |
339 | | /* Build the command string accordingly to protocol */ |
340 | 2 | curarg = sdsempty(); |
341 | 2 | if (curarg == NULL) |
342 | 0 | return -1; |
343 | | |
344 | 7 | while(*c != '\0') { |
345 | 5 | if (*c != '%' || c[1] == '\0') { |
346 | 5 | if (*c == ' ') { |
347 | 0 | if (touched) { |
348 | 0 | newargv = hi_realloc(curargv,sizeof(char*)*(argc+1)); |
349 | 0 | if (newargv == NULL) goto memory_err; |
350 | 0 | curargv = newargv; |
351 | 0 | curargv[argc++] = curarg; |
352 | 0 | totlen += bulklen(sdslen(curarg)); |
353 | | |
354 | | /* curarg is put in argv so it can be overwritten. */ |
355 | 0 | curarg = sdsempty(); |
356 | 0 | if (curarg == NULL) goto memory_err; |
357 | 0 | touched = 0; |
358 | 0 | } |
359 | 5 | } else { |
360 | 5 | newarg = sdscatlen(curarg,c,1); |
361 | 5 | if (newarg == NULL) goto memory_err; |
362 | 5 | curarg = newarg; |
363 | 5 | touched = 1; |
364 | 5 | } |
365 | 5 | } else { |
366 | 0 | char *arg; |
367 | 0 | size_t size; |
368 | | |
369 | | /* Set newarg so it can be checked even if it is not touched. */ |
370 | 0 | newarg = curarg; |
371 | |
|
372 | 0 | switch(c[1]) { |
373 | 0 | case 's': |
374 | 0 | arg = va_arg(ap,char*); |
375 | 0 | size = strlen(arg); |
376 | 0 | if (size > 0) |
377 | 0 | newarg = sdscatlen(curarg,arg,size); |
378 | 0 | break; |
379 | 0 | case 'b': |
380 | 0 | arg = va_arg(ap,char*); |
381 | 0 | size = va_arg(ap,size_t); |
382 | 0 | if (size > 0) |
383 | 0 | newarg = sdscatlen(curarg,arg,size); |
384 | 0 | break; |
385 | 0 | case '%': |
386 | 0 | newarg = sdscat(curarg,"%"); |
387 | 0 | break; |
388 | 0 | default: |
389 | | /* Try to detect printf format */ |
390 | 0 | { |
391 | 0 | static const char intfmts[] = "diouxX"; |
392 | 0 | static const char flags[] = "#0-+ "; |
393 | 0 | char _format[16]; |
394 | 0 | const char *_p = c+1; |
395 | 0 | size_t _l = 0; |
396 | 0 | va_list _cpy; |
397 | | |
398 | | /* Flags */ |
399 | 0 | while (*_p != '\0' && strchr(flags,*_p) != NULL) _p++; |
400 | | |
401 | | /* Field width */ |
402 | 0 | while (*_p != '\0' && isdigit((int) *_p)) _p++; |
403 | | |
404 | | /* Precision */ |
405 | 0 | if (*_p == '.') { |
406 | 0 | _p++; |
407 | 0 | while (*_p != '\0' && isdigit((int) *_p)) _p++; |
408 | 0 | } |
409 | | |
410 | | /* Copy va_list before consuming with va_arg */ |
411 | 0 | va_copy(_cpy,ap); |
412 | | |
413 | | /* Make sure we have more characters otherwise strchr() accepts |
414 | | * '\0' as an integer specifier. This is checked after above |
415 | | * va_copy() to avoid UB in fmt_invalid's call to va_end(). */ |
416 | 0 | if (*_p == '\0') goto fmt_invalid; |
417 | | |
418 | | /* Integer conversion (without modifiers) */ |
419 | 0 | if (strchr(intfmts,*_p) != NULL) { |
420 | 0 | va_arg(ap,int); |
421 | 0 | goto fmt_valid; |
422 | 0 | } |
423 | | |
424 | | /* Double conversion (without modifiers) */ |
425 | 0 | if (strchr("eEfFgGaA",*_p) != NULL) { |
426 | 0 | va_arg(ap,double); |
427 | 0 | goto fmt_valid; |
428 | 0 | } |
429 | | |
430 | | /* Size: char */ |
431 | 0 | if (_p[0] == 'h' && _p[1] == 'h') { |
432 | 0 | _p += 2; |
433 | 0 | if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { |
434 | 0 | va_arg(ap,int); /* char gets promoted to int */ |
435 | 0 | goto fmt_valid; |
436 | 0 | } |
437 | 0 | goto fmt_invalid; |
438 | 0 | } |
439 | | |
440 | | /* Size: short */ |
441 | 0 | if (_p[0] == 'h') { |
442 | 0 | _p += 1; |
443 | 0 | if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { |
444 | 0 | va_arg(ap,int); /* short gets promoted to int */ |
445 | 0 | goto fmt_valid; |
446 | 0 | } |
447 | 0 | goto fmt_invalid; |
448 | 0 | } |
449 | | |
450 | | /* Size: long long */ |
451 | 0 | if (_p[0] == 'l' && _p[1] == 'l') { |
452 | 0 | _p += 2; |
453 | 0 | if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { |
454 | 0 | va_arg(ap,long long); |
455 | 0 | goto fmt_valid; |
456 | 0 | } |
457 | 0 | goto fmt_invalid; |
458 | 0 | } |
459 | | |
460 | | /* Size: long */ |
461 | 0 | if (_p[0] == 'l') { |
462 | 0 | _p += 1; |
463 | 0 | if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { |
464 | 0 | va_arg(ap,long); |
465 | 0 | goto fmt_valid; |
466 | 0 | } |
467 | 0 | goto fmt_invalid; |
468 | 0 | } |
469 | | |
470 | 0 | fmt_invalid: |
471 | 0 | va_end(_cpy); |
472 | 0 | goto format_err; |
473 | | |
474 | 0 | fmt_valid: |
475 | 0 | _l = (_p+1)-c; |
476 | 0 | if (_l < sizeof(_format)-2) { |
477 | 0 | memcpy(_format,c,_l); |
478 | 0 | _format[_l] = '\0'; |
479 | 0 | newarg = sdscatvprintf(curarg,_format,_cpy); |
480 | | |
481 | | /* Update current position (note: outer blocks |
482 | | * increment c twice so compensate here) */ |
483 | 0 | c = _p-1; |
484 | 0 | } |
485 | |
|
486 | 0 | va_end(_cpy); |
487 | 0 | break; |
488 | 0 | } |
489 | 0 | } |
490 | | |
491 | 0 | if (newarg == NULL) goto memory_err; |
492 | 0 | curarg = newarg; |
493 | |
|
494 | 0 | touched = 1; |
495 | 0 | c++; |
496 | 0 | if (*c == '\0') |
497 | 0 | break; |
498 | 0 | } |
499 | 5 | c++; |
500 | 5 | } |
501 | | |
502 | | /* Add the last argument if needed */ |
503 | 2 | if (touched) { |
504 | 1 | newargv = hi_realloc(curargv,sizeof(char*)*(argc+1)); |
505 | 1 | if (newargv == NULL) goto memory_err; |
506 | 1 | curargv = newargv; |
507 | 1 | curargv[argc++] = curarg; |
508 | 1 | totlen += bulklen(sdslen(curarg)); |
509 | 1 | } else { |
510 | 1 | sdsfree(curarg); |
511 | 1 | } |
512 | | |
513 | | /* Clear curarg because it was put in curargv or was free'd. */ |
514 | 2 | curarg = NULL; |
515 | | |
516 | | /* Add bytes needed to hold multi bulk count */ |
517 | 2 | totlen += 1+countDigits(argc)+2; |
518 | | |
519 | | /* Build the command at protocol level */ |
520 | 2 | cmd = hi_malloc(totlen+1); |
521 | 2 | if (cmd == NULL) goto memory_err; |
522 | | |
523 | 2 | pos = sprintf(cmd,"*%d\r\n",argc); |
524 | 3 | for (j = 0; j < argc; j++) { |
525 | 1 | pos += sprintf(cmd+pos,"$%zu\r\n",sdslen(curargv[j])); |
526 | 1 | memcpy(cmd+pos,curargv[j],sdslen(curargv[j])); |
527 | 1 | pos += sdslen(curargv[j]); |
528 | 1 | sdsfree(curargv[j]); |
529 | 1 | cmd[pos++] = '\r'; |
530 | 1 | cmd[pos++] = '\n'; |
531 | 1 | } |
532 | 2 | assert(pos == totlen); |
533 | 0 | cmd[pos] = '\0'; |
534 | | |
535 | 2 | hi_free(curargv); |
536 | 2 | *target = cmd; |
537 | 2 | return totlen; |
538 | | |
539 | 0 | format_err: |
540 | 0 | error_type = -2; |
541 | 0 | goto cleanup; |
542 | | |
543 | 0 | memory_err: |
544 | 0 | error_type = -1; |
545 | 0 | goto cleanup; |
546 | | |
547 | 0 | cleanup: |
548 | 0 | if (curargv) { |
549 | 0 | while(argc--) |
550 | 0 | sdsfree(curargv[argc]); |
551 | 0 | hi_free(curargv); |
552 | 0 | } |
553 | |
|
554 | 0 | sdsfree(curarg); |
555 | 0 | hi_free(cmd); |
556 | |
|
557 | 0 | return error_type; |
558 | 2 | } |
559 | | |
560 | | /* Format a command according to the Redis protocol. This function |
561 | | * takes a format similar to printf: |
562 | | * |
563 | | * %s represents a C null terminated string you want to interpolate |
564 | | * %b represents a binary safe string |
565 | | * |
566 | | * When using %b you need to provide both the pointer to the string |
567 | | * and the length in bytes as a size_t. Examples: |
568 | | * |
569 | | * len = redisFormatCommand(target, "GET %s", mykey); |
570 | | * len = redisFormatCommand(target, "SET %s %b", mykey, myval, myvallen); |
571 | | */ |
572 | 2 | int redisFormatCommand(char **target, const char *format, ...) { |
573 | 2 | va_list ap; |
574 | 2 | int len; |
575 | 2 | va_start(ap,format); |
576 | 2 | len = redisvFormatCommand(target,format,ap); |
577 | 2 | va_end(ap); |
578 | | |
579 | | /* The API says "-1" means bad result, but we now also return "-2" in some |
580 | | * cases. Force the return value to always be -1. */ |
581 | 2 | if (len < 0) |
582 | 0 | len = -1; |
583 | | |
584 | 2 | return len; |
585 | 2 | } |
586 | | |
587 | | /* Format a command according to the Redis protocol using an sds string and |
588 | | * sdscatfmt for the processing of arguments. This function takes the |
589 | | * number of arguments, an array with arguments and an array with their |
590 | | * lengths. If the latter is set to NULL, strlen will be used to compute the |
591 | | * argument lengths. |
592 | | */ |
593 | | long long redisFormatSdsCommandArgv(sds *target, int argc, const char **argv, |
594 | | const size_t *argvlen) |
595 | 0 | { |
596 | 0 | sds cmd, aux; |
597 | 0 | unsigned long long totlen, len; |
598 | 0 | int j; |
599 | | |
600 | | /* Abort on a NULL target */ |
601 | 0 | if (target == NULL) |
602 | 0 | return -1; |
603 | | |
604 | | /* Calculate our total size */ |
605 | 0 | totlen = 1+countDigits(argc)+2; |
606 | 0 | for (j = 0; j < argc; j++) { |
607 | 0 | len = argvlen ? argvlen[j] : strlen(argv[j]); |
608 | 0 | totlen += bulklen(len); |
609 | 0 | } |
610 | | |
611 | | /* Use an SDS string for command construction */ |
612 | 0 | cmd = sdsempty(); |
613 | 0 | if (cmd == NULL) |
614 | 0 | return -1; |
615 | | |
616 | | /* We already know how much storage we need */ |
617 | 0 | aux = sdsMakeRoomFor(cmd, totlen); |
618 | 0 | if (aux == NULL) { |
619 | 0 | sdsfree(cmd); |
620 | 0 | return -1; |
621 | 0 | } |
622 | | |
623 | 0 | cmd = aux; |
624 | | |
625 | | /* Construct command */ |
626 | 0 | cmd = sdscatfmt(cmd, "*%i\r\n", argc); |
627 | 0 | for (j=0; j < argc; j++) { |
628 | 0 | len = argvlen ? argvlen[j] : strlen(argv[j]); |
629 | 0 | cmd = sdscatfmt(cmd, "$%U\r\n", len); |
630 | 0 | cmd = sdscatlen(cmd, argv[j], len); |
631 | 0 | cmd = sdscatlen(cmd, "\r\n", sizeof("\r\n")-1); |
632 | 0 | } |
633 | |
|
634 | 0 | assert(sdslen(cmd)==totlen); |
635 | | |
636 | 0 | *target = cmd; |
637 | 0 | return totlen; |
638 | 0 | } |
639 | | |
640 | 0 | void redisFreeSdsCommand(sds cmd) { |
641 | 0 | sdsfree(cmd); |
642 | 0 | } |
643 | | |
644 | | /* Format a command according to the Redis protocol. This function takes the |
645 | | * number of arguments, an array with arguments and an array with their |
646 | | * lengths. If the latter is set to NULL, strlen will be used to compute the |
647 | | * argument lengths. |
648 | | */ |
649 | 0 | long long redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen) { |
650 | 0 | char *cmd = NULL; /* final command */ |
651 | 0 | size_t pos; /* position in final command */ |
652 | 0 | size_t len, totlen; |
653 | 0 | int j; |
654 | | |
655 | | /* Abort on a NULL target */ |
656 | 0 | if (target == NULL) |
657 | 0 | return -1; |
658 | | |
659 | | /* Calculate number of bytes needed for the command */ |
660 | 0 | totlen = 1+countDigits(argc)+2; |
661 | 0 | for (j = 0; j < argc; j++) { |
662 | 0 | len = argvlen ? argvlen[j] : strlen(argv[j]); |
663 | 0 | totlen += bulklen(len); |
664 | 0 | } |
665 | | |
666 | | /* Build the command at protocol level */ |
667 | 0 | cmd = hi_malloc(totlen+1); |
668 | 0 | if (cmd == NULL) |
669 | 0 | return -1; |
670 | | |
671 | 0 | pos = sprintf(cmd,"*%d\r\n",argc); |
672 | 0 | for (j = 0; j < argc; j++) { |
673 | 0 | len = argvlen ? argvlen[j] : strlen(argv[j]); |
674 | 0 | pos += sprintf(cmd+pos,"$%zu\r\n",len); |
675 | 0 | memcpy(cmd+pos,argv[j],len); |
676 | 0 | pos += len; |
677 | 0 | cmd[pos++] = '\r'; |
678 | 0 | cmd[pos++] = '\n'; |
679 | 0 | } |
680 | 0 | assert(pos == totlen); |
681 | 0 | cmd[pos] = '\0'; |
682 | |
|
683 | 0 | *target = cmd; |
684 | 0 | return totlen; |
685 | 0 | } |
686 | | |
687 | 0 | void redisFreeCommand(char *cmd) { |
688 | 0 | hi_free(cmd); |
689 | 0 | } |
690 | | |
691 | 0 | void __redisSetError(redisContext *c, int type, const char *str) { |
692 | 0 | size_t len; |
693 | |
|
694 | 0 | c->err = type; |
695 | 0 | if (str != NULL) { |
696 | 0 | len = strlen(str); |
697 | 0 | len = len < (sizeof(c->errstr)-1) ? len : (sizeof(c->errstr)-1); |
698 | 0 | memcpy(c->errstr,str,len); |
699 | 0 | c->errstr[len] = '\0'; |
700 | 0 | } else { |
701 | | /* Only REDIS_ERR_IO may lack a description! */ |
702 | 0 | assert(type == REDIS_ERR_IO); |
703 | 0 | strerror_r(errno, c->errstr, sizeof(c->errstr)); |
704 | 0 | } |
705 | 0 | } |
706 | | |
707 | 0 | redisReader *redisReaderCreate(void) { |
708 | 0 | return redisReaderCreateWithFunctions(&defaultFunctions); |
709 | 0 | } |
710 | | |
711 | 0 | static void redisPushAutoFree(void *privdata, void *reply) { |
712 | 0 | (void)privdata; |
713 | 0 | freeReplyObject(reply); |
714 | 0 | } |
715 | | |
716 | 0 | static redisContext *redisContextInit(void) { |
717 | 0 | redisContext *c; |
718 | |
|
719 | 0 | c = hi_calloc(1, sizeof(*c)); |
720 | 0 | if (c == NULL) |
721 | 0 | return NULL; |
722 | | |
723 | 0 | c->funcs = &redisContextDefaultFuncs; |
724 | |
|
725 | 0 | c->obuf = sdsempty(); |
726 | 0 | c->reader = redisReaderCreate(); |
727 | 0 | c->fd = REDIS_INVALID_FD; |
728 | |
|
729 | 0 | if (c->obuf == NULL || c->reader == NULL) { |
730 | 0 | redisFree(c); |
731 | 0 | return NULL; |
732 | 0 | } |
733 | | |
734 | 0 | return c; |
735 | 0 | } |
736 | | |
737 | 0 | void redisFree(redisContext *c) { |
738 | 0 | if (c == NULL) |
739 | 0 | return; |
740 | | |
741 | 0 | if (c->funcs && c->funcs->close) { |
742 | 0 | c->funcs->close(c); |
743 | 0 | } |
744 | |
|
745 | 0 | sdsfree(c->obuf); |
746 | 0 | redisReaderFree(c->reader); |
747 | 0 | hi_free(c->tcp.host); |
748 | 0 | hi_free(c->tcp.source_addr); |
749 | 0 | hi_free(c->unix_sock.path); |
750 | 0 | hi_free(c->connect_timeout); |
751 | 0 | hi_free(c->command_timeout); |
752 | 0 | hi_free(c->saddr); |
753 | |
|
754 | 0 | if (c->privdata && c->free_privdata) |
755 | 0 | c->free_privdata(c->privdata); |
756 | |
|
757 | 0 | if (c->funcs && c->funcs->free_privctx) |
758 | 0 | c->funcs->free_privctx(c->privctx); |
759 | |
|
760 | 0 | memset(c, 0xff, sizeof(*c)); |
761 | 0 | hi_free(c); |
762 | 0 | } |
763 | | |
764 | 0 | redisFD redisFreeKeepFd(redisContext *c) { |
765 | 0 | redisFD fd = c->fd; |
766 | 0 | c->fd = REDIS_INVALID_FD; |
767 | 0 | redisFree(c); |
768 | 0 | return fd; |
769 | 0 | } |
770 | | |
771 | 0 | int redisReconnect(redisContext *c) { |
772 | 0 | c->err = 0; |
773 | 0 | memset(c->errstr, '\0', strlen(c->errstr)); |
774 | |
|
775 | 0 | if (c->privctx && c->funcs->free_privctx) { |
776 | 0 | c->funcs->free_privctx(c->privctx); |
777 | 0 | c->privctx = NULL; |
778 | 0 | } |
779 | |
|
780 | 0 | if (c->funcs && c->funcs->close) { |
781 | 0 | c->funcs->close(c); |
782 | 0 | } |
783 | |
|
784 | 0 | sdsfree(c->obuf); |
785 | 0 | redisReaderFree(c->reader); |
786 | |
|
787 | 0 | c->obuf = sdsempty(); |
788 | 0 | c->reader = redisReaderCreate(); |
789 | |
|
790 | 0 | if (c->obuf == NULL || c->reader == NULL) { |
791 | 0 | __redisSetError(c, REDIS_ERR_OOM, "Out of memory"); |
792 | 0 | return REDIS_ERR; |
793 | 0 | } |
794 | | |
795 | 0 | int ret = REDIS_ERR; |
796 | 0 | if (c->connection_type == REDIS_CONN_TCP) { |
797 | 0 | ret = redisContextConnectBindTcp(c, c->tcp.host, c->tcp.port, |
798 | 0 | c->connect_timeout, c->tcp.source_addr); |
799 | 0 | } else if (c->connection_type == REDIS_CONN_UNIX) { |
800 | 0 | ret = redisContextConnectUnix(c, c->unix_sock.path, c->connect_timeout); |
801 | 0 | } else { |
802 | | /* Something bad happened here and shouldn't have. There isn't |
803 | | enough information in the context to reconnect. */ |
804 | 0 | __redisSetError(c,REDIS_ERR_OTHER,"Not enough information to reconnect"); |
805 | 0 | ret = REDIS_ERR; |
806 | 0 | } |
807 | |
|
808 | 0 | if (c->command_timeout != NULL && (c->flags & REDIS_BLOCK) && c->fd != REDIS_INVALID_FD) { |
809 | 0 | redisContextSetTimeout(c, *c->command_timeout); |
810 | 0 | } |
811 | |
|
812 | 0 | return ret; |
813 | 0 | } |
814 | | |
815 | 0 | redisContext *redisConnectWithOptions(const redisOptions *options) { |
816 | 0 | redisContext *c = redisContextInit(); |
817 | 0 | if (c == NULL) { |
818 | 0 | return NULL; |
819 | 0 | } |
820 | 0 | if (!(options->options & REDIS_OPT_NONBLOCK)) { |
821 | 0 | c->flags |= REDIS_BLOCK; |
822 | 0 | } |
823 | 0 | if (options->options & REDIS_OPT_REUSEADDR) { |
824 | 0 | c->flags |= REDIS_REUSEADDR; |
825 | 0 | } |
826 | 0 | if (options->options & REDIS_OPT_NOAUTOFREE) { |
827 | 0 | c->flags |= REDIS_NO_AUTO_FREE; |
828 | 0 | } |
829 | 0 | if (options->options & REDIS_OPT_NOAUTOFREEREPLIES) { |
830 | 0 | c->flags |= REDIS_NO_AUTO_FREE_REPLIES; |
831 | 0 | } |
832 | 0 | if (options->options & REDIS_OPT_PREFER_IPV4) { |
833 | 0 | c->flags |= REDIS_PREFER_IPV4; |
834 | 0 | } |
835 | 0 | if (options->options & REDIS_OPT_PREFER_IPV6) { |
836 | 0 | c->flags |= REDIS_PREFER_IPV6; |
837 | 0 | } |
838 | | |
839 | | /* Set any user supplied RESP3 PUSH handler or use freeReplyObject |
840 | | * as a default unless specifically flagged that we don't want one. */ |
841 | 0 | if (options->push_cb != NULL) |
842 | 0 | redisSetPushCallback(c, options->push_cb); |
843 | 0 | else if (!(options->options & REDIS_OPT_NO_PUSH_AUTOFREE)) |
844 | 0 | redisSetPushCallback(c, redisPushAutoFree); |
845 | |
|
846 | 0 | c->privdata = options->privdata; |
847 | 0 | c->free_privdata = options->free_privdata; |
848 | |
|
849 | 0 | if (redisContextUpdateConnectTimeout(c, options->connect_timeout) != REDIS_OK || |
850 | 0 | redisContextUpdateCommandTimeout(c, options->command_timeout) != REDIS_OK) { |
851 | 0 | __redisSetError(c, REDIS_ERR_OOM, "Out of memory"); |
852 | 0 | return c; |
853 | 0 | } |
854 | | |
855 | 0 | if (options->type == REDIS_CONN_TCP) { |
856 | 0 | redisContextConnectBindTcp(c, options->endpoint.tcp.ip, |
857 | 0 | options->endpoint.tcp.port, options->connect_timeout, |
858 | 0 | options->endpoint.tcp.source_addr); |
859 | 0 | } else if (options->type == REDIS_CONN_UNIX) { |
860 | 0 | redisContextConnectUnix(c, options->endpoint.unix_socket, |
861 | 0 | options->connect_timeout); |
862 | 0 | } else if (options->type == REDIS_CONN_USERFD) { |
863 | 0 | c->fd = options->endpoint.fd; |
864 | 0 | c->flags |= REDIS_CONNECTED; |
865 | 0 | } else { |
866 | 0 | redisFree(c); |
867 | 0 | return NULL; |
868 | 0 | } |
869 | | |
870 | 0 | if (c->err == 0 && c->fd != REDIS_INVALID_FD && |
871 | 0 | options->command_timeout != NULL && (c->flags & REDIS_BLOCK)) |
872 | 0 | { |
873 | 0 | redisContextSetTimeout(c, *options->command_timeout); |
874 | 0 | } |
875 | |
|
876 | 0 | return c; |
877 | 0 | } |
878 | | |
879 | | /* Connect to a Redis instance. On error the field error in the returned |
880 | | * context will be set to the return value of the error function. |
881 | | * When no set of reply functions is given, the default set will be used. */ |
882 | 0 | redisContext *redisConnect(const char *ip, int port) { |
883 | 0 | redisOptions options = {0}; |
884 | 0 | REDIS_OPTIONS_SET_TCP(&options, ip, port); |
885 | 0 | return redisConnectWithOptions(&options); |
886 | 0 | } |
887 | | |
888 | 0 | redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv) { |
889 | 0 | redisOptions options = {0}; |
890 | 0 | REDIS_OPTIONS_SET_TCP(&options, ip, port); |
891 | 0 | options.connect_timeout = &tv; |
892 | 0 | return redisConnectWithOptions(&options); |
893 | 0 | } |
894 | | |
895 | 0 | redisContext *redisConnectNonBlock(const char *ip, int port) { |
896 | 0 | redisOptions options = {0}; |
897 | 0 | REDIS_OPTIONS_SET_TCP(&options, ip, port); |
898 | 0 | options.options |= REDIS_OPT_NONBLOCK; |
899 | 0 | return redisConnectWithOptions(&options); |
900 | 0 | } |
901 | | |
902 | | redisContext *redisConnectBindNonBlock(const char *ip, int port, |
903 | 0 | const char *source_addr) { |
904 | 0 | redisOptions options = {0}; |
905 | 0 | REDIS_OPTIONS_SET_TCP(&options, ip, port); |
906 | 0 | options.endpoint.tcp.source_addr = source_addr; |
907 | 0 | options.options |= REDIS_OPT_NONBLOCK; |
908 | 0 | return redisConnectWithOptions(&options); |
909 | 0 | } |
910 | | |
911 | | redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port, |
912 | 0 | const char *source_addr) { |
913 | 0 | redisOptions options = {0}; |
914 | 0 | REDIS_OPTIONS_SET_TCP(&options, ip, port); |
915 | 0 | options.endpoint.tcp.source_addr = source_addr; |
916 | 0 | options.options |= REDIS_OPT_NONBLOCK|REDIS_OPT_REUSEADDR; |
917 | 0 | return redisConnectWithOptions(&options); |
918 | 0 | } |
919 | | |
920 | 0 | redisContext *redisConnectUnix(const char *path) { |
921 | 0 | redisOptions options = {0}; |
922 | 0 | REDIS_OPTIONS_SET_UNIX(&options, path); |
923 | 0 | return redisConnectWithOptions(&options); |
924 | 0 | } |
925 | | |
926 | 0 | redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv) { |
927 | 0 | redisOptions options = {0}; |
928 | 0 | REDIS_OPTIONS_SET_UNIX(&options, path); |
929 | 0 | options.connect_timeout = &tv; |
930 | 0 | return redisConnectWithOptions(&options); |
931 | 0 | } |
932 | | |
933 | 0 | redisContext *redisConnectUnixNonBlock(const char *path) { |
934 | 0 | redisOptions options = {0}; |
935 | 0 | REDIS_OPTIONS_SET_UNIX(&options, path); |
936 | 0 | options.options |= REDIS_OPT_NONBLOCK; |
937 | 0 | return redisConnectWithOptions(&options); |
938 | 0 | } |
939 | | |
940 | 0 | redisContext *redisConnectFd(redisFD fd) { |
941 | 0 | redisOptions options = {0}; |
942 | 0 | options.type = REDIS_CONN_USERFD; |
943 | 0 | options.endpoint.fd = fd; |
944 | 0 | return redisConnectWithOptions(&options); |
945 | 0 | } |
946 | | |
947 | | /* Set read/write timeout on a blocking socket. */ |
948 | 0 | int redisSetTimeout(redisContext *c, const struct timeval tv) { |
949 | 0 | if (c->flags & REDIS_BLOCK) |
950 | 0 | return redisContextSetTimeout(c,tv); |
951 | 0 | return REDIS_ERR; |
952 | 0 | } |
953 | | |
954 | 0 | int redisEnableKeepAliveWithInterval(redisContext *c, int interval) { |
955 | 0 | return redisKeepAlive(c, interval); |
956 | 0 | } |
957 | | |
958 | | /* Enable connection KeepAlive. */ |
959 | 0 | int redisEnableKeepAlive(redisContext *c) { |
960 | 0 | return redisKeepAlive(c, REDIS_KEEPALIVE_INTERVAL); |
961 | 0 | } |
962 | | |
963 | | /* Set the socket option TCP_USER_TIMEOUT. */ |
964 | 0 | int redisSetTcpUserTimeout(redisContext *c, unsigned int timeout) { |
965 | 0 | return redisContextSetTcpUserTimeout(c, timeout); |
966 | 0 | } |
967 | | |
968 | | /* Set a user provided RESP3 PUSH handler and return any old one set. */ |
969 | 0 | redisPushFn *redisSetPushCallback(redisContext *c, redisPushFn *fn) { |
970 | 0 | redisPushFn *old = c->push_cb; |
971 | 0 | c->push_cb = fn; |
972 | 0 | return old; |
973 | 0 | } |
974 | | |
975 | | /* Use this function to handle a read event on the descriptor. It will try |
976 | | * and read some bytes from the socket and feed them to the reply parser. |
977 | | * |
978 | | * After this function is called, you may use redisGetReplyFromReader to |
979 | | * see if there is a reply available. */ |
980 | 0 | int redisBufferRead(redisContext *c) { |
981 | 0 | char buf[1024*16]; |
982 | 0 | int nread; |
983 | | |
984 | | /* Return early when the context has seen an error. */ |
985 | 0 | if (c->err) |
986 | 0 | return REDIS_ERR; |
987 | | |
988 | 0 | nread = c->funcs->read(c, buf, sizeof(buf)); |
989 | 0 | if (nread < 0) { |
990 | 0 | return REDIS_ERR; |
991 | 0 | } |
992 | 0 | if (nread > 0 && redisReaderFeed(c->reader, buf, nread) != REDIS_OK) { |
993 | 0 | __redisSetError(c, c->reader->err, c->reader->errstr); |
994 | 0 | return REDIS_ERR; |
995 | 0 | } |
996 | 0 | return REDIS_OK; |
997 | 0 | } |
998 | | |
999 | | /* Write the output buffer to the socket. |
1000 | | * |
1001 | | * Returns REDIS_OK when the buffer is empty, or (a part of) the buffer was |
1002 | | * successfully written to the socket. When the buffer is empty after the |
1003 | | * write operation, "done" is set to 1 (if given). |
1004 | | * |
1005 | | * Returns REDIS_ERR if an unrecoverable error occurred in the underlying |
1006 | | * c->funcs->write function. |
1007 | | */ |
1008 | 0 | int redisBufferWrite(redisContext *c, int *done) { |
1009 | | |
1010 | | /* Return early when the context has seen an error. */ |
1011 | 0 | if (c->err) |
1012 | 0 | return REDIS_ERR; |
1013 | | |
1014 | 0 | if (sdslen(c->obuf) > 0) { |
1015 | 0 | ssize_t nwritten = c->funcs->write(c); |
1016 | 0 | if (nwritten < 0) { |
1017 | 0 | return REDIS_ERR; |
1018 | 0 | } else if (nwritten > 0) { |
1019 | 0 | if (nwritten == (ssize_t)sdslen(c->obuf)) { |
1020 | 0 | sdsfree(c->obuf); |
1021 | 0 | c->obuf = sdsempty(); |
1022 | 0 | if (c->obuf == NULL) |
1023 | 0 | goto oom; |
1024 | 0 | } else { |
1025 | 0 | if (sdsrange(c->obuf,nwritten,-1) < 0) goto oom; |
1026 | 0 | } |
1027 | 0 | } |
1028 | 0 | } |
1029 | 0 | if (done != NULL) *done = (sdslen(c->obuf) == 0); |
1030 | 0 | return REDIS_OK; |
1031 | | |
1032 | 0 | oom: |
1033 | 0 | __redisSetError(c, REDIS_ERR_OOM, "Out of memory"); |
1034 | 0 | return REDIS_ERR; |
1035 | 0 | } |
1036 | | |
1037 | | /* Internal helper that returns 1 if the reply was a RESP3 PUSH |
1038 | | * message and we handled it with a user-provided callback. */ |
1039 | 0 | static int redisHandledPushReply(redisContext *c, void *reply) { |
1040 | 0 | if (reply && c->push_cb && redisIsPushReply(reply)) { |
1041 | 0 | c->push_cb(c->privdata, reply); |
1042 | 0 | return 1; |
1043 | 0 | } |
1044 | | |
1045 | 0 | return 0; |
1046 | 0 | } |
1047 | | |
1048 | | /* Get a reply from our reader or set an error in the context. */ |
1049 | 0 | int redisGetReplyFromReader(redisContext *c, void **reply) { |
1050 | 0 | if (redisReaderGetReply(c->reader, reply) == REDIS_ERR) { |
1051 | 0 | __redisSetError(c,c->reader->err,c->reader->errstr); |
1052 | 0 | return REDIS_ERR; |
1053 | 0 | } |
1054 | | |
1055 | 0 | return REDIS_OK; |
1056 | 0 | } |
1057 | | |
1058 | | /* Internal helper to get the next reply from our reader while handling |
1059 | | * any PUSH messages we encounter along the way. This is separate from |
1060 | | * redisGetReplyFromReader so as to not change its behavior. */ |
1061 | 0 | static int redisNextInBandReplyFromReader(redisContext *c, void **reply) { |
1062 | 0 | do { |
1063 | 0 | if (redisGetReplyFromReader(c, reply) == REDIS_ERR) |
1064 | 0 | return REDIS_ERR; |
1065 | 0 | } while (redisHandledPushReply(c, *reply)); |
1066 | | |
1067 | 0 | return REDIS_OK; |
1068 | 0 | } |
1069 | | |
1070 | 0 | int redisGetReply(redisContext *c, void **reply) { |
1071 | 0 | int wdone = 0; |
1072 | 0 | void *aux = NULL; |
1073 | | |
1074 | | /* Try to read pending replies */ |
1075 | 0 | if (redisNextInBandReplyFromReader(c,&aux) == REDIS_ERR) |
1076 | 0 | return REDIS_ERR; |
1077 | | |
1078 | | /* For the blocking context, flush output buffer and read reply */ |
1079 | 0 | if (aux == NULL && c->flags & REDIS_BLOCK) { |
1080 | | /* Write until done */ |
1081 | 0 | do { |
1082 | 0 | if (redisBufferWrite(c,&wdone) == REDIS_ERR) |
1083 | 0 | return REDIS_ERR; |
1084 | 0 | } while (!wdone); |
1085 | | |
1086 | | /* Read until there is a reply */ |
1087 | 0 | do { |
1088 | 0 | if (redisBufferRead(c) == REDIS_ERR) |
1089 | 0 | return REDIS_ERR; |
1090 | | |
1091 | 0 | if (redisNextInBandReplyFromReader(c,&aux) == REDIS_ERR) |
1092 | 0 | return REDIS_ERR; |
1093 | 0 | } while (aux == NULL); |
1094 | 0 | } |
1095 | | |
1096 | | /* Set reply or free it if we were passed NULL */ |
1097 | 0 | if (reply != NULL) { |
1098 | 0 | *reply = aux; |
1099 | 0 | } else { |
1100 | 0 | freeReplyObject(aux); |
1101 | 0 | } |
1102 | |
|
1103 | 0 | return REDIS_OK; |
1104 | 0 | } |
1105 | | |
1106 | | |
1107 | | /* Helper function for the redisAppendCommand* family of functions. |
1108 | | * |
1109 | | * Write a formatted command to the output buffer. When this family |
1110 | | * is used, you need to call redisGetReply yourself to retrieve |
1111 | | * the reply (or replies in pub/sub). |
1112 | | */ |
1113 | 0 | int __redisAppendCommand(redisContext *c, const char *cmd, size_t len) { |
1114 | 0 | sds newbuf; |
1115 | |
|
1116 | 0 | newbuf = sdscatlen(c->obuf,cmd,len); |
1117 | 0 | if (newbuf == NULL) { |
1118 | 0 | __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); |
1119 | 0 | return REDIS_ERR; |
1120 | 0 | } |
1121 | | |
1122 | 0 | c->obuf = newbuf; |
1123 | 0 | return REDIS_OK; |
1124 | 0 | } |
1125 | | |
1126 | 0 | int redisAppendFormattedCommand(redisContext *c, const char *cmd, size_t len) { |
1127 | |
|
1128 | 0 | if (__redisAppendCommand(c, cmd, len) != REDIS_OK) { |
1129 | 0 | return REDIS_ERR; |
1130 | 0 | } |
1131 | | |
1132 | 0 | return REDIS_OK; |
1133 | 0 | } |
1134 | | |
1135 | 0 | int redisvAppendCommand(redisContext *c, const char *format, va_list ap) { |
1136 | 0 | char *cmd; |
1137 | 0 | int len; |
1138 | |
|
1139 | 0 | len = redisvFormatCommand(&cmd,format,ap); |
1140 | 0 | if (len == -1) { |
1141 | 0 | __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); |
1142 | 0 | return REDIS_ERR; |
1143 | 0 | } else if (len == -2) { |
1144 | 0 | __redisSetError(c,REDIS_ERR_OTHER,"Invalid format string"); |
1145 | 0 | return REDIS_ERR; |
1146 | 0 | } |
1147 | | |
1148 | 0 | if (__redisAppendCommand(c,cmd,len) != REDIS_OK) { |
1149 | 0 | hi_free(cmd); |
1150 | 0 | return REDIS_ERR; |
1151 | 0 | } |
1152 | | |
1153 | 0 | hi_free(cmd); |
1154 | 0 | return REDIS_OK; |
1155 | 0 | } |
1156 | | |
1157 | 0 | int redisAppendCommand(redisContext *c, const char *format, ...) { |
1158 | 0 | va_list ap; |
1159 | 0 | int ret; |
1160 | |
|
1161 | 0 | va_start(ap,format); |
1162 | 0 | ret = redisvAppendCommand(c,format,ap); |
1163 | 0 | va_end(ap); |
1164 | 0 | return ret; |
1165 | 0 | } |
1166 | | |
1167 | 0 | int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) { |
1168 | 0 | sds cmd; |
1169 | 0 | long long len; |
1170 | |
|
1171 | 0 | len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen); |
1172 | 0 | if (len == -1) { |
1173 | 0 | __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); |
1174 | 0 | return REDIS_ERR; |
1175 | 0 | } |
1176 | | |
1177 | 0 | if (__redisAppendCommand(c,cmd,len) != REDIS_OK) { |
1178 | 0 | sdsfree(cmd); |
1179 | 0 | return REDIS_ERR; |
1180 | 0 | } |
1181 | | |
1182 | 0 | sdsfree(cmd); |
1183 | 0 | return REDIS_OK; |
1184 | 0 | } |
1185 | | |
1186 | | /* Helper function for the redisCommand* family of functions. |
1187 | | * |
1188 | | * Write a formatted command to the output buffer. If the given context is |
1189 | | * blocking, immediately read the reply into the "reply" pointer. When the |
1190 | | * context is non-blocking, the "reply" pointer will not be used and the |
1191 | | * command is simply appended to the write buffer. |
1192 | | * |
1193 | | * Returns the reply when a reply was successfully retrieved. Returns NULL |
1194 | | * otherwise. When NULL is returned in a blocking context, the error field |
1195 | | * in the context will be set. |
1196 | | */ |
1197 | 0 | static void *__redisBlockForReply(redisContext *c) { |
1198 | 0 | void *reply; |
1199 | |
|
1200 | 0 | if (c->flags & REDIS_BLOCK) { |
1201 | 0 | if (redisGetReply(c,&reply) != REDIS_OK) |
1202 | 0 | return NULL; |
1203 | 0 | return reply; |
1204 | 0 | } |
1205 | 0 | return NULL; |
1206 | 0 | } |
1207 | | |
1208 | 0 | void *redisvCommand(redisContext *c, const char *format, va_list ap) { |
1209 | 0 | if (redisvAppendCommand(c,format,ap) != REDIS_OK) |
1210 | 0 | return NULL; |
1211 | 0 | return __redisBlockForReply(c); |
1212 | 0 | } |
1213 | | |
1214 | 0 | void *redisCommand(redisContext *c, const char *format, ...) { |
1215 | 0 | va_list ap; |
1216 | 0 | va_start(ap,format); |
1217 | 0 | void *reply = redisvCommand(c,format,ap); |
1218 | 0 | va_end(ap); |
1219 | 0 | return reply; |
1220 | 0 | } |
1221 | | |
1222 | 0 | void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) { |
1223 | 0 | if (redisAppendCommandArgv(c,argc,argv,argvlen) != REDIS_OK) |
1224 | 0 | return NULL; |
1225 | 0 | return __redisBlockForReply(c); |
1226 | 0 | } |