Coverage Report

Created: 2024-02-11 06:16

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