Unverified Commit d2b5a579 authored by Oran Agra's avatar Oran Agra Committed by GitHub
Browse files

Merge pull request #10355 from oranagra/release-7.0-rc2

Release 7.0 RC2
parents d5915a16 10dc57ab
...@@ -2,7 +2,10 @@ ...@@ -2,7 +2,10 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <hiredis.h> #include <hiredis.h>
#include <win32.h>
#ifdef _MSC_VER
#include <winsock2.h> /* For struct timeval */
#endif
int main(int argc, char **argv) { int main(int argc, char **argv) {
unsigned int j, isunix = 0; unsigned int j, isunix = 0;
......
#ifndef __HIREDIS_FMACRO_H #ifndef __HIREDIS_FMACRO_H
#define __HIREDIS_FMACRO_H #define __HIREDIS_FMACRO_H
#ifndef _AIX
#define _XOPEN_SOURCE 600 #define _XOPEN_SOURCE 600
#define _POSIX_C_SOURCE 200112L #define _POSIX_C_SOURCE 200112L
#endif
#if defined(__APPLE__) && defined(__MACH__) #if defined(__APPLE__) && defined(__MACH__)
/* Enable TCP_KEEPALIVE */ /* Enable TCP_KEEPALIVE */
......
/*
* Copyright (c) 2020, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2020, Pieter Noordhuis <pcnoordhuis at gmail dot com>
* Copyright (c) 2020, Matt Stancliff <matt at genges dot com>,
* Jan-Erik Rediger <janerik at fnordig dot com>
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdlib.h>
#include <string.h>
#include "hiredis.h"
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
char *new_str, *cmd;
if (size < 3)
return 0;
new_str = malloc(size+1);
if (new_str == NULL)
return 0;
memcpy(new_str, data, size);
new_str[size] = '\0';
redisFormatCommand(&cmd, new_str);
if (cmd != NULL)
hi_free(cmd);
free(new_str);
return 0;
}
...@@ -96,6 +96,8 @@ void freeReplyObject(void *reply) { ...@@ -96,6 +96,8 @@ void freeReplyObject(void *reply) {
switch(r->type) { switch(r->type) {
case REDIS_REPLY_INTEGER: case REDIS_REPLY_INTEGER:
case REDIS_REPLY_NIL:
case REDIS_REPLY_BOOL:
break; /* Nothing to free */ break; /* Nothing to free */
case REDIS_REPLY_ARRAY: case REDIS_REPLY_ARRAY:
case REDIS_REPLY_MAP: case REDIS_REPLY_MAP:
...@@ -112,6 +114,7 @@ void freeReplyObject(void *reply) { ...@@ -112,6 +114,7 @@ void freeReplyObject(void *reply) {
case REDIS_REPLY_STRING: case REDIS_REPLY_STRING:
case REDIS_REPLY_DOUBLE: case REDIS_REPLY_DOUBLE:
case REDIS_REPLY_VERB: case REDIS_REPLY_VERB:
case REDIS_REPLY_BIGNUM:
hi_free(r->str); hi_free(r->str);
break; break;
} }
...@@ -129,7 +132,8 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len ...@@ -129,7 +132,8 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len
assert(task->type == REDIS_REPLY_ERROR || assert(task->type == REDIS_REPLY_ERROR ||
task->type == REDIS_REPLY_STATUS || task->type == REDIS_REPLY_STATUS ||
task->type == REDIS_REPLY_STRING || task->type == REDIS_REPLY_STRING ||
task->type == REDIS_REPLY_VERB); task->type == REDIS_REPLY_VERB ||
task->type == REDIS_REPLY_BIGNUM);
/* Copy string value */ /* Copy string value */
if (task->type == REDIS_REPLY_VERB) { if (task->type == REDIS_REPLY_VERB) {
...@@ -235,12 +239,14 @@ static void *createDoubleObject(const redisReadTask *task, double value, char *s ...@@ -235,12 +239,14 @@ static void *createDoubleObject(const redisReadTask *task, double value, char *s
* decimal string conversion artifacts. */ * decimal string conversion artifacts. */
memcpy(r->str, str, len); memcpy(r->str, str, len);
r->str[len] = '\0'; r->str[len] = '\0';
r->len = len;
if (task->parent) { if (task->parent) {
parent = task->parent->obj; parent = task->parent->obj;
assert(parent->type == REDIS_REPLY_ARRAY || assert(parent->type == REDIS_REPLY_ARRAY ||
parent->type == REDIS_REPLY_MAP || parent->type == REDIS_REPLY_MAP ||
parent->type == REDIS_REPLY_SET); parent->type == REDIS_REPLY_SET ||
parent->type == REDIS_REPLY_PUSH);
parent->element[task->idx] = r; parent->element[task->idx] = r;
} }
return r; return r;
...@@ -277,7 +283,8 @@ static void *createBoolObject(const redisReadTask *task, int bval) { ...@@ -277,7 +283,8 @@ static void *createBoolObject(const redisReadTask *task, int bval) {
parent = task->parent->obj; parent = task->parent->obj;
assert(parent->type == REDIS_REPLY_ARRAY || assert(parent->type == REDIS_REPLY_ARRAY ||
parent->type == REDIS_REPLY_MAP || parent->type == REDIS_REPLY_MAP ||
parent->type == REDIS_REPLY_SET); parent->type == REDIS_REPLY_SET ||
parent->type == REDIS_REPLY_PUSH);
parent->element[task->idx] = r; parent->element[task->idx] = r;
} }
return r; return r;
...@@ -565,13 +572,12 @@ int redisFormatCommand(char **target, const char *format, ...) { ...@@ -565,13 +572,12 @@ int redisFormatCommand(char **target, const char *format, ...) {
* lengths. If the latter is set to NULL, strlen will be used to compute the * lengths. If the latter is set to NULL, strlen will be used to compute the
* argument lengths. * argument lengths.
*/ */
int redisFormatSdsCommandArgv(hisds *target, int argc, const char **argv, long long redisFormatSdsCommandArgv(hisds *target, int argc, const char **argv,
const size_t *argvlen) const size_t *argvlen)
{ {
hisds cmd, aux; hisds cmd, aux;
unsigned long long totlen; unsigned long long totlen, len;
int j; int j;
size_t len;
/* Abort on a NULL target */ /* Abort on a NULL target */
if (target == NULL) if (target == NULL)
...@@ -602,7 +608,7 @@ int redisFormatSdsCommandArgv(hisds *target, int argc, const char **argv, ...@@ -602,7 +608,7 @@ int redisFormatSdsCommandArgv(hisds *target, int argc, const char **argv,
cmd = hi_sdscatfmt(cmd, "*%i\r\n", argc); cmd = hi_sdscatfmt(cmd, "*%i\r\n", argc);
for (j=0; j < argc; j++) { for (j=0; j < argc; j++) {
len = argvlen ? argvlen[j] : strlen(argv[j]); len = argvlen ? argvlen[j] : strlen(argv[j]);
cmd = hi_sdscatfmt(cmd, "$%u\r\n", len); cmd = hi_sdscatfmt(cmd, "$%U\r\n", len);
cmd = hi_sdscatlen(cmd, argv[j], len); cmd = hi_sdscatlen(cmd, argv[j], len);
cmd = hi_sdscatlen(cmd, "\r\n", sizeof("\r\n")-1); cmd = hi_sdscatlen(cmd, "\r\n", sizeof("\r\n")-1);
} }
...@@ -622,11 +628,11 @@ void redisFreeSdsCommand(hisds cmd) { ...@@ -622,11 +628,11 @@ void redisFreeSdsCommand(hisds cmd) {
* lengths. If the latter is set to NULL, strlen will be used to compute the * lengths. If the latter is set to NULL, strlen will be used to compute the
* argument lengths. * argument lengths.
*/ */
int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen) { long long redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen) {
char *cmd = NULL; /* final command */ char *cmd = NULL; /* final command */
int pos; /* position in final command */ size_t pos; /* position in final command */
size_t len; size_t len, totlen;
int totlen, j; int j;
/* Abort on a NULL target */ /* Abort on a NULL target */
if (target == NULL) if (target == NULL)
...@@ -797,6 +803,9 @@ redisContext *redisConnectWithOptions(const redisOptions *options) { ...@@ -797,6 +803,9 @@ redisContext *redisConnectWithOptions(const redisOptions *options) {
if (options->options & REDIS_OPT_NOAUTOFREE) { if (options->options & REDIS_OPT_NOAUTOFREE) {
c->flags |= REDIS_NO_AUTO_FREE; c->flags |= REDIS_NO_AUTO_FREE;
} }
if (options->options & REDIS_OPT_NOAUTOFREEREPLIES) {
c->flags |= REDIS_NO_AUTO_FREE_REPLIES;
}
/* Set any user supplied RESP3 PUSH handler or use freeReplyObject /* Set any user supplied RESP3 PUSH handler or use freeReplyObject
* as a default unless specifically flagged that we don't want one. */ * as a default unless specifically flagged that we don't want one. */
...@@ -825,7 +834,7 @@ redisContext *redisConnectWithOptions(const redisOptions *options) { ...@@ -825,7 +834,7 @@ redisContext *redisConnectWithOptions(const redisOptions *options) {
c->fd = options->endpoint.fd; c->fd = options->endpoint.fd;
c->flags |= REDIS_CONNECTED; c->flags |= REDIS_CONNECTED;
} else { } else {
// Unknown type - FIXME - FREE redisFree(c);
return NULL; return NULL;
} }
...@@ -939,13 +948,11 @@ int redisBufferRead(redisContext *c) { ...@@ -939,13 +948,11 @@ int redisBufferRead(redisContext *c) {
return REDIS_ERR; return REDIS_ERR;
nread = c->funcs->read(c, buf, sizeof(buf)); nread = c->funcs->read(c, buf, sizeof(buf));
if (nread > 0) { if (nread < 0) {
if (redisReaderFeed(c->reader, buf, nread) != REDIS_OK) { return REDIS_ERR;
__redisSetError(c, c->reader->err, c->reader->errstr); }
return REDIS_ERR; if (nread > 0 && redisReaderFeed(c->reader, buf, nread) != REDIS_OK) {
} else { __redisSetError(c, c->reader->err, c->reader->errstr);
}
} else if (nread < 0) {
return REDIS_ERR; return REDIS_ERR;
} }
return REDIS_OK; return REDIS_OK;
...@@ -989,17 +996,6 @@ oom: ...@@ -989,17 +996,6 @@ oom:
return REDIS_ERR; return REDIS_ERR;
} }
/* Internal helper function to try and get a reply from the reader,
* or set an error in the context otherwise. */
int redisGetReplyFromReader(redisContext *c, void **reply) {
if (redisReaderGetReply(c->reader,reply) == REDIS_ERR) {
__redisSetError(c,c->reader->err,c->reader->errstr);
return REDIS_ERR;
}
return REDIS_OK;
}
/* Internal helper that returns 1 if the reply was a RESP3 PUSH /* Internal helper that returns 1 if the reply was a RESP3 PUSH
* message and we handled it with a user-provided callback. */ * message and we handled it with a user-provided callback. */
static int redisHandledPushReply(redisContext *c, void *reply) { static int redisHandledPushReply(redisContext *c, void *reply) {
...@@ -1011,12 +1007,34 @@ static int redisHandledPushReply(redisContext *c, void *reply) { ...@@ -1011,12 +1007,34 @@ static int redisHandledPushReply(redisContext *c, void *reply) {
return 0; return 0;
} }
/* Get a reply from our reader or set an error in the context. */
int redisGetReplyFromReader(redisContext *c, void **reply) {
if (redisReaderGetReply(c->reader, reply) == REDIS_ERR) {
__redisSetError(c,c->reader->err,c->reader->errstr);
return REDIS_ERR;
}
return REDIS_OK;
}
/* Internal helper to get the next reply from our reader while handling
* any PUSH messages we encounter along the way. This is separate from
* redisGetReplyFromReader so as to not change its behavior. */
static int redisNextInBandReplyFromReader(redisContext *c, void **reply) {
do {
if (redisGetReplyFromReader(c, reply) == REDIS_ERR)
return REDIS_ERR;
} while (redisHandledPushReply(c, *reply));
return REDIS_OK;
}
int redisGetReply(redisContext *c, void **reply) { int redisGetReply(redisContext *c, void **reply) {
int wdone = 0; int wdone = 0;
void *aux = NULL; void *aux = NULL;
/* Try to read pending replies */ /* Try to read pending replies */
if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) if (redisNextInBandReplyFromReader(c,&aux) == REDIS_ERR)
return REDIS_ERR; return REDIS_ERR;
/* For the blocking context, flush output buffer and read reply */ /* For the blocking context, flush output buffer and read reply */
...@@ -1032,12 +1050,8 @@ int redisGetReply(redisContext *c, void **reply) { ...@@ -1032,12 +1050,8 @@ int redisGetReply(redisContext *c, void **reply) {
if (redisBufferRead(c) == REDIS_ERR) if (redisBufferRead(c) == REDIS_ERR)
return REDIS_ERR; return REDIS_ERR;
/* We loop here in case the user has specified a RESP3 if (redisNextInBandReplyFromReader(c,&aux) == REDIS_ERR)
* PUSH handler (e.g. for client tracking). */ return REDIS_ERR;
do {
if (redisGetReplyFromReader(c,&aux) == REDIS_ERR)
return REDIS_ERR;
} while (redisHandledPushReply(c, aux));
} while (aux == NULL); } while (aux == NULL);
} }
...@@ -1114,7 +1128,7 @@ int redisAppendCommand(redisContext *c, const char *format, ...) { ...@@ -1114,7 +1128,7 @@ int redisAppendCommand(redisContext *c, const char *format, ...) {
int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) { int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) {
hisds cmd; hisds cmd;
int len; long long len;
len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen); len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen);
if (len == -1) { if (len == -1) {
......
...@@ -47,8 +47,8 @@ typedef long long ssize_t; ...@@ -47,8 +47,8 @@ typedef long long ssize_t;
#define HIREDIS_MAJOR 1 #define HIREDIS_MAJOR 1
#define HIREDIS_MINOR 0 #define HIREDIS_MINOR 0
#define HIREDIS_PATCH 0 #define HIREDIS_PATCH 3
#define HIREDIS_SONAME 1.0.0 #define HIREDIS_SONAME 1.0.3-dev
/* Connection type can be blocking or non-blocking and is set in the /* Connection type can be blocking or non-blocking and is set in the
* least significant bit of the flags field in redisContext. */ * least significant bit of the flags field in redisContext. */
...@@ -80,12 +80,18 @@ typedef long long ssize_t; ...@@ -80,12 +80,18 @@ typedef long long ssize_t;
/* Flag that is set when we should set SO_REUSEADDR before calling bind() */ /* Flag that is set when we should set SO_REUSEADDR before calling bind() */
#define REDIS_REUSEADDR 0x80 #define REDIS_REUSEADDR 0x80
/* Flag that is set when the async connection supports push replies. */
#define REDIS_SUPPORTS_PUSH 0x100
/** /**
* Flag that indicates the user does not want the context to * Flag that indicates the user does not want the context to
* be automatically freed upon error * be automatically freed upon error
*/ */
#define REDIS_NO_AUTO_FREE 0x200 #define REDIS_NO_AUTO_FREE 0x200
/* Flag that indicates the user does not want replies to be automatically freed */
#define REDIS_NO_AUTO_FREE_REPLIES 0x400
#define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */ #define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */
/* number of times we retry to connect in the case of EADDRNOTAVAIL and /* number of times we retry to connect in the case of EADDRNOTAVAIL and
...@@ -112,7 +118,8 @@ typedef struct redisReply { ...@@ -112,7 +118,8 @@ typedef struct redisReply {
double dval; /* The double when type is REDIS_REPLY_DOUBLE */ double dval; /* The double when type is REDIS_REPLY_DOUBLE */
size_t len; /* Length of string */ size_t len; /* Length of string */
char *str; /* Used for REDIS_REPLY_ERROR, REDIS_REPLY_STRING char *str; /* Used for REDIS_REPLY_ERROR, REDIS_REPLY_STRING
REDIS_REPLY_VERB, and REDIS_REPLY_DOUBLE (in additional to dval). */ REDIS_REPLY_VERB, REDIS_REPLY_DOUBLE (in additional to dval),
and REDIS_REPLY_BIGNUM. */
char vtype[4]; /* Used for REDIS_REPLY_VERB, contains the null char vtype[4]; /* Used for REDIS_REPLY_VERB, contains the null
terminated 3 character content type, such as "txt". */ terminated 3 character content type, such as "txt". */
size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */ size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */
...@@ -127,8 +134,8 @@ void freeReplyObject(void *reply); ...@@ -127,8 +134,8 @@ void freeReplyObject(void *reply);
/* Functions to format a command according to the protocol. */ /* Functions to format a command according to the protocol. */
int redisvFormatCommand(char **target, const char *format, va_list ap); int redisvFormatCommand(char **target, const char *format, va_list ap);
int redisFormatCommand(char **target, const char *format, ...); int redisFormatCommand(char **target, const char *format, ...);
int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen); long long redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen);
int redisFormatSdsCommandArgv(hisds *target, int argc, const char ** argv, const size_t *argvlen); long long redisFormatSdsCommandArgv(hisds *target, int argc, const char ** argv, const size_t *argvlen);
void redisFreeCommand(char *cmd); void redisFreeCommand(char *cmd);
void redisFreeSdsCommand(hisds cmd); void redisFreeSdsCommand(hisds cmd);
...@@ -152,6 +159,11 @@ struct redisSsl; ...@@ -152,6 +159,11 @@ struct redisSsl;
/* Don't automatically intercept and free RESP3 PUSH replies. */ /* Don't automatically intercept and free RESP3 PUSH replies. */
#define REDIS_OPT_NO_PUSH_AUTOFREE 0x08 #define REDIS_OPT_NO_PUSH_AUTOFREE 0x08
/**
* Don't automatically free replies
*/
#define REDIS_OPT_NOAUTOFREEREPLIES 0x10
/* In Unix systems a file descriptor is a regular signed int, with -1 /* In Unix systems a file descriptor is a regular signed int, with -1
* representing an invalid descriptor. In Windows it is a SOCKET * representing an invalid descriptor. In Windows it is a SOCKET
* (32- or 64-bit unsigned integer depending on the architecture), where * (32- or 64-bit unsigned integer depending on the architecture), where
...@@ -255,7 +267,7 @@ typedef struct redisContext { ...@@ -255,7 +267,7 @@ typedef struct redisContext {
} unix_sock; } unix_sock;
/* For non-blocking connect */ /* For non-blocking connect */
struct sockadr *saddr; struct sockaddr *saddr;
size_t addrlen; size_t addrlen;
/* Optional data and corresponding destructor users can use to provide /* Optional data and corresponding destructor users can use to provide
......
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)\..\..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<AdditionalLibraryDirectories>$(MSBuildThisFileDirectory)\..\..\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>
</Project>
\ No newline at end of file
...@@ -56,7 +56,9 @@ typedef enum { ...@@ -56,7 +56,9 @@ typedef enum {
REDIS_SSL_CTX_CERT_KEY_REQUIRED, /* Client cert and key must both be specified or skipped */ REDIS_SSL_CTX_CERT_KEY_REQUIRED, /* Client cert and key must both be specified or skipped */
REDIS_SSL_CTX_CA_CERT_LOAD_FAILED, /* Failed to load CA Certificate or CA Path */ REDIS_SSL_CTX_CA_CERT_LOAD_FAILED, /* Failed to load CA Certificate or CA Path */
REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED, /* Failed to load client certificate */ REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED, /* Failed to load client certificate */
REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED /* Failed to load private key */ REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED, /* Failed to load private key */
REDIS_SSL_CTX_OS_CERTSTORE_OPEN_FAILED, /* Failed to open system certifcate store */
REDIS_SSL_CTX_OS_CERT_ADD_FAILED /* Failed to add CA certificates obtained from system to the SSL context */
} redisSSLContextError; } redisSSLContextError;
/** /**
......
...@@ -123,29 +123,28 @@ static char *readBytes(redisReader *r, unsigned int bytes) { ...@@ -123,29 +123,28 @@ static char *readBytes(redisReader *r, unsigned int bytes) {
/* Find pointer to \r\n. */ /* Find pointer to \r\n. */
static char *seekNewline(char *s, size_t len) { static char *seekNewline(char *s, size_t len) {
int pos = 0; char *ret;
int _len = len-1;
/* We cannot match with fewer than 2 bytes */
/* Position should be < len-1 because the character at "pos" should be if (len < 2)
* followed by a \n. Note that strchr cannot be used because it doesn't return NULL;
* allow to search a limited length and the buffer that is being searched
* might not have a trailing NULL character. */ /* Search up to len - 1 characters */
while (pos < _len) { len--;
while(pos < _len && s[pos] != '\r') pos++;
if (pos==_len) { /* Look for the \r */
/* Not found. */ while ((ret = memchr(s, '\r', len)) != NULL) {
return NULL; if (ret[1] == '\n') {
} else { /* Found. */
if (s[pos+1] == '\n') { break;
/* Found. */
return s+pos;
} else {
/* Continue searching. */
pos++;
}
} }
/* Continue searching. */
ret++;
len -= ret - s;
s = ret;
} }
return NULL;
return ret;
} }
/* Convert a string into a long long. Returns REDIS_OK if the string could be /* Convert a string into a long long. Returns REDIS_OK if the string could be
...@@ -274,60 +273,104 @@ static int processLineItem(redisReader *r) { ...@@ -274,60 +273,104 @@ static int processLineItem(redisReader *r) {
if ((p = readLine(r,&len)) != NULL) { if ((p = readLine(r,&len)) != NULL) {
if (cur->type == REDIS_REPLY_INTEGER) { if (cur->type == REDIS_REPLY_INTEGER) {
long long v;
if (string2ll(p, len, &v) == REDIS_ERR) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Bad integer value");
return REDIS_ERR;
}
if (r->fn && r->fn->createInteger) { if (r->fn && r->fn->createInteger) {
long long v;
if (string2ll(p, len, &v) == REDIS_ERR) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Bad integer value");
return REDIS_ERR;
}
obj = r->fn->createInteger(cur,v); obj = r->fn->createInteger(cur,v);
} else { } else {
obj = (void*)REDIS_REPLY_INTEGER; obj = (void*)REDIS_REPLY_INTEGER;
} }
} else if (cur->type == REDIS_REPLY_DOUBLE) { } else if (cur->type == REDIS_REPLY_DOUBLE) {
if (r->fn && r->fn->createDouble) { char buf[326], *eptr;
char buf[326], *eptr; double d;
double d;
if ((size_t)len >= sizeof(buf)) { if ((size_t)len >= sizeof(buf)) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Double value is too large");
return REDIS_ERR;
}
memcpy(buf,p,len);
buf[len] = '\0';
if (len == 3 && strcasecmp(buf,"inf") == 0) {
d = INFINITY; /* Positive infinite. */
} else if (len == 4 && strcasecmp(buf,"-inf") == 0) {
d = -INFINITY; /* Negative infinite. */
} else {
d = strtod((char*)buf,&eptr);
/* RESP3 only allows "inf", "-inf", and finite values, while
* strtod() allows other variations on infinity, NaN,
* etc. We explicity handle our two allowed infinite cases
* above, so strtod() should only result in finite values. */
if (buf[0] == '\0' || eptr != &buf[len] || !isfinite(d)) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL, __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Double value is too large"); "Bad double value");
return REDIS_ERR; return REDIS_ERR;
} }
}
memcpy(buf,p,len); if (r->fn && r->fn->createDouble) {
buf[len] = '\0';
if (strcasecmp(buf,",inf") == 0) {
d = INFINITY; /* Positive infinite. */
} else if (strcasecmp(buf,",-inf") == 0) {
d = -INFINITY; /* Negative infinite. */
} else {
d = strtod((char*)buf,&eptr);
if (buf[0] == '\0' || eptr[0] != '\0' || isnan(d)) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Bad double value");
return REDIS_ERR;
}
}
obj = r->fn->createDouble(cur,d,buf,len); obj = r->fn->createDouble(cur,d,buf,len);
} else { } else {
obj = (void*)REDIS_REPLY_DOUBLE; obj = (void*)REDIS_REPLY_DOUBLE;
} }
} else if (cur->type == REDIS_REPLY_NIL) { } else if (cur->type == REDIS_REPLY_NIL) {
if (len != 0) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Bad nil value");
return REDIS_ERR;
}
if (r->fn && r->fn->createNil) if (r->fn && r->fn->createNil)
obj = r->fn->createNil(cur); obj = r->fn->createNil(cur);
else else
obj = (void*)REDIS_REPLY_NIL; obj = (void*)REDIS_REPLY_NIL;
} else if (cur->type == REDIS_REPLY_BOOL) { } else if (cur->type == REDIS_REPLY_BOOL) {
int bval = p[0] == 't' || p[0] == 'T'; int bval;
if (len != 1 || !strchr("tTfF", p[0])) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Bad bool value");
return REDIS_ERR;
}
bval = p[0] == 't' || p[0] == 'T';
if (r->fn && r->fn->createBool) if (r->fn && r->fn->createBool)
obj = r->fn->createBool(cur,bval); obj = r->fn->createBool(cur,bval);
else else
obj = (void*)REDIS_REPLY_BOOL; obj = (void*)REDIS_REPLY_BOOL;
} else if (cur->type == REDIS_REPLY_BIGNUM) {
/* Ensure all characters are decimal digits (with possible leading
* minus sign). */
for (int i = 0; i < len; i++) {
/* XXX Consider: Allow leading '+'? Error on leading '0's? */
if (i == 0 && p[0] == '-') continue;
if (p[i] < '0' || p[i] > '9') {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Bad bignum value");
return REDIS_ERR;
}
}
if (r->fn && r->fn->createString)
obj = r->fn->createString(cur,p,len);
else
obj = (void*)REDIS_REPLY_BIGNUM;
} else { } else {
/* Type will be error or status. */ /* Type will be error or status. */
for (int i = 0; i < len; i++) {
if (p[i] == '\r' || p[i] == '\n') {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Bad simple string value");
return REDIS_ERR;
}
}
if (r->fn && r->fn->createString) if (r->fn && r->fn->createString)
obj = r->fn->createString(cur,p,len); obj = r->fn->createString(cur,p,len);
else else
...@@ -453,7 +496,6 @@ static int processAggregateItem(redisReader *r) { ...@@ -453,7 +496,6 @@ static int processAggregateItem(redisReader *r) {
long long elements; long long elements;
int root = 0, len; int root = 0, len;
/* Set error for nested multi bulks with depth > 7 */
if (r->ridx == r->tasks - 1) { if (r->ridx == r->tasks - 1) {
if (redisReaderGrow(r) == REDIS_ERR) if (redisReaderGrow(r) == REDIS_ERR)
return REDIS_ERR; return REDIS_ERR;
...@@ -569,6 +611,9 @@ static int processItem(redisReader *r) { ...@@ -569,6 +611,9 @@ static int processItem(redisReader *r) {
case '>': case '>':
cur->type = REDIS_REPLY_PUSH; cur->type = REDIS_REPLY_PUSH;
break; break;
case '(':
cur->type = REDIS_REPLY_BIGNUM;
break;
default: default:
__redisReaderSetErrorProtocolByte(r,*p); __redisReaderSetErrorProtocolByte(r,*p);
return REDIS_ERR; return REDIS_ERR;
...@@ -587,6 +632,7 @@ static int processItem(redisReader *r) { ...@@ -587,6 +632,7 @@ static int processItem(redisReader *r) {
case REDIS_REPLY_DOUBLE: case REDIS_REPLY_DOUBLE:
case REDIS_REPLY_NIL: case REDIS_REPLY_NIL:
case REDIS_REPLY_BOOL: case REDIS_REPLY_BOOL:
case REDIS_REPLY_BIGNUM:
return processLineItem(r); return processLineItem(r);
case REDIS_REPLY_STRING: case REDIS_REPLY_STRING:
case REDIS_REPLY_VERB: case REDIS_REPLY_VERB:
......
...@@ -72,7 +72,7 @@ static inline char hi_sdsReqType(size_t string_size) { ...@@ -72,7 +72,7 @@ static inline char hi_sdsReqType(size_t string_size) {
* and 'initlen'. * and 'initlen'.
* If NULL is used for 'init' the string is initialized with zero bytes. * If NULL is used for 'init' the string is initialized with zero bytes.
* *
* The string is always null-termined (all the hisds strings are, always) so * The string is always null-terminated (all the hisds strings are, always) so
* even if you create an hisds string with: * even if you create an hisds string with:
* *
* mystring = hi_sdsnewlen("abc",3); * mystring = hi_sdsnewlen("abc",3);
...@@ -415,7 +415,7 @@ hisds hi_sdscpylen(hisds s, const char *t, size_t len) { ...@@ -415,7 +415,7 @@ hisds hi_sdscpylen(hisds s, const char *t, size_t len) {
return s; return s;
} }
/* Like hi_sdscpylen() but 't' must be a null-termined string so that the length /* Like hi_sdscpylen() but 't' must be a null-terminated string so that the length
* of the string is obtained with strlen(). */ * of the string is obtained with strlen(). */
hisds hi_sdscpy(hisds s, const char *t) { hisds hi_sdscpy(hisds s, const char *t) {
return hi_sdscpylen(s, t, strlen(t)); return hi_sdscpylen(s, t, strlen(t));
......
...@@ -38,6 +38,7 @@ ...@@ -38,6 +38,7 @@
#include <string.h> #include <string.h>
#ifdef _WIN32 #ifdef _WIN32
#include <windows.h> #include <windows.h>
#include <wincrypt.h>
#else #else
#include <pthread.h> #include <pthread.h>
#endif #endif
...@@ -182,6 +183,10 @@ const char *redisSSLContextGetError(redisSSLContextError error) ...@@ -182,6 +183,10 @@ const char *redisSSLContextGetError(redisSSLContextError error)
return "Failed to load client certificate"; return "Failed to load client certificate";
case REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED: case REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED:
return "Failed to load private key"; return "Failed to load private key";
case REDIS_SSL_CTX_OS_CERTSTORE_OPEN_FAILED:
return "Failed to open system certifcate store";
case REDIS_SSL_CTX_OS_CERT_ADD_FAILED:
return "Failed to add CA certificates obtained from system to the SSL context";
default: default:
return "Unknown error code"; return "Unknown error code";
} }
...@@ -214,6 +219,11 @@ redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char * ...@@ -214,6 +219,11 @@ redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char *
const char *cert_filename, const char *private_key_filename, const char *cert_filename, const char *private_key_filename,
const char *server_name, redisSSLContextError *error) const char *server_name, redisSSLContextError *error)
{ {
#ifdef _WIN32
HCERTSTORE win_store = NULL;
PCCERT_CONTEXT win_ctx = NULL;
#endif
redisSSLContext *ctx = hi_calloc(1, sizeof(redisSSLContext)); redisSSLContext *ctx = hi_calloc(1, sizeof(redisSSLContext));
if (ctx == NULL) if (ctx == NULL)
goto error; goto error;
...@@ -234,6 +244,31 @@ redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char * ...@@ -234,6 +244,31 @@ redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char *
} }
if (capath || cacert_filename) { if (capath || cacert_filename) {
#ifdef _WIN32
if (0 == strcmp(cacert_filename, "wincert")) {
win_store = CertOpenSystemStore(NULL, "Root");
if (!win_store) {
if (error) *error = REDIS_SSL_CTX_OS_CERTSTORE_OPEN_FAILED;
goto error;
}
X509_STORE* store = SSL_CTX_get_cert_store(ctx->ssl_ctx);
while (win_ctx = CertEnumCertificatesInStore(win_store, win_ctx)) {
X509* x509 = NULL;
x509 = d2i_X509(NULL, (const unsigned char**)&win_ctx->pbCertEncoded, win_ctx->cbCertEncoded);
if (x509) {
if ((1 != X509_STORE_add_cert(store, x509)) ||
(1 != SSL_CTX_add_client_CA(ctx->ssl_ctx, x509)))
{
if (error) *error = REDIS_SSL_CTX_OS_CERT_ADD_FAILED;
goto error;
}
X509_free(x509);
}
}
CertFreeCertificateContext(win_ctx);
CertCloseStore(win_store, 0);
} else
#endif
if (!SSL_CTX_load_verify_locations(ctx->ssl_ctx, cacert_filename, capath)) { if (!SSL_CTX_load_verify_locations(ctx->ssl_ctx, cacert_filename, capath)) {
if (error) *error = REDIS_SSL_CTX_CA_CERT_LOAD_FAILED; if (error) *error = REDIS_SSL_CTX_CA_CERT_LOAD_FAILED;
goto error; goto error;
...@@ -257,6 +292,10 @@ redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char * ...@@ -257,6 +292,10 @@ redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char *
return ctx; return ctx;
error: error:
#ifdef _WIN32
CertFreeCertificateContext(win_ctx);
CertCloseStore(win_store, 0);
#endif
redisFreeSSLContext(ctx); redisFreeSSLContext(ctx);
return NULL; return NULL;
} }
...@@ -353,7 +392,11 @@ int redisInitiateSSLWithContext(redisContext *c, redisSSLContext *redis_ssl_ctx) ...@@ -353,7 +392,11 @@ int redisInitiateSSLWithContext(redisContext *c, redisSSLContext *redis_ssl_ctx)
} }
} }
return redisSSLConnect(c, ssl); if (redisSSLConnect(c, ssl) != REDIS_OK) {
goto error;
}
return REDIS_OK;
error: error:
if (ssl) if (ssl)
......
This diff is collapsed.
...@@ -213,7 +213,9 @@ tcp-keepalive 300 ...@@ -213,7 +213,9 @@ tcp-keepalive 300
# #
# tls-client-key-file-pass secret # tls-client-key-file-pass secret
# Configure a DH parameters file to enable Diffie-Hellman (DH) key exchange: # Configure a DH parameters file to enable Diffie-Hellman (DH) key exchange,
# required by older versions of OpenSSL (<3.0). Newer versions do not require
# this configuration and recommend against it.
# #
# tls-dh-params-file redis.dh # tls-dh-params-file redis.dh
...@@ -641,7 +643,7 @@ repl-diskless-sync-max-replicas 0 ...@@ -641,7 +643,7 @@ repl-diskless-sync-max-replicas 0
# you risk an OOM kill. # you risk an OOM kill.
repl-diskless-load disabled repl-diskless-load disabled
# Replicas send PINGs to server in a predefined interval. It's possible to # Master send PINGs to its replicas in a predefined interval. It's possible to
# change this interval with the repl_ping_replica_period option. The default # change this interval with the repl_ping_replica_period option. The default
# value is 10 seconds. # value is 10 seconds.
# #
...@@ -1678,7 +1680,7 @@ aof-timestamp-enabled no ...@@ -1678,7 +1680,7 @@ aof-timestamp-enabled no
# routing. By default this value is only shown as additional metadata in the CLUSTER SLOTS # routing. By default this value is only shown as additional metadata in the CLUSTER SLOTS
# command, but can be changed using 'cluster-preferred-endpoint-type' config. This value is # command, but can be changed using 'cluster-preferred-endpoint-type' config. This value is
# communicated along the clusterbus to all nodes, setting it to an empty string will remove # communicated along the clusterbus to all nodes, setting it to an empty string will remove
# the hostname and also propgate the removal. # the hostname and also propagate the removal.
# #
# cluster-announce-hostname "" # cluster-announce-hostname ""
......
...@@ -44,6 +44,7 @@ $TCLSH tests/test_helper.tcl \ ...@@ -44,6 +44,7 @@ $TCLSH tests/test_helper.tcl \
--single unit/moduleapi/aclcheck \ --single unit/moduleapi/aclcheck \
--single unit/moduleapi/subcommands \ --single unit/moduleapi/subcommands \
--single unit/moduleapi/reply \ --single unit/moduleapi/reply \
--single unit/moduleapi/cmdintrospection \
--single unit/moduleapi/eventloop \ --single unit/moduleapi/eventloop \
--single unit/moduleapi/timer \
"${@}" "${@}"
...@@ -1531,18 +1531,15 @@ static int ACLSelectorCheckKey(aclSelector *selector, const char *key, int keyle ...@@ -1531,18 +1531,15 @@ static int ACLSelectorCheckKey(aclSelector *selector, const char *key, int keyle
return ACL_DENIED_KEY; return ACL_DENIED_KEY;
} }
/* Returns if a given command may possibly access channels. For this context, /* Checks a channel against a provided list of channels. The is_pattern
* the unsubscribe commands do not have channels. */ * argument should only be used when subscribing (not when publishing)
static int ACLDoesCommandHaveChannels(struct redisCommand *cmd) { * and controls whether the input channel is evaluated as a channel pattern
return (cmd->proc == publishCommand * (like in PSUBSCRIBE) or a plain channel name (like in SUBSCRIBE).
|| cmd->proc == subscribeCommand *
|| cmd->proc == psubscribeCommand * Note that a plain channel name like in PUBLISH or SUBSCRIBE can be
|| cmd->proc == spublishCommand * matched against ACL channel patterns, but the pattern provided in PSUBSCRIBE
|| cmd->proc == ssubscribeCommand); * can only be matched as a literal against an ACL pattern (using plain string compare). */
} static int ACLCheckChannelAgainstList(list *reference, const char *channel, int channellen, int is_pattern) {
/* Checks a channel against a provide list of channels. */
static int ACLCheckChannelAgainstList(list *reference, const char *channel, int channellen, int literal) {
listIter li; listIter li;
listNode *ln; listNode *ln;
...@@ -1550,8 +1547,10 @@ static int ACLCheckChannelAgainstList(list *reference, const char *channel, int ...@@ -1550,8 +1547,10 @@ static int ACLCheckChannelAgainstList(list *reference, const char *channel, int
while((ln = listNext(&li))) { while((ln = listNext(&li))) {
sds pattern = listNodeValue(ln); sds pattern = listNodeValue(ln);
size_t plen = sdslen(pattern); size_t plen = sdslen(pattern);
if ((literal && !strcmp(pattern,channel)) || /* Channel patterns are matched literally against the channels in
(!literal && stringmatchlen(pattern,plen,channel,channellen,0))) * the list. Regular channels perform pattern matching. */
if ((is_pattern && !strcmp(pattern,channel)) ||
(!is_pattern && stringmatchlen(pattern,plen,channel,channellen,0)))
{ {
return ACL_OK; return ACL_OK;
} }
...@@ -1559,28 +1558,6 @@ static int ACLCheckChannelAgainstList(list *reference, const char *channel, int ...@@ -1559,28 +1558,6 @@ static int ACLCheckChannelAgainstList(list *reference, const char *channel, int
return ACL_DENIED_CHANNEL; return ACL_DENIED_CHANNEL;
} }
/* Check if the pub/sub channels of the command can be executed
* according to the ACL channels associated with the specified selector.
*
* idx and count are the index and count of channel arguments from the
* command. The literal argument controls whether the selector's ACL channels are
* evaluated as literal values or matched as glob-like patterns.
*
* If the selector can execute the command ACL_OK is returned, otherwise
* ACL_DENIED_CHANNEL. */
static int ACLSelectorCheckPubsubArguments(aclSelector *s, robj **argv, int idx, int count, int literal, int *idxptr) {
for (int j = idx; j < idx+count; j++) {
if (ACLCheckChannelAgainstList(s->channels, argv[j]->ptr, sdslen(argv[j]->ptr), literal != ACL_OK)) {
if (idxptr) *idxptr = j;
return ACL_DENIED_CHANNEL;
}
}
/* If we survived all the above checks, the selector can execute the
* command. */
return ACL_OK;
}
/* To prevent duplicate calls to getKeysResult, a cache is maintained /* To prevent duplicate calls to getKeysResult, a cache is maintained
* in between calls to the various selectors. */ * in between calls to the various selectors. */
typedef struct { typedef struct {
...@@ -1645,7 +1622,7 @@ static int ACLSelectorCheckCmd(aclSelector *selector, struct redisCommand *cmd, ...@@ -1645,7 +1622,7 @@ static int ACLSelectorCheckCmd(aclSelector *selector, struct redisCommand *cmd,
int idx = resultidx[j].pos; int idx = resultidx[j].pos;
ret = ACLSelectorCheckKey(selector, argv[idx]->ptr, sdslen(argv[idx]->ptr), resultidx[j].flags); ret = ACLSelectorCheckKey(selector, argv[idx]->ptr, sdslen(argv[idx]->ptr), resultidx[j].flags);
if (ret != ACL_OK) { if (ret != ACL_OK) {
if (resultidx) *keyidxptr = resultidx[j].pos; if (keyidxptr) *keyidxptr = resultidx[j].pos;
return ret; return ret;
} }
} }
...@@ -1653,26 +1630,30 @@ static int ACLSelectorCheckCmd(aclSelector *selector, struct redisCommand *cmd, ...@@ -1653,26 +1630,30 @@ static int ACLSelectorCheckCmd(aclSelector *selector, struct redisCommand *cmd,
/* Check if the user can execute commands explicitly touching the channels /* Check if the user can execute commands explicitly touching the channels
* mentioned in the command arguments */ * mentioned in the command arguments */
if (!(selector->flags & SELECTOR_FLAG_ALLCHANNELS) && ACLDoesCommandHaveChannels(cmd)) { const int channel_flags = CMD_CHANNEL_PUBLISH | CMD_CHANNEL_SUBSCRIBE;
if (cmd->proc == publishCommand || cmd->proc == spublishCommand) { if (!(selector->flags & SELECTOR_FLAG_ALLCHANNELS) && doesCommandHaveChannelsWithFlags(cmd, channel_flags)) {
ret = ACLSelectorCheckPubsubArguments(selector,argv, 1, 1, 0, keyidxptr); getKeysResult channels = (getKeysResult) GETKEYS_RESULT_INIT;
} else if (cmd->proc == subscribeCommand || cmd->proc == ssubscribeCommand) { getChannelsFromCommand(cmd, argv, argc, &channels);
ret = ACLSelectorCheckPubsubArguments(selector, argv, 1, argc-1, 0, keyidxptr); keyReference *channelref = channels.keys;
} else if (cmd->proc == psubscribeCommand) { for (int j = 0; j < channels.numkeys; j++) {
ret = ACLSelectorCheckPubsubArguments(selector, argv, 1, argc-1, 1, keyidxptr); int idx = channelref[j].pos;
} else { if (!(channelref[j].flags & channel_flags)) continue;
serverPanic("Encountered a command declared with channels but not handled"); int is_pattern = channelref[j].flags & CMD_CHANNEL_PATTERN;
} int ret = ACLCheckChannelAgainstList(selector->channels, argv[idx]->ptr, sdslen(argv[idx]->ptr), is_pattern);
if (ret != ACL_OK) { if (ret != ACL_OK) {
/* keyidxptr is set by ACLSelectorCheckPubsubArguments */ if (keyidxptr) *keyidxptr = channelref[j].pos;
return ret; getKeysFreeResult(&channels);
return ret;
}
} }
getKeysFreeResult(&channels);
} }
return ACL_OK; return ACL_OK;
} }
/* Check if the key can be accessed by the client according to /* Check if the key can be accessed by the client according to
* the ACLs associated with the specified user. * the ACLs associated with the specified user according to the
* keyspec access flags.
* *
* If the user can access the key, ACL_OK is returned, otherwise * If the user can access the key, ACL_OK is returned, otherwise
* ACL_DENIED_KEY is returned. */ * ACL_DENIED_KEY is returned. */
...@@ -1699,7 +1680,7 @@ int ACLUserCheckKeyPerm(user *u, const char *key, int keylen, int flags) { ...@@ -1699,7 +1680,7 @@ int ACLUserCheckKeyPerm(user *u, const char *key, int keylen, int flags) {
* *
* If the user can access the key, ACL_OK is returned, otherwise * If the user can access the key, ACL_OK is returned, otherwise
* ACL_DENIED_CHANNEL is returned. */ * ACL_DENIED_CHANNEL is returned. */
int ACLUserCheckChannelPerm(user *u, sds channel, int literal) { int ACLUserCheckChannelPerm(user *u, sds channel, int is_pattern) {
listIter li; listIter li;
listNode *ln; listNode *ln;
...@@ -1714,7 +1695,7 @@ int ACLUserCheckChannelPerm(user *u, sds channel, int literal) { ...@@ -1714,7 +1695,7 @@ int ACLUserCheckChannelPerm(user *u, sds channel, int literal) {
if (s->flags & SELECTOR_FLAG_ALLCHANNELS) return ACL_OK; if (s->flags & SELECTOR_FLAG_ALLCHANNELS) return ACL_OK;
/* Otherwise, loop over the selectors list and check each channel */ /* Otherwise, loop over the selectors list and check each channel */
if (ACLCheckChannelAgainstList(s->channels, channel, sdslen(channel), literal) == ACL_OK) { if (ACLCheckChannelAgainstList(s->channels, channel, sdslen(channel), is_pattern) == ACL_OK) {
return ACL_OK; return ACL_OK;
} }
} }
......
...@@ -42,11 +42,13 @@ ...@@ -42,11 +42,13 @@
#include <sys/param.h> #include <sys/param.h>
void freeClientArgv(client *c); void freeClientArgv(client *c);
off_t getAppendOnlyFileSize(sds filename); off_t getAppendOnlyFileSize(sds filename, int *status);
off_t getBaseAndIncrAppendOnlyFilesSize(aofManifest *am); off_t getBaseAndIncrAppendOnlyFilesSize(aofManifest *am, int *status);
int getBaseAndIncrAppendOnlyFilesNum(aofManifest *am); int getBaseAndIncrAppendOnlyFilesNum(aofManifest *am);
int aofFileExist(char *filename); int aofFileExist(char *filename);
int rewriteAppendOnlyFile(char *filename); int rewriteAppendOnlyFile(char *filename);
aofManifest *aofLoadManifestFromFile(sds am_filepath);
void aofManifestFreeAndUpdate(aofManifest *am);
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
* AOF Manifest file implementation. * AOF Manifest file implementation.
...@@ -226,13 +228,8 @@ sds getAofManifestAsString(aofManifest *am) { ...@@ -226,13 +228,8 @@ sds getAofManifestAsString(aofManifest *am) {
* in order to support seamless upgrades from previous versions which did not * in order to support seamless upgrades from previous versions which did not
* use them. * use them.
*/ */
#define MANIFEST_MAX_LINE 1024
void aofLoadManifestFromDisk(void) { void aofLoadManifestFromDisk(void) {
const char *err = NULL;
long long maxseq = 0;
server.aof_manifest = aofManifestCreate(); server.aof_manifest = aofManifestCreate();
if (!dirExists(server.aof_dirname)) { if (!dirExists(server.aof_dirname)) {
serverLog(LL_NOTICE, "The AOF directory %s doesn't exist", server.aof_dirname); serverLog(LL_NOTICE, "The AOF directory %s doesn't exist", server.aof_dirname);
return; return;
...@@ -247,16 +244,26 @@ void aofLoadManifestFromDisk(void) { ...@@ -247,16 +244,26 @@ void aofLoadManifestFromDisk(void) {
return; return;
} }
aofManifest *am = aofLoadManifestFromFile(am_filepath);
if (am) aofManifestFreeAndUpdate(am);
sdsfree(am_name);
sdsfree(am_filepath);
}
/* Generic manifest loading function, used in `aofLoadManifestFromDisk` and redis-check-aof tool. */
#define MANIFEST_MAX_LINE 1024
aofManifest *aofLoadManifestFromFile(sds am_filepath) {
const char *err = NULL;
long long maxseq = 0;
aofManifest *am = aofManifestCreate();
FILE *fp = fopen(am_filepath, "r"); FILE *fp = fopen(am_filepath, "r");
if (fp == NULL) { if (fp == NULL) {
serverLog(LL_WARNING, "Fatal error: can't open the AOF manifest " serverLog(LL_WARNING, "Fatal error: can't open the AOF manifest "
"file %s for reading: %s", am_name, strerror(errno)); "file %s for reading: %s", am_filepath, strerror(errno));
exit(1); exit(1);
} }
sdsfree(am_name);
sdsfree(am_filepath);
char buf[MANIFEST_MAX_LINE+1]; char buf[MANIFEST_MAX_LINE+1];
sds *argv = NULL; sds *argv = NULL;
int argc; int argc;
...@@ -292,14 +299,14 @@ void aofLoadManifestFromDisk(void) { ...@@ -292,14 +299,14 @@ void aofLoadManifestFromDisk(void) {
line = sdstrim(sdsnew(buf), " \t\r\n"); line = sdstrim(sdsnew(buf), " \t\r\n");
if (!sdslen(line)) { if (!sdslen(line)) {
err = "The AOF manifest file is invalid format"; err = "Invalid AOF manifest file format";
goto loaderr; goto loaderr;
} }
argv = sdssplitargs(line, &argc); argv = sdssplitargs(line, &argc);
/* 'argc < 6' was done for forward compatibility. */ /* 'argc < 6' was done for forward compatibility. */
if (argv == NULL || argc < 6 || (argc % 2)) { if (argv == NULL || argc < 6 || (argc % 2)) {
err = "The AOF manifest file is invalid format"; err = "Invalid AOF manifest file format";
goto loaderr; goto loaderr;
} }
...@@ -321,7 +328,7 @@ void aofLoadManifestFromDisk(void) { ...@@ -321,7 +328,7 @@ void aofLoadManifestFromDisk(void) {
/* We have to make sure we load all the information. */ /* We have to make sure we load all the information. */
if (!ai->file_name || !ai->file_seq || !ai->file_type) { if (!ai->file_name || !ai->file_seq || !ai->file_type) {
err = "The AOF manifest file is invalid format"; err = "Invalid AOF manifest file format";
goto loaderr; goto loaderr;
} }
...@@ -329,21 +336,21 @@ void aofLoadManifestFromDisk(void) { ...@@ -329,21 +336,21 @@ void aofLoadManifestFromDisk(void) {
argv = NULL; argv = NULL;
if (ai->file_type == AOF_FILE_TYPE_BASE) { if (ai->file_type == AOF_FILE_TYPE_BASE) {
if (server.aof_manifest->base_aof_info) { if (am->base_aof_info) {
err = "Found duplicate base file information"; err = "Found duplicate base file information";
goto loaderr; goto loaderr;
} }
server.aof_manifest->base_aof_info = ai; am->base_aof_info = ai;
server.aof_manifest->curr_base_file_seq = ai->file_seq; am->curr_base_file_seq = ai->file_seq;
} else if (ai->file_type == AOF_FILE_TYPE_HIST) { } else if (ai->file_type == AOF_FILE_TYPE_HIST) {
listAddNodeTail(server.aof_manifest->history_aof_list, ai); listAddNodeTail(am->history_aof_list, ai);
} else if (ai->file_type == AOF_FILE_TYPE_INCR) { } else if (ai->file_type == AOF_FILE_TYPE_INCR) {
if (ai->file_seq <= maxseq) { if (ai->file_seq <= maxseq) {
err = "Found a non-monotonic sequence number"; err = "Found a non-monotonic sequence number";
goto loaderr; goto loaderr;
} }
listAddNodeTail(server.aof_manifest->incr_aof_list, ai); listAddNodeTail(am->incr_aof_list, ai);
server.aof_manifest->curr_incr_file_seq = ai->file_seq; am->curr_incr_file_seq = ai->file_seq;
maxseq = ai->file_seq; maxseq = ai->file_seq;
} else { } else {
err = "Unknown AOF file type"; err = "Unknown AOF file type";
...@@ -356,7 +363,7 @@ void aofLoadManifestFromDisk(void) { ...@@ -356,7 +363,7 @@ void aofLoadManifestFromDisk(void) {
} }
fclose(fp); fclose(fp);
return; return am;
loaderr: loaderr:
/* Sanitizer suppression: may report a false positive if we goto loaderr /* Sanitizer suppression: may report a false positive if we goto loaderr
...@@ -627,7 +634,7 @@ void aofUpgradePrepare(aofManifest *am) { ...@@ -627,7 +634,7 @@ void aofUpgradePrepare(aofManifest *am) {
server.aof_dirname, server.aof_dirname,
strerror(errno)); strerror(errno));
sdsfree(aof_filepath); sdsfree(aof_filepath);
exit(1);; exit(1);
} }
sdsfree(aof_filepath); sdsfree(aof_filepath);
...@@ -721,7 +728,7 @@ void aofOpenIfNeededOnServerStart(void) { ...@@ -721,7 +728,7 @@ void aofOpenIfNeededOnServerStart(void) {
exit(1); exit(1);
} }
server.aof_last_incr_size = getAppendOnlyFileSize(aof_name); server.aof_last_incr_size = getAppendOnlyFileSize(aof_name, NULL);
} }
int aofFileExist(char *filename) { int aofFileExist(char *filename) {
...@@ -1338,26 +1345,35 @@ int loadSingleAppendOnlyFile(char *filename) { ...@@ -1338,26 +1345,35 @@ int loadSingleAppendOnlyFile(char *filename) {
client *old_client = server.current_client; client *old_client = server.current_client;
fakeClient = server.current_client = createAOFClient(); fakeClient = server.current_client = createAOFClient();
/* Check if this AOF file has an RDB preamble. In that case we need to /* Check if the AOF file is in RDB format (it may be RDB encoded base AOF
* load the RDB file and later continue loading the AOF tail. */ * or old style RDB-preamble AOF). In that case we need to load the RDB file
* and later continue loading the AOF tail if it is an old style RDB-preamble AOF. */
char sig[5]; /* "REDIS" */ char sig[5]; /* "REDIS" */
if (fread(sig,1,5,fp) != 5 || memcmp(sig,"REDIS",5) != 0) { if (fread(sig,1,5,fp) != 5 || memcmp(sig,"REDIS",5) != 0) {
/* No RDB preamble, seek back at 0 offset. */ /* Not in RDB format, seek back at 0 offset. */
if (fseek(fp,0,SEEK_SET) == -1) goto readerr; if (fseek(fp,0,SEEK_SET) == -1) goto readerr;
} else { } else {
/* RDB preamble. Pass loading the RDB functions. */ /* RDB format. Pass loading the RDB functions. */
rio rdb; rio rdb;
int old_style = !strcmp(filename, server.aof_filename);
if (old_style)
serverLog(LL_NOTICE, "Reading RDB preamble from AOF file...");
else
serverLog(LL_NOTICE, "Reading RDB base file on AOF loading...");
serverLog(LL_NOTICE,"Reading RDB preamble from AOF file...");
if (fseek(fp,0,SEEK_SET) == -1) goto readerr; if (fseek(fp,0,SEEK_SET) == -1) goto readerr;
rioInitWithFile(&rdb,fp); rioInitWithFile(&rdb,fp);
if (rdbLoadRio(&rdb,RDBFLAGS_AOF_PREAMBLE,NULL) != C_OK) { if (rdbLoadRio(&rdb,RDBFLAGS_AOF_PREAMBLE,NULL) != C_OK) {
serverLog(LL_WARNING,"Error reading the RDB preamble of the AOF file %s, AOF loading aborted", filename); if (old_style)
serverLog(LL_WARNING, "Error reading the RDB preamble of the AOF file %s, AOF loading aborted", filename);
else
serverLog(LL_WARNING, "Error reading the RDB base file %s, AOF loading aborted", filename);
goto readerr; goto readerr;
} else { } else {
loadingAbsProgress(ftello(fp)); loadingAbsProgress(ftello(fp));
last_progress_report_size = ftello(fp); last_progress_report_size = ftello(fp);
serverLog(LL_NOTICE,"Reading the remaining AOF tail..."); if (old_style) serverLog(LL_NOTICE, "Reading the remaining AOF tail...");
} }
} }
...@@ -1517,15 +1533,15 @@ uxeof: /* Unexpected AOF end of file. */ ...@@ -1517,15 +1533,15 @@ uxeof: /* Unexpected AOF end of file. */
} }
} }
} }
serverLog(LL_WARNING,"Unexpected end of file reading the append only file %s. You can: \ serverLog(LL_WARNING, "Unexpected end of file reading the append only file %s. You can: "
1) Make a backup of your AOF file, then use ./redis-check-aof --fix <filename>. \ "1) Make a backup of your AOF file, then use ./redis-check-aof --fix <filename.manifest>. "
2) Alternatively you can set the 'aof-load-truncated' configuration option to yes and restart the server.", filename); "2) Alternatively you can set the 'aof-load-truncated' configuration option to yes and restart the server.", filename);
ret = AOF_FAILED; ret = AOF_FAILED;
goto cleanup; goto cleanup;
fmterr: /* Format error. */ fmterr: /* Format error. */
serverLog(LL_WARNING,"Bad file format reading the append only file %s: \ serverLog(LL_WARNING, "Bad file format reading the append only file %s: "
make a backup of your AOF file, then use ./redis-check-aof --fix <filename>", filename); "make a backup of your AOF file, then use ./redis-check-aof --fix <filename.manifest>", filename);
ret = AOF_FAILED; ret = AOF_FAILED;
/* fall through to cleanup. */ /* fall through to cleanup. */
...@@ -1540,7 +1556,7 @@ cleanup: ...@@ -1540,7 +1556,7 @@ cleanup:
/* Load the AOF files according the aofManifest pointed by am. */ /* Load the AOF files according the aofManifest pointed by am. */
int loadAppendOnlyFiles(aofManifest *am) { int loadAppendOnlyFiles(aofManifest *am) {
serverAssert(am != NULL); serverAssert(am != NULL);
int ret = C_OK; int status, ret = C_OK;
long long start; long long start;
off_t total_size = 0; off_t total_size = 0;
sds aof_name; sds aof_name;
...@@ -1574,7 +1590,16 @@ int loadAppendOnlyFiles(aofManifest *am) { ...@@ -1574,7 +1590,16 @@ int loadAppendOnlyFiles(aofManifest *am) {
/* Here we calculate the total size of all BASE and INCR files in /* Here we calculate the total size of all BASE and INCR files in
* advance, it will be set to `server.loading_total_bytes`. */ * advance, it will be set to `server.loading_total_bytes`. */
total_size = getBaseAndIncrAppendOnlyFilesSize(am); total_size = getBaseAndIncrAppendOnlyFilesSize(am, &status);
if (status != AOF_OK) {
/* If an AOF exists in the manifest but not on the disk, we consider this to be a fatal error. */
if (status == AOF_NOT_EXIST) status = AOF_FAILED;
return status;
} else if (total_size == 0) {
return AOF_EMPTY;
}
startLoading(total_size, RDBFLAGS_AOF_PREAMBLE, 0); startLoading(total_size, RDBFLAGS_AOF_PREAMBLE, 0);
/* Load BASE AOF if needed. */ /* Load BASE AOF if needed. */
...@@ -1590,9 +1615,8 @@ int loadAppendOnlyFiles(aofManifest *am) { ...@@ -1590,9 +1615,8 @@ int loadAppendOnlyFiles(aofManifest *am) {
aof_name, (float)(ustime()-start)/1000000); aof_name, (float)(ustime()-start)/1000000);
} }
/* If an AOF exists in the manifest but not on the disk, Or the truncated /* If the truncated file is not the last file, we consider this to be a fatal error. */
* file is not the last file, we consider this to be a fatal error. */ if (ret == AOF_TRUNCATED && !last_file) {
if (ret == AOF_NOT_EXIST || (ret == AOF_TRUNCATED && !last_file)) {
ret = AOF_FAILED; ret = AOF_FAILED;
} }
...@@ -1620,7 +1644,11 @@ int loadAppendOnlyFiles(aofManifest *am) { ...@@ -1620,7 +1644,11 @@ int loadAppendOnlyFiles(aofManifest *am) {
aof_name, (float)(ustime()-start)/1000000); aof_name, (float)(ustime()-start)/1000000);
} }
if (ret == AOF_NOT_EXIST || (ret == AOF_TRUNCATED && !last_file)) { /* We know that (at least) one of the AOF files has data (total_size > 0),
* so empty incr AOF file doesn't count as a AOF_EMPTY result */
if (ret == AOF_EMPTY) ret = AOF_OK;
if (ret == AOF_TRUNCATED && !last_file) {
ret = AOF_FAILED; ret = AOF_FAILED;
} }
...@@ -1635,7 +1663,7 @@ int loadAppendOnlyFiles(aofManifest *am) { ...@@ -1635,7 +1663,7 @@ int loadAppendOnlyFiles(aofManifest *am) {
server.aof_fsync_offset = server.aof_current_size; server.aof_fsync_offset = server.aof_current_size;
cleanup: cleanup:
stopLoading(ret == AOF_OK); stopLoading(ret == AOF_OK || ret == AOF_TRUNCATED);
return ret; return ret;
} }
...@@ -2007,10 +2035,14 @@ int rewriteStreamObject(rio *r, robj *key, robj *o) { ...@@ -2007,10 +2035,14 @@ int rewriteStreamObject(rio *r, robj *key, robj *o) {
/* Append XSETID after XADD, make sure lastid is correct, /* Append XSETID after XADD, make sure lastid is correct,
* in case of XDEL lastid. */ * in case of XDEL lastid. */
if (!rioWriteBulkCount(r,'*',3) || if (!rioWriteBulkCount(r,'*',7) ||
!rioWriteBulkString(r,"XSETID",6) || !rioWriteBulkString(r,"XSETID",6) ||
!rioWriteBulkObject(r,key) || !rioWriteBulkObject(r,key) ||
!rioWriteBulkStreamID(r,&s->last_id)) !rioWriteBulkStreamID(r,&s->last_id) ||
!rioWriteBulkString(r,"ENTRIESADDED",12) ||
!rioWriteBulkLongLong(r,s->entries_added) ||
!rioWriteBulkString(r,"MAXDELETEDID",12) ||
!rioWriteBulkStreamID(r,&s->max_deleted_entry_id))
{ {
streamIteratorStop(&si); streamIteratorStop(&si);
return 0; return 0;
...@@ -2025,12 +2057,14 @@ int rewriteStreamObject(rio *r, robj *key, robj *o) { ...@@ -2025,12 +2057,14 @@ int rewriteStreamObject(rio *r, robj *key, robj *o) {
while(raxNext(&ri)) { while(raxNext(&ri)) {
streamCG *group = ri.data; streamCG *group = ri.data;
/* Emit the XGROUP CREATE in order to create the group. */ /* Emit the XGROUP CREATE in order to create the group. */
if (!rioWriteBulkCount(r,'*',5) || if (!rioWriteBulkCount(r,'*',7) ||
!rioWriteBulkString(r,"XGROUP",6) || !rioWriteBulkString(r,"XGROUP",6) ||
!rioWriteBulkString(r,"CREATE",6) || !rioWriteBulkString(r,"CREATE",6) ||
!rioWriteBulkObject(r,key) || !rioWriteBulkObject(r,key) ||
!rioWriteBulkString(r,(char*)ri.key,ri.key_len) || !rioWriteBulkString(r,(char*)ri.key,ri.key_len) ||
!rioWriteBulkStreamID(r,&group->last_id)) !rioWriteBulkStreamID(r,&group->last_id) ||
!rioWriteBulkString(r,"ENTRIESREAD",11) ||
!rioWriteBulkLongLong(r,group->entries_read))
{ {
raxStop(&ri); raxStop(&ri);
streamIteratorStop(&si); streamIteratorStop(&si);
...@@ -2332,7 +2366,7 @@ int rewriteAppendOnlyFileBackground(void) { ...@@ -2332,7 +2366,7 @@ int rewriteAppendOnlyFileBackground(void) {
server.aof_selected_db = -1; server.aof_selected_db = -1;
flushAppendOnlyFile(1); flushAppendOnlyFile(1);
if (openNewIncrAofForAppend() != C_OK) return C_ERR; if (openNewIncrAofForAppend() != C_OK) return C_ERR;
server.stat_aof_rewrites++;
if ((childpid = redisFork(CHILD_TYPE_AOF)) == 0) { if ((childpid = redisFork(CHILD_TYPE_AOF)) == 0) {
char tmpfile[256]; char tmpfile[256];
...@@ -2388,7 +2422,10 @@ void aofRemoveTempFile(pid_t childpid) { ...@@ -2388,7 +2422,10 @@ void aofRemoveTempFile(pid_t childpid) {
bg_unlink(tmpfile); bg_unlink(tmpfile);
} }
off_t getAppendOnlyFileSize(sds filename) { /* Get size of an AOF file.
* The status argument is an optional output argument to be filled with
* one of the AOF_ status values. */
off_t getAppendOnlyFileSize(sds filename, int *status) {
struct redis_stat sb; struct redis_stat sb;
off_t size; off_t size;
mstime_t latency; mstime_t latency;
...@@ -2396,10 +2433,12 @@ off_t getAppendOnlyFileSize(sds filename) { ...@@ -2396,10 +2433,12 @@ off_t getAppendOnlyFileSize(sds filename) {
sds aof_filepath = makePath(server.aof_dirname, filename); sds aof_filepath = makePath(server.aof_dirname, filename);
latencyStartMonitor(latency); latencyStartMonitor(latency);
if (redis_stat(aof_filepath, &sb) == -1) { if (redis_stat(aof_filepath, &sb) == -1) {
if (status) *status = errno == ENOENT ? AOF_NOT_EXIST : AOF_OPEN_ERR;
serverLog(LL_WARNING, "Unable to obtain the AOF file %s length. stat: %s", serverLog(LL_WARNING, "Unable to obtain the AOF file %s length. stat: %s",
filename, strerror(errno)); filename, strerror(errno));
size = 0; size = 0;
} else { } else {
if (status) *status = AOF_OK;
size = sb.st_size; size = sb.st_size;
} }
latencyEndMonitor(latency); latencyEndMonitor(latency);
...@@ -2408,22 +2447,27 @@ off_t getAppendOnlyFileSize(sds filename) { ...@@ -2408,22 +2447,27 @@ off_t getAppendOnlyFileSize(sds filename) {
return size; return size;
} }
off_t getBaseAndIncrAppendOnlyFilesSize(aofManifest *am) { /* Get size of all AOF files referred by the manifest (excluding history).
* The status argument is an output argument to be filled with
* one of the AOF_ status values. */
off_t getBaseAndIncrAppendOnlyFilesSize(aofManifest *am, int *status) {
off_t size = 0; off_t size = 0;
listNode *ln; listNode *ln;
listIter li; listIter li;
if (am->base_aof_info) { if (am->base_aof_info) {
serverAssert(am->base_aof_info->file_type == AOF_FILE_TYPE_BASE); serverAssert(am->base_aof_info->file_type == AOF_FILE_TYPE_BASE);
size += getAppendOnlyFileSize(am->base_aof_info->file_name);
size += getAppendOnlyFileSize(am->base_aof_info->file_name, status);
if (*status != AOF_OK) return 0;
} }
listRewind(am->incr_aof_list, &li); listRewind(am->incr_aof_list, &li);
while ((ln = listNext(&li)) != NULL) { while ((ln = listNext(&li)) != NULL) {
aofInfo *ai = (aofInfo*)ln->value; aofInfo *ai = (aofInfo*)ln->value;
serverAssert(ai->file_type == AOF_FILE_TYPE_INCR); serverAssert(ai->file_type == AOF_FILE_TYPE_INCR);
size += getAppendOnlyFileSize(ai->file_name); size += getAppendOnlyFileSize(ai->file_name, status);
if (*status != AOF_OK) return 0;
} }
return size; return size;
...@@ -2497,7 +2541,7 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) { ...@@ -2497,7 +2541,7 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
if (server.aof_fd != -1) { if (server.aof_fd != -1) {
/* AOF enabled. */ /* AOF enabled. */
server.aof_selected_db = -1; /* Make sure SELECT is re-issued */ server.aof_selected_db = -1; /* Make sure SELECT is re-issued */
server.aof_current_size = getAppendOnlyFileSize(new_base_filename) + server.aof_last_incr_size; server.aof_current_size = getAppendOnlyFileSize(new_base_filename, NULL) + server.aof_last_incr_size;
server.aof_rewrite_base_size = server.aof_current_size; server.aof_rewrite_base_size = server.aof_current_size;
server.aof_fsync_offset = server.aof_current_size; server.aof_fsync_offset = server.aof_current_size;
server.aof_last_fsync = server.unixtime; server.aof_last_fsync = server.unixtime;
......
...@@ -108,9 +108,11 @@ void blockClient(client *c, int btype) { ...@@ -108,9 +108,11 @@ void blockClient(client *c, int btype) {
/* This function is called after a client has finished a blocking operation /* This function is called after a client has finished a blocking operation
* in order to update the total command duration, log the command into * in order to update the total command duration, log the command into
* the Slow log if needed, and log the reply duration event if needed. */ * the Slow log if needed, and log the reply duration event if needed. */
void updateStatsOnUnblock(client *c, long blocked_us, long reply_us){ void updateStatsOnUnblock(client *c, long blocked_us, long reply_us, int had_errors){
const ustime_t total_cmd_duration = c->duration + blocked_us + reply_us; const ustime_t total_cmd_duration = c->duration + blocked_us + reply_us;
c->lastcmd->microseconds += total_cmd_duration; c->lastcmd->microseconds += total_cmd_duration;
if (had_errors)
c->lastcmd->failed_calls++;
if (server.latency_tracking_enabled) if (server.latency_tracking_enabled)
updateCommandLatencyHistogram(&(c->lastcmd->latency_histogram), total_cmd_duration*1000); updateCommandLatencyHistogram(&(c->lastcmd->latency_histogram), total_cmd_duration*1000);
/* Log the command into the Slow log if needed. */ /* Log the command into the Slow log if needed. */
...@@ -314,6 +316,7 @@ void serveClientsBlockedOnListKey(robj *o, readyList *rl) { ...@@ -314,6 +316,7 @@ void serveClientsBlockedOnListKey(robj *o, readyList *rl) {
* call. */ * call. */
if (dstkey) incrRefCount(dstkey); if (dstkey) incrRefCount(dstkey);
long long prev_error_replies = server.stat_total_error_replies;
client *old_client = server.current_client; client *old_client = server.current_client;
server.current_client = receiver; server.current_client = receiver;
monotime replyTimer; monotime replyTimer;
...@@ -322,7 +325,7 @@ void serveClientsBlockedOnListKey(robj *o, readyList *rl) { ...@@ -322,7 +325,7 @@ void serveClientsBlockedOnListKey(robj *o, readyList *rl) {
rl->key, dstkey, rl->db, rl->key, dstkey, rl->db,
wherefrom, whereto, wherefrom, whereto,
&deleted); &deleted);
updateStatsOnUnblock(receiver, 0, elapsedUs(replyTimer)); updateStatsOnUnblock(receiver, 0, elapsedUs(replyTimer), server.stat_total_error_replies != prev_error_replies);
unblockClient(receiver); unblockClient(receiver);
afterCommand(receiver); afterCommand(receiver);
server.current_client = old_client; server.current_client = old_client;
...@@ -366,6 +369,7 @@ void serveClientsBlockedOnSortedSetKey(robj *o, readyList *rl) { ...@@ -366,6 +369,7 @@ void serveClientsBlockedOnSortedSetKey(robj *o, readyList *rl) {
? 1 : 0; ? 1 : 0;
int reply_nil_when_empty = use_nested_array; int reply_nil_when_empty = use_nested_array;
long long prev_error_replies = server.stat_total_error_replies;
client *old_client = server.current_client; client *old_client = server.current_client;
server.current_client = receiver; server.current_client = receiver;
monotime replyTimer; monotime replyTimer;
...@@ -388,7 +392,7 @@ void serveClientsBlockedOnSortedSetKey(robj *o, readyList *rl) { ...@@ -388,7 +392,7 @@ void serveClientsBlockedOnSortedSetKey(robj *o, readyList *rl) {
decrRefCount(argv[1]); decrRefCount(argv[1]);
if (count != -1) decrRefCount(argv[2]); if (count != -1) decrRefCount(argv[2]);
updateStatsOnUnblock(receiver, 0, elapsedUs(replyTimer)); updateStatsOnUnblock(receiver, 0, elapsedUs(replyTimer), server.stat_total_error_replies != prev_error_replies);
unblockClient(receiver); unblockClient(receiver);
afterCommand(receiver); afterCommand(receiver);
server.current_client = old_client; server.current_client = old_client;
...@@ -421,6 +425,12 @@ void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) { ...@@ -421,6 +425,12 @@ void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) {
bkinfo *bki = dictFetchValue(receiver->bpop.keys,rl->key); bkinfo *bki = dictFetchValue(receiver->bpop.keys,rl->key);
streamID *gt = &bki->stream_id; streamID *gt = &bki->stream_id;
long long prev_error_replies = server.stat_total_error_replies;
client *old_client = server.current_client;
server.current_client = receiver;
monotime replyTimer;
elapsedStart(&replyTimer);
/* If we blocked in the context of a consumer /* If we blocked in the context of a consumer
* group, we need to resolve the group and update the * group, we need to resolve the group and update the
* last ID the client is blocked for: this is needed * last ID the client is blocked for: this is needed
...@@ -440,8 +450,7 @@ void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) { ...@@ -440,8 +450,7 @@ void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) {
addReplyError(receiver, addReplyError(receiver,
"-NOGROUP the consumer group this client " "-NOGROUP the consumer group this client "
"was blocked on no longer exists"); "was blocked on no longer exists");
unblockClient(receiver); goto unblock_receiver;
continue;
} else { } else {
*gt = group->last_id; *gt = group->last_id;
} }
...@@ -470,10 +479,6 @@ void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) { ...@@ -470,10 +479,6 @@ void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) {
} }
} }
client *old_client = server.current_client;
server.current_client = receiver;
monotime replyTimer;
elapsedStart(&replyTimer);
/* Emit the two elements sub-array consisting of /* Emit the two elements sub-array consisting of
* the name of the stream and the data we * the name of the stream and the data we
* extracted from it. Wrapped in a single-item * extracted from it. Wrapped in a single-item
...@@ -493,11 +498,13 @@ void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) { ...@@ -493,11 +498,13 @@ void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) {
streamReplyWithRange(receiver,s,&start,NULL, streamReplyWithRange(receiver,s,&start,NULL,
receiver->bpop.xread_count, receiver->bpop.xread_count,
0, group, consumer, noack, &pi); 0, group, consumer, noack, &pi);
updateStatsOnUnblock(receiver, 0, elapsedUs(replyTimer));
/* Note that after we unblock the client, 'gt' /* Note that after we unblock the client, 'gt'
* and other receiver->bpop stuff are no longer * and other receiver->bpop stuff are no longer
* valid, so we must do the setup above before * valid, so we must do the setup above before
* this call. */ * the unblockClient call. */
unblock_receiver:
updateStatsOnUnblock(receiver, 0, elapsedUs(replyTimer), server.stat_total_error_replies != prev_error_replies);
unblockClient(receiver); unblockClient(receiver);
afterCommand(receiver); afterCommand(receiver);
server.current_client = old_client; server.current_client = old_client;
...@@ -545,12 +552,13 @@ void serveClientsBlockedOnKeyByModule(readyList *rl) { ...@@ -545,12 +552,13 @@ void serveClientsBlockedOnKeyByModule(readyList *rl) {
* different modules with different triggers to consider if a key * different modules with different triggers to consider if a key
* is ready or not. This means we can't exit the loop but need * is ready or not. This means we can't exit the loop but need
* to continue after the first failure. */ * to continue after the first failure. */
long long prev_error_replies = server.stat_total_error_replies;
client *old_client = server.current_client; client *old_client = server.current_client;
server.current_client = receiver; server.current_client = receiver;
monotime replyTimer; monotime replyTimer;
elapsedStart(&replyTimer); elapsedStart(&replyTimer);
if (!moduleTryServeClientBlockedOnKey(receiver, rl->key)) continue; if (!moduleTryServeClientBlockedOnKey(receiver, rl->key)) continue;
updateStatsOnUnblock(receiver, 0, elapsedUs(replyTimer)); updateStatsOnUnblock(receiver, 0, elapsedUs(replyTimer), server.stat_total_error_replies != prev_error_replies);
moduleUnblockClient(receiver); moduleUnblockClient(receiver);
afterCommand(receiver); afterCommand(receiver);
server.current_client = old_client; server.current_client = old_client;
......
...@@ -60,7 +60,7 @@ struct CallReply { ...@@ -60,7 +60,7 @@ struct CallReply {
double d; /* Reply value for double reply. */ double d; /* Reply value for double reply. */
struct CallReply *array; /* Array of sub-reply elements. used for set, array, map, and attribute */ struct CallReply *array; /* Array of sub-reply elements. used for set, array, map, and attribute */
} val; } val;
list *deferred_error_list; /* list of errors in sds form or NULL */
struct CallReply *attribute; /* attribute reply, NULL if not exists */ struct CallReply *attribute; /* attribute reply, NULL if not exists */
}; };
...@@ -237,6 +237,8 @@ void freeCallReply(CallReply *rep) { ...@@ -237,6 +237,8 @@ void freeCallReply(CallReply *rep) {
freeCallReplyInternal(rep); freeCallReplyInternal(rep);
} }
sdsfree(rep->original_proto); sdsfree(rep->original_proto);
if (rep->deferred_error_list)
listRelease(rep->deferred_error_list);
zfree(rep); zfree(rep);
} }
...@@ -488,6 +490,11 @@ int callReplyIsResp3(CallReply *rep) { ...@@ -488,6 +490,11 @@ int callReplyIsResp3(CallReply *rep) {
return rep->flags & REPLY_FLAG_RESP3; return rep->flags & REPLY_FLAG_RESP3;
} }
/* Returns a list of errors in sds form, or NULL. */
list *callReplyDeferredErrorList(CallReply *rep) {
return rep->deferred_error_list;
}
/* Create a new CallReply struct from the reply blob. /* Create a new CallReply struct from the reply blob.
* *
* The function will own the reply blob, so it must not be used or freed by * The function will own the reply blob, so it must not be used or freed by
...@@ -496,6 +503,9 @@ int callReplyIsResp3(CallReply *rep) { ...@@ -496,6 +503,9 @@ int callReplyIsResp3(CallReply *rep) {
* The reply blob will be freed when the returned CallReply struct is later * The reply blob will be freed when the returned CallReply struct is later
* freed using freeCallReply(). * freed using freeCallReply().
* *
* The deferred_error_list is an optional list of errors that are present
* in the reply blob, if given, this function will take ownership on it.
*
* The private_data is optional and can later be accessed using * The private_data is optional and can later be accessed using
* callReplyGetPrivateData(). * callReplyGetPrivateData().
* *
...@@ -504,7 +514,7 @@ int callReplyIsResp3(CallReply *rep) { ...@@ -504,7 +514,7 @@ int callReplyIsResp3(CallReply *rep) {
* DESIGNED TO HANDLE USER INPUT and using it to parse invalid replies is * DESIGNED TO HANDLE USER INPUT and using it to parse invalid replies is
* unsafe. * unsafe.
*/ */
CallReply *callReplyCreate(sds reply, void *private_data) { CallReply *callReplyCreate(sds reply, list *deferred_error_list, void *private_data) {
CallReply *res = zmalloc(sizeof(*res)); CallReply *res = zmalloc(sizeof(*res));
res->flags = REPLY_FLAG_ROOT; res->flags = REPLY_FLAG_ROOT;
res->original_proto = reply; res->original_proto = reply;
...@@ -512,5 +522,6 @@ CallReply *callReplyCreate(sds reply, void *private_data) { ...@@ -512,5 +522,6 @@ CallReply *callReplyCreate(sds reply, void *private_data) {
res->proto_len = sdslen(reply); res->proto_len = sdslen(reply);
res->private_data = private_data; res->private_data = private_data;
res->attribute = NULL; res->attribute = NULL;
res->deferred_error_list = deferred_error_list;
return res; return res;
} }
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
typedef struct CallReply CallReply; typedef struct CallReply CallReply;
CallReply *callReplyCreate(sds reply, void *private_data); CallReply *callReplyCreate(sds reply, list *deferred_error_list, void *private_data);
int callReplyType(CallReply *rep); int callReplyType(CallReply *rep);
const char *callReplyGetString(CallReply *rep, size_t *len); const char *callReplyGetString(CallReply *rep, size_t *len);
long long callReplyGetLongLong(CallReply *rep); long long callReplyGetLongLong(CallReply *rep);
...@@ -51,6 +51,7 @@ const char *callReplyGetVerbatim(CallReply *rep, size_t *len, const char **forma ...@@ -51,6 +51,7 @@ const char *callReplyGetVerbatim(CallReply *rep, size_t *len, const char **forma
const char *callReplyGetProto(CallReply *rep, size_t *len); const char *callReplyGetProto(CallReply *rep, size_t *len);
void *callReplyGetPrivateData(CallReply *rep); void *callReplyGetPrivateData(CallReply *rep);
int callReplyIsResp3(CallReply *rep); int callReplyIsResp3(CallReply *rep);
list *callReplyDeferredErrorList(CallReply *rep);
void freeCallReply(CallReply *rep); void freeCallReply(CallReply *rep);
#endif /* SRC_CALL_REPLY_H_ */ #endif /* SRC_CALL_REPLY_H_ */
...@@ -106,7 +106,7 @@ dictType clusterNodesDictType = { ...@@ -106,7 +106,7 @@ dictType clusterNodesDictType = {
}; };
/* Cluster re-addition blacklist. This maps node IDs to the time /* Cluster re-addition blacklist. This maps node IDs to the time
* we can re-add this node. The goal is to avoid readding a removed * we can re-add this node. The goal is to avoid reading a removed
* node for some time. */ * node for some time. */
dictType clusterNodesBlackListDictType = { dictType clusterNodesBlackListDictType = {
dictSdsCaseHash, /* hash function */ dictSdsCaseHash, /* hash function */
...@@ -243,10 +243,9 @@ int clusterLoadConfig(char *filename) { ...@@ -243,10 +243,9 @@ int clusterLoadConfig(char *filename) {
if (hostname) { if (hostname) {
*hostname = '\0'; *hostname = '\0';
hostname++; hostname++;
zfree(n->hostname); n->hostname = sdscpy(n->hostname, hostname);
n->hostname = zstrdup(hostname); } else if (sdslen(n->hostname) != 0) {
} else { sdsclear(n->hostname);
n->hostname = NULL;
} }
/* The plaintext port for client in a TLS cluster (n->pport) is not /* The plaintext port for client in a TLS cluster (n->pport) is not
...@@ -570,20 +569,15 @@ void clusterUpdateMyselfIp(void) { ...@@ -570,20 +569,15 @@ void clusterUpdateMyselfIp(void) {
/* Update the hostname for the specified node with the provided C string. */ /* Update the hostname for the specified node with the provided C string. */
static void updateAnnouncedHostname(clusterNode *node, char *new) { static void updateAnnouncedHostname(clusterNode *node, char *new) {
if (!node->hostname && !new) {
return;
}
/* Previous and new hostname are the same, no need to update. */ /* Previous and new hostname are the same, no need to update. */
if (new && node->hostname && !strcmp(new, node->hostname)) { if (new && !strcmp(new, node->hostname)) {
return; return;
} }
if (node->hostname) zfree(node->hostname);
if (new) { if (new) {
node->hostname = zstrdup(new); node->hostname = sdscpy(node->hostname, new);
} else { } else if (sdslen(node->hostname) != 0) {
node->hostname = NULL; sdsclear(node->hostname);
} }
} }
...@@ -959,7 +953,7 @@ clusterNode *createClusterNode(char *nodename, int flags) { ...@@ -959,7 +953,7 @@ clusterNode *createClusterNode(char *nodename, int flags) {
node->link = NULL; node->link = NULL;
node->inbound_link = NULL; node->inbound_link = NULL;
memset(node->ip,0,sizeof(node->ip)); memset(node->ip,0,sizeof(node->ip));
node->hostname = NULL; node->hostname = sdsempty();
node->port = 0; node->port = 0;
node->cport = 0; node->cport = 0;
node->pport = 0; node->pport = 0;
...@@ -1125,7 +1119,7 @@ void freeClusterNode(clusterNode *n) { ...@@ -1125,7 +1119,7 @@ void freeClusterNode(clusterNode *n) {
nodename = sdsnewlen(n->name, CLUSTER_NAMELEN); nodename = sdsnewlen(n->name, CLUSTER_NAMELEN);
serverAssert(dictDelete(server.cluster->nodes,nodename) == DICT_OK); serverAssert(dictDelete(server.cluster->nodes,nodename) == DICT_OK);
sdsfree(nodename); sdsfree(nodename);
zfree(n->hostname); sdsfree(n->hostname);
/* Release links and associated data structures. */ /* Release links and associated data structures. */
if (n->link) freeClusterLink(n->link); if (n->link) freeClusterLink(n->link);
...@@ -1947,9 +1941,9 @@ static clusterMsgPingExt *getNextPingExt(clusterMsgPingExt *ext) { ...@@ -1947,9 +1941,9 @@ static clusterMsgPingExt *getNextPingExt(clusterMsgPingExt *ext) {
* will be 8 byte padded. */ * will be 8 byte padded. */
int getHostnamePingExtSize() { int getHostnamePingExtSize() {
/* If hostname is not set, we don't send this extension */ /* If hostname is not set, we don't send this extension */
if (!myself->hostname) return 0; if (sdslen(myself->hostname) == 0) return 0;
int totlen = sizeof(clusterMsgPingExt) + EIGHT_BYTE_ALIGN(strlen(myself->hostname) + 1); int totlen = sizeof(clusterMsgPingExt) + EIGHT_BYTE_ALIGN(sdslen(myself->hostname) + 1);
return totlen; return totlen;
} }
...@@ -1958,19 +1952,18 @@ int getHostnamePingExtSize() { ...@@ -1958,19 +1952,18 @@ int getHostnamePingExtSize() {
* will return the amount of bytes written. */ * will return the amount of bytes written. */
int writeHostnamePingExt(clusterMsgPingExt **cursor) { int writeHostnamePingExt(clusterMsgPingExt **cursor) {
/* If hostname is not set, we don't send this extension */ /* If hostname is not set, we don't send this extension */
if (!myself->hostname) return 0; if (sdslen(myself->hostname) == 0) return 0;
/* Add the hostname information at the extension cursor */ /* Add the hostname information at the extension cursor */
clusterMsgPingExtHostname *ext = &(*cursor)->ext[0].hostname; clusterMsgPingExtHostname *ext = &(*cursor)->ext[0].hostname;
size_t hostname_len = strlen(myself->hostname); memcpy(ext->hostname, myself->hostname, sdslen(myself->hostname));
memcpy(ext->hostname, myself->hostname, hostname_len);
uint32_t extension_size = getHostnamePingExtSize(); uint32_t extension_size = getHostnamePingExtSize();
/* Move the write cursor */ /* Move the write cursor */
(*cursor)->type = CLUSTERMSG_EXT_TYPE_HOSTNAME; (*cursor)->type = CLUSTERMSG_EXT_TYPE_HOSTNAME;
(*cursor)->length = htonl(extension_size); (*cursor)->length = htonl(extension_size);
/* Make sure the string is NULL terminated by adding 1 */ /* Make sure the string is NULL terminated by adding 1 */
*cursor = (clusterMsgPingExt *) (ext->hostname + EIGHT_BYTE_ALIGN(strlen(myself->hostname) + 1)); *cursor = (clusterMsgPingExt *) (ext->hostname + EIGHT_BYTE_ALIGN(sdslen(myself->hostname) + 1));
return extension_size; return extension_size;
} }
...@@ -2975,7 +2968,7 @@ void clusterSendPing(clusterLink *link, int type) { ...@@ -2975,7 +2968,7 @@ void clusterSendPing(clusterLink *link, int type) {
/* Set the initial extension position */ /* Set the initial extension position */
clusterMsgPingExt *cursor = getInitialPingExt(hdr, gossipcount); clusterMsgPingExt *cursor = getInitialPingExt(hdr, gossipcount);
/* Add in the extensions */ /* Add in the extensions */
if (myself->hostname) { if (sdslen(myself->hostname) != 0) {
hdr->mflags[0] |= CLUSTERMSG_FLAG0_EXT_DATA; hdr->mflags[0] |= CLUSTERMSG_FLAG0_EXT_DATA;
totlen += writeHostnamePingExt(&cursor); totlen += writeHostnamePingExt(&cursor);
extensions++; extensions++;
...@@ -3959,7 +3952,8 @@ void clusterCron(void) { ...@@ -3959,7 +3952,8 @@ void clusterCron(void) {
iteration++; /* Number of times this function was called so far. */ iteration++; /* Number of times this function was called so far. */
updateAnnouncedHostname(myself, server.cluster_announce_hostname); clusterUpdateMyselfHostname();
/* The handshake timeout is the time after which a handshake node that was /* The handshake timeout is the time after which a handshake node that was
* not turned into a normal node is removed from the nodes. Usually it is * not turned into a normal node is removed from the nodes. Usually it is
* just the NODE_TIMEOUT value, but when NODE_TIMEOUT is too small we use * just the NODE_TIMEOUT value, but when NODE_TIMEOUT is too small we use
...@@ -4578,7 +4572,7 @@ sds clusterGenNodeDescription(clusterNode *node, int use_pport) { ...@@ -4578,7 +4572,7 @@ sds clusterGenNodeDescription(clusterNode *node, int use_pport) {
/* Node coordinates */ /* Node coordinates */
ci = sdscatlen(sdsempty(),node->name,CLUSTER_NAMELEN); ci = sdscatlen(sdsempty(),node->name,CLUSTER_NAMELEN);
if (node->hostname) { if (sdslen(node->hostname) != 0) {
ci = sdscatfmt(ci," %s:%i@%i,%s ", ci = sdscatfmt(ci," %s:%i@%i,%s ",
node->ip, node->ip,
port, port,
...@@ -4804,7 +4798,7 @@ void addReplyClusterLinksDescription(client *c) { ...@@ -4804,7 +4798,7 @@ void addReplyClusterLinksDescription(client *c) {
const char *getPreferredEndpoint(clusterNode *n) { const char *getPreferredEndpoint(clusterNode *n) {
switch(server.cluster_preferred_endpoint_type) { switch(server.cluster_preferred_endpoint_type) {
case CLUSTER_ENDPOINT_TYPE_IP: return n->ip; case CLUSTER_ENDPOINT_TYPE_IP: return n->ip;
case CLUSTER_ENDPOINT_TYPE_HOSTNAME: return n->hostname ? n->hostname : "?"; case CLUSTER_ENDPOINT_TYPE_HOSTNAME: return (sdslen(n->hostname) != 0) ? n->hostname : "?";
case CLUSTER_ENDPOINT_TYPE_UNKNOWN_ENDPOINT: return ""; case CLUSTER_ENDPOINT_TYPE_UNKNOWN_ENDPOINT: return "";
} }
return "unknown"; return "unknown";
...@@ -4898,7 +4892,7 @@ void addNodeToNodeReply(client *c, clusterNode *node) { ...@@ -4898,7 +4892,7 @@ void addNodeToNodeReply(client *c, clusterNode *node) {
if (server.cluster_preferred_endpoint_type == CLUSTER_ENDPOINT_TYPE_IP) { if (server.cluster_preferred_endpoint_type == CLUSTER_ENDPOINT_TYPE_IP) {
addReplyBulkCString(c, node->ip); addReplyBulkCString(c, node->ip);
} else if (server.cluster_preferred_endpoint_type == CLUSTER_ENDPOINT_TYPE_HOSTNAME) { } else if (server.cluster_preferred_endpoint_type == CLUSTER_ENDPOINT_TYPE_HOSTNAME) {
addReplyBulkCString(c, node->hostname ? node->hostname : "?"); addReplyBulkCString(c, sdslen(node->hostname) != 0 ? node->hostname : "?");
} else if (server.cluster_preferred_endpoint_type == CLUSTER_ENDPOINT_TYPE_UNKNOWN_ENDPOINT) { } else if (server.cluster_preferred_endpoint_type == CLUSTER_ENDPOINT_TYPE_UNKNOWN_ENDPOINT) {
addReplyNull(c); addReplyNull(c);
} else { } else {
...@@ -4921,7 +4915,7 @@ void addNodeToNodeReply(client *c, clusterNode *node) { ...@@ -4921,7 +4915,7 @@ void addNodeToNodeReply(client *c, clusterNode *node) {
length++; length++;
} }
if (server.cluster_preferred_endpoint_type != CLUSTER_ENDPOINT_TYPE_HOSTNAME if (server.cluster_preferred_endpoint_type != CLUSTER_ENDPOINT_TYPE_HOSTNAME
&& node->hostname) && sdslen(node->hostname) != 0)
{ {
addReplyBulkCString(c, "hostname"); addReplyBulkCString(c, "hostname");
addReplyBulkCString(c, node->hostname); addReplyBulkCString(c, node->hostname);
...@@ -5032,7 +5026,7 @@ void clusterCommand(client *c) { ...@@ -5032,7 +5026,7 @@ void clusterCommand(client *c) {
" Reset current node (default: soft).", " Reset current node (default: soft).",
"SET-CONFIG-EPOCH <epoch>", "SET-CONFIG-EPOCH <epoch>",
" Set config epoch of current node.", " Set config epoch of current node.",
"SETSLOT <slot> (IMPORTING|MIGRATING|STABLE|NODE <node-id>)", "SETSLOT <slot> (IMPORTING <node-id>|MIGRATING <node-id>|STABLE|NODE <node-id>)",
" Set slot state.", " Set slot state.",
"REPLICAS <node-id>", "REPLICAS <node-id>",
" Return <node-id> replicas.", " Return <node-id> replicas.",
...@@ -5226,6 +5220,10 @@ NULL ...@@ -5226,6 +5220,10 @@ NULL
(char*)c->argv[4]->ptr); (char*)c->argv[4]->ptr);
return; return;
} }
if (nodeIsSlave(n)) {
addReplyError(c,"Target node is not a master");
return;
}
/* If this hash slot was served by 'myself' before to switch /* If this hash slot was served by 'myself' before to switch
* make sure there are no longer local keys for this hash slot. */ * make sure there are no longer local keys for this hash slot. */
if (server.cluster->slots[slot] == myself && n != myself) { if (server.cluster->slots[slot] == myself && n != myself) {
...@@ -5285,7 +5283,7 @@ NULL ...@@ -5285,7 +5283,7 @@ NULL
addReplySds(c,reply); addReplySds(c,reply);
} else if (!strcasecmp(c->argv[1]->ptr,"info") && c->argc == 2) { } else if (!strcasecmp(c->argv[1]->ptr,"info") && c->argc == 2) {
/* CLUSTER INFO */ /* CLUSTER INFO */
char *statestr[] = {"ok","fail","needhelp"}; char *statestr[] = {"ok","fail"};
int slots_assigned = 0, slots_ok = 0, slots_pfail = 0, slots_fail = 0; int slots_assigned = 0, slots_ok = 0, slots_pfail = 0, slots_fail = 0;
uint64_t myepoch; uint64_t myepoch;
int j; int j;
...@@ -5703,7 +5701,7 @@ int verifyDumpPayload(unsigned char *p, size_t len, uint16_t *rdbver_ptr) { ...@@ -5703,7 +5701,7 @@ int verifyDumpPayload(unsigned char *p, size_t len, uint16_t *rdbver_ptr) {
if (len < 10) return C_ERR; if (len < 10) return C_ERR;
footer = p+(len-10); footer = p+(len-10);
/* Verify RDB version */ /* Set and verify RDB version. */
rdbver = (footer[1] << 8) | footer[0]; rdbver = (footer[1] << 8) | footer[0];
if (rdbver_ptr) { if (rdbver_ptr) {
*rdbver_ptr = rdbver; *rdbver_ptr = rdbver;
......
...@@ -96,8 +96,8 @@ typedef struct clusterLink { ...@@ -96,8 +96,8 @@ typedef struct clusterLink {
#define CLUSTERMSG_TYPE_UPDATE 7 /* Another node slots configuration */ #define CLUSTERMSG_TYPE_UPDATE 7 /* Another node slots configuration */
#define CLUSTERMSG_TYPE_MFSTART 8 /* Pause clients for manual failover */ #define CLUSTERMSG_TYPE_MFSTART 8 /* Pause clients for manual failover */
#define CLUSTERMSG_TYPE_MODULE 9 /* Module cluster API message. */ #define CLUSTERMSG_TYPE_MODULE 9 /* Module cluster API message. */
#define CLUSTERMSG_TYPE_COUNT 10 /* Total number of message types. */ #define CLUSTERMSG_TYPE_PUBLISHSHARD 10 /* Pub/Sub Publish shard propagation */
#define CLUSTERMSG_TYPE_PUBLISHSHARD 11 /* Pub/Sub Publish shard propagation */ #define CLUSTERMSG_TYPE_COUNT 11 /* Total number of message types. */
/* Flags that a module can set in order to prevent certain Redis Cluster /* Flags that a module can set in order to prevent certain Redis Cluster
* features to be enabled. Useful when implementing a different distributed * features to be enabled. Useful when implementing a different distributed
...@@ -134,8 +134,8 @@ typedef struct clusterNode { ...@@ -134,8 +134,8 @@ typedef struct clusterNode {
mstime_t repl_offset_time; /* Unix time we received offset for this node */ mstime_t repl_offset_time; /* Unix time we received offset for this node */
mstime_t orphaned_time; /* Starting time of orphaned master condition */ mstime_t orphaned_time; /* Starting time of orphaned master condition */
long long repl_offset; /* Last known repl offset for this node. */ long long repl_offset; /* Last known repl offset for this node. */
char ip[NET_IP_STR_LEN]; /* Latest known IP address of this node */ char ip[NET_IP_STR_LEN]; /* Latest known IP address of this node */
char *hostname; /* The known hostname for this node */ sds hostname; /* The known hostname for this node */
int port; /* Latest known clients port (TLS or plain). */ int port; /* Latest known clients port (TLS or plain). */
int pport; /* Latest known clients plaintext port. Only used int pport; /* Latest known clients plaintext port. Only used
if the main clients port is for TLS. */ if the main clients port is for TLS. */
...@@ -339,8 +339,6 @@ typedef struct { ...@@ -339,8 +339,6 @@ typedef struct {
* changes in clusterMsg be caught at compile time. * changes in clusterMsg be caught at compile time.
*/ */
/* Avoid static_assert on non-C11 compilers. */
#if __STDC_VERSION__ >= 201112L
static_assert(offsetof(clusterMsg, sig) == 0, "unexpected field offset"); static_assert(offsetof(clusterMsg, sig) == 0, "unexpected field offset");
static_assert(offsetof(clusterMsg, totlen) == 4, "unexpected field offset"); static_assert(offsetof(clusterMsg, totlen) == 4, "unexpected field offset");
static_assert(offsetof(clusterMsg, ver) == 8, "unexpected field offset"); static_assert(offsetof(clusterMsg, ver) == 8, "unexpected field offset");
...@@ -362,7 +360,6 @@ static_assert(offsetof(clusterMsg, flags) == 2250, "unexpected field offset"); ...@@ -362,7 +360,6 @@ static_assert(offsetof(clusterMsg, flags) == 2250, "unexpected field offset");
static_assert(offsetof(clusterMsg, state) == 2252, "unexpected field offset"); static_assert(offsetof(clusterMsg, state) == 2252, "unexpected field offset");
static_assert(offsetof(clusterMsg, mflags) == 2253, "unexpected field offset"); static_assert(offsetof(clusterMsg, mflags) == 2253, "unexpected field offset");
static_assert(offsetof(clusterMsg, data) == 2256, "unexpected field offset"); static_assert(offsetof(clusterMsg, data) == 2256, "unexpected field offset");
#endif
#define CLUSTERMSG_MIN_LEN (sizeof(clusterMsg)-sizeof(union clusterMsgData)) #define CLUSTERMSG_MIN_LEN (sizeof(clusterMsg)-sizeof(union clusterMsgData))
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment