hiredis-futureproof 0.6.3

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