Coverage Report

Created: 2023-06-07 06:47

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