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