hiredis-client 0.4.0

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