From 471557c3492d790ab54595609d60b98a040ece3c Mon Sep 17 00:00:00 2001 From: charsyam Date: Sat, 17 Mar 2018 17:41:43 +0900 Subject: fix common realloc mistake and add null check more --- sds.c | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/sds.c b/sds.c index 923ffd8..44777b1 100644 --- a/sds.c +++ b/sds.c @@ -219,7 +219,10 @@ sds sdsMakeRoomFor(sds s, size_t addlen) { hdrlen = sdsHdrSize(type); if (oldtype==type) { newsh = s_realloc(sh, hdrlen+newlen+1); - if (newsh == NULL) return NULL; + if (newsh == NULL) { + s_free(sh); + return NULL; + } s = (char*)newsh+hdrlen; } else { /* Since the header size changes, need to move the string forward, @@ -592,6 +595,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) { /* Make sure there is always space for at least 1 char. */ if (sdsavail(s)==0) { s = sdsMakeRoomFor(s,1); + if (s == NULL) goto fmt_error; } switch(*f) { @@ -605,6 +609,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) { l = (next == 's') ? strlen(str) : sdslen(str); if (sdsavail(s) < l) { s = sdsMakeRoomFor(s,l); + if (s == NULL) goto fmt_error; } memcpy(s+i,str,l); sdsinclen(s,l); @@ -621,6 +626,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) { l = sdsll2str(buf,num); if (sdsavail(s) < l) { s = sdsMakeRoomFor(s,l); + if (s == NULL) goto fmt_error; } memcpy(s+i,buf,l); sdsinclen(s,l); @@ -638,6 +644,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) { l = sdsull2str(buf,unum); if (sdsavail(s) < l) { s = sdsMakeRoomFor(s,l); + if (s == NULL) goto fmt_error; } memcpy(s+i,buf,l); sdsinclen(s,l); @@ -662,6 +669,10 @@ sds sdscatfmt(sds s, char const *fmt, ...) { /* Add null-term */ s[i] = '\0'; return s; + +fmt_error: + va_end(ap); + return NULL; } /* Remove the part of the string from left and from right composed just of @@ -1018,10 +1029,18 @@ sds *sdssplitargs(const char *line, int *argc) { if (*p) p++; } /* add the token to the vector */ - vector = s_realloc(vector,((*argc)+1)*sizeof(char*)); - vector[*argc] = current; - (*argc)++; - current = NULL; + { + char **new_vector = s_realloc(vector,((*argc)+1)*sizeof(char*)); + if (new_vector == NULL) { + s_free(vector); + return NULL; + } + + vector = new_vector; + vector[*argc] = current; + (*argc)++; + current = NULL; + } } else { /* Even on empty input string return something not NULL. */ if (vector == NULL) vector = s_malloc(sizeof(void*)); -- cgit v1.2.3 From 967027c881c8cf7c94d6f0223d079b9466c16cc0 Mon Sep 17 00:00:00 2001 From: Justin Brewer Date: Mon, 21 May 2018 10:58:40 -0500 Subject: Add Changelog entry about the integer parsing changes This should have been included in 93421f9d84868989ab0e401fb3be7b31c7a9c181 but was missed. Signed-off-by: Justin Brewer --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f40ec53..840a470 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ * Import latest upstream sds. This breaks applications that are linked against the old hiredis v0.13 * Fix warnings, when compiled with -Wshadow * Make hiredis compile in Cygwin on Windows, now CI-tested +* Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now + protocol errors. This is consistent with the RESP specification. On 32-bit + platforms, the upper bound is lowered to `SIZE_MAX`. **BREAKING CHANGES**: -- cgit v1.2.3 From a65537a672de845f3f4530050d1e7bd88d51ac67 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Thu, 31 May 2018 02:52:45 -0500 Subject: Use -dynamiclib instead of -shared on macOS -dynamiclib is the correct documented flag to use to create dynamic libraries on macOS. Newer toolchains recognize -shared as a synonym of -dynamiclib but older toolchains don't. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 5d7e23c..b2e45f0 100644 --- a/Makefile +++ b/Makefile @@ -62,7 +62,7 @@ endif ifeq ($(uname_S),Darwin) DYLIBSUFFIX=dylib DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_SONAME).$(DYLIBSUFFIX) - DYLIB_MAKE_CMD=$(CC) -shared -Wl,-install_name,$(PREFIX)/$(LIBRARY_PATH)/$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS) + DYLIB_MAKE_CMD=$(CC) -dynamiclib -Wl,-install_name,$(PREFIX)/$(LIBRARY_PATH)/$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS) endif all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME) -- cgit v1.2.3 From c3188bfb1fc59bd169b970ec159e1c9a2b949bf9 Mon Sep 17 00:00:00 2001 From: Tom Lee Date: Mon, 24 Sep 2018 16:14:08 -0700 Subject: Make string2ll static to avoid conflict with redis See discussion on #609. This should also make it easier for redis to update the vendored/bundled hiredis (though not by much). --- read.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/read.c b/read.c index ef483f7..cc21267 100644 --- a/read.c +++ b/read.c @@ -154,7 +154,7 @@ static char *seekNewline(char *s, size_t len) { * Because of its strictness, it is safe to use this function to check if * you can convert a string into a long long, and obtain back the string * from the number without any loss in the string representation. */ -int string2ll(const char *s, size_t slen, long long *value) { +static int string2ll(const char *s, size_t slen, long long *value) { const char *p = s; size_t plen = 0; int negative = 0; -- cgit v1.2.3 From 29c82b728c0b61d35be09f45de03fb675e47a466 Mon Sep 17 00:00:00 2001 From: Tom Lee Date: Tue, 25 Sep 2018 15:47:34 -0700 Subject: Prepare for the 0.14.0 release SONAME bumped to 0.14 because we've broken ABI compatibility with 0.13.x --- hiredis.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hiredis.h b/hiredis.h index a743760..bd53e7e 100644 --- a/hiredis.h +++ b/hiredis.h @@ -40,9 +40,9 @@ #include "sds.h" /* for sds */ #define HIREDIS_MAJOR 0 -#define HIREDIS_MINOR 13 -#define HIREDIS_PATCH 3 -#define HIREDIS_SONAME 0.13 +#define HIREDIS_MINOR 14 +#define HIREDIS_PATCH 0 +#define HIREDIS_SONAME 0.14 /* Connection type can be blocking or non-blocking and is set in the * least significant bit of the flags field in redisContext. */ -- cgit v1.2.3 From 49974c9359ad6b58cea15106cf6511bdb31d31a9 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Sun, 4 Mar 2018 18:17:16 +0200 Subject: Call connect(2) again for non-blocking connect This retrieves the actual error which occurred, as getsockopt is not always reliable in this regard. --- async.c | 24 ++++++++++++------------ async.h | 4 ++++ hiredis.c | 2 ++ hiredis.h | 3 +++ net.c | 35 ++++++++++++++++++++++++++++++++++- net.h | 1 + 6 files changed, 56 insertions(+), 13 deletions(-) diff --git a/async.c b/async.c index cb5b841..7309344 100644 --- a/async.c +++ b/async.c @@ -506,22 +506,22 @@ void redisProcessCallbacks(redisAsyncContext *ac) { * write event fires. When connecting was not successful, the connect callback * is called with a REDIS_ERR status and the context is free'd. */ static int __redisAsyncHandleConnect(redisAsyncContext *ac) { + int completed = 0; redisContext *c = &(ac->c); - - if (redisCheckSocketError(c) == REDIS_ERR) { - /* Try again later when connect(2) is still in progress. */ - if (errno == EINPROGRESS) - return REDIS_OK; - - if (ac->onConnect) ac->onConnect(ac,REDIS_ERR); + if (redisFinishAsyncConnect(c, &completed) == REDIS_ERR) { + /* Error! */ + redisCheckSocketError(c); + if (ac->onConnect) ac->onConnect(ac, REDIS_ERR); __redisAsyncDisconnect(ac); return REDIS_ERR; + } else if (completed == 1) { + /* connected! */ + if (ac->onConnect) ac->onConnect(ac, REDIS_OK); + c->flags |= REDIS_CONNECTED; + return REDIS_OK; + } else { + return REDIS_OK; } - - /* Mark context as connected. */ - c->flags |= REDIS_CONNECTED; - if (ac->onConnect) ac->onConnect(ac,REDIS_OK); - return REDIS_OK; } /* This function should be called when the socket is readable. diff --git a/async.h b/async.h index e69d840..740555c 100644 --- a/async.h +++ b/async.h @@ -93,6 +93,10 @@ typedef struct redisAsyncContext { /* Regular command callbacks */ redisCallbackList replies; + /* Address used for connect() */ + struct sockaddr *saddr; + size_t addrlen; + /* Subscription callbacks */ struct { redisCallbackList invalid; diff --git a/hiredis.c b/hiredis.c index 98f43c9..351dfbc 100644 --- a/hiredis.c +++ b/hiredis.c @@ -606,12 +606,14 @@ void redisFree(redisContext *c) { return; if (c->fd > 0) close(c->fd); + sdsfree(c->obuf); redisReaderFree(c->reader); free(c->tcp.host); free(c->tcp.source_addr); free(c->unix_sock.path); free(c->timeout); + free(c->saddr); free(c); } diff --git a/hiredis.h b/hiredis.h index bd53e7e..1b0d5e6 100644 --- a/hiredis.h +++ b/hiredis.h @@ -134,6 +134,9 @@ typedef struct redisContext { char *path; } unix_sock; + /* For non-blocking connect */ + struct sockadr *saddr; + size_t addrlen; } redisContext; redisContext *redisConnect(const char *ip, int port); diff --git a/net.c b/net.c index 5d50f7c..62c6ca0 100644 --- a/net.c +++ b/net.c @@ -232,8 +232,28 @@ static int redisContextWaitReady(redisContext *c, long msec) { return REDIS_ERR; } +int redisFinishAsyncConnect(redisContext *c, int *completed) { + int rc = connect(c->fd, (const struct sockaddr *)c->saddr, c->addrlen); + if (rc == 0) { + *completed = 1; + return REDIS_OK; + } + switch (errno) { + case EISCONN: + *completed = 1; + return REDIS_OK; + case EALREADY: + case EINPROGRESS: + case EWOULDBLOCK: + *completed = 0; + return REDIS_OK; + default: + return REDIS_ERR; + } +} + int redisCheckSocketError(redisContext *c) { - int err = 0; + int err = 0, errno_saved = errno; socklen_t errlen = sizeof(err); if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) { @@ -241,6 +261,10 @@ int redisCheckSocketError(redisContext *c) { return REDIS_ERR; } + if (err == 0) { + err = errno_saved; + } + if (err) { errno = err; __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); @@ -373,6 +397,15 @@ addrretry: goto error; } } + + /* For repeat connection */ + if (c->saddr) { + free(c->saddr); + } + c->saddr = malloc(sizeof(*p->ai_addr)); + memcpy(c->saddr, p->ai_addr, p->ai_addrlen); + c->addrlen = p->ai_addrlen; + if (connect(s,p->ai_addr,p->ai_addrlen) == -1) { if (errno == EHOSTUNREACH) { redisContextCloseFd(c); diff --git a/net.h b/net.h index d9dc362..c35facc 100644 --- a/net.h +++ b/net.h @@ -45,5 +45,6 @@ int redisContextConnectBindTcp(redisContext *c, const char *addr, int port, const char *source_addr); int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout); int redisKeepAlive(redisContext *c, int interval); +int redisFinishAsyncConnect(redisContext *c, int *completed); #endif -- cgit v1.2.3 From 5e6bbf8c6075ce6406d424e7ed031c7a6c676fb2 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Mon, 5 Mar 2018 11:57:22 +0200 Subject: saddr should be addrlen bytes Not sizeof saddr. --- net.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net.c b/net.c index 62c6ca0..7d5588e 100644 --- a/net.c +++ b/net.c @@ -402,7 +402,7 @@ addrretry: if (c->saddr) { free(c->saddr); } - c->saddr = malloc(sizeof(*p->ai_addr)); + c->saddr = malloc(p->ai_addrlen); memcpy(c->saddr, p->ai_addr, p->ai_addrlen); c->addrlen = p->ai_addrlen; -- cgit v1.2.3 From cbe4ae63aecee40674509a2bf23bc5db067e7f3d Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Mon, 5 Mar 2018 12:17:49 +0200 Subject: Handle connection errors better in blocking mode as well --- async.c | 2 +- net.c | 17 +++++++++++++---- net.h | 2 +- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/async.c b/async.c index 7309344..0cecd30 100644 --- a/async.c +++ b/async.c @@ -508,7 +508,7 @@ void redisProcessCallbacks(redisAsyncContext *ac) { static int __redisAsyncHandleConnect(redisAsyncContext *ac) { int completed = 0; redisContext *c = &(ac->c); - if (redisFinishAsyncConnect(c, &completed) == REDIS_ERR) { + if (redisCheckConnectDone(c, &completed) == REDIS_ERR) { /* Error! */ redisCheckSocketError(c); if (ac->onConnect) ac->onConnect(ac, REDIS_ERR); diff --git a/net.c b/net.c index 7d5588e..a4b3abc 100644 --- a/net.c +++ b/net.c @@ -221,8 +221,10 @@ static int redisContextWaitReady(redisContext *c, long msec) { return REDIS_ERR; } - if (redisCheckSocketError(c) != REDIS_OK) + if (redisCheckConnectDone(c, &res) != REDIS_OK || res == 0) { + redisCheckSocketError(c); return REDIS_ERR; + } return REDIS_OK; } @@ -232,7 +234,7 @@ static int redisContextWaitReady(redisContext *c, long msec) { return REDIS_ERR; } -int redisFinishAsyncConnect(redisContext *c, int *completed) { +int redisCheckConnectDone(redisContext *c, int *completed) { int rc = connect(c->fd, (const struct sockaddr *)c->saddr, c->addrlen); if (rc == 0) { *completed = 1; @@ -410,8 +412,14 @@ addrretry: if (errno == EHOSTUNREACH) { redisContextCloseFd(c); continue; - } else if (errno == EINPROGRESS && !blocking) { - /* This is ok. */ + } else if (errno == EINPROGRESS) { + if (blocking) { + goto wait_for_ready; + } + /* This is ok. + * Note that even when it's in blocking mode, we unset blocking + * for `connect()` + */ } else if (errno == EADDRNOTAVAIL && reuseaddr) { if (++reuses >= REDIS_CONNECT_RETRIES) { goto error; @@ -420,6 +428,7 @@ addrretry: goto addrretry; } } else { + wait_for_ready: if (redisContextWaitReady(c,timeout_msec) != REDIS_OK) goto error; } diff --git a/net.h b/net.h index c35facc..a11594e 100644 --- a/net.h +++ b/net.h @@ -45,6 +45,6 @@ int redisContextConnectBindTcp(redisContext *c, const char *addr, int port, const char *source_addr); int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout); int redisKeepAlive(redisContext *c, int interval); -int redisFinishAsyncConnect(redisContext *c, int *completed); +int redisCheckConnectDone(redisContext *c, int *completed); #endif -- cgit v1.2.3 From 3cb4fb2395919ff91d29c7ab66634f5211eaca89 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Tue, 25 Sep 2018 20:50:02 -0400 Subject: Skip NXDOMAIN test when using evil ISPs Some ISPs like to inject their own "Suggestions" page whenever you hit NXDOMAIN. This confuses Redis as well as addrinfo (black-holing the route). --- test.c | 55 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/test.c b/test.c index 0f5bfe5..a5eb9c7 100644 --- a/test.c +++ b/test.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -91,7 +92,7 @@ static int disconnect(redisContext *c, int keep_fd) { return -1; } -static redisContext *connect(struct config config) { +static redisContext *do_connect(struct config config) { redisContext *c = NULL; if (config.type == CONN_TCP) { @@ -248,7 +249,7 @@ static void test_append_formatted_commands(struct config config) { char *cmd; int len; - c = connect(config); + c = do_connect(config); test("Append format command: "); @@ -434,18 +435,32 @@ static void test_free_null(void) { static void test_blocking_connection_errors(void) { redisContext *c; - - test("Returns error when host cannot be resolved: "); - c = redisConnect((char*)"idontexist.test", 6379); - test_cond(c->err == REDIS_ERR_OTHER && - (strcmp(c->errstr,"Name or service not known") == 0 || - strcmp(c->errstr,"Can't resolve: idontexist.test") == 0 || - strcmp(c->errstr,"nodename nor servname provided, or not known") == 0 || - strcmp(c->errstr,"No address associated with hostname") == 0 || - strcmp(c->errstr,"Temporary failure in name resolution") == 0 || - strcmp(c->errstr,"hostname nor servname provided, or not known") == 0 || - strcmp(c->errstr,"no address associated with name") == 0)); - redisFree(c); + struct addrinfo hints = {.ai_family = AF_INET}; + struct addrinfo *ai_tmp = NULL; + const char *bad_domain = "idontexist.com"; + + int rv = getaddrinfo(bad_domain, "6379", &hints, &ai_tmp); + if (rv != 0) { + // Address does *not* exist + test("Returns error when host cannot be resolved: "); + // First see if this domain name *actually* resolves to NXDOMAIN + c = redisConnect("dontexist.com", 6379); + test_cond( + c->err == REDIS_ERR_OTHER && + (strcmp(c->errstr, "Name or service not known") == 0 || + strcmp(c->errstr, "Can't resolve: sadkfjaskfjsa.com") == 0 || + strcmp(c->errstr, + "nodename nor servname provided, or not known") == 0 || + strcmp(c->errstr, "No address associated with hostname") == 0 || + strcmp(c->errstr, "Temporary failure in name resolution") == 0 || + strcmp(c->errstr, + "hostname nor servname provided, or not known") == 0 || + strcmp(c->errstr, "no address associated with name") == 0)); + redisFree(c); + } else { + printf("Skipping NXDOMAIN test. Found evil ISP!\n"); + freeaddrinfo(ai_tmp); + } test("Returns error when the port is not open: "); c = redisConnect((char*)"localhost", 1); @@ -463,7 +478,7 @@ static void test_blocking_connection(struct config config) { redisContext *c; redisReply *reply; - c = connect(config); + c = do_connect(config); test("Is able to deliver commands: "); reply = redisCommand(c,"PING"); @@ -544,7 +559,7 @@ static void test_blocking_connection_timeouts(struct config config) { const char *cmd = "DEBUG SLEEP 3\r\n"; struct timeval tv; - c = connect(config); + c = do_connect(config); test("Successfully completes a command when the timeout is not exceeded: "); reply = redisCommand(c,"SET foo fast"); freeReplyObject(reply); @@ -556,7 +571,7 @@ static void test_blocking_connection_timeouts(struct config config) { freeReplyObject(reply); disconnect(c, 0); - c = connect(config); + c = do_connect(config); test("Does not return a reply when the command times out: "); s = write(c->fd, cmd, strlen(cmd)); tv.tv_sec = 0; @@ -590,7 +605,7 @@ static void test_blocking_io_errors(struct config config) { int major, minor; /* Connect to target given by config. */ - c = connect(config); + c = do_connect(config); { /* Find out Redis version to determine the path for the next test */ const char *field = "redis_version:"; @@ -625,7 +640,7 @@ static void test_blocking_io_errors(struct config config) { strcmp(c->errstr,"Server closed the connection") == 0); redisFree(c); - c = connect(config); + c = do_connect(config); test("Returns I/O error on socket timeout: "); struct timeval tv = { 0, 1000 }; assert(redisSetTimeout(c,tv) == REDIS_OK); @@ -659,7 +674,7 @@ static void test_invalid_timeout_errors(struct config config) { } static void test_throughput(struct config config) { - redisContext *c = connect(config); + redisContext *c = do_connect(config); redisReply **replies; int i, num; long long t1, t2; -- cgit v1.2.3 From fbf2d037e7de6b7b7f9177a2263a6113f99a7ceb Mon Sep 17 00:00:00 2001 From: michael-grunder Date: Wed, 26 Sep 2018 12:17:25 -0700 Subject: Update changelog for 0.14.0 --- CHANGELOG.md | 54 +++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 840a470..a7fe3ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,21 +1,57 @@ ### 1.0.0 (unreleased) -**Fixes**: +**BREAKING CHANGES**: -* Catch a buffer overflow when formatting the error message -* Import latest upstream sds. This breaks applications that are linked against the old hiredis v0.13 -* Fix warnings, when compiled with -Wshadow -* Make hiredis compile in Cygwin on Windows, now CI-tested * Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now protocol errors. This is consistent with the RESP specification. On 32-bit platforms, the upper bound is lowered to `SIZE_MAX`. -**BREAKING CHANGES**: - * Change `redisReply.len` to `size_t`, as it denotes the the size of a string -User code should compare this to `size_t` values as well. -If it was used to compare to other values, casting might be necessary or can be removed, if casting was applied before. + User code should compare this to `size_t` values as well. If it was used to + compare to other values, casting might be necessary or can be removed, if + casting was applied before. + +### 0.14.0 (2018-09-25) + +* Make string2ll static to fix conflict with Redis (Tom Lee [c3188b]) +* Use -dynamiclib instead of -shared for OSX (Ryan Schmidt [a65537]) +* Use string2ll from Redis w/added tests (Michael Grunder [7bef04, 60f622]) +* Makefile - OSX compilation fixes (Ryan Schmidt [881fcb, 0e9af8]) +* Remove redundant NULL checks (Justin Brewer [54acc8, 58e6b8]) +* Fix bulk and multi-bulk length truncation (Justin Brewer [109197]) +* Fix SIGSEGV in OpenBSD by checking for NULL before calling freeaddrinfo (Justin Brewer [546d94]) +* Several POSIX compatibility fixes (Justin Brewer [bbeab8, 49bbaa, d1c1b6]) +* Makefile - Compatibility fixes (Dimitri Vorobiev [3238cf, 12a9d1]) +* Makefile - Fix make install on FreeBSD (Zach Shipko [a2ef2b]) +* Makefile - don't assume $(INSTALL) is cp (Igor Gnatenko [725a96]) +* Separate side-effect causing function from assert and small cleanup (amallia [b46413, 3c3234]) +* Don't send negative values to `__redisAsyncCommand` (Frederik Deweerdt [706129]) +* Fix leak if setsockopt fails (Frederik Deweerdt [e21c9c]) +* Fix libevent leak (zfz [515228]) +* Clean up GCC warning (Ichito Nagata [2ec774]) +* Keep track of errno in `__redisSetErrorFromErrno()` as snprintf may use it (Jin Qing [25cd88]) +* Solaris compilation fix (Donald Whyte [41b07d]) +* Reorder linker arguments when building examples (Tustfarm-heart [06eedd]) +* Keep track of subscriptions in case of rapid subscribe/unsubscribe (Hyungjin Kim [073dc8, be76c5, d46999]) +* libuv use after free fix (Paul Scott [cbb956]) +* Properly close socket fd on reconnect attempt (WSL [64d1ec]) +* Skip valgrind in OSX tests (Jan-Erik Rediger [9deb78]) +* Various updates for Travis testing OSX (Ted Nyman [fa3774, 16a459, bc0ea5]) +* Update libevent (Chris Xin [386802]) +* Change sds.h for building in C++ projects (Ali Volkan ATLI [f5b32e]) +* Use proper format specifier in redisFormatSdsCommandArgv (Paulino Huerta, Jan-Erik Rediger [360a06, 8655a6]) +* Better handling of NULL reply in example code (Jan-Erik Rediger [1b8ed3]) +* Prevent overflow when formatting an error (Jan-Erik Rediger [0335cb]) +* Compatibility fix for strerror_r (Tom Lee [bb1747]) +* Properly detect integer parse/overflow errors (Justin Brewer [93421f]) +* Adds CI for Windows and cygwin fixes (owent, [6c53d6, 6c3e40]) +* Catch a buffer overflow when formatting the error message +* Import latest upstream sds. This breaks applications that are linked against the old hiredis v0.13 +* Fix warnings, when compiled with -Wshadow +* Make hiredis compile in Cygwin on Windows, now CI-tested + +**BREAKING CHANGES**: * Remove backwards compatibility macro's -- cgit v1.2.3 From 3bcf286356f0780ff695c73a7dc178c1f970315b Mon Sep 17 00:00:00 2001 From: Tom Lee Date: Wed, 26 Sep 2018 13:25:24 -0700 Subject: Add CPPFLAGS to REAL_CFLAGS --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b2e45f0..06ca994 100644 --- a/Makefile +++ b/Makefile @@ -41,7 +41,7 @@ CXX:=$(shell sh -c 'type $${CXX%% *} >/dev/null 2>/dev/null && echo $(CXX) || ec OPTIMIZATION?=-O3 WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings DEBUG_FLAGS?= -g -ggdb -REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS) +REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CPPFLAGS) $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS) REAL_LDFLAGS=$(LDFLAGS) DYLIBSUFFIX=so -- cgit v1.2.3 From 169fcc708b4a001715bf2ba985e007875d829083 Mon Sep 17 00:00:00 2001 From: Stefan Hacker Date: Wed, 24 Oct 2018 23:36:44 +0200 Subject: Fix redisBufferRead documentation Referred to redisContextReadReply which I cannot find in this codebase nor the old redis-tools one. Presumably this meant to say redisGetReplyFromReader which is how redisBufferRead is used in this file. Could've also meant the interface function redisReaderGetReply. --- hiredis.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hiredis.c b/hiredis.c index 351dfbc..bfbf483 100644 --- a/hiredis.c +++ b/hiredis.c @@ -777,7 +777,7 @@ int redisEnableKeepAlive(redisContext *c) { /* Use this function to handle a read event on the descriptor. It will try * and read some bytes from the socket and feed them to the reply parser. * - * After this function is called, you may use redisContextReadReply to + * After this function is called, you may use redisGetReplyFromReader to * see if there is a reply available. */ int redisBufferRead(redisContext *c) { char buf[1024*16]; -- cgit v1.2.3 From 9ff1cc7826f6d546fc0fcdd7f08554e729a447a1 Mon Sep 17 00:00:00 2001 From: Jean Flach Date: Fri, 26 Oct 2018 15:56:27 +0200 Subject: Update README.md Add note about using context->data to pass user data to connect and disconnect callbacks --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 01223ea..b004746 100644 --- a/README.md +++ b/README.md @@ -286,6 +286,7 @@ return `REDIS_ERR`. The function to set the disconnect callback has the followin ```c int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); ``` +`ac->data` may be used to pass user data to this callback, the same can be done for redisConnectCallback. ### Sending commands and their callbacks In an asynchronous context, commands are automatically pipelined due to the nature of an event loop. -- cgit v1.2.3 From 9ce15c4b39253792899cbbe338cee430811d0eb2 Mon Sep 17 00:00:00 2001 From: Yossi Gottlieb Date: Sun, 4 Nov 2018 09:52:02 +0200 Subject: Fix errors not propagating properly with libuv.h. --- adapters/libuv.h | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/adapters/libuv.h b/adapters/libuv.h index ff08c25..39ef7cf 100644 --- a/adapters/libuv.h +++ b/adapters/libuv.h @@ -15,15 +15,12 @@ typedef struct redisLibuvEvents { static void redisLibuvPoll(uv_poll_t* handle, int status, int events) { redisLibuvEvents* p = (redisLibuvEvents*)handle->data; + int ev = (status ? p->events : events); - if (status != 0) { - return; - } - - if (p->context != NULL && (events & UV_READABLE)) { + if (p->context != NULL && (ev & UV_READABLE)) { redisAsyncHandleRead(p->context); } - if (p->context != NULL && (events & UV_WRITABLE)) { + if (p->context != NULL && (ev & UV_WRITABLE)) { redisAsyncHandleWrite(p->context); } } -- cgit v1.2.3 From d9fe8bdf67a33c24c75ffe8fffc033105ca83896 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Mon, 26 Nov 2018 16:20:51 +0000 Subject: FreeBSD build fix --- test.c | 1 + 1 file changed, 1 insertion(+) diff --git a/test.c b/test.c index a5eb9c7..79cff43 100644 --- a/test.c +++ b/test.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include -- cgit v1.2.3 From 2fa34e37af0881ff3700fea64fe211bbb8b028a2 Mon Sep 17 00:00:00 2001 From: Jörg Thalheim Date: Tue, 4 Dec 2018 20:47:37 +0000 Subject: Makefile: use predefined AR This allows to override it for cross compilation. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 06ca994..07b8a83 100644 --- a/Makefile +++ b/Makefile @@ -51,7 +51,7 @@ DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR) DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX) DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS) STLIBNAME=$(LIBNAME).$(STLIBSUFFIX) -STLIB_MAKE_CMD=ar rcs $(STLIBNAME) +STLIB_MAKE_CMD=$(AR) rcs $(STLIBNAME) # Platform-specific overrides uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') -- cgit v1.2.3 From 8633a2f32eecb43a0c12e9de1bb358da9e6b7d43 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Wed, 5 Dec 2018 07:02:26 -0500 Subject: Allow connections to unix socket in example To minimize code changes, a simple `u` (or UNIX, Unix, unix, etc -- as long as the first character is u or U) is used as a marker for the 'port' argument. In this case, the hostname is interpreted to be the path to the unix socket. --- examples/example.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/examples/example.c b/examples/example.c index 4d494c5..0e93fc8 100644 --- a/examples/example.c +++ b/examples/example.c @@ -5,14 +5,27 @@ #include int main(int argc, char **argv) { - unsigned int j; + unsigned int j, isunix = 0; redisContext *c; redisReply *reply; const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1"; + + if (argc > 2) { + if (*argv[2] == 'u' || *argv[2] == 'U') { + isunix = 1; + /* in this case, host is the path to the unix socket */ + printf("Will connect to unix socket @%s\n", hostname); + } + } + int port = (argc > 2) ? atoi(argv[2]) : 6379; struct timeval timeout = { 1, 500000 }; // 1.5 seconds - c = redisConnectWithTimeout(hostname, port, timeout); + if (isunix) { + c = redisConnectUnixWithTimeout(hostname, timeout); + } else { + c = redisConnectWithTimeout(hostname, port, timeout); + } if (c == NULL || c->err) { if (c) { printf("Connection error: %s\n", c->errstr); -- cgit v1.2.3 From 088d1469b30ba4e51c550cf1ef84580c4575a307 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Wed, 5 Dec 2018 07:47:05 -0500 Subject: Fix regression when connecting with Unix sockets (#629) --- net.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/net.c b/net.c index a4b3abc..4fc5a9d 100644 --- a/net.c +++ b/net.c @@ -472,7 +472,7 @@ int redisContextConnectBindTcp(redisContext *c, const char *addr, int port, int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) { int blocking = (c->flags & REDIS_BLOCK); - struct sockaddr_un sa; + struct sockaddr_un *sa; long timeout_msec = -1; if (redisCreateSocket(c,AF_UNIX) < 0) @@ -499,9 +499,10 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK) return REDIS_ERR; - sa.sun_family = AF_UNIX; - strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1); - if (connect(c->fd, (struct sockaddr*)&sa, sizeof(sa)) == -1) { + sa = (struct sockaddr_un*)(c->saddr = malloc(sizeof(struct sockaddr_un))); + sa->sun_family = AF_UNIX; + strncpy(sa->sun_path,path,sizeof(sa->sun_path)-1); + if (connect(c->fd, (struct sockaddr*)sa, sizeof(*sa)) == -1) { if (errno == EINPROGRESS && !blocking) { /* This is ok. */ } else { -- cgit v1.2.3 From be6bb39fda8cd1fe300e9dfb0fd4d1e598c37435 Mon Sep 17 00:00:00 2001 From: Code Hz Date: Mon, 28 Jan 2019 21:54:42 +0800 Subject: Fix Invalid argument after redisAsyncConnectUnix --- net.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net.c b/net.c index 4fc5a9d..f09183e 100644 --- a/net.c +++ b/net.c @@ -500,6 +500,7 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time return REDIS_ERR; sa = (struct sockaddr_un*)(c->saddr = malloc(sizeof(struct sockaddr_un))); + c->addrlen = sizeof(struct sockaddr_un); sa->sun_family = AF_UNIX; strncpy(sa->sun_path,path,sizeof(sa->sun_path)-1); if (connect(c->fd, (struct sockaddr*)sa, sizeof(*sa)) == -1) { -- cgit v1.2.3 From ead586a2cb7de8a17073a96704a4200d63a380e5 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Tue, 25 Sep 2018 19:44:58 -0400 Subject: Add CMake system. Initial commit This provides a target to build a DSO, as well as the ability to install it. --- CMakeLists.txt | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..b1771d5 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,47 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 3.0.0) +INCLUDE(GNUInstallDirs) +PROJECT(redisearch) + +# Get the version numbers +MACRO(getVersionBit name) + +EXECUTE_PROCESS( + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" + COMMAND grep "${name}" hiredis.h COMMAND awk "{print $3}" + OUTPUT_VARIABLE "${name}" + OUTPUT_STRIP_TRAILING_WHITESPACE) +ENDMACRO(getVersionBit) + +getVersionBit(HIREDIS_MAJOR) +getVersionBit(HIREDIS_MINOR) +getVersionBit(HIREDIS_PATCH) + +MESSAGE("Detected version: ${HIREDIS_MAJOR}.${HIREDIS_MINOR}.${HIREDIS_PATCH}") + +ADD_LIBRARY(hiredis SHARED + async.c + dict.c + hiredis.c + net.c + read.c + sds.c) + +SET_TARGET_PROPERTIES(hiredis + PROPERTIES + VERSION "${HIREDIS_MAJOR}.${HIREDIS_MINOR}.${HIREDIS_PATCH}") + +INSTALL(TARGETS hiredis + DESTINATION "${CMAKE_INSTALL_LIBDIR}") + +INSTALL(FILES hiredis.h read.h sds.h + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis) + +# Add tests: Currently, I don't know how to make the tests actually run +# without hanging! +ENABLE_TESTING() +ADD_EXECUTABLE(hiredis-test + test.c) +TARGET_LINK_LIBRARIES(hiredis-test hiredis) + +# Add examples + -- cgit v1.2.3 From bbad21f5cac8295e83e4d118d450aaa32e4435d8 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Mon, 1 Oct 2018 21:38:45 -0400 Subject: Add examples to CMakeLists --- CMakeLists.txt | 3 +++ examples/CMakeLists.txt | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 examples/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index b1771d5..f3a87f6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,8 @@ getVersionBit(HIREDIS_PATCH) MESSAGE("Detected version: ${HIREDIS_MAJOR}.${HIREDIS_MINOR}.${HIREDIS_PATCH}") +INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}) + ADD_LIBRARY(hiredis SHARED async.c dict.c @@ -44,4 +46,5 @@ ADD_EXECUTABLE(hiredis-test TARGET_LINK_LIBRARIES(hiredis-test hiredis) # Add examples +ADD_SUBDIRECTORY(examples) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..27388cb --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,41 @@ +INCLUDE(FindPkgConfig) +# Check for GLib + +PKG_CHECK_MODULES(GLIB2 glib-2.0) +if (GLIB2_FOUND) + INCLUDE_DIRECTORIES(${GLIB2_INCLUDE_DIRS}) + LINK_DIRECTORIES(${GLIB2_LIBRARY_DIRS}) + ADD_EXECUTABLE(example-glib example-glib.c) + TARGET_LINK_LIBRARIES(example-glib hiredis ${GLIB2_LIBRARIES}) +ENDIF(GLIB2_FOUND) + +FIND_PATH(LIBEV ev.h + HINTS /usr/local /usr/opt/local + ENV LIBEV_INCLUDE_DIR) + +if (LIBEV) + # Just compile and link with libev + ADD_EXECUTABLE(example-libev example-libev.c) + TARGET_LINK_LIBRARIES(example-libev hiredis ev) +ENDIF() + +FIND_PATH(LIBEVENT event.h) +if (LIBEVENT) + ADD_EXECUTABLE(example-libevent example-libevent) + TARGET_LINK_LIBRARIES(example-libevent hiredis event) +ENDIF() + +FIND_PATH(LIBUV uv.h) +IF (LIBUV) + ADD_EXECUTABLE(example-libuv example-libuv.c) + TARGET_LINK_LIBRARIES(example-libuv hiredis uv) +ENDIF() + +IF (APPLE) + FIND_LIBRARY(CF CoreFoundation) + ADD_EXECUTABLE(example-macosx example-macosx.c) + TARGET_LINK_LIBRARIES(example-macosx hiredis ${CF}) +ENDIF() + +ADD_EXECUTABLE(example example.c) +TARGET_LINK_LIBRARIES(example hiredis) \ No newline at end of file -- cgit v1.2.3 From 0c57a083c702609d3e65789338ea2477de92c6f3 Mon Sep 17 00:00:00 2001 From: Justin Brewer Date: Mon, 21 Jan 2019 15:05:04 -0600 Subject: Add ENABLE_EXAMPLES option, off by default Signed-off-by: Justin Brewer --- CMakeLists.txt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f3a87f6..d5df625 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,8 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.0.0) INCLUDE(GNUInstallDirs) PROJECT(redisearch) +SET(ENABLE_EXAMPLES OFF CACHE BOOL "Enable building hiredis examples") + # Get the version numbers MACRO(getVersionBit name) @@ -46,5 +48,6 @@ ADD_EXECUTABLE(hiredis-test TARGET_LINK_LIBRARIES(hiredis-test hiredis) # Add examples -ADD_SUBDIRECTORY(examples) - +IF(ENABLE_EXAMPLES) + ADD_SUBDIRECTORY(examples) +ENDIF(ENABLE_EXAMPLES) -- cgit v1.2.3 From edfab35cdcc5d5767918322b8af2dc41915f416d Mon Sep 17 00:00:00 2001 From: Justin Brewer Date: Mon, 21 Jan 2019 13:41:58 -0600 Subject: Install async.h Signed-off-by: Justin Brewer --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d5df625..e58bfa7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,7 +37,7 @@ SET_TARGET_PROPERTIES(hiredis INSTALL(TARGETS hiredis DESTINATION "${CMAKE_INSTALL_LIBDIR}") -INSTALL(FILES hiredis.h read.h sds.h +INSTALL(FILES hiredis.h read.h sds.h async.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis) # Add tests: Currently, I don't know how to make the tests actually run -- cgit v1.2.3 From 9ea7ddec0c8b83cb38ce41477116f31614d6623e Mon Sep 17 00:00:00 2001 From: Justin Brewer Date: Mon, 21 Jan 2019 13:42:58 -0600 Subject: Generate and install hiredis.pc Signed-off-by: Justin Brewer --- CMakeLists.txt | 10 ++++++++-- hiredis.pc.in | 11 +++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 hiredis.pc.in diff --git a/CMakeLists.txt b/CMakeLists.txt index e58bfa7..2ef322a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,8 @@ getVersionBit(HIREDIS_MAJOR) getVersionBit(HIREDIS_MINOR) getVersionBit(HIREDIS_PATCH) -MESSAGE("Detected version: ${HIREDIS_MAJOR}.${HIREDIS_MINOR}.${HIREDIS_PATCH}") +SET(VERSION "${HIREDIS_MAJOR}.${HIREDIS_MINOR}.${HIREDIS_PATCH}") +MESSAGE("Detected version: ${VERSION}") INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}) @@ -32,7 +33,9 @@ ADD_LIBRARY(hiredis SHARED SET_TARGET_PROPERTIES(hiredis PROPERTIES - VERSION "${HIREDIS_MAJOR}.${HIREDIS_MINOR}.${HIREDIS_PATCH}") + VERSION "${VERSION}") + +CONFIGURE_FILE(hiredis.pc.in hiredis.pc @ONLY) INSTALL(TARGETS hiredis DESTINATION "${CMAKE_INSTALL_LIBDIR}") @@ -40,6 +43,9 @@ INSTALL(TARGETS hiredis INSTALL(FILES hiredis.h read.h sds.h async.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis) +INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis.pc + DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) + # Add tests: Currently, I don't know how to make the tests actually run # without hanging! ENABLE_TESTING() diff --git a/hiredis.pc.in b/hiredis.pc.in new file mode 100644 index 0000000..22ec937 --- /dev/null +++ b/hiredis.pc.in @@ -0,0 +1,11 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} +libdir=${exec_prefix}/lib +includedir=${prefix}/include +pkgincludedir=${includedir}/hiredis + +Name: hiredis +Description: Minimalistic C client library for Redis. +Version: @VERSION@ +Libs: -L${libdir} -lhiredis +Cflags: -I${pkgincludedir} -D_FILE_OFFSET_BITS=64 -- cgit v1.2.3 From 58d0e283331e778b11f4104d8cb4e8d5d7d9aa09 Mon Sep 17 00:00:00 2001 From: Justin Brewer Date: Mon, 21 Jan 2019 13:56:59 -0600 Subject: Enable make test Signed-off-by: Justin Brewer --- CMakeLists.txt | 4 ++-- test.sh | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) create mode 100755 test.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ef322a..51020ee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,12 +46,12 @@ INSTALL(FILES hiredis.h read.h sds.h async.h INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) -# Add tests: Currently, I don't know how to make the tests actually run -# without hanging! ENABLE_TESTING() ADD_EXECUTABLE(hiredis-test test.c) TARGET_LINK_LIBRARIES(hiredis-test hiredis) +ADD_TEST(NAME hiredis-test + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test.sh) # Add examples IF(ENABLE_EXAMPLES) diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..8dce61b --- /dev/null +++ b/test.sh @@ -0,0 +1,25 @@ +#!/bin/sh -ue + +REDIS_SERVER=${REDIS_SERVER:-redis-server} +REDIS_PORT=${REDIS_PORT:-56379} + +tmpdir=$(mktemp -d) +PID_FILE=${tmpdir}/hiredis-test-redis.pid +SOCK_FILE=${tmpdir}/hiredis-test-redis.sock + +cleanup() { + set +e + kill $(cat ${PID_FILE}) + rm -rf ${tmpdir} +} +trap cleanup INT TERM EXIT + +${REDIS_SERVER} - < Date: Mon, 21 Jan 2019 13:57:11 -0600 Subject: Call project() with parsed version string Signed-off-by: Justin Brewer --- CMakeLists.txt | 19 ++++++++----------- hiredis.pc.in | 2 +- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 51020ee..f40bbc3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,26 +1,23 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.0.0) INCLUDE(GNUInstallDirs) -PROJECT(redisearch) -SET(ENABLE_EXAMPLES OFF CACHE BOOL "Enable building hiredis examples") - -# Get the version numbers MACRO(getVersionBit name) - -EXECUTE_PROCESS( - WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" - COMMAND grep "${name}" hiredis.h COMMAND awk "{print $3}" - OUTPUT_VARIABLE "${name}" - OUTPUT_STRIP_TRAILING_WHITESPACE) + SET(VERSION_REGEX "^#define ${name} (.+)$") + FILE(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/hiredis.h" + VERSION_BIT REGEX ${VERSION_REGEX}) + STRING(REGEX REPLACE ${VERSION_REGEX} "\\1" ${name} "${VERSION_BIT}") ENDMACRO(getVersionBit) getVersionBit(HIREDIS_MAJOR) getVersionBit(HIREDIS_MINOR) getVersionBit(HIREDIS_PATCH) - SET(VERSION "${HIREDIS_MAJOR}.${HIREDIS_MINOR}.${HIREDIS_PATCH}") MESSAGE("Detected version: ${VERSION}") +PROJECT(hiredis VERSION "${VERSION}") + +SET(ENABLE_EXAMPLES OFF CACHE BOOL "Enable building hiredis examples") + INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}) ADD_LIBRARY(hiredis SHARED diff --git a/hiredis.pc.in b/hiredis.pc.in index 22ec937..140b040 100644 --- a/hiredis.pc.in +++ b/hiredis.pc.in @@ -6,6 +6,6 @@ pkgincludedir=${includedir}/hiredis Name: hiredis Description: Minimalistic C client library for Redis. -Version: @VERSION@ +Version: @PROJECT_VERSION@ Libs: -L${libdir} -lhiredis Cflags: -I${pkgincludedir} -D_FILE_OFFSET_BITS=64 -- cgit v1.2.3 From ebe1657c8f76b4940c135ca26f40ffd06835c363 Mon Sep 17 00:00:00 2001 From: Justin Brewer Date: Tue, 22 Jan 2019 15:39:24 -0600 Subject: Obey HIREDIS_SONAME for shared library Signed-off-by: Justin Brewer --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f40bbc3..89ae962 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,7 @@ ENDMACRO(getVersionBit) getVersionBit(HIREDIS_MAJOR) getVersionBit(HIREDIS_MINOR) getVersionBit(HIREDIS_PATCH) +getVersionBit(HIREDIS_SONAME) SET(VERSION "${HIREDIS_MAJOR}.${HIREDIS_MINOR}.${HIREDIS_PATCH}") MESSAGE("Detected version: ${VERSION}") @@ -30,7 +31,7 @@ ADD_LIBRARY(hiredis SHARED SET_TARGET_PROPERTIES(hiredis PROPERTIES - VERSION "${VERSION}") + VERSION "${HIREDIS_SONAME}") CONFIGURE_FILE(hiredis.pc.in hiredis.pc @ONLY) -- cgit v1.2.3 From 93261d465f663ef81290081cc6d353d6db480bc0 Mon Sep 17 00:00:00 2001 From: Justin Brewer Date: Mon, 21 Jan 2019 14:14:33 -0600 Subject: Update .travis-ci.yml to use cmake Signed-off-by: Justin Brewer --- .travis.yml | 52 ++++++++++++++++++++++++++++++++-------------------- test.sh | 2 +- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/.travis.yml b/.travis.yml index faf2ce6..4da8186 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,12 +8,6 @@ os: - linux - osx -branches: - only: - - staging - - trying - - master - before_script: - if [ "$TRAVIS_OS_NAME" == "osx" ] ; then brew update; brew install redis; fi @@ -26,20 +20,38 @@ addons: - libc6-dev-i386 - libc6-dbg:i386 - gcc-multilib + - g++-multilib - valgrind env: - - CFLAGS="-Werror" - - PRE="valgrind --track-origins=yes --leak-check=full" - - TARGET="32bit" TARGET_VARS="32bit-vars" CFLAGS="-Werror" - - TARGET="32bit" TARGET_VARS="32bit-vars" PRE="valgrind --track-origins=yes --leak-check=full" - -matrix: - exclude: - - os: osx - env: PRE="valgrind --track-origins=yes --leak-check=full" - - - os: osx - env: TARGET="32bit" TARGET_VARS="32bit-vars" PRE="valgrind --track-origins=yes --leak-check=full" - -script: make $TARGET CFLAGS="$CFLAGS" && make check PRE="$PRE" && make $TARGET_VARS hiredis-example + - BITS="32" + - BITS="64" + +script: + - if [ "$TRAVIS_OS_NAME" == "osx" ]; then + if [ "$BITS" == "32" ]; then + CFLAGS="-m32 -Werror"; + CXXFLAGS="-m32 -Werror"; + LDFLAGS="-m32"; + else + EXTRA_CMAKE_OPTS="-DENABLE_EXAMPLES:BOOL=ON"; + CFLAGS="-Werror"; + CXXFLAGS="-Werror"; + fi; + else + TEST_PREFIX="valgrind --track-origins=yes --leak-check=full"; + if [ "$BITS" == "32" ]; then + CFLAGS="-m32 -Werror"; + CXXFLAGS="-m32 -Werror"; + LDFLAGS="-m32"; + else + EXTRA_CMAKE_OPTS="-DENABLE_EXAMPLES:BOOL=ON"; + CFLAGS="-Werror"; + CXXFLAGS="-Werror"; + fi; + fi; + export CFLAGS CXXFLAGS LDFLAGS TEST_PREFIX EXTRA_CMAKE_OPTS + - mkdir build/ && cd build/ + - cmake .. ${EXTRA_CMAKE_OPTS} + - make VERBOSE=1 + - ctest -V diff --git a/test.sh b/test.sh index 8dce61b..9a507ae 100755 --- a/test.sh +++ b/test.sh @@ -22,4 +22,4 @@ bind 127.0.0.1 unixsocket ${SOCK_FILE} EOF -./hiredis-test -h 127.0.0.1 -p ${REDIS_PORT} -s ${SOCK_FILE} +${TEST_PREFIX:-} ./hiredis-test -h 127.0.0.1 -p ${REDIS_PORT} -s ${SOCK_FILE} -- cgit v1.2.3 From 4d00404b8fb47e618474d5538e4a720ac1c95d95 Mon Sep 17 00:00:00 2001 From: Justin Brewer Date: Mon, 21 Jan 2019 15:32:49 -0600 Subject: Update appveyor.yml to cmake Signed-off-by: Justin Brewer --- appveyor.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 819efbd..5b43fdb 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -5,8 +5,9 @@ environment: CC: gcc - CYG_BASH: C:\cygwin\bin\bash CC: gcc - TARGET: 32bit - TARGET_VARS: 32bit-vars + CFLAGS: -m32 + CXXFLAGS: -m32 + LDFLAGS: -m32 clone_depth: 1 @@ -20,4 +21,4 @@ install: build_script: - 'echo building...' - - '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0 Date: Mon, 27 Nov 2017 13:10:21 +0000 Subject: Initial SSL (sync) implementation --- Makefile | 16 +++-- hiredis.c | 73 +++++++++++++++-------- hiredis.h | 15 +++++ sslio.c | 197 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sslio.h | 48 +++++++++++++++ 5 files changed, 322 insertions(+), 27 deletions(-) create mode 100644 sslio.c create mode 100644 sslio.h diff --git a/Makefile b/Makefile index 07b8a83..ea96419 100644 --- a/Makefile +++ b/Makefile @@ -3,8 +3,8 @@ # Copyright (C) 2010-2011 Pieter Noordhuis # This file is released under the BSD license, see the COPYING file -OBJ=net.o hiredis.o sds.o async.o read.o -EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib +OBJ=net.o hiredis.o sds.o async.o read.o sslio.o +EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib hiredis-example-ssl TESTS=hiredis-test LIBNAME=libhiredis PKGCONFNAME=hiredis.pc @@ -53,6 +53,10 @@ DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(L STLIBNAME=$(LIBNAME).$(STLIBSUFFIX) STLIB_MAKE_CMD=$(AR) rcs $(STLIBNAME) +OPENSSL_PREFIX=/usr/local/opt/openssl +CFLAGS+=-I$(OPENSSL_PREFIX)/include +LDFLAGS+=-L$(OPENSSL_PREFIX)/lib -lssl -lcrypto + # Platform-specific overrides uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') ifeq ($(uname_S),SunOS) @@ -70,10 +74,11 @@ all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME) # Deps (use make dep to generate this) async.o: async.c fmacros.h async.h hiredis.h read.h sds.h net.h dict.c dict.h dict.o: dict.c fmacros.h dict.h -hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h net.h +hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h net.h sslio.h net.o: net.c fmacros.h net.h hiredis.h read.h sds.h read.o: read.c fmacros.h read.h sds.h sds.o: sds.c sds.h +sslio.o: sslio.c sslio.h hiredis.h test.o: test.c fmacros.h hiredis.h read.h sds.h $(DYLIBNAME): $(OBJ) @@ -101,6 +106,9 @@ hiredis-example-ivykis: examples/example-ivykis.c adapters/ivykis.h $(STLIBNAME) hiredis-example-macosx: examples/example-macosx.c adapters/macosx.h $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -framework CoreFoundation $(STLIBNAME) +hiredis-example-ssl: examples/example-ssl.c $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< $(STLIBNAME) + ifndef AE_DIR hiredis-example-ae: @echo "Please specify AE_DIR (e.g. /src)" @@ -158,7 +166,7 @@ clean: rm -rf $(DYLIBNAME) $(STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov dep: - $(CC) -MM *.c + $(CC) $(CPPFLAGS) $(CFLAGS) -MM *.c INSTALL?= cp -pPR diff --git a/hiredis.c b/hiredis.c index bfbf483..fae8094 100644 --- a/hiredis.c +++ b/hiredis.c @@ -42,6 +42,7 @@ #include "hiredis.h" #include "net.h" #include "sds.h" +#include "sslio.h" static redisReply *createReplyObject(int type); static void *createStringObject(const redisReadTask *task, char *str, size_t len); @@ -614,7 +615,9 @@ void redisFree(redisContext *c) { free(c->unix_sock.path); free(c->timeout); free(c->saddr); - free(c); + if (c->ssl) { + redisFreeSsl(c->ssl); + } } int redisFreeKeepFd(redisContext *c) { @@ -760,6 +763,11 @@ redisContext *redisConnectFd(int fd) { return c; } +int redisSecureConnection(redisContext *c, const char *caPath, + const char *certPath, const char *keyPath) { + return redisSslCreate(c, caPath, certPath, keyPath); +} + /* Set read/write timeout on a blocking socket. */ int redisSetTimeout(redisContext *c, const struct timeval tv) { if (c->flags & REDIS_BLOCK) @@ -774,6 +782,24 @@ int redisEnableKeepAlive(redisContext *c) { return REDIS_OK; } +static int rawRead(redisContext *c, char *buf, size_t bufcap) { + int nread = read(c->fd, buf, bufcap); + if (nread == -1) { + if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { + /* Try again later */ + return 0; + } else { + __redisSetError(c, REDIS_ERR_IO, NULL); + return -1; + } + } else if (nread == 0) { + __redisSetError(c, REDIS_ERR_EOF, "Server closed the connection"); + return -1; + } else { + return nread; + } +} + /* Use this function to handle a read event on the descriptor. It will try * and read some bytes from the socket and feed them to the reply parser. * @@ -787,26 +813,33 @@ int redisBufferRead(redisContext *c) { if (c->err) return REDIS_ERR; - nread = read(c->fd,buf,sizeof(buf)); - if (nread == -1) { - if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { - /* Try again later */ - } else { - __redisSetError(c,REDIS_ERR_IO,NULL); + nread = c->flags & REDIS_SSL ? + redisSslRead(c, buf, sizeof(buf)) : rawRead(c, buf, sizeof(buf)); + if (nread > 0) { + if (redisReaderFeed(c->reader, buf, nread) != REDIS_OK) { + __redisSetError(c, c->reader->err, c->reader->errstr); return REDIS_ERR; + } else { } - } else if (nread == 0) { - __redisSetError(c,REDIS_ERR_EOF,"Server closed the connection"); + } else if (nread < 0) { return REDIS_ERR; - } else { - if (redisReaderFeed(c->reader,buf,nread) != REDIS_OK) { - __redisSetError(c,c->reader->err,c->reader->errstr); - return REDIS_ERR; - } } return REDIS_OK; } +static int rawWrite(redisContext *c) { + int nwritten = write(c->fd, c->obuf, sdslen(c->obuf)); + if (nwritten < 0) { + if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { + /* Try again later */ + } else { + __redisSetError(c, REDIS_ERR_IO, NULL); + return -1; + } + } + return nwritten; +} + /* Write the output buffer to the socket. * * Returns REDIS_OK when the buffer is empty, or (a part of) the buffer was @@ -817,21 +850,15 @@ int redisBufferRead(redisContext *c) { * c->errstr to hold the appropriate error string. */ int redisBufferWrite(redisContext *c, int *done) { - int nwritten; /* Return early when the context has seen an error. */ if (c->err) return REDIS_ERR; if (sdslen(c->obuf) > 0) { - nwritten = write(c->fd,c->obuf,sdslen(c->obuf)); - if (nwritten == -1) { - if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { - /* Try again later */ - } else { - __redisSetError(c,REDIS_ERR_IO,NULL); - return REDIS_ERR; - } + int nwritten = (c->flags & REDIS_SSL) ? redisSslWrite(c) : rawWrite(c); + if (nwritten < 0) { + return REDIS_ERR; } else if (nwritten > 0) { if (nwritten == (signed)sdslen(c->obuf)) { sdsfree(c->obuf); diff --git a/hiredis.h b/hiredis.h index 1b0d5e6..29c0253 100644 --- a/hiredis.h +++ b/hiredis.h @@ -74,6 +74,9 @@ /* Flag that is set when we should set SO_REUSEADDR before calling bind() */ #define REDIS_REUSEADDR 0x80 +/* Flag that is set when this connection is done through SSL */ +#define REDIS_SSL 0x100 + #define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */ /* number of times we retry to connect in the case of EADDRNOTAVAIL and @@ -112,6 +115,8 @@ enum redisConnectionType { REDIS_CONN_UNIX }; +struct redisSsl; + /* Context for a connection to Redis */ typedef struct redisContext { int err; /* Error flags, 0 when there is no error */ @@ -137,6 +142,9 @@ typedef struct redisContext { /* For non-blocking connect */ struct sockadr *saddr; size_t addrlen; + /* For SSL communication */ + struct redisSsl *ssl; + } redisContext; redisContext *redisConnect(const char *ip, int port); @@ -151,6 +159,13 @@ redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval redisContext *redisConnectUnixNonBlock(const char *path); redisContext *redisConnectFd(int fd); +/** + * Secure the connection using SSL. This should be done before any command is + * executed on the connection. + */ +int redisSecureConnection(redisContext *c, const char *capath, const char *certpath, + const char *keypath); + /** * Reconnect the given context using the saved information. * diff --git a/sslio.c b/sslio.c new file mode 100644 index 0000000..9ee015b --- /dev/null +++ b/sslio.c @@ -0,0 +1,197 @@ +#include "hiredis.h" +#include "sslio.h" + +#include +#ifndef HIREDIS_NOSSL +#include + +void __redisSetError(redisContext *c, int type, const char *str); + +static void sslLogCallback(const SSL *ssl, int where, int ret) { + const char *retstr = ""; + int should_log = 1; + /* Ignore low-level SSL stuff */ + + if (where & SSL_CB_ALERT) { + should_log = 1; + } + if (where == SSL_CB_HANDSHAKE_START || where == SSL_CB_HANDSHAKE_DONE) { + should_log = 1; + } + if ((where & SSL_CB_EXIT) && ret == 0) { + should_log = 1; + } + + if (!should_log) { + return; + } + + retstr = SSL_alert_type_string(ret); + printf("ST(0x%x). %s. R(0x%x)%s\n", where, SSL_state_string_long(ssl), ret, retstr); + + if (where == SSL_CB_HANDSHAKE_DONE) { + printf("Using SSL version %s. Cipher=%s\n", SSL_get_version(ssl), SSL_get_cipher_name(ssl)); + } +} + +typedef pthread_mutex_t sslLockType; +static void sslLockInit(sslLockType *l) { + pthread_mutex_init(l, NULL); +} +static void sslLockAcquire(sslLockType *l) { + pthread_mutex_lock(l); +} +static void sslLockRelease(sslLockType *l) { + pthread_mutex_unlock(l); +} +static pthread_mutex_t *ossl_locks; + +static void opensslDoLock(int mode, int lkid, const char *f, int line) { + sslLockType *l = ossl_locks + lkid; + + if (mode & CRYPTO_LOCK) { + sslLockAcquire(l); + } else { + sslLockRelease(l); + } + + (void)f; + (void)line; +} + +static void initOpensslLocks(void) { + unsigned ii, nlocks; + if (CRYPTO_get_locking_callback() != NULL) { + /* Someone already set the callback before us. Don't destroy it! */ + return; + } + nlocks = CRYPTO_num_locks(); + ossl_locks = malloc(sizeof(*ossl_locks) * nlocks); + for (ii = 0; ii < nlocks; ii++) { + sslLockInit(ossl_locks + ii); + } + CRYPTO_set_locking_callback(opensslDoLock); +} + +void redisFreeSsl(redisSsl *ssl){ + if (ssl->ctx) { + SSL_CTX_free(ssl->ctx); + } + if (ssl->ssl) { + SSL_free(ssl->ssl); + } + free(ssl); +} + +int redisSslCreate(redisContext *c, const char *capath, const char *certpath, + const char *keypath) { + assert(!c->ssl); + c->ssl = calloc(1, sizeof(*c->ssl)); + static int isInit = 0; + if (!isInit) { + isInit = 1; + SSL_library_init(); + initOpensslLocks(); + } + + redisSsl *s = c->ssl; + s->ctx = SSL_CTX_new(SSLv23_client_method()); + SSL_CTX_set_info_callback(s->ctx, sslLogCallback); + SSL_CTX_set_mode(s->ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + SSL_CTX_set_options(s->ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); + SSL_CTX_set_verify(s->ctx, SSL_VERIFY_PEER, NULL); + + if ((certpath != NULL && keypath == NULL) || (keypath != NULL && certpath == NULL)) { + __redisSetError(c, REDIS_ERR, "certpath and keypath must be specified together"); + return REDIS_ERR; + } + + if (capath) { + if (!SSL_CTX_load_verify_locations(s->ctx, capath, NULL)) { + __redisSetError(c, REDIS_ERR, "Invalid CA certificate"); + return REDIS_ERR; + } + } + if (certpath) { + if (!SSL_CTX_use_certificate_chain_file(s->ctx, certpath)) { + __redisSetError(c, REDIS_ERR, "Invalid client certificate"); + return REDIS_ERR; + } + if (!SSL_CTX_use_PrivateKey_file(s->ctx, keypath, SSL_FILETYPE_PEM)) { + __redisSetError(c, REDIS_ERR, "Invalid client key"); + return REDIS_ERR; + } + printf("Loaded certificate!\n"); + } + + s->ssl = SSL_new(s->ctx); + if (!s->ssl) { + __redisSetError(c, REDIS_ERR, "Couldn't create new SSL instance"); + return REDIS_ERR; + } + + SSL_set_fd(s->ssl, c->fd); + SSL_set_connect_state(s->ssl); + + c->flags |= REDIS_SSL; + printf("Before SSL_connect()\n"); + int rv = SSL_connect(c->ssl->ssl); + if (rv == 1) { + printf("SSL_connect() success!\n"); + return REDIS_OK; + } + printf("ConnectRV: %d\n", rv); + + rv = SSL_get_error(s->ssl, rv); + if (((c->flags & REDIS_BLOCK) == 0) && + (rv == SSL_ERROR_WANT_READ || rv == SSL_ERROR_WANT_WRITE)) { + return REDIS_OK; + } + + if (c->err == 0) { + __redisSetError(c, REDIS_ERR_IO, "SSL_connect() failed"); + printf("rv: %d\n", rv); + } + printf("ERROR!!!\n"); + return REDIS_ERR; +} + +int redisSslRead(redisContext *c, char *buf, size_t bufcap) { + int nread = SSL_read(c->ssl->ssl, buf, bufcap); + if (nread > 0) { + return nread; + } else if (nread == 0) { + __redisSetError(c, REDIS_ERR_EOF, "Server closed the connection"); + return -1; + } else { + int err = SSL_get_error(c->ssl->ssl, nread); + if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) { + return 0; + } else { + __redisSetError(c, REDIS_ERR_IO, NULL); + return -1; + } + } +} + +int redisSslWrite(redisContext *c) { + size_t len = c->ssl->lastLen ? c->ssl->lastLen : sdslen(c->obuf); + int rv = SSL_write(c->ssl->ssl, c->obuf, len); + + if (rv > 0) { + c->ssl->lastLen = 0; + } else if (rv < 0) { + c->ssl->lastLen = len; + + int err = SSL_get_error(c->ssl->ssl, rv); + if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) { + return 0; + } else { + __redisSetError(c, REDIS_ERR_IO, NULL); + return -1; + } + } + return rv; +} + +#endif diff --git a/sslio.h b/sslio.h new file mode 100644 index 0000000..a410cb3 --- /dev/null +++ b/sslio.h @@ -0,0 +1,48 @@ +#ifndef REDIS_SSLIO_H +#define REDIS_SSLIO_H + + +#ifdef HIREDIS_NOSSL +typedef struct redisSsl { + int dummy; +} redisSsl; +static void redisFreeSsl(redisSsl *) { +} +static int redisSslCreate(struct redisContext *c) { + return REDIS_ERR; +} +static int redisSslRead(struct redisContect *c, char *s, size_t, n) { + return -1; +} +static int redisSslWrite(struct redisContext *c) { + return -1; +} +#else +#include + +/** + * This file contains routines for HIREDIS' SSL + */ + +typedef struct redisSsl { + SSL *ssl; + SSL_CTX *ctx; + + /** + * SSL_write() requires to be called again with the same arguments it was + * previously called with in the event of an SSL_read/SSL_write situation + */ + size_t lastLen; +} redisSsl; + +struct redisContext; + +void redisFreeSsl(redisSsl *); +int redisSslCreate(struct redisContext *c, const char *caPath, + const char *certPath, const char *keyPath); + +int redisSslRead(struct redisContext *c, char *buf, size_t bufcap); +int redisSslWrite(struct redisContext *c); + +#endif /* !HIREDIS_NOSSL */ +#endif /* HIREDIS_SSLIO_H */ -- cgit v1.2.3 From 5f50eb4131c98cb7c418c107317373292ad6ae6c Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Mon, 27 Nov 2017 09:24:44 -0500 Subject: Remove extra printfs --- sslio.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/sslio.c b/sslio.c index 9ee015b..6958d37 100644 --- a/sslio.c +++ b/sslio.c @@ -7,6 +7,9 @@ void __redisSetError(redisContext *c, int type, const char *str); +/** + * Callback used for debugging + */ static void sslLogCallback(const SSL *ssl, int where, int ret) { const char *retstr = ""; int should_log = 1; @@ -96,7 +99,7 @@ int redisSslCreate(redisContext *c, const char *capath, const char *certpath, redisSsl *s = c->ssl; s->ctx = SSL_CTX_new(SSLv23_client_method()); - SSL_CTX_set_info_callback(s->ctx, sslLogCallback); + /* SSL_CTX_set_info_callback(s->ctx, sslLogCallback); */ SSL_CTX_set_mode(s->ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); SSL_CTX_set_options(s->ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); SSL_CTX_set_verify(s->ctx, SSL_VERIFY_PEER, NULL); @@ -121,7 +124,6 @@ int redisSslCreate(redisContext *c, const char *capath, const char *certpath, __redisSetError(c, REDIS_ERR, "Invalid client key"); return REDIS_ERR; } - printf("Loaded certificate!\n"); } s->ssl = SSL_new(s->ctx); @@ -134,13 +136,10 @@ int redisSslCreate(redisContext *c, const char *capath, const char *certpath, SSL_set_connect_state(s->ssl); c->flags |= REDIS_SSL; - printf("Before SSL_connect()\n"); int rv = SSL_connect(c->ssl->ssl); if (rv == 1) { - printf("SSL_connect() success!\n"); return REDIS_OK; } - printf("ConnectRV: %d\n", rv); rv = SSL_get_error(s->ssl, rv); if (((c->flags & REDIS_BLOCK) == 0) && @@ -150,9 +149,7 @@ int redisSslCreate(redisContext *c, const char *capath, const char *certpath, if (c->err == 0) { __redisSetError(c, REDIS_ERR_IO, "SSL_connect() failed"); - printf("rv: %d\n", rv); } - printf("ERROR!!!\n"); return REDIS_ERR; } -- cgit v1.2.3 From ba947bc93cc262c37db181f4684db1c37e803d6a Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Mon, 27 Nov 2017 09:24:53 -0500 Subject: Add SSL example --- examples/example-ssl.c | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 examples/example-ssl.c diff --git a/examples/example-ssl.c b/examples/example-ssl.c new file mode 100644 index 0000000..28489e4 --- /dev/null +++ b/examples/example-ssl.c @@ -0,0 +1,92 @@ +#include +#include +#include + +#include + +int main(int argc, char **argv) { + unsigned int j; + redisContext *c; + redisReply *reply; + if (argc < 4) { + printf("Usage: %s [ca]\n", argv[0]); + exit(1); + } + const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1"; + int port = atoi(argv[2]); + const char *cert = argv[3]; + const char *key = argv[4]; + const char *ca = argc > 4 ? argv[5] : NULL; + + struct timeval timeout = { 1, 500000 }; // 1.5 seconds + c = redisConnectWithTimeout(hostname, port, timeout); + if (c == NULL || c->err) { + if (c) { + printf("Connection error: %s\n", c->errstr); + redisFree(c); + } else { + printf("Connection error: can't allocate redis context\n"); + } + exit(1); + } + + if (redisSecureConnection(c, ca, cert, key) != REDIS_OK) { + printf("Couldn't initialize SSL!\n"); + printf("Error: %s\n", c->errstr); + redisFree(c); + exit(1); + } + + /* PING server */ + reply = redisCommand(c,"PING"); + printf("PING: %s\n", reply->str); + freeReplyObject(reply); + + /* Set a key */ + reply = redisCommand(c,"SET %s %s", "foo", "hello world"); + printf("SET: %s\n", reply->str); + freeReplyObject(reply); + + /* Set a key using binary safe API */ + reply = redisCommand(c,"SET %b %b", "bar", (size_t) 3, "hello", (size_t) 5); + printf("SET (binary API): %s\n", reply->str); + freeReplyObject(reply); + + /* Try a GET and two INCR */ + reply = redisCommand(c,"GET foo"); + printf("GET foo: %s\n", reply->str); + freeReplyObject(reply); + + reply = redisCommand(c,"INCR counter"); + printf("INCR counter: %lld\n", reply->integer); + freeReplyObject(reply); + /* again ... */ + reply = redisCommand(c,"INCR counter"); + printf("INCR counter: %lld\n", reply->integer); + freeReplyObject(reply); + + /* Create a list of numbers, from 0 to 9 */ + reply = redisCommand(c,"DEL mylist"); + freeReplyObject(reply); + for (j = 0; j < 10; j++) { + char buf[64]; + + snprintf(buf,64,"%u",j); + reply = redisCommand(c,"LPUSH mylist element-%s", buf); + freeReplyObject(reply); + } + + /* Let's check what we have inside the list */ + reply = redisCommand(c,"LRANGE mylist 0 -1"); + if (reply->type == REDIS_REPLY_ARRAY) { + for (j = 0; j < reply->elements; j++) { + printf("%u) %s\n", j, reply->element[j]->str); + } + } + freeReplyObject(reply); + + /* Disconnects and frees the context */ + redisFree(c); + + return 0; +} -- cgit v1.2.3 From 08efa46599410e6b56ab19ed1c9a72c67476db9c Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Mon, 27 Nov 2017 15:49:28 -0500 Subject: SSL for async I/O --- Makefile | 6 ++- async.c | 92 +++++++++++++++++++++++++++++++++++++++++ examples/example-libevent-ssl.c | 72 ++++++++++++++++++++++++++++++++ sslio.c | 22 ++++++++-- sslio.h | 9 ++++ 5 files changed, 197 insertions(+), 4 deletions(-) create mode 100644 examples/example-libevent-ssl.c diff --git a/Makefile b/Makefile index ea96419..b723245 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,8 @@ # This file is released under the BSD license, see the COPYING file OBJ=net.o hiredis.o sds.o async.o read.o sslio.o -EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib hiredis-example-ssl +EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib \ + hiredis-example-ssl hiredis-example-libevent-ssl TESTS=hiredis-test LIBNAME=libhiredis PKGCONFNAME=hiredis.pc @@ -94,6 +95,9 @@ static: $(STLIBNAME) hiredis-example-libevent: examples/example-libevent.c adapters/libevent.h $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -levent $(STLIBNAME) +hiredis-example-libevent-ssl: examples/example-libevent-ssl.c adapters/libevent.h $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -levent $(STLIBNAME) + hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -lev $(STLIBNAME) diff --git a/async.c b/async.c index 0cecd30..5a14d45 100644 --- a/async.c +++ b/async.c @@ -40,6 +40,7 @@ #include "net.h" #include "dict.c" #include "sds.h" +#include "sslio.h" #define _EL_ADD_READ(ctx) do { \ if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \ @@ -524,6 +525,87 @@ static int __redisAsyncHandleConnect(redisAsyncContext *ac) { } } +#ifndef HIREDIS_NOSSL +/** + * Handle SSL when socket becomes available for reading. This also handles + * read-while-write and write-while-read + */ +static void asyncSslRead(redisAsyncContext *ac) { + int rv; + redisSsl *ssl = ac->c.ssl; + redisContext *c = &ac->c; + + ssl->wantRead = 0; + + if (ssl->pendingWrite) { + int done; + + /* This is probably just a write event */ + ssl->pendingWrite = 0; + rv = redisBufferWrite(c, &done); + if (rv == REDIS_ERR) { + __redisAsyncDisconnect(ac); + return; + } else if (!done) { + _EL_ADD_WRITE(ac); + } + } + + rv = redisBufferRead(c); + if (rv == REDIS_ERR) { + __redisAsyncDisconnect(ac); + } else { + _EL_ADD_READ(ac); + redisProcessCallbacks(ac); + } +} + +/** + * Handle SSL when socket becomes available for writing + */ +static void asyncSslWrite(redisAsyncContext *ac) { + int rv, done = 0; + redisSsl *ssl = ac->c.ssl; + redisContext *c = &ac->c; + + ssl->pendingWrite = 0; + rv = redisBufferWrite(c, &done); + if (rv == REDIS_ERR) { + __redisAsyncDisconnect(ac); + return; + } + + if (!done) { + if (ssl->wantRead) { + /* Need to read-before-write */ + ssl->pendingWrite = 1; + _EL_DEL_WRITE(ac); + } else { + /* No extra reads needed, just need to write more */ + _EL_ADD_WRITE(ac); + } + } else { + /* Already done! */ + _EL_DEL_WRITE(ac); + } + + /* Always reschedule a read */ + _EL_ADD_READ(ac); +} +#else + +/* Just so we're able to compile */ +static void asyncSslRead(redisAsyncContext *ac) { + abort(); + (void)ac; +} +static void asyncSslWrite(redisAsyncContext *ac) { + abort(); + (void)ac; +} + +#endif + /* This function should be called when the socket is readable. * It processes all replies that can be read and executes their callbacks. */ @@ -539,6 +621,11 @@ void redisAsyncHandleRead(redisAsyncContext *ac) { return; } + if (c->flags & REDIS_SSL) { + asyncSslRead(ac); + return; + } + if (redisBufferRead(c) == REDIS_ERR) { __redisAsyncDisconnect(ac); } else { @@ -561,6 +648,11 @@ void redisAsyncHandleWrite(redisAsyncContext *ac) { return; } + if (c->flags & REDIS_SSL) { + asyncSslWrite(ac); + return; + } + if (redisBufferWrite(c,&done) == REDIS_ERR) { __redisAsyncDisconnect(ac); } else { diff --git a/examples/example-libevent-ssl.c b/examples/example-libevent-ssl.c new file mode 100644 index 0000000..f780e3e --- /dev/null +++ b/examples/example-libevent-ssl.c @@ -0,0 +1,72 @@ +#include +#include +#include +#include + +#include +#include +#include + +void getCallback(redisAsyncContext *c, void *r, void *privdata) { + redisReply *reply = r; + if (reply == NULL) return; + printf("argv[%s]: %s\n", (char*)privdata, reply->str); + + /* Disconnect after receiving the reply to GET */ + redisAsyncDisconnect(c); +} + +void connectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Connected...\n"); +} + +void disconnectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Disconnected...\n"); +} + +int main (int argc, char **argv) { + signal(SIGPIPE, SIG_IGN); + struct event_base *base = event_base_new(); + if (argc < 5) { + fprintf(stderr, + "Usage: %s [ca]\n", argv[0]); + exit(1); + } + + const char *value = argv[1]; + size_t nvalue = strlen(value); + + const char *hostname = argv[2]; + int port = atoi(argv[3]); + + const char *cert = argv[4]; + const char *certKey = argv[5]; + const char *caCert = argc > 5 ? argv[6] : NULL; + + redisAsyncContext *c = redisAsyncConnect(hostname, port); + if (c->err) { + /* Let *c leak for now... */ + printf("Error: %s\n", c->errstr); + return 1; + } + if (redisSecureConnection(&c->c, caCert, cert, certKey) != REDIS_OK) { + printf("SSL Error!\n"); + exit(1); + } + + redisLibeventAttach(c,base); + redisAsyncSetConnectCallback(c,connectCallback); + redisAsyncSetDisconnectCallback(c,disconnectCallback); + redisAsyncCommand(c, NULL, NULL, "SET key %b", value, nvalue); + redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); + event_base_dispatch(base); + return 0; +} diff --git a/sslio.c b/sslio.c index 6958d37..3b08140 100644 --- a/sslio.c +++ b/sslio.c @@ -99,7 +99,7 @@ int redisSslCreate(redisContext *c, const char *capath, const char *certpath, redisSsl *s = c->ssl; s->ctx = SSL_CTX_new(SSLv23_client_method()); - /* SSL_CTX_set_info_callback(s->ctx, sslLogCallback); */ + SSL_CTX_set_info_callback(s->ctx, sslLogCallback); SSL_CTX_set_mode(s->ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); SSL_CTX_set_options(s->ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); SSL_CTX_set_verify(s->ctx, SSL_VERIFY_PEER, NULL); @@ -153,6 +153,22 @@ int redisSslCreate(redisContext *c, const char *capath, const char *certpath, return REDIS_ERR; } +static int maybeCheckWant(redisSsl *rssl, int rv) { + /** + * If the error is WANT_READ or WANT_WRITE, the appropriate flags are set + * and true is returned. False is returned otherwise + */ + if (rv == SSL_ERROR_WANT_READ) { + rssl->wantRead = 1; + return 1; + } else if (rv == SSL_ERROR_WANT_WRITE) { + rssl->pendingWrite = 1; + return 1; + } else { + return 0; + } +} + int redisSslRead(redisContext *c, char *buf, size_t bufcap) { int nread = SSL_read(c->ssl->ssl, buf, bufcap); if (nread > 0) { @@ -162,7 +178,7 @@ int redisSslRead(redisContext *c, char *buf, size_t bufcap) { return -1; } else { int err = SSL_get_error(c->ssl->ssl, nread); - if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) { + if (maybeCheckWant(c->ssl, err)) { return 0; } else { __redisSetError(c, REDIS_ERR_IO, NULL); @@ -181,7 +197,7 @@ int redisSslWrite(redisContext *c) { c->ssl->lastLen = len; int err = SSL_get_error(c->ssl->ssl, rv); - if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) { + if (maybeCheckWant(c->ssl, err)) { return 0; } else { __redisSetError(c, REDIS_ERR_IO, NULL); diff --git a/sslio.h b/sslio.h index a410cb3..1f46b03 100644 --- a/sslio.h +++ b/sslio.h @@ -33,6 +33,15 @@ typedef struct redisSsl { * previously called with in the event of an SSL_read/SSL_write situation */ size_t lastLen; + + /** Whether the SSL layer requires read (possibly before a write) */ + int wantRead; + + /** + * Whether a write was requested prior to a read. If set, the write() + * should resume whenever a read takes place, if possible + */ + int pendingWrite; } redisSsl; struct redisContext; -- cgit v1.2.3 From 82549a53de4483b368008485d74028f302fd0e60 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Mon, 8 Jan 2018 16:07:10 -0500 Subject: Disable SSL by default --- Makefile | 8 +++++--- sslio.c | 2 +- sslio.h | 21 ++++++++++++++------- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index b723245..f7ed675 100644 --- a/Makefile +++ b/Makefile @@ -54,9 +54,11 @@ DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(L STLIBNAME=$(LIBNAME).$(STLIBSUFFIX) STLIB_MAKE_CMD=$(AR) rcs $(STLIBNAME) -OPENSSL_PREFIX=/usr/local/opt/openssl -CFLAGS+=-I$(OPENSSL_PREFIX)/include -LDFLAGS+=-L$(OPENSSL_PREFIX)/lib -lssl -lcrypto +ifdef USE_SSL + OPENSSL_PREFIX=/usr/local/opt/openssl + CFLAGS+=-I$(OPENSSL_PREFIX)/include -DHIREDIS_SSL + LDFLAGS+=-L$(OPENSSL_PREFIX)/lib -lssl -lcrypto +endif # Platform-specific overrides uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') diff --git a/sslio.c b/sslio.c index 3b08140..721a63a 100644 --- a/sslio.c +++ b/sslio.c @@ -2,7 +2,7 @@ #include "sslio.h" #include -#ifndef HIREDIS_NOSSL +#ifdef HIREDIS_SSL #include void __redisSetError(redisContext *c, int type, const char *str); diff --git a/sslio.h b/sslio.h index 1f46b03..9edac87 100644 --- a/sslio.h +++ b/sslio.h @@ -2,19 +2,26 @@ #define REDIS_SSLIO_H -#ifdef HIREDIS_NOSSL +#ifndef HIREDIS_SSL typedef struct redisSsl { - int dummy; + size_t lastLen; + int wantRead; + int pendingWrite; } redisSsl; -static void redisFreeSsl(redisSsl *) { +static inline void redisFreeSsl(redisSsl *ssl) { + (void)ssl; } -static int redisSslCreate(struct redisContext *c) { +static inline int redisSslCreate(struct redisContext *c, const char *ca, + const char *cert, const char *key) { + (void)c;(void)ca;(void)cert;(void)key; return REDIS_ERR; } -static int redisSslRead(struct redisContect *c, char *s, size_t, n) { +static inline int redisSslRead(struct redisContext *c, char *s, size_t n) { + (void)c;(void)s;(void)n; return -1; } -static int redisSslWrite(struct redisContext *c) { +static inline int redisSslWrite(struct redisContext *c) { + (void)c; return -1; } #else @@ -53,5 +60,5 @@ int redisSslCreate(struct redisContext *c, const char *caPath, int redisSslRead(struct redisContext *c, char *buf, size_t bufcap); int redisSslWrite(struct redisContext *c); -#endif /* !HIREDIS_NOSSL */ +#endif /* HIREDIS_SSL */ #endif /* HIREDIS_SSLIO_H */ -- cgit v1.2.3 From bc2a8f372a9f073e2fc00c34ba11cb3cb671f252 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Mon, 8 Jan 2018 16:09:53 -0500 Subject: Minor SSL-related fixes --- Makefile | 4 +++- sslio.c | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index f7ed675..7e3e2bf 100644 --- a/Makefile +++ b/Makefile @@ -55,7 +55,9 @@ STLIBNAME=$(LIBNAME).$(STLIBSUFFIX) STLIB_MAKE_CMD=$(AR) rcs $(STLIBNAME) ifdef USE_SSL - OPENSSL_PREFIX=/usr/local/opt/openssl + # This is the prefix of openssl on my system. This should be the sane default + # based on the platform + OPENSSL_PREFIX?=/usr/local/opt/openssl CFLAGS+=-I$(OPENSSL_PREFIX)/include -DHIREDIS_SSL LDFLAGS+=-L$(OPENSSL_PREFIX)/lib -lssl -lcrypto endif diff --git a/sslio.c b/sslio.c index 721a63a..8933f95 100644 --- a/sslio.c +++ b/sslio.c @@ -57,7 +57,7 @@ static void opensslDoLock(int mode, int lkid, const char *f, int line) { } else { sslLockRelease(l); } - + (void)f; (void)line; } -- cgit v1.2.3 From 4127e4488bcebceac59aefe6013e3f854b192394 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Tue, 9 Jan 2018 06:57:17 -0500 Subject: Don't add dead code for HIREDIS_NOSSL We changed this to `HIREDIS_SSL` --- async.c | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/async.c b/async.c index 5a14d45..db59036 100644 --- a/async.c +++ b/async.c @@ -525,10 +525,12 @@ static int __redisAsyncHandleConnect(redisAsyncContext *ac) { } } -#ifndef HIREDIS_NOSSL /** * Handle SSL when socket becomes available for reading. This also handles - * read-while-write and write-while-read + * read-while-write and write-while-read. + * + * These functions will not work properly unless `HIREDIS_SSL` is defined + * (however, they will compile) */ static void asyncSslRead(redisAsyncContext *ac) { int rv; @@ -592,19 +594,6 @@ static void asyncSslWrite(redisAsyncContext *ac) { /* Always reschedule a read */ _EL_ADD_READ(ac); } -#else - -/* Just so we're able to compile */ -static void asyncSslRead(redisAsyncContext *ac) { - abort(); - (void)ac; -} -static void asyncSslWrite(redisAsyncContext *ac) { - abort(); - (void)ac; -} - -#endif /* This function should be called when the socket is readable. * It processes all replies that can be read and executes their callbacks. -- cgit v1.2.3 From d329cc952a61fc937c2bfe6887ed6d64d763f4f3 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Mon, 21 May 2018 10:35:16 -0400 Subject: Use SSL by default --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 7e3e2bf..b2a9fb5 100644 --- a/Makefile +++ b/Makefile @@ -54,6 +54,8 @@ DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(L STLIBNAME=$(LIBNAME).$(STLIBSUFFIX) STLIB_MAKE_CMD=$(AR) rcs $(STLIBNAME) +USE_SSL:=1 + ifdef USE_SSL # This is the prefix of openssl on my system. This should be the sane default # based on the platform -- cgit v1.2.3 From e4a78006e73fada8084b15618b7ba269fdda9237 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Wed, 5 Dec 2018 20:28:13 -0500 Subject: Provide option-struct initialization This reduces the boilerplate of all the redisConnectXXX functions, and allows us to provide more connection options in the future. --- hiredis.c | 151 ++++++++++++++++++++++++++++++-------------------------------- hiredis.h | 45 ++++++++++++++++++- 2 files changed, 117 insertions(+), 79 deletions(-) diff --git a/hiredis.c b/hiredis.c index fae8094..d33e393 100644 --- a/hiredis.c +++ b/hiredis.c @@ -584,14 +584,21 @@ redisReader *redisReaderCreate(void) { return redisReaderCreateWithFunctions(&defaultFunctions); } -static redisContext *redisContextInit(void) { +static redisContext *redisContextInit(const redisOptions *options) { redisContext *c; - c = calloc(1,sizeof(redisContext)); + c = calloc(1, sizeof(*c)); if (c == NULL) return NULL; + c->err = 0; + c->errstr[0] = '\0'; c->obuf = sdsempty(); + c->flags = 0; + c->tcp.host = NULL; + c->tcp.source_addr = NULL; + c->unix_sock.path = NULL; + c->timeout = NULL; c->reader = redisReaderCreate(); if (c->obuf == NULL || c->reader == NULL) { @@ -655,112 +662,100 @@ int redisReconnect(redisContext *c) { return REDIS_ERR; } +redisContext *redisConnectWithOptions(const redisOptions *options) { + redisContext *c = redisContextInit(options); + if (c == NULL) { + return NULL; + } + if (!(options->options & REDIS_OPT_NONBLOCK)) { + c->flags |= REDIS_BLOCK; + } + if (options->options & REDIS_OPT_REUSEADDR) { + c->flags |= REDIS_REUSEADDR; + } + + if (options->type == REDIS_CONN_TCP) { + redisContextConnectBindTcp(c, options->endpoint.tcp.ip, + options->endpoint.tcp.port, options->timeout, + options->endpoint.tcp.source_addr); + } else if (options->type == REDIS_CONN_UNIX) { + redisContextConnectUnix(c, options->endpoint.unix_socket, + options->timeout); + } else if (options->type == REDIS_CONN_USERFD) { + c->fd = options->endpoint.fd; + c->flags |= REDIS_CONNECTED; + } else { + // Unknown type - FIXME - FREE + return NULL; + } + return c; +} + /* Connect to a Redis instance. On error the field error in the returned * context will be set to the return value of the error function. * When no set of reply functions is given, the default set will be used. */ redisContext *redisConnect(const char *ip, int port) { - redisContext *c; - - c = redisContextInit(); - if (c == NULL) - return NULL; - - c->flags |= REDIS_BLOCK; - redisContextConnectTcp(c,ip,port,NULL); - return c; + redisOptions options = {0}; + REDIS_OPTIONS_SET_TCP(&options, ip, port); + return redisConnectWithOptions(&options); } redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv) { - redisContext *c; - - c = redisContextInit(); - if (c == NULL) - return NULL; - - c->flags |= REDIS_BLOCK; - redisContextConnectTcp(c,ip,port,&tv); - return c; + redisOptions options = {0}; + REDIS_OPTIONS_SET_TCP(&options, ip, port); + options.timeout = &tv; + return redisConnectWithOptions(&options); } redisContext *redisConnectNonBlock(const char *ip, int port) { - redisContext *c; - - c = redisContextInit(); - if (c == NULL) - return NULL; - - c->flags &= ~REDIS_BLOCK; - redisContextConnectTcp(c,ip,port,NULL); - return c; + redisOptions options = {0}; + REDIS_OPTIONS_SET_TCP(&options, ip, port); + options.options |= REDIS_OPT_NONBLOCK; + return redisConnectWithOptions(&options); } redisContext *redisConnectBindNonBlock(const char *ip, int port, const char *source_addr) { - redisContext *c = redisContextInit(); - if (c == NULL) - return NULL; - c->flags &= ~REDIS_BLOCK; - redisContextConnectBindTcp(c,ip,port,NULL,source_addr); - return c; + redisOptions options = {0}; + REDIS_OPTIONS_SET_TCP(&options, ip, port); + options.endpoint.tcp.source_addr = source_addr; + options.options |= REDIS_OPT_NONBLOCK; + return redisConnectWithOptions(&options); } redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port, const char *source_addr) { - redisContext *c = redisContextInit(); - if (c == NULL) - return NULL; - c->flags &= ~REDIS_BLOCK; - c->flags |= REDIS_REUSEADDR; - redisContextConnectBindTcp(c,ip,port,NULL,source_addr); - return c; + redisOptions options = {0}; + REDIS_OPTIONS_SET_TCP(&options, ip, port); + options.endpoint.tcp.source_addr = source_addr; + options.options |= REDIS_OPT_NONBLOCK|REDIS_OPT_REUSEADDR; + return redisConnectWithOptions(&options); } redisContext *redisConnectUnix(const char *path) { - redisContext *c; - - c = redisContextInit(); - if (c == NULL) - return NULL; - - c->flags |= REDIS_BLOCK; - redisContextConnectUnix(c,path,NULL); - return c; + redisOptions options = {0}; + REDIS_OPTIONS_SET_UNIX(&options, path); + return redisConnectWithOptions(&options); } redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv) { - redisContext *c; - - c = redisContextInit(); - if (c == NULL) - return NULL; - - c->flags |= REDIS_BLOCK; - redisContextConnectUnix(c,path,&tv); - return c; + redisOptions options = {0}; + REDIS_OPTIONS_SET_UNIX(&options, path); + options.timeout = &tv; + return redisConnectWithOptions(&options); } redisContext *redisConnectUnixNonBlock(const char *path) { - redisContext *c; - - c = redisContextInit(); - if (c == NULL) - return NULL; - - c->flags &= ~REDIS_BLOCK; - redisContextConnectUnix(c,path,NULL); - return c; + redisOptions options = {0}; + REDIS_OPTIONS_SET_UNIX(&options, path); + options.options |= REDIS_OPT_NONBLOCK; + return redisConnectWithOptions(&options); } redisContext *redisConnectFd(int fd) { - redisContext *c; - - c = redisContextInit(); - if (c == NULL) - return NULL; - - c->fd = fd; - c->flags |= REDIS_BLOCK | REDIS_CONNECTED; - return c; + redisOptions options = {REDIS_CONN_USERFD}; + options.endpoint.fd = fd; + return redisConnectWithOptions(&options); } int redisSecureConnection(redisContext *c, const char *caPath, diff --git a/hiredis.h b/hiredis.h index 29c0253..621ea5b 100644 --- a/hiredis.h +++ b/hiredis.h @@ -112,11 +112,53 @@ void redisFreeSdsCommand(sds cmd); enum redisConnectionType { REDIS_CONN_TCP, - REDIS_CONN_UNIX + REDIS_CONN_UNIX, + REDIS_CONN_USERFD }; struct redisSsl; +#define REDIS_OPT_NONBLOCK 0x01 +#define REDIS_OPT_REUSEADDR 0x02 + +typedef struct { + /* + * the type of connection to use. This also indicates which + * `endpoint` member field to use + */ + int type; + /* bit field of REDIS_OPT_xxx */ + int options; + /* timeout value. if NULL, no timeout is used */ + const struct timeval *timeout; + union { + /** use this field for tcp/ip connections */ + struct { + const char *source_addr; + const char *ip; + int port; + } tcp; + /** use this field for unix domain sockets */ + const char *unix_socket; + /** + * use this field to have hiredis operate an already-open + * file descriptor */ + int fd; + } endpoint; +} redisOptions; + +/** + * Helper macros to initialize options to their specified fields. + */ +#define REDIS_OPTIONS_SET_TCP(opts, ip_, port_) \ + (opts)->type = REDIS_CONN_TCP; \ + (opts)->endpoint.tcp.ip = ip_; \ + (opts)->endpoint.tcp.port = port; + +#define REDIS_OPTIONS_SET_UNIX(opts, path) \ + (opts)->type = REDIS_CONN_UNIX; \ + (opts)->endpoint.unix_socket = path; + /* Context for a connection to Redis */ typedef struct redisContext { int err; /* Error flags, 0 when there is no error */ @@ -147,6 +189,7 @@ typedef struct redisContext { } redisContext; +redisContext *redisConnectWithOptions(const redisOptions *options); redisContext *redisConnect(const char *ip, int port); redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv); redisContext *redisConnectNonBlock(const char *ip, int port); -- cgit v1.2.3 From 35a0a1f369cdeb8d7d93fda8278099d7aa60aee6 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Thu, 8 Mar 2018 11:21:07 +0200 Subject: read/write timeouts --- adapters/libevent.h | 79 ++++++++++++++++++++++++++++++++++++++--------------- async.c | 62 ++++++++++++++++++++++++++++++++++++++--- async.h | 5 ++++ read.h | 1 + 4 files changed, 121 insertions(+), 26 deletions(-) diff --git a/adapters/libevent.h b/adapters/libevent.h index 7d2bef1..9843696 100644 --- a/adapters/libevent.h +++ b/adapters/libevent.h @@ -36,48 +36,81 @@ typedef struct redisLibeventEvents { redisAsyncContext *context; - struct event *rev, *wev; + struct event *ev, *tmr; + struct event_base *base; + struct timeval tv; + short flags; } redisLibeventEvents; -static void redisLibeventReadEvent(int fd, short event, void *arg) { - ((void)fd); ((void)event); +static void redisLibeventHandler(int fd, short event, void *arg) { + ((void)fd); redisLibeventEvents *e = (redisLibeventEvents*)arg; - redisAsyncHandleRead(e->context); + if (event & EV_TIMEOUT) { + redisAsyncHandleTimeout(e->context); + } + if (e->context && (event & EV_READ)) { + redisAsyncHandleRead(e->context); + } + if (e->context && (event & EV_WRITE)) { + redisAsyncHandleWrite(e->context); + } } -static void redisLibeventWriteEvent(int fd, short event, void *arg) { - ((void)fd); ((void)event); - redisLibeventEvents *e = (redisLibeventEvents*)arg; - redisAsyncHandleWrite(e->context); +static void redisLibeventUpdate(void *privdata, short flag, int isRemove) { + redisLibeventEvents *e = (redisLibeventEvents *)privdata; + const struct timeval *tv = e->tv.tv_sec || e->tv.tv_usec ? &e->tv : NULL; + + if (isRemove) { + if ((e->flags & flag) == 0) { + return; + } else { + e->flags &= ~flag; + } + } else { + if (e->flags & flag) { + return; + } else { + e->flags |= flag; + } + } + + event_del(e->ev); + event_assign(e->ev, e->base, e->context->c.fd, e->flags, + redisLibeventHandler, privdata); + event_add(e->ev, tv); } static void redisLibeventAddRead(void *privdata) { - redisLibeventEvents *e = (redisLibeventEvents*)privdata; - event_add(e->rev,NULL); + redisLibeventUpdate(privdata, EV_READ, 0); } static void redisLibeventDelRead(void *privdata) { - redisLibeventEvents *e = (redisLibeventEvents*)privdata; - event_del(e->rev); + redisLibeventUpdate(privdata, EV_READ, 1); } static void redisLibeventAddWrite(void *privdata) { - redisLibeventEvents *e = (redisLibeventEvents*)privdata; - event_add(e->wev,NULL); + redisLibeventUpdate(privdata, EV_WRITE, 0); } static void redisLibeventDelWrite(void *privdata) { - redisLibeventEvents *e = (redisLibeventEvents*)privdata; - event_del(e->wev); + redisLibeventUpdate(privdata, EV_WRITE, 1); } static void redisLibeventCleanup(void *privdata) { redisLibeventEvents *e = (redisLibeventEvents*)privdata; - event_free(e->rev); - event_free(e->wev); + event_free(e->ev); free(e); } +static void redisLibeventSetTimeout(void *privdata, struct timeval tv) { + redisLibeventEvents *e = (redisLibeventEvents *)privdata; + short flags = e->flags; + e->flags = 0; + e->tv = tv; + event_del(e->ev); + redisLibeventUpdate(e, flags, 0); +} + static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) { redisContext *c = &(ac->c); redisLibeventEvents *e; @@ -96,13 +129,15 @@ static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) { ac->ev.addWrite = redisLibeventAddWrite; ac->ev.delWrite = redisLibeventDelWrite; ac->ev.cleanup = redisLibeventCleanup; + ac->ev.scheduleTimer = redisLibeventSetTimeout; ac->ev.data = e; /* Initialize and install read/write events */ - e->rev = event_new(base, c->fd, EV_READ, redisLibeventReadEvent, e); - e->wev = event_new(base, c->fd, EV_WRITE, redisLibeventWriteEvent, e); - event_add(e->rev, NULL); - event_add(e->wev, NULL); + e->ev = event_new(base, c->fd, EV_READ | EV_WRITE, redisLibeventHandler, e); + e->flags = 0; + e->base = base; + e->tv.tv_sec = 0; + e->tv.tv_usec = 0; return REDIS_OK; } #endif diff --git a/async.c b/async.c index db59036..b789f93 100644 --- a/async.c +++ b/async.c @@ -42,15 +42,19 @@ #include "sds.h" #include "sslio.h" -#define _EL_ADD_READ(ctx) do { \ +#define _EL_ADD_READ(ctx) \ + do { \ + refreshTimeout(ctx); \ if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \ - } while(0) + } while (0) #define _EL_DEL_READ(ctx) do { \ if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \ } while(0) -#define _EL_ADD_WRITE(ctx) do { \ +#define _EL_ADD_WRITE(ctx) \ + do { \ + refreshTimeout(ctx); \ if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \ - } while(0) + } while (0) #define _EL_DEL_WRITE(ctx) do { \ if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \ } while(0) @@ -58,6 +62,19 @@ if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \ } while(0); +static void refreshTimeout(redisAsyncContext *ctx) { + if (ctx->c.timeout && ctx->ev.scheduleTimer && + (ctx->c.timeout->tv_sec || ctx->c.timeout->tv_usec)) { + ctx->ev.scheduleTimer(ctx->ev.data, *ctx->c.timeout); + // } else { + // printf("Not scheduling timer.. (tmo=%p)\n", ctx->c.timeout); + // if (ctx->c.timeout){ + // printf("tv_sec: %u. tv_usec: %u\n", ctx->c.timeout->tv_sec, + // ctx->c.timeout->tv_usec); + // } + } +} + /* Forward declaration of function in hiredis.c */ int __redisAppendCommand(redisContext *c, const char *cmd, size_t len); @@ -656,6 +673,30 @@ void redisAsyncHandleWrite(redisAsyncContext *ac) { } } +void __redisSetError(redisContext *c, int type, const char *str); + +void redisAsyncHandleTimeout(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + redisCallback cb; + + if ((c->flags & REDIS_CONNECTED) && ac->replies.head == NULL) { + /* Nothing to do - just an idle timeout */ + return; + } + + if (!c->err) { + __redisSetError(c, REDIS_ERR_TIMEOUT, "Timeout"); + } + + if (!(c->flags & REDIS_CONNECTED) && ac->onConnect) { + ac->onConnect(ac, REDIS_ERR); + } + + while (__redisShiftCallback(&ac->replies, &cb) == REDIS_OK) { + __redisRunCallback(ac, &cb, NULL); + } +} + /* Sets a pointer to the first argument and its length starting at p. Returns * the number of bytes to skip to get to the following argument. */ static const char *nextArgument(const char *start, const char **str, size_t *len) { @@ -795,3 +836,16 @@ int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void int status = __redisAsyncCommand(ac,fn,privdata,cmd,len); return status; } + +void redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv) { + if (!ac->c.timeout) { + ac->c.timeout = calloc(1, sizeof(tv)); + } + + if (tv.tv_sec == ac->c.timeout->tv_sec && + tv.tv_usec == ac->c.timeout->tv_usec) { + return; + } + + *ac->c.timeout = tv; +} \ No newline at end of file diff --git a/async.h b/async.h index 740555c..3d51a35 100644 --- a/async.h +++ b/async.h @@ -57,6 +57,7 @@ typedef struct redisCallbackList { /* Connection callback prototypes */ typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status); typedef void (redisConnectCallback)(const struct redisAsyncContext*, int status); +typedef void(redisTimerCallback)(void *timer, void *privdata); /* Context for an async connection to Redis */ typedef struct redisAsyncContext { @@ -81,6 +82,7 @@ typedef struct redisAsyncContext { void (*addWrite)(void *privdata); void (*delWrite)(void *privdata); void (*cleanup)(void *privdata); + void (*scheduleTimer)(void *privdata, struct timeval tv); } ev; /* Called when either the connection is terminated due to an error or per @@ -113,12 +115,15 @@ redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port, redisAsyncContext *redisAsyncConnectUnix(const char *path); int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn); int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); + +void redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv); void redisAsyncDisconnect(redisAsyncContext *ac); void redisAsyncFree(redisAsyncContext *ac); /* Handle read/write events */ void redisAsyncHandleRead(redisAsyncContext *ac); void redisAsyncHandleWrite(redisAsyncContext *ac); +void redisAsyncHandleTimeout(redisAsyncContext *ac); /* Command functions for an async context. Write the command to the * output buffer and register the provided callback. */ diff --git a/read.h b/read.h index 2988aa4..0894b78 100644 --- a/read.h +++ b/read.h @@ -45,6 +45,7 @@ #define REDIS_ERR_EOF 3 /* End of file */ #define REDIS_ERR_PROTOCOL 4 /* Protocol error */ #define REDIS_ERR_OOM 5 /* Out of memory */ +#define REDIS_ERR_TIMEOUT 6 /* Timed out */ #define REDIS_ERR_OTHER 2 /* Everything else... */ #define REDIS_REPLY_STRING 1 -- cgit v1.2.3 From deba8d956d578547319919adedda4714f779dbb9 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Thu, 6 Dec 2018 04:54:05 -0500 Subject: Allow connectWithOptions for async as well --- async.c | 52 ++++++++++++++++++++++++---------------------------- async.h | 1 + 2 files changed, 25 insertions(+), 28 deletions(-) diff --git a/async.c b/async.c index b789f93..a32d528 100644 --- a/async.c +++ b/async.c @@ -168,56 +168,52 @@ static void __redisAsyncCopyError(redisAsyncContext *ac) { ac->errstr = c->errstr; } -redisAsyncContext *redisAsyncConnect(const char *ip, int port) { +redisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options) { + redisOptions myOptions = *options; redisContext *c; redisAsyncContext *ac; - c = redisConnectNonBlock(ip,port); - if (c == NULL) + myOptions.options |= REDIS_OPT_NONBLOCK; + c = redisConnectWithOptions(&myOptions); + if (c == NULL) { return NULL; - + } ac = redisAsyncInitialize(c); if (ac == NULL) { redisFree(c); return NULL; } - __redisAsyncCopyError(ac); return ac; } +redisAsyncContext *redisAsyncConnect(const char *ip, int port) { + redisOptions options = {0}; + REDIS_OPTIONS_SET_TCP(&options, ip, port); + return redisAsyncConnectWithOptions(&options); +} + redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, const char *source_addr) { - redisContext *c = redisConnectBindNonBlock(ip,port,source_addr); - redisAsyncContext *ac = redisAsyncInitialize(c); - __redisAsyncCopyError(ac); - return ac; + redisOptions options = {0}; + REDIS_OPTIONS_SET_TCP(&options, ip, port); + options.endpoint.tcp.source_addr = source_addr; + return redisAsyncConnectWithOptions(&options); } redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port, const char *source_addr) { - redisContext *c = redisConnectBindNonBlockWithReuse(ip,port,source_addr); - redisAsyncContext *ac = redisAsyncInitialize(c); - __redisAsyncCopyError(ac); - return ac; + redisOptions options = {0}; + REDIS_OPTIONS_SET_TCP(&options, ip, port); + options.options |= REDIS_OPT_REUSEADDR; + options.endpoint.tcp.source_addr = source_addr; + return redisAsyncConnectWithOptions(&options); } redisAsyncContext *redisAsyncConnectUnix(const char *path) { - redisContext *c; - redisAsyncContext *ac; - - c = redisConnectUnixNonBlock(path); - if (c == NULL) - return NULL; - - ac = redisAsyncInitialize(c); - if (ac == NULL) { - redisFree(c); - return NULL; - } - - __redisAsyncCopyError(ac); - return ac; + redisOptions options = {0}; + REDIS_OPTIONS_SET_UNIX(&options, path); + return redisAsyncConnectWithOptions(&options); } int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) { diff --git a/async.h b/async.h index 3d51a35..40a1819 100644 --- a/async.h +++ b/async.h @@ -108,6 +108,7 @@ typedef struct redisAsyncContext { } redisAsyncContext; /* Functions that proxy to hiredis */ +redisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options); redisAsyncContext *redisAsyncConnect(const char *ip, int port); redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, const char *source_addr); redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port, -- cgit v1.2.3 From 53d9b12b76858bb236113628ad8585abbccd9f87 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Thu, 6 Dec 2018 04:54:29 -0500 Subject: Fix bug in options macro --- hiredis.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hiredis.h b/hiredis.h index 621ea5b..bb9b6a7 100644 --- a/hiredis.h +++ b/hiredis.h @@ -153,7 +153,7 @@ typedef struct { #define REDIS_OPTIONS_SET_TCP(opts, ip_, port_) \ (opts)->type = REDIS_CONN_TCP; \ (opts)->endpoint.tcp.ip = ip_; \ - (opts)->endpoint.tcp.port = port; + (opts)->endpoint.tcp.port = port_; #define REDIS_OPTIONS_SET_UNIX(opts, path) \ (opts)->type = REDIS_CONN_UNIX; \ -- cgit v1.2.3 From 7b705936f66f34f79ec0bc45c6c7cf4ab40296e7 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Thu, 6 Dec 2018 04:55:47 -0500 Subject: libevent-example: Use timeout --- examples/example-libevent.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/examples/example-libevent.c b/examples/example-libevent.c index d333c22..1fe71ae 100644 --- a/examples/example-libevent.c +++ b/examples/example-libevent.c @@ -9,7 +9,12 @@ void getCallback(redisAsyncContext *c, void *r, void *privdata) { redisReply *reply = r; - if (reply == NULL) return; + if (reply == NULL) { + if (c->errstr) { + printf("errstr: %s\n", c->errstr); + } + return; + } printf("argv[%s]: %s\n", (char*)privdata, reply->str); /* Disconnect after receiving the reply to GET */ @@ -35,8 +40,14 @@ void disconnectCallback(const redisAsyncContext *c, int status) { int main (int argc, char **argv) { signal(SIGPIPE, SIG_IGN); struct event_base *base = event_base_new(); + redisOptions options = {0}; + REDIS_OPTIONS_SET_TCP(&options, "127.0.0.1", 6379); + struct timeval tv = {0}; + tv.tv_sec = 1; + options.timeout = &tv; + - redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); + redisAsyncContext *c = redisAsyncConnectWithOptions(&options); if (c->err) { /* Let *c leak for now... */ printf("Error: %s\n", c->errstr); -- cgit v1.2.3 From f4f6b6d65c83df53ac4e7a6730440ea7af368fcd Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Wed, 12 Dec 2018 11:36:52 -0500 Subject: minor fixes: initialize options struct with 0 always also, clean up redisContextInit -- we're just zeoring the struct --- hiredis.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/hiredis.c b/hiredis.c index d33e393..7ba51f6 100644 --- a/hiredis.c +++ b/hiredis.c @@ -591,21 +591,14 @@ static redisContext *redisContextInit(const redisOptions *options) { if (c == NULL) return NULL; - c->err = 0; - c->errstr[0] = '\0'; c->obuf = sdsempty(); - c->flags = 0; - c->tcp.host = NULL; - c->tcp.source_addr = NULL; - c->unix_sock.path = NULL; - c->timeout = NULL; c->reader = redisReaderCreate(); if (c->obuf == NULL || c->reader == NULL) { redisFree(c); return NULL; } - + (void)options; /* options are used in other functions */ return c; } @@ -753,7 +746,8 @@ redisContext *redisConnectUnixNonBlock(const char *path) { } redisContext *redisConnectFd(int fd) { - redisOptions options = {REDIS_CONN_USERFD}; + redisOptions options = {0}; + options.type = REDIS_CONN_USERFD; options.endpoint.fd = fd; return redisConnectWithOptions(&options); } -- cgit v1.2.3 From f51363a70cdee179ed30b580f1e47b715cde06fb Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Wed, 12 Dec 2018 11:39:41 -0500 Subject: Don't warn on missing field initializers --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b2a9fb5..3532812 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ export REDIS_TEST_CONFIG CC:=$(shell sh -c 'type $${CC%% *} >/dev/null 2>/dev/null && echo $(CC) || echo gcc') CXX:=$(shell sh -c 'type $${CXX%% *} >/dev/null 2>/dev/null && echo $(CXX) || echo g++') OPTIMIZATION?=-O3 -WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings +WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings -Wno-missing-field-initializers DEBUG_FLAGS?= -g -ggdb REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CPPFLAGS) $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS) REAL_LDFLAGS=$(LDFLAGS) -- cgit v1.2.3 From 3a547b8ec045704bf4836d0c5c9657fa311698f0 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Thu, 13 Dec 2018 11:24:40 -0500 Subject: Unix: set addrlen so async reconnect uses proper size --- net.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net.c b/net.c index f09183e..45dca7a 100644 --- a/net.c +++ b/net.c @@ -502,7 +502,8 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time sa = (struct sockaddr_un*)(c->saddr = malloc(sizeof(struct sockaddr_un))); c->addrlen = sizeof(struct sockaddr_un); sa->sun_family = AF_UNIX; - strncpy(sa->sun_path,path,sizeof(sa->sun_path)-1); + c->addrlen = sizeof(*sa); + strncpy(sa->sun_path, path, sizeof(sa->sun_path) - 1); if (connect(c->fd, (struct sockaddr*)sa, sizeof(*sa)) == -1) { if (errno == EINPROGRESS && !blocking) { /* This is ok. */ -- cgit v1.2.3 From 389e694abe6bb9ed6e5a44674d89a866c5231422 Mon Sep 17 00:00:00 2001 From: valentino Date: Sun, 9 Dec 2018 12:33:56 +0200 Subject: Fix compilation on Ubuntu --- Makefile | 47 ++++++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/Makefile b/Makefile index 3532812..111dee8 100644 --- a/Makefile +++ b/Makefile @@ -50,22 +50,28 @@ STLIBSUFFIX=a DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME) DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR) DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX) -DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS) +DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) STLIBNAME=$(LIBNAME).$(STLIBSUFFIX) STLIB_MAKE_CMD=$(AR) rcs $(STLIBNAME) -USE_SSL:=1 +# Platform-specific overrides +uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') + +USE_SSL?=1 ifdef USE_SSL # This is the prefix of openssl on my system. This should be the sane default # based on the platform - OPENSSL_PREFIX?=/usr/local/opt/openssl - CFLAGS+=-I$(OPENSSL_PREFIX)/include -DHIREDIS_SSL - LDFLAGS+=-L$(OPENSSL_PREFIX)/lib -lssl -lcrypto + ifeq ($(uname_S),Linux) + CFLAGS+=-DHIREDIS_SSL + LDFLAGS+=-lssl -lcrypto + else + OPENSSL_PREFIX?=/usr/local/opt/openssl + CFLAGS+=-I$(OPENSSL_PREFIX)/include -DHIREDIS_SSL + LDFLAGS+=-L$(OPENSSL_PREFIX)/lib -lssl -lcrypto + endif endif -# Platform-specific overrides -uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') ifeq ($(uname_S),SunOS) REAL_LDFLAGS+= -ldl -lnsl -lsocket DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS) @@ -89,7 +95,7 @@ sslio.o: sslio.c sslio.h hiredis.h test.o: test.c fmacros.h hiredis.h read.h sds.h $(DYLIBNAME): $(OBJ) - $(DYLIB_MAKE_CMD) $(OBJ) + $(DYLIB_MAKE_CMD) $(OBJ) $(REAL_LDFLAGS) $(STLIBNAME): $(OBJ) $(STLIB_MAKE_CMD) $(OBJ) @@ -99,25 +105,25 @@ static: $(STLIBNAME) # Binaries: hiredis-example-libevent: examples/example-libevent.c adapters/libevent.h $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -levent $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -levent $(STLIBNAME) $(REAL_LDFLAGS) hiredis-example-libevent-ssl: examples/example-libevent-ssl.c adapters/libevent.h $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -levent $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -levent $(STLIBNAME) $(REAL_LDFLAGS) hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -lev $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -lev $(STLIBNAME) $(REAL_LDFLAGS) hiredis-example-glib: examples/example-glib.c adapters/glib.h $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< $(shell pkg-config --cflags --libs glib-2.0) $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(shell pkg-config --cflags --libs glib-2.0) $(STLIBNAME) $(REAL_LDFLAGS) hiredis-example-ivykis: examples/example-ivykis.c adapters/ivykis.h $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -livykis $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -livykis $(STLIBNAME) $(REAL_LDFLAGS) hiredis-example-macosx: examples/example-macosx.c adapters/macosx.h $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -framework CoreFoundation $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -framework CoreFoundation $(STLIBNAME) $(REAL_LDFLAGS) hiredis-example-ssl: examples/example-ssl.c $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(REAL_LDFLAGS) ifndef AE_DIR hiredis-example-ae: @@ -134,7 +140,7 @@ hiredis-example-libuv: @false else hiredis-example-libuv: examples/example-libuv.c adapters/libuv.h $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(LIBUV_DIR)/include $< $(LIBUV_DIR)/.libs/libuv.a -lpthread -lrt $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) -I. -I$(LIBUV_DIR)/include $< $(LIBUV_DIR)/.libs/libuv.a -lpthread -lrt $(STLIBNAME) $(REAL_LDFLAGS) endif ifeq ($(and $(QT_MOC),$(QT_INCLUDE_DIR),$(QT_LIBRARY_DIR)),) @@ -151,14 +157,14 @@ hiredis-example-qt: examples/example-qt.cpp adapters/qt.h $(STLIBNAME) endif hiredis-example: examples/example.c $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(REAL_LDFLAGS) examples: $(EXAMPLES) hiredis-test: test.o $(STLIBNAME) hiredis-%: %.o $(STLIBNAME) - $(CC) $(REAL_CFLAGS) -o $@ $(REAL_LDFLAGS) $< $(STLIBNAME) + $(CC) $(REAL_CFLAGS) -o $@ $< $(STLIBNAME) $(REAL_LDFLAGS) test: hiredis-test ./hiredis-test @@ -191,11 +197,14 @@ $(PKGCONFNAME): hiredis.h @echo Description: Minimalistic C client library for Redis. >> $@ @echo Version: $(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(HIREDIS_PATCH) >> $@ @echo Libs: -L\$${libdir} -lhiredis >> $@ +ifdef USE_SSL + @echo Libs.private: lssl -lcrypto >> $@ +endif @echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@ install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME) mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL_LIBRARY_PATH) - $(INSTALL) hiredis.h async.h read.h sds.h $(INSTALL_INCLUDE_PATH) + $(INSTALL) hiredis.h async.h read.h sds.h sslio.h $(INSTALL_INCLUDE_PATH) $(INSTALL) adapters/*.h $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME) cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIBNAME) -- cgit v1.2.3 From 58222c26f4889c3f83c453bd7ec87e387459fd0c Mon Sep 17 00:00:00 2001 From: valentino Date: Thu, 20 Dec 2018 16:26:24 +0200 Subject: Support SNI --- examples/example-libevent-ssl.c | 2 +- examples/example-ssl.c | 2 +- hiredis.c | 4 ++-- hiredis.h | 2 +- sslio.c | 8 +++++++- sslio.h | 6 +++--- 6 files changed, 15 insertions(+), 9 deletions(-) diff --git a/examples/example-libevent-ssl.c b/examples/example-libevent-ssl.c index f780e3e..562e1a1 100644 --- a/examples/example-libevent-ssl.c +++ b/examples/example-libevent-ssl.c @@ -57,7 +57,7 @@ int main (int argc, char **argv) { printf("Error: %s\n", c->errstr); return 1; } - if (redisSecureConnection(&c->c, caCert, cert, certKey) != REDIS_OK) { + if (redisSecureConnection(&c->c, caCert, cert, certKey, "sni") != REDIS_OK) { printf("SSL Error!\n"); exit(1); } diff --git a/examples/example-ssl.c b/examples/example-ssl.c index 28489e4..a90b78a 100644 --- a/examples/example-ssl.c +++ b/examples/example-ssl.c @@ -30,7 +30,7 @@ int main(int argc, char **argv) { exit(1); } - if (redisSecureConnection(c, ca, cert, key) != REDIS_OK) { + if (redisSecureConnection(c, ca, cert, key, "sni") != REDIS_OK) { printf("Couldn't initialize SSL!\n"); printf("Error: %s\n", c->errstr); redisFree(c); diff --git a/hiredis.c b/hiredis.c index 7ba51f6..dd499b8 100644 --- a/hiredis.c +++ b/hiredis.c @@ -753,8 +753,8 @@ redisContext *redisConnectFd(int fd) { } int redisSecureConnection(redisContext *c, const char *caPath, - const char *certPath, const char *keyPath) { - return redisSslCreate(c, caPath, certPath, keyPath); + const char *certPath, const char *keyPath, const char *servername) { + return redisSslCreate(c, caPath, certPath, keyPath, servername); } /* Set read/write timeout on a blocking socket. */ diff --git a/hiredis.h b/hiredis.h index bb9b6a7..0d646f8 100644 --- a/hiredis.h +++ b/hiredis.h @@ -207,7 +207,7 @@ redisContext *redisConnectFd(int fd); * executed on the connection. */ int redisSecureConnection(redisContext *c, const char *capath, const char *certpath, - const char *keypath); + const char *keypath, const char *servername); /** * Reconnect the given context using the saved information. diff --git a/sslio.c b/sslio.c index 8933f95..efbf06e 100644 --- a/sslio.c +++ b/sslio.c @@ -87,7 +87,7 @@ void redisFreeSsl(redisSsl *ssl){ } int redisSslCreate(redisContext *c, const char *capath, const char *certpath, - const char *keypath) { + const char *keypath, const char *servername) { assert(!c->ssl); c->ssl = calloc(1, sizeof(*c->ssl)); static int isInit = 0; @@ -131,6 +131,12 @@ int redisSslCreate(redisContext *c, const char *capath, const char *certpath, __redisSetError(c, REDIS_ERR, "Couldn't create new SSL instance"); return REDIS_ERR; } + if (servername) { + if (!SSL_set_tlsext_host_name(s->ssl, servername)) { + __redisSetError(c, REDIS_ERR, "Couldn't set server name indication"); + return REDIS_ERR; + } + } SSL_set_fd(s->ssl, c->fd); SSL_set_connect_state(s->ssl); diff --git a/sslio.h b/sslio.h index 9edac87..e5493b7 100644 --- a/sslio.h +++ b/sslio.h @@ -12,8 +12,8 @@ static inline void redisFreeSsl(redisSsl *ssl) { (void)ssl; } static inline int redisSslCreate(struct redisContext *c, const char *ca, - const char *cert, const char *key) { - (void)c;(void)ca;(void)cert;(void)key; + const char *cert, const char *key, const char *servername) { + (void)c;(void)ca;(void)cert;(void)key;(void)servername; return REDIS_ERR; } static inline int redisSslRead(struct redisContext *c, char *s, size_t n) { @@ -55,7 +55,7 @@ struct redisContext; void redisFreeSsl(redisSsl *); int redisSslCreate(struct redisContext *c, const char *caPath, - const char *certPath, const char *keyPath); + const char *certPath, const char *keyPath, const char *servername); int redisSslRead(struct redisContext *c, char *buf, size_t bufcap); int redisSslWrite(struct redisContext *c); -- cgit v1.2.3 From 847a20122f3e3c6e69179943f2d397da6712de80 Mon Sep 17 00:00:00 2001 From: valentino Date: Sun, 20 Jan 2019 16:07:55 +0200 Subject: Fix memory leaks --- adapters/libevent.h | 5 ++--- hiredis.c | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/adapters/libevent.h b/adapters/libevent.h index 9843696..5959e89 100644 --- a/adapters/libevent.h +++ b/adapters/libevent.h @@ -48,10 +48,10 @@ static void redisLibeventHandler(int fd, short event, void *arg) { if (event & EV_TIMEOUT) { redisAsyncHandleTimeout(e->context); } - if (e->context && (event & EV_READ)) { + if ((event & EV_READ) && e->context) { redisAsyncHandleRead(e->context); } - if (e->context && (event & EV_WRITE)) { + if ((event & EV_WRITE) && e->context) { redisAsyncHandleWrite(e->context); } } @@ -107,7 +107,6 @@ static void redisLibeventSetTimeout(void *privdata, struct timeval tv) { short flags = e->flags; e->flags = 0; e->tv = tv; - event_del(e->ev); redisLibeventUpdate(e, flags, 0); } diff --git a/hiredis.c b/hiredis.c index dd499b8..9c2788a 100644 --- a/hiredis.c +++ b/hiredis.c @@ -618,6 +618,7 @@ void redisFree(redisContext *c) { if (c->ssl) { redisFreeSsl(c->ssl); } + free(c); } int redisFreeKeepFd(redisContext *c) { -- cgit v1.2.3 From 0bc2356ed5b587f4b4e41878a51b741e12901016 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Wed, 6 Feb 2019 14:14:46 -0500 Subject: CMake: update for SSL --- CMakeLists.txt | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 89ae962..4952083 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,8 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.0.0) INCLUDE(GNUInstallDirs) +PROJECT(hiredis) + +OPTION(HIREDIS_SSL "Link against OpenSSL" ON) MACRO(getVersionBit name) SET(VERSION_REGEX "^#define ${name} (.+)$") @@ -27,7 +30,8 @@ ADD_LIBRARY(hiredis SHARED hiredis.c net.c read.c - sds.c) + sds.c + sslio.c) SET_TARGET_PROPERTIES(hiredis PROPERTIES @@ -44,9 +48,22 @@ INSTALL(FILES hiredis.h read.h sds.h async.h INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) +IF(HIREDIS_SSL) + IF (NOT OPENSSL_ROOT_DIR) + IF (APPLE) + SET(OPENSSL_ROOT_DIR "/usr/local/opt/openssl") + ENDIF() + ENDIF() + FIND_PACKAGE(OpenSSL REQUIRED) + ADD_DEFINITIONS(-DHIREDIS_SSL) + INCLUDE_DIRECTORIES("${OPENSSL_INCLUDE_DIR}") + TARGET_LINK_LIBRARIES(hiredis ${OPENSSL_LIBRARIES}) +ENDIF() + ENABLE_TESTING() -ADD_EXECUTABLE(hiredis-test - test.c) +ADD_EXECUTABLE(hiredis-test test.c) + + TARGET_LINK_LIBRARIES(hiredis-test hiredis) ADD_TEST(NAME hiredis-test COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test.sh) -- cgit v1.2.3 From 5f633ac4ec0ee818fb9785e751fbbdfab48f9542 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Mon, 11 Feb 2019 11:26:15 -0500 Subject: fix potential uninitialized read If callback was set before scheduleTimer was set (i..e before one of the attach()) calls. --- async.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/async.c b/async.c index a32d528..0ed973e 100644 --- a/async.c +++ b/async.c @@ -144,6 +144,7 @@ static redisAsyncContext *redisAsyncInitialize(redisContext *c) { ac->ev.addWrite = NULL; ac->ev.delWrite = NULL; ac->ev.cleanup = NULL; + ac->ev.scheduleTimer = NULL; ac->onConnect = NULL; ac->onDisconnect = NULL; @@ -844,4 +845,4 @@ void redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv) { } *ac->c.timeout = tv; -} \ No newline at end of file +} -- cgit v1.2.3 From 24e6166fedbd76a47db2ca0b4f7ab52edc0044e8 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Mon, 11 Feb 2019 14:52:37 -0500 Subject: libevent: fix invalid mem access on delete within callback enter --- adapters/libevent.h | 41 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/adapters/libevent.h b/adapters/libevent.h index 5959e89..58bab4b 100644 --- a/adapters/libevent.h +++ b/adapters/libevent.h @@ -34,26 +34,49 @@ #include "../hiredis.h" #include "../async.h" +#define REDIS_LIBEVENT_DELETED 0x01 +#define REDIS_LIBEVENT_ENTERED 0x02 + typedef struct redisLibeventEvents { redisAsyncContext *context; struct event *ev, *tmr; struct event_base *base; struct timeval tv; short flags; + short state; } redisLibeventEvents; +static void redisLibeventDestroy(redisLibeventEvents *e) { + free(e); +} + static void redisLibeventHandler(int fd, short event, void *arg) { ((void)fd); redisLibeventEvents *e = (redisLibeventEvents*)arg; - if (event & EV_TIMEOUT) { + e->state |= REDIS_LIBEVENT_ENTERED; + + #define CHECK_DELETED() if (e->state & REDIS_LIBEVENT_DELETED) {\ + redisLibeventDestroy(e);\ + return; \ + } + + if ((event & EV_TIMEOUT) && (e->state & REDIS_LIBEVENT_DELETED) == 0) { redisAsyncHandleTimeout(e->context); + CHECK_DELETED(); } - if ((event & EV_READ) && e->context) { + + if ((event & EV_READ) && e->context && (e->state & REDIS_LIBEVENT_DELETED) == 0) { redisAsyncHandleRead(e->context); + CHECK_DELETED(); } - if ((event & EV_WRITE) && e->context) { + + if ((event & EV_WRITE) && e->context && (e->state & REDIS_LIBEVENT_DELETED) == 0) { redisAsyncHandleWrite(e->context); + CHECK_DELETED(); } + + e->state &= ~REDIS_LIBEVENT_ENTERED; + #undef CHECK_DELETED } static void redisLibeventUpdate(void *privdata, short flag, int isRemove) { @@ -98,8 +121,18 @@ static void redisLibeventDelWrite(void *privdata) { static void redisLibeventCleanup(void *privdata) { redisLibeventEvents *e = (redisLibeventEvents*)privdata; + if (!e) { + return; + } + event_del(e->ev); event_free(e->ev); - free(e); + e->ev = NULL; + + if (e->state & REDIS_LIBEVENT_ENTERED) { + e->state |= REDIS_LIBEVENT_DELETED; + } else { + free(e); + } } static void redisLibeventSetTimeout(void *privdata, struct timeval tv) { -- cgit v1.2.3 From f60c5506feab92f2b6185661cce16a303d28a6a3 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Mon, 11 Feb 2019 14:53:42 -0500 Subject: Add EV_PERSIST flag to read events This will avoid the need to constantly reschedule the event --- adapters/libevent.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adapters/libevent.h b/adapters/libevent.h index 58bab4b..fb5558e 100644 --- a/adapters/libevent.h +++ b/adapters/libevent.h @@ -98,7 +98,7 @@ static void redisLibeventUpdate(void *privdata, short flag, int isRemove) { } event_del(e->ev); - event_assign(e->ev, e->base, e->context->c.fd, e->flags, + event_assign(e->ev, e->base, e->context->c.fd, e->flags | EV_PERSIST, redisLibeventHandler, privdata); event_add(e->ev, tv); } -- cgit v1.2.3 From 1eb44cf0c4e004dda6d8b9c7500cd55e367cc87b Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Mon, 11 Feb 2019 14:55:07 -0500 Subject: scrub redisContext before freeing This helps us detect use-after-free --- hiredis.c | 1 + 1 file changed, 1 insertion(+) diff --git a/hiredis.c b/hiredis.c index 9c2788a..d28fcb9 100644 --- a/hiredis.c +++ b/hiredis.c @@ -618,6 +618,7 @@ void redisFree(redisContext *c) { if (c->ssl) { redisFreeSsl(c->ssl); } + memset(0xff, c, sizeof(*c)); free(c); } -- cgit v1.2.3 From f0a7595056d00383701fcb2a6247ea1ec91c2ae9 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Mon, 11 Feb 2019 14:56:56 -0500 Subject: libevent: call destroy from cleanup also, indentation fix --- adapters/libevent.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/adapters/libevent.h b/adapters/libevent.h index fb5558e..1c86c15 100644 --- a/adapters/libevent.h +++ b/adapters/libevent.h @@ -47,7 +47,7 @@ typedef struct redisLibeventEvents { } redisLibeventEvents; static void redisLibeventDestroy(redisLibeventEvents *e) { - free(e); + free(e); } static void redisLibeventHandler(int fd, short event, void *arg) { @@ -56,8 +56,8 @@ static void redisLibeventHandler(int fd, short event, void *arg) { e->state |= REDIS_LIBEVENT_ENTERED; #define CHECK_DELETED() if (e->state & REDIS_LIBEVENT_DELETED) {\ - redisLibeventDestroy(e);\ - return; \ + redisLibeventDestroy(e);\ + return; \ } if ((event & EV_TIMEOUT) && (e->state & REDIS_LIBEVENT_DELETED) == 0) { @@ -122,16 +122,16 @@ static void redisLibeventDelWrite(void *privdata) { static void redisLibeventCleanup(void *privdata) { redisLibeventEvents *e = (redisLibeventEvents*)privdata; if (!e) { - return; + return; } event_del(e->ev); event_free(e->ev); e->ev = NULL; if (e->state & REDIS_LIBEVENT_ENTERED) { - e->state |= REDIS_LIBEVENT_DELETED; + e->state |= REDIS_LIBEVENT_DELETED; } else { - free(e); + redisLibeventDestroy(e); } } -- cgit v1.2.3 From ea9f9d2515081d191507ea055cd0e614a078d914 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Tue, 12 Feb 2019 09:30:43 -0500 Subject: fixed wrong memset args --- hiredis.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hiredis.c b/hiredis.c index d28fcb9..ba8d316 100644 --- a/hiredis.c +++ b/hiredis.c @@ -618,7 +618,7 @@ void redisFree(redisContext *c) { if (c->ssl) { redisFreeSsl(c->ssl); } - memset(0xff, c, sizeof(*c)); + memset(c, 0xff, sizeof(*c)); free(c); } -- cgit v1.2.3 From 271f33953ff75f32d100714ae9ea2a666bca6469 Mon Sep 17 00:00:00 2001 From: valentino Date: Sun, 17 Feb 2019 18:19:21 +0200 Subject: fix pkg config --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 111dee8..815c9d8 100644 --- a/Makefile +++ b/Makefile @@ -198,7 +198,7 @@ $(PKGCONFNAME): hiredis.h @echo Version: $(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(HIREDIS_PATCH) >> $@ @echo Libs: -L\$${libdir} -lhiredis >> $@ ifdef USE_SSL - @echo Libs.private: lssl -lcrypto >> $@ + @echo Libs.private: -lssl -lcrypto >> $@ endif @echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@ -- cgit v1.2.3 From d9e0299f1cf923ebba0e2027a2044954c5a22a38 Mon Sep 17 00:00:00 2001 From: valentino Date: Tue, 19 Feb 2019 19:17:41 +0200 Subject: fix redisLibeventEvents init --- adapters/libevent.h | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/adapters/libevent.h b/adapters/libevent.h index 1c86c15..a495277 100644 --- a/adapters/libevent.h +++ b/adapters/libevent.h @@ -39,7 +39,7 @@ typedef struct redisLibeventEvents { redisAsyncContext *context; - struct event *ev, *tmr; + struct event *ev; struct event_base *base; struct timeval tv; short flags; @@ -152,7 +152,7 @@ static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) { return REDIS_ERR; /* Create container for context and r/w events */ - e = (redisLibeventEvents*)malloc(sizeof(*e)); + e = (redisLibeventEvents*)calloc(1, sizeof(*e)); e->context = ac; /* Register functions to start/stop listening for events */ @@ -166,10 +166,7 @@ static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) { /* Initialize and install read/write events */ e->ev = event_new(base, c->fd, EV_READ | EV_WRITE, redisLibeventHandler, e); - e->flags = 0; e->base = base; - e->tv.tv_sec = 0; - e->tv.tv_usec = 0; return REDIS_OK; } #endif -- cgit v1.2.3 From 4b904296405c888dde38c4e10818714f8165bced Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Wed, 20 Feb 2019 04:50:02 -0500 Subject: Remove redundant line after rebase --- net.c | 1 - 1 file changed, 1 deletion(-) diff --git a/net.c b/net.c index 45dca7a..3cd0402 100644 --- a/net.c +++ b/net.c @@ -502,7 +502,6 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time sa = (struct sockaddr_un*)(c->saddr = malloc(sizeof(struct sockaddr_un))); c->addrlen = sizeof(struct sockaddr_un); sa->sun_family = AF_UNIX; - c->addrlen = sizeof(*sa); strncpy(sa->sun_path, path, sizeof(sa->sun_path) - 1); if (connect(c->fd, (struct sockaddr*)sa, sizeof(*sa)) == -1) { if (errno == EINPROGRESS && !blocking) { -- cgit v1.2.3 From 3511c8df68b8ff640a8e1748f96197cab71311bb Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Wed, 20 Feb 2019 04:53:21 -0500 Subject: gitignore: dSYM --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index c44b5c5..8e50b54 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ /*.dylib /*.a /*.pc +*.dSYM -- cgit v1.2.3 From 3949c8a1536bf67293817315f616ec6d1462290e Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Wed, 20 Feb 2019 07:23:37 -0500 Subject: Disable SSL by default --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 815c9d8..9ca40ee 100644 --- a/Makefile +++ b/Makefile @@ -57,7 +57,7 @@ STLIB_MAKE_CMD=$(AR) rcs $(STLIBNAME) # Platform-specific overrides uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') -USE_SSL?=1 +USE_SSL?=0 ifdef USE_SSL # This is the prefix of openssl on my system. This should be the sane default -- cgit v1.2.3 From 1ec4aefba6d3690c461ea870eab0984a2cb597f0 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Thu, 21 Feb 2019 06:21:53 -0500 Subject: Fix ifeq condition (thanks @regae) --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9ca40ee..b253904 100644 --- a/Makefile +++ b/Makefile @@ -59,7 +59,7 @@ uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') USE_SSL?=0 -ifdef USE_SSL +ifeq ($(USE_SSL),1) # This is the prefix of openssl on my system. This should be the sane default # based on the platform ifeq ($(uname_S),Linux) -- cgit v1.2.3 From 5eb6958870b71d30e3ed63e65e9f1d546dc419ec Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Thu, 21 Feb 2019 11:49:25 -0500 Subject: Allow option for async connections to not automatically free --- async.c | 7 ++++++- hiredis.c | 3 +++ hiredis.h | 12 ++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/async.c b/async.c index 0ed973e..0a2659c 100644 --- a/async.c +++ b/async.c @@ -361,7 +361,9 @@ static void __redisAsyncDisconnect(redisAsyncContext *ac) { /* For non-clean disconnects, __redisAsyncFree() will execute pending * callbacks with a NULL-reply. */ - __redisAsyncFree(ac); + if (!(c->flags & REDIS_NO_AUTO_FREE)) { + __redisAsyncFree(ac); + } } /* Tries to do a clean disconnect from Redis, meaning it stops new commands @@ -373,6 +375,9 @@ static void __redisAsyncDisconnect(redisAsyncContext *ac) { void redisAsyncDisconnect(redisAsyncContext *ac) { redisContext *c = &(ac->c); c->flags |= REDIS_DISCONNECTING; + + /** unset the auto-free flag here, because disconnect undoes this */ + c->flags &= ~REDIS_NO_AUTO_FREE; if (!(c->flags & REDIS_IN_CALLBACK) && ac->replies.head == NULL) __redisAsyncDisconnect(ac); } diff --git a/hiredis.c b/hiredis.c index ba8d316..2506d51 100644 --- a/hiredis.c +++ b/hiredis.c @@ -668,6 +668,9 @@ redisContext *redisConnectWithOptions(const redisOptions *options) { if (options->options & REDIS_OPT_REUSEADDR) { c->flags |= REDIS_REUSEADDR; } + if (options->options & REDIS_OPT_NOAUTOFREE) { + c->flags |= REDIS_NO_AUTO_FREE; + } if (options->type == REDIS_CONN_TCP) { redisContextConnectBindTcp(c, options->endpoint.tcp.ip, diff --git a/hiredis.h b/hiredis.h index 0d646f8..4025869 100644 --- a/hiredis.h +++ b/hiredis.h @@ -77,6 +77,12 @@ /* Flag that is set when this connection is done through SSL */ #define REDIS_SSL 0x100 +/** + * Flag that indicates the user does not want the context to + * be automatically freed upon error + */ +#define REDIS_NO_AUTO_FREE 0x200 + #define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */ /* number of times we retry to connect in the case of EADDRNOTAVAIL and @@ -121,6 +127,12 @@ struct redisSsl; #define REDIS_OPT_NONBLOCK 0x01 #define REDIS_OPT_REUSEADDR 0x02 +/** + * Don't automatically free the async object on a connection failure, + * or other implicit conditions. Only free on an explicit call to disconnect() or free() + */ +#define REDIS_OPT_NOAUTOFREE 0x04 + typedef struct { /* * the type of connection to use. This also indicates which -- cgit v1.2.3 From 792bdbab7e781d1391adf242cb5fda33364ffeae Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Thu, 21 Feb 2019 12:04:53 -0500 Subject: cmake: ssl disabled by default --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4952083..cce2c61 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.0.0) INCLUDE(GNUInstallDirs) PROJECT(hiredis) -OPTION(HIREDIS_SSL "Link against OpenSSL" ON) +OPTION(HIREDIS_SSL "Link against OpenSSL" OFF) MACRO(getVersionBit name) SET(VERSION_REGEX "^#define ${name} (.+)$") -- cgit v1.2.3 From ffceb87ec3aa8504aea933ee4584ff3bc540622f Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Thu, 21 Feb 2019 12:09:17 -0500 Subject: SSL: build in travis --- .travis.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4da8186..51171c0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,13 +28,14 @@ env: - BITS="64" script: - - if [ "$TRAVIS_OS_NAME" == "osx" ]; then + - EXTRA_CMAKE_OPTS="-DENABLE_EXAMPLES:BOOL=ON -DHIREDIS_SSL:BOOL=ON"; + if [ "$TRAVIS_OS_NAME" == "osx" ]; then if [ "$BITS" == "32" ]; then CFLAGS="-m32 -Werror"; CXXFLAGS="-m32 -Werror"; LDFLAGS="-m32"; + EXTRA_CMAKE_OPTS=; else - EXTRA_CMAKE_OPTS="-DENABLE_EXAMPLES:BOOL=ON"; CFLAGS="-Werror"; CXXFLAGS="-Werror"; fi; @@ -44,8 +45,8 @@ script: CFLAGS="-m32 -Werror"; CXXFLAGS="-m32 -Werror"; LDFLAGS="-m32"; + EXTRA_CMAKE_OPTS=; else - EXTRA_CMAKE_OPTS="-DENABLE_EXAMPLES:BOOL=ON"; CFLAGS="-Werror"; CXXFLAGS="-Werror"; fi; -- cgit v1.2.3 From 17b41740899ffcf22c150b3ab2d981938bb1a3e2 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Mon, 11 Mar 2019 09:01:32 -0400 Subject: Retain the same semantics for connection error on connection timeout This ensures that a disconnect occurs. This commit also ensures that disconnects will clean the socket even if the user is in no-auto-free mode --- async.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/async.c b/async.c index 0a2659c..b6163f2 100644 --- a/async.c +++ b/async.c @@ -60,6 +60,7 @@ } while(0) #define _EL_CLEANUP(ctx) do { \ if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \ + ctx->ev.cleanup = NULL; \ } while(0); static void refreshTimeout(redisAsyncContext *ctx) { @@ -359,6 +360,10 @@ static void __redisAsyncDisconnect(redisAsyncContext *ac) { c->flags |= REDIS_DISCONNECTING; } + /* cleanup event library on disconnect. + * this is safe to call multiple times */ + _EL_CLEANUP(ac); + /* For non-clean disconnects, __redisAsyncFree() will execute pending * callbacks with a NULL-reply. */ if (!(c->flags & REDIS_NO_AUTO_FREE)) { @@ -697,6 +702,12 @@ void redisAsyncHandleTimeout(redisAsyncContext *ac) { while (__redisShiftCallback(&ac->replies, &cb) == REDIS_OK) { __redisRunCallback(ac, &cb, NULL); } + + /** + * TODO: Don't automatically sever the connection, + * rather, allow to ignore responses before the queue is clear + */ + __redisAsyncDisconnect(ac); } /* Sets a pointer to the first argument and its length starting at p. Returns -- cgit v1.2.3 From afcc11cc5a694052adefd5c88d661c540d1fe6e9 Mon Sep 17 00:00:00 2001 From: michael-grunder Date: Sun, 31 Mar 2019 13:46:05 -0700 Subject: Fix NXDOMAIN test case As it turns out 'idontexist.com' actually does exist. --- test.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test.c b/test.c index 79cff43..4af99b2 100644 --- a/test.c +++ b/test.c @@ -434,22 +434,22 @@ static void test_free_null(void) { test_cond(reply == NULL); } +#define HIREDIS_BAD_DOMAIN "idontexist-noreally.com" static void test_blocking_connection_errors(void) { redisContext *c; struct addrinfo hints = {.ai_family = AF_INET}; struct addrinfo *ai_tmp = NULL; - const char *bad_domain = "idontexist.com"; - int rv = getaddrinfo(bad_domain, "6379", &hints, &ai_tmp); + int rv = getaddrinfo(HIREDIS_BAD_DOMAIN, "6379", &hints, &ai_tmp); if (rv != 0) { // Address does *not* exist test("Returns error when host cannot be resolved: "); // First see if this domain name *actually* resolves to NXDOMAIN - c = redisConnect("dontexist.com", 6379); + c = redisConnect(HIREDIS_BAD_DOMAIN, 6379); test_cond( c->err == REDIS_ERR_OTHER && (strcmp(c->errstr, "Name or service not known") == 0 || - strcmp(c->errstr, "Can't resolve: sadkfjaskfjsa.com") == 0 || + strcmp(c->errstr, "Can't resolve: " HIREDIS_BAD_DOMAIN) == 0 || strcmp(c->errstr, "nodename nor servname provided, or not known") == 0 || strcmp(c->errstr, "No address associated with hostname") == 0 || -- cgit v1.2.3 From 1788f41f168e7fe19a79908a58fc1841b60ab170 Mon Sep 17 00:00:00 2001 From: m Date: Sun, 31 Mar 2019 18:03:11 +0200 Subject: Move network I/O calls to net.c This makes hiredis.c free from system calls related to socket I/O. This is also makes the treatment of raw socket connections more similar to the SSL backend. --- hiredis.c | 43 ++++--------------------------------------- net.c | 51 +++++++++++++++++++++++++++++++++++++++++---------- net.h | 4 ++++ 3 files changed, 49 insertions(+), 49 deletions(-) diff --git a/hiredis.c b/hiredis.c index 2506d51..629f43e 100644 --- a/hiredis.c +++ b/hiredis.c @@ -34,7 +34,6 @@ #include "fmacros.h" #include #include -#include #include #include #include @@ -605,8 +604,7 @@ static redisContext *redisContextInit(const redisOptions *options) { void redisFree(redisContext *c) { if (c == NULL) return; - if (c->fd > 0) - close(c->fd); + redisNetClose(c); sdsfree(c->obuf); redisReaderFree(c->reader); @@ -633,9 +631,7 @@ int redisReconnect(redisContext *c) { c->err = 0; memset(c->errstr, '\0', strlen(c->errstr)); - if (c->fd > 0) { - close(c->fd); - } + redisNetClose(c); sdsfree(c->obuf); redisReaderFree(c->reader); @@ -776,24 +772,6 @@ int redisEnableKeepAlive(redisContext *c) { return REDIS_OK; } -static int rawRead(redisContext *c, char *buf, size_t bufcap) { - int nread = read(c->fd, buf, bufcap); - if (nread == -1) { - if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { - /* Try again later */ - return 0; - } else { - __redisSetError(c, REDIS_ERR_IO, NULL); - return -1; - } - } else if (nread == 0) { - __redisSetError(c, REDIS_ERR_EOF, "Server closed the connection"); - return -1; - } else { - return nread; - } -} - /* Use this function to handle a read event on the descriptor. It will try * and read some bytes from the socket and feed them to the reply parser. * @@ -808,7 +786,7 @@ int redisBufferRead(redisContext *c) { return REDIS_ERR; nread = c->flags & REDIS_SSL ? - redisSslRead(c, buf, sizeof(buf)) : rawRead(c, buf, sizeof(buf)); + redisSslRead(c, buf, sizeof(buf)) : redisNetRead(c, buf, sizeof(buf)); if (nread > 0) { if (redisReaderFeed(c->reader, buf, nread) != REDIS_OK) { __redisSetError(c, c->reader->err, c->reader->errstr); @@ -821,19 +799,6 @@ int redisBufferRead(redisContext *c) { return REDIS_OK; } -static int rawWrite(redisContext *c) { - int nwritten = write(c->fd, c->obuf, sdslen(c->obuf)); - if (nwritten < 0) { - if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { - /* Try again later */ - } else { - __redisSetError(c, REDIS_ERR_IO, NULL); - return -1; - } - } - return nwritten; -} - /* Write the output buffer to the socket. * * Returns REDIS_OK when the buffer is empty, or (a part of) the buffer was @@ -850,7 +815,7 @@ int redisBufferWrite(redisContext *c, int *done) { return REDIS_ERR; if (sdslen(c->obuf) > 0) { - int nwritten = (c->flags & REDIS_SSL) ? redisSslWrite(c) : rawWrite(c); + int nwritten = (c->flags & REDIS_SSL) ? redisSslWrite(c) : redisNetWrite(c); if (nwritten < 0) { return REDIS_ERR; } else if (nwritten > 0) { diff --git a/net.c b/net.c index 3cd0402..a1913cd 100644 --- a/net.c +++ b/net.c @@ -57,13 +57,44 @@ /* Defined in hiredis.c */ void __redisSetError(redisContext *c, int type, const char *str); -static void redisContextCloseFd(redisContext *c) { +void redisNetClose(redisContext *c) { if (c && c->fd >= 0) { close(c->fd); c->fd = -1; } } +int redisNetRead(redisContext *c, char *buf, size_t bufcap) { + int nread = read(c->fd, buf, bufcap); + if (nread == -1) { + if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { + /* Try again later */ + return 0; + } else { + __redisSetError(c, REDIS_ERR_IO, NULL); + return -1; + } + } else if (nread == 0) { + __redisSetError(c, REDIS_ERR_EOF, "Server closed the connection"); + return -1; + } else { + return nread; + } +} + +int redisNetWrite(redisContext *c) { + int nwritten = write(c->fd, c->obuf, sdslen(c->obuf)); + if (nwritten < 0) { + if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { + /* Try again later */ + } else { + __redisSetError(c, REDIS_ERR_IO, NULL); + return -1; + } + } + return nwritten; +} + static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) { int errorno = errno; /* snprintf() may change errno */ char buf[128] = { 0 }; @@ -79,7 +110,7 @@ static int redisSetReuseAddr(redisContext *c) { int on = 1; if (setsockopt(c->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); - redisContextCloseFd(c); + redisNetClose(c); return REDIS_ERR; } return REDIS_OK; @@ -108,7 +139,7 @@ static int redisSetBlocking(redisContext *c, int blocking) { * interrupted by a signal. */ if ((flags = fcntl(c->fd, F_GETFL)) == -1) { __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)"); - redisContextCloseFd(c); + redisNetClose(c); return REDIS_ERR; } @@ -119,7 +150,7 @@ static int redisSetBlocking(redisContext *c, int blocking) { if (fcntl(c->fd, F_SETFL, flags) == -1) { __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)"); - redisContextCloseFd(c); + redisNetClose(c); return REDIS_ERR; } return REDIS_OK; @@ -170,7 +201,7 @@ static int redisSetTcpNoDelay(redisContext *c) { int yes = 1; if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) { __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)"); - redisContextCloseFd(c); + redisNetClose(c); return REDIS_ERR; } return REDIS_OK; @@ -212,12 +243,12 @@ static int redisContextWaitReady(redisContext *c, long msec) { if ((res = poll(wfd, 1, msec)) == -1) { __redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)"); - redisContextCloseFd(c); + redisNetClose(c); return REDIS_ERR; } else if (res == 0) { errno = ETIMEDOUT; __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); - redisContextCloseFd(c); + redisNetClose(c); return REDIS_ERR; } @@ -230,7 +261,7 @@ static int redisContextWaitReady(redisContext *c, long msec) { } __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); - redisContextCloseFd(c); + redisNetClose(c); return REDIS_ERR; } @@ -410,7 +441,7 @@ addrretry: if (connect(s,p->ai_addr,p->ai_addrlen) == -1) { if (errno == EHOSTUNREACH) { - redisContextCloseFd(c); + redisNetClose(c); continue; } else if (errno == EINPROGRESS) { if (blocking) { @@ -424,7 +455,7 @@ addrretry: if (++reuses >= REDIS_CONNECT_RETRIES) { goto error; } else { - redisContextCloseFd(c); + redisNetClose(c); goto addrretry; } } else { diff --git a/net.h b/net.h index a11594e..a4393c0 100644 --- a/net.h +++ b/net.h @@ -37,6 +37,10 @@ #include "hiredis.h" +void redisNetClose(redisContext *c); +int redisNetRead(redisContext *c, char *buf, size_t bufcap); +int redisNetWrite(redisContext *c); + int redisCheckSocketError(redisContext *c); int redisContextSetTimeout(redisContext *c, const struct timeval tv); int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout); -- cgit v1.2.3 From e84086cb92bfcab88e7dfd7760351e3d2a65950d Mon Sep 17 00:00:00 2001 From: m Date: Sun, 31 Mar 2019 18:05:32 +0200 Subject: Introduce a redisFD type The redisFD type should be equal to the system native socket file desciptor type (for POSIX, this is a plain int). We also introduce the REDIS_INVALID_FD value, which maps to -1 on POSIX systems. --- hiredis.c | 8 ++++---- hiredis.h | 11 +++++++---- net.c | 15 ++++++++------- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/hiredis.c b/hiredis.c index 629f43e..4a30da6 100644 --- a/hiredis.c +++ b/hiredis.c @@ -620,9 +620,9 @@ void redisFree(redisContext *c) { free(c); } -int redisFreeKeepFd(redisContext *c) { - int fd = c->fd; - c->fd = -1; +redisFD redisFreeKeepFd(redisContext *c) { + redisFD fd = c->fd; + c->fd = REDIS_INVALID_FD; redisFree(c); return fd; } @@ -746,7 +746,7 @@ redisContext *redisConnectUnixNonBlock(const char *path) { return redisConnectWithOptions(&options); } -redisContext *redisConnectFd(int fd) { +redisContext *redisConnectFd(redisFD fd) { redisOptions options = {0}; options.type = REDIS_CONN_USERFD; options.endpoint.fd = fd; diff --git a/hiredis.h b/hiredis.h index 4025869..3e138cb 100644 --- a/hiredis.h +++ b/hiredis.h @@ -133,6 +133,9 @@ struct redisSsl; */ #define REDIS_OPT_NOAUTOFREE 0x04 +typedef int redisFD; +#define REDIS_INVALID_FD -1 + typedef struct { /* * the type of connection to use. This also indicates which @@ -155,7 +158,7 @@ typedef struct { /** * use this field to have hiredis operate an already-open * file descriptor */ - int fd; + redisFD fd; } endpoint; } redisOptions; @@ -175,7 +178,7 @@ typedef struct { typedef struct redisContext { int err; /* Error flags, 0 when there is no error */ char errstr[128]; /* String representation of error when applicable */ - int fd; + redisFD fd; int flags; char *obuf; /* Write buffer */ redisReader *reader; /* Protocol reader */ @@ -212,7 +215,7 @@ redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port, redisContext *redisConnectUnix(const char *path); redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv); redisContext *redisConnectUnixNonBlock(const char *path); -redisContext *redisConnectFd(int fd); +redisContext *redisConnectFd(redisFD fd); /** * Secure the connection using SSL. This should be done before any command is @@ -235,7 +238,7 @@ int redisReconnect(redisContext *c); int redisSetTimeout(redisContext *c, const struct timeval tv); int redisEnableKeepAlive(redisContext *c); void redisFree(redisContext *c); -int redisFreeKeepFd(redisContext *c); +redisFD redisFreeKeepFd(redisContext *c); int redisBufferRead(redisContext *c); int redisBufferWrite(redisContext *c, int *done); diff --git a/net.c b/net.c index a1913cd..87b7fbb 100644 --- a/net.c +++ b/net.c @@ -58,9 +58,9 @@ void __redisSetError(redisContext *c, int type, const char *str); void redisNetClose(redisContext *c) { - if (c && c->fd >= 0) { + if (c && c->fd != REDIS_INVALID_FD) { close(c->fd); - c->fd = -1; + c->fd = REDIS_INVALID_FD; } } @@ -117,8 +117,8 @@ static int redisSetReuseAddr(redisContext *c) { } static int redisCreateSocket(redisContext *c, int type) { - int s; - if ((s = socket(type, SOCK_STREAM, 0)) == -1) { + redisFD s; + if ((s = socket(type, SOCK_STREAM, 0)) == REDIS_INVALID_FD) { __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); return REDIS_ERR; } @@ -158,7 +158,7 @@ static int redisSetBlocking(redisContext *c, int blocking) { int redisKeepAlive(redisContext *c, int interval) { int val = 1; - int fd = c->fd; + redisFD fd = c->fd; if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){ __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); @@ -322,7 +322,8 @@ int redisContextSetTimeout(redisContext *c, const struct timeval tv) { static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout, const char *source_addr) { - int s, rv, n; + redisFD s; + int rv, n; char _port[6]; /* strlen("65535"); */ struct addrinfo hints, *servinfo, *bservinfo, *p, *b; int blocking = (c->flags & REDIS_BLOCK); @@ -391,7 +392,7 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, } for (p = servinfo; p != NULL; p = p->ai_next) { addrretry: - if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1) + if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == REDIS_INVALID_FD) continue; c->fd = s; -- cgit v1.2.3 From 1d092a235aae92cb9a4fe260c385a850eadfc611 Mon Sep 17 00:00:00 2001 From: m Date: Sun, 31 Mar 2019 18:10:34 +0200 Subject: Use recv/send instead of read/write The recv/send calls are more portable than read/write, since unlike the latter, the former work with Windows sockets. We also check for EWOULDBLOCK instead of EAGAIN. On most Unices, EAGAIN and EWOULDBLBOCK are the same thing. However, on Windows they are different, and send/recv are expected to give EWOULDBLOCK for non-blocking sockets. --- net.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/net.c b/net.c index 87b7fbb..0c8f5b3 100644 --- a/net.c +++ b/net.c @@ -65,9 +65,9 @@ void redisNetClose(redisContext *c) { } int redisNetRead(redisContext *c, char *buf, size_t bufcap) { - int nread = read(c->fd, buf, bufcap); + int nread = recv(c->fd, buf, bufcap, 0); if (nread == -1) { - if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { + if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { /* Try again later */ return 0; } else { @@ -83,9 +83,9 @@ int redisNetRead(redisContext *c, char *buf, size_t bufcap) { } int redisNetWrite(redisContext *c) { - int nwritten = write(c->fd, c->obuf, sdslen(c->obuf)); + int nwritten = send(c->fd, c->obuf, sdslen(c->obuf), 0); if (nwritten < 0) { - if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { + if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { /* Try again later */ } else { __redisSetError(c, REDIS_ERR_IO, NULL); -- cgit v1.2.3 From dc6d19b9ece7204609980272e4b158deff224a9a Mon Sep 17 00:00:00 2001 From: m Date: Sun, 31 Mar 2019 18:17:19 +0200 Subject: Port network layer to Winsock With this change, Hiredis builds with MinGW and runs on Windows. --- CMakeLists.txt | 20 +++-- Makefile | 7 +- hiredis.c | 1 + hiredis.h | 13 ++++ net.c | 27 ++++--- sockcompat.c | 226 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sockcompat.h | 87 ++++++++++++++++++++++ win32.h | 8 +- 8 files changed, 367 insertions(+), 22 deletions(-) create mode 100644 sockcompat.c create mode 100644 sockcompat.h diff --git a/CMakeLists.txt b/CMakeLists.txt index cce2c61..0616fa6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 3.0.0) +CMAKE_MINIMUM_REQUIRED(VERSION 3.4.0) INCLUDE(GNUInstallDirs) PROJECT(hiredis) @@ -31,11 +31,15 @@ ADD_LIBRARY(hiredis SHARED net.c read.c sds.c + sockcompat.c sslio.c) SET_TARGET_PROPERTIES(hiredis PROPERTIES VERSION "${HIREDIS_SONAME}") +IF(WIN32 OR MINGW) + TARGET_LINK_LIBRARIES(hiredis PRIVATE ws2_32) +ENDIF() CONFIGURE_FILE(hiredis.pc.in hiredis.pc @ONLY) @@ -60,13 +64,13 @@ IF(HIREDIS_SSL) TARGET_LINK_LIBRARIES(hiredis ${OPENSSL_LIBRARIES}) ENDIF() -ENABLE_TESTING() -ADD_EXECUTABLE(hiredis-test test.c) - - -TARGET_LINK_LIBRARIES(hiredis-test hiredis) -ADD_TEST(NAME hiredis-test - COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test.sh) +IF(NOT (WIN32 OR MINGW)) + ENABLE_TESTING() + ADD_EXECUTABLE(hiredis-test test.c) + TARGET_LINK_LIBRARIES(hiredis-test hiredis) + ADD_TEST(NAME hiredis-test + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test.sh) +ENDIF() # Add examples IF(ENABLE_EXAMPLES) diff --git a/Makefile b/Makefile index b253904..7a55f41 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ # Copyright (C) 2010-2011 Pieter Noordhuis # This file is released under the BSD license, see the COPYING file -OBJ=net.o hiredis.o sds.o async.o read.o sslio.o +OBJ=net.o hiredis.o sds.o async.o read.o sockcompat.o sslio.o EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib \ hiredis-example-ssl hiredis-example-libevent-ssl TESTS=hiredis-test @@ -87,10 +87,11 @@ all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME) # Deps (use make dep to generate this) async.o: async.c fmacros.h async.h hiredis.h read.h sds.h net.h dict.c dict.h dict.o: dict.c fmacros.h dict.h -hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h net.h sslio.h -net.o: net.c fmacros.h net.h hiredis.h read.h sds.h +hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h net.h sslio.h win32.h +net.o: net.c fmacros.h net.h hiredis.h read.h sds.h sockcompat.h win32.h read.o: read.c fmacros.h read.h sds.h sds.o: sds.c sds.h +sockcompat.o: sockcompat.c sockcompat.h sslio.o: sslio.c sslio.h hiredis.h test.o: test.c fmacros.h hiredis.h read.h sds.h diff --git a/hiredis.c b/hiredis.c index 4a30da6..5943306 100644 --- a/hiredis.c +++ b/hiredis.c @@ -42,6 +42,7 @@ #include "net.h" #include "sds.h" #include "sslio.h" +#include "win32.h" static redisReply *createReplyObject(int type); static void *createStringObject(const redisReadTask *task, char *str, size_t len); diff --git a/hiredis.h b/hiredis.h index 3e138cb..6e56c5e 100644 --- a/hiredis.h +++ b/hiredis.h @@ -133,8 +133,21 @@ struct redisSsl; */ #define REDIS_OPT_NOAUTOFREE 0x04 +/* In Unix systems a file descriptor is a regular signed int, with -1 + * representing an invalid descriptor. In Windows it is a SOCKET + * (32- or 64-bit unsigned integer depending on the architecture), where + * all bits set (~0) is INVALID_SOCKET. */ +#ifndef _WIN32 typedef int redisFD; #define REDIS_INVALID_FD -1 +#else +#ifdef _WIN64 +typedef unsigned long long redisFD; /* SOCKET = 64-bit UINT_PTR */ +#else +typedef unsigned long redisFD; /* SOCKET = 32-bit UINT_PTR */ +#endif +#define REDIS_INVALID_FD ((redisFD)(~0)) /* INVALID_SOCKET */ +#endif typedef struct { /* diff --git a/net.c b/net.c index 0c8f5b3..ac304b4 100644 --- a/net.c +++ b/net.c @@ -34,25 +34,18 @@ #include "fmacros.h" #include -#include -#include -#include -#include -#include -#include -#include #include #include -#include #include #include #include -#include #include #include #include "net.h" #include "sds.h" +#include "sockcompat.h" +#include "win32.h" /* Defined in hiredis.c */ void __redisSetError(redisContext *c, int type, const char *str); @@ -132,6 +125,7 @@ static int redisCreateSocket(redisContext *c, int type) { } static int redisSetBlocking(redisContext *c, int blocking) { +#ifndef _WIN32 int flags; /* Set the socket nonblocking. @@ -153,6 +147,14 @@ static int redisSetBlocking(redisContext *c, int blocking) { redisNetClose(c); return REDIS_ERR; } +#else + u_long mode = blocking ? 0 : 1; + if (ioctl(c->fd, FIONBIO, &mode) == -1) { + __redisSetErrorFromErrno(c, REDIS_ERR_IO, "ioctl(FIONBIO)"); + redisNetClose(c); + return REDIS_ERR; + } +#endif /* _WIN32 */ return REDIS_OK; } @@ -503,6 +505,7 @@ int redisContextConnectBindTcp(redisContext *c, const char *addr, int port, } int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) { +#ifndef _WIN32 int blocking = (c->flags & REDIS_BLOCK); struct sockaddr_un *sa; long timeout_msec = -1; @@ -550,4 +553,10 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time c->flags |= REDIS_CONNECTED; return REDIS_OK; +#else + /* We currently do not support Unix sockets for Windows. */ + /* TODO(m): https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/ */ + errno = EPROTONOSUPPORT; + return REDIS_ERR; +#endif /* _WIN32 */ } diff --git a/sockcompat.c b/sockcompat.c new file mode 100644 index 0000000..b52cbc6 --- /dev/null +++ b/sockcompat.c @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2019, Marcus Geelnard + * + * 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. + */ + +#define REDIS_SOCKCOMPAT_IMPLEMENTATION +#include "sockcompat.h" + +#ifdef _WIN32 +static int _wsaErrorToErrno(int err) { + switch (err) { + case WSAEWOULDBLOCK: + return EWOULDBLOCK; + case WSAEINPROGRESS: + return EINPROGRESS; + case WSAEALREADY: + return EALREADY; + case WSAENOTSOCK: + return ENOTSOCK; + case WSAEDESTADDRREQ: + return EDESTADDRREQ; + case WSAEMSGSIZE: + return EMSGSIZE; + case WSAEPROTOTYPE: + return EPROTOTYPE; + case WSAENOPROTOOPT: + return ENOPROTOOPT; + case WSAEPROTONOSUPPORT: + return EPROTONOSUPPORT; + case WSAEOPNOTSUPP: + return EOPNOTSUPP; + case WSAEAFNOSUPPORT: + return EAFNOSUPPORT; + case WSAEADDRINUSE: + return EADDRINUSE; + case WSAEADDRNOTAVAIL: + return EADDRNOTAVAIL; + case WSAENETDOWN: + return ENETDOWN; + case WSAENETUNREACH: + return ENETUNREACH; + case WSAENETRESET: + return ENETRESET; + case WSAECONNABORTED: + return ECONNABORTED; + case WSAECONNRESET: + return ECONNRESET; + case WSAENOBUFS: + return ENOBUFS; + case WSAEISCONN: + return EISCONN; + case WSAENOTCONN: + return ENOTCONN; + case WSAETIMEDOUT: + return ETIMEDOUT; + case WSAECONNREFUSED: + return ECONNREFUSED; + case WSAELOOP: + return ELOOP; + case WSAENAMETOOLONG: + return ENAMETOOLONG; + case WSAEHOSTUNREACH: + return EHOSTUNREACH; + case WSAENOTEMPTY: + return ENOTEMPTY; + default: + /* We just return a generic I/O error if we could not find a relevant error. */ + return EIO; + } +} + +static void _updateErrno(int success) { + errno = success ? 0 : _wsaErrorToErrno(WSAGetLastError()); +} + +static int _initWinsock() { + static int s_initialized = 0; + if (!s_initialized) { + static WSADATA wsadata; + int err = WSAStartup(MAKEWORD(2,2), &wsadata); + if (err != 0) { + errno = _wsaErrorToErrno(err); + return 0; + } + s_initialized = 1; + } + return 1; +} + +int win32_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res) { + /* Note: This function is likely to be called before other functions, so run init here. */ + if (!_initWinsock()) { + return EAI_FAIL; + } + + switch (getaddrinfo(node, service, hints, res)) { + case 0: return 0; + case WSATRY_AGAIN: return EAI_AGAIN; + case WSAEINVAL: return EAI_BADFLAGS; + case WSAEAFNOSUPPORT: return EAI_FAMILY; + case WSA_NOT_ENOUGH_MEMORY: return EAI_MEMORY; + case WSAHOST_NOT_FOUND: return EAI_NONAME; + case WSATYPE_NOT_FOUND: return EAI_SERVICE; + case WSAESOCKTNOSUPPORT: return EAI_SOCKTYPE; + default: return EAI_FAIL; /* Including WSANO_RECOVERY */ + } +} + +const char *win32_gai_strerror(int errcode) { + switch (errcode) { + case 0: errcode = 0; break; + case EAI_AGAIN: errcode = WSATRY_AGAIN; break; + case EAI_BADFLAGS: errcode = WSAEINVAL; break; + case EAI_FAMILY: errcode = WSAEAFNOSUPPORT; break; + case EAI_MEMORY: errcode = WSA_NOT_ENOUGH_MEMORY; break; + case EAI_NONAME: errcode = WSAHOST_NOT_FOUND; break; + case EAI_SERVICE: errcode = WSATYPE_NOT_FOUND; break; + case EAI_SOCKTYPE: errcode = WSAESOCKTNOSUPPORT; break; + default: errcode = WSANO_RECOVERY; break; /* Including EAI_FAIL */ + } + return gai_strerror(errcode); +} + +void win32_freeaddrinfo(struct addrinfo *res) { + freeaddrinfo(res); +} + +SOCKET win32_socket(int domain, int type, int protocol) { + SOCKET s; + + /* Note: This function is likely to be called before other functions, so run init here. */ + if (!_initWinsock()) { + return INVALID_SOCKET; + } + + _updateErrno((s = socket(domain, type, protocol)) != INVALID_SOCKET); + return s; +} + +int win32_ioctl(SOCKET fd, unsigned long request, unsigned long *argp) { + int ret = ioctlsocket(fd, (long)request, argp); + _updateErrno(ret != SOCKET_ERROR); + return ret != SOCKET_ERROR ? ret : -1; +} + +int win32_bind(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen) { + int ret = bind(sockfd, addr, addrlen); + _updateErrno(ret != SOCKET_ERROR); + return ret != SOCKET_ERROR ? ret : -1; +} + +int win32_connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen) { + int ret = connect(sockfd, addr, addrlen); + _updateErrno(ret != SOCKET_ERROR); + + /* For Winsock connect(), the WSAEWOULDBLOCK error means the same thing as + * EINPROGRESS for POSIX connect(), so we do that translation to keep POSIX + * logic consistent. */ + if (errno == EWOULDBLOCK) { + errno = EINPROGRESS; + } + + return ret != SOCKET_ERROR ? ret : -1; +} + +int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, socklen_t *optlen) { + int ret = getsockopt(sockfd, level, optname, (char*)optval, optlen); + _updateErrno(ret != SOCKET_ERROR); + return ret != SOCKET_ERROR ? ret : -1; +} + +int win32_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen) { + int ret = setsockopt(sockfd, level, optname, (const char*)optval, optlen); + _updateErrno(ret != SOCKET_ERROR); + return ret != SOCKET_ERROR ? ret : -1; +} + +int win32_close(SOCKET fd) { + int ret = closesocket(fd); + _updateErrno(ret != SOCKET_ERROR); + return ret != SOCKET_ERROR ? ret : -1; +} + +ssize_t win32_recv(SOCKET sockfd, void *buf, size_t len, int flags) { + int ret = recv(sockfd, (char*)buf, (int)len, flags); + _updateErrno(ret != SOCKET_ERROR); + return ret != SOCKET_ERROR ? ret : -1; +} + +ssize_t win32_send(SOCKET sockfd, const void *buf, size_t len, int flags) { + int ret = send(sockfd, (const char*)buf, (int)len, flags); + _updateErrno(ret != SOCKET_ERROR); + return ret != SOCKET_ERROR ? ret : -1; +} + +int win32_poll(struct pollfd *fds, nfds_t nfds, int timeout) { + int ret = WSAPoll(fds, nfds, timeout); + _updateErrno(ret != SOCKET_ERROR); + return ret != SOCKET_ERROR ? ret : -1; +} +#endif /* _WIN32 */ diff --git a/sockcompat.h b/sockcompat.h new file mode 100644 index 0000000..4965b7b --- /dev/null +++ b/sockcompat.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2019, Marcus Geelnard + * + * 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. + */ + +#ifndef __SOCKCOMPAT_H +#define __SOCKCOMPAT_H + +#ifndef _WIN32 +/* For POSIX systems we use the standard BSD socket API. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#else +/* For Windows we use winsock. */ +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x0600 /* To get WSAPoll etc. */ +#include +#include +#include + +/* Emulate the parts of the BSD socket API that we need (override the winsock signatures). */ +int win32_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res); +const char *win32_gai_strerror(int errcode); +void win32_freeaddrinfo(struct addrinfo *res); +SOCKET win32_socket(int domain, int type, int protocol); +int win32_ioctl(SOCKET fd, unsigned long request, unsigned long *argp); +int win32_bind(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen); +int win32_connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen); +int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, socklen_t *optlen); +int win32_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen); +int win32_close(SOCKET fd); +ssize_t win32_recv(SOCKET sockfd, void *buf, size_t len, int flags); +ssize_t win32_send(SOCKET sockfd, const void *buf, size_t len, int flags); +typedef ULONG nfds_t; +int win32_poll(struct pollfd *fds, nfds_t nfds, int timeout); + +#ifndef REDIS_SOCKCOMPAT_IMPLEMENTATION +#define getaddrinfo(node, service, hints, res) win32_getaddrinfo(node, service, hints, res) +#undef gai_strerror +#define gai_strerror(errcode) win32_gai_strerror(errcode) +#define freeaddrinfo(res) win32_freeaddrinfo(res) +#define socket(domain, type, protocol) win32_socket(domain, type, protocol) +#define ioctl(fd, request, argp) win32_ioctl(fd, request, argp) +#define bind(sockfd, addr, addrlen) win32_bind(sockfd, addr, addrlen) +#define connect(sockfd, addr, addrlen) win32_connect(sockfd, addr, addrlen) +#define getsockopt(sockfd, level, optname, optval, optlen) win32_getsockopt(sockfd, level, optname, optval, optlen) +#define setsockopt(sockfd, level, optname, optval, optlen) win32_setsockopt(sockfd, level, optname, optval, optlen) +#define close(fd) win32_close(fd) +#define recv(sockfd, buf, len, flags) win32_recv(sockfd, buf, len, flags) +#define send(sockfd, buf, len, flags) win32_send(sockfd, buf, len, flags) +#define poll(fds, nfds, timeout) win32_poll(fds, nfds, timeout) +#endif /* REDIS_SOCKCOMPAT_IMPLEMENTATION */ +#endif /* _WIN32 */ + +#endif /* __SOCKCOMPAT_H */ diff --git a/win32.h b/win32.h index 1a27c18..7cb5706 100644 --- a/win32.h +++ b/win32.h @@ -37,6 +37,10 @@ __inline int c99_snprintf(char* str, size_t size, const char* format, ...) return count; } #endif +#endif /* _MSC_VER */ -#endif -#endif \ No newline at end of file +#ifdef _WIN32 +#define strerror_r(errno,buf,len) strerror_s(buf,len,errno) +#endif /* _WIN32 */ + +#endif /* _WIN32_HELPER_INCLUDE */ -- cgit v1.2.3 From e38cd75562ddb3a20e5dff8969c96568a4fb1ea1 Mon Sep 17 00:00:00 2001 From: m Date: Sun, 31 Mar 2019 19:15:11 +0200 Subject: CMake: Minor modernization Rely more on transitive dependencies, as provided by TARGET_LINK_LIBRARIES. Avoid using ADD_DEFINITIONS and INCLUDE_DIRECTORIES. This avoids leakage/pollution of defines and includes. --- CMakeLists.txt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0616fa6..c8c8071 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,8 +22,6 @@ PROJECT(hiredis VERSION "${VERSION}") SET(ENABLE_EXAMPLES OFF CACHE BOOL "Enable building hiredis examples") -INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}) - ADD_LIBRARY(hiredis SHARED async.c dict.c @@ -40,6 +38,7 @@ SET_TARGET_PROPERTIES(hiredis IF(WIN32 OR MINGW) TARGET_LINK_LIBRARIES(hiredis PRIVATE ws2_32) ENDIF() +TARGET_INCLUDE_DIRECTORIES(hiredis PUBLIC .) CONFIGURE_FILE(hiredis.pc.in hiredis.pc @ONLY) @@ -59,9 +58,9 @@ IF(HIREDIS_SSL) ENDIF() ENDIF() FIND_PACKAGE(OpenSSL REQUIRED) - ADD_DEFINITIONS(-DHIREDIS_SSL) - INCLUDE_DIRECTORIES("${OPENSSL_INCLUDE_DIR}") - TARGET_LINK_LIBRARIES(hiredis ${OPENSSL_LIBRARIES}) + TARGET_COMPILE_DEFINITIONS(hiredis PRIVATE HIREDIS_SSL) + TARGET_INCLUDE_DIRECTORIES(hiredis PRIVATE "${OPENSSL_INCLUDE_DIR}") + TARGET_LINK_LIBRARIES(hiredis PRIVATE ${OPENSSL_LIBRARIES}) ENDIF() IF(NOT (WIN32 OR MINGW)) -- cgit v1.2.3 From 4830786c84b96d2a2e95b49fba55a1548d9971e1 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Wed, 10 Apr 2019 08:28:36 -0400 Subject: ensure that blocking timeout is set --- hiredis.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hiredis.c b/hiredis.c index 5943306..6f60294 100644 --- a/hiredis.c +++ b/hiredis.c @@ -593,6 +593,7 @@ static redisContext *redisContextInit(const redisOptions *options) { c->obuf = sdsempty(); c->reader = redisReaderCreate(); + c->fd = REDIS_INVALID_FD; if (c->obuf == NULL || c->reader == NULL) { redisFree(c); @@ -683,6 +684,9 @@ redisContext *redisConnectWithOptions(const redisOptions *options) { // Unknown type - FIXME - FREE return NULL; } + if (options->timeout != NULL && (c->flags & REDIS_BLOCK) && c->fd != REDIS_INVALID_FD) { + redisContextSetTimeout(c, *options->timeout); + } return c; } -- cgit v1.2.3 From dc3c6ce85cf17e1da653a4a17bf3cca69ed02732 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Wed, 10 Apr 2019 08:36:34 -0400 Subject: build ssl example if ssl is enabled --- examples/CMakeLists.txt | 5 +++++ examples/example-ssl.c | 8 ++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 27388cb..8ab4b42 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -37,5 +37,10 @@ IF (APPLE) TARGET_LINK_LIBRARIES(example-macosx hiredis ${CF}) ENDIF() +IF (HIREDIS_SSL) + ADD_EXECUTABLE(example-ssl example-ssl.c) + TARGET_LINK_LIBRARIES(example-ssl hiredis) +ENDIF() + ADD_EXECUTABLE(example example.c) TARGET_LINK_LIBRARIES(example hiredis) \ No newline at end of file diff --git a/examples/example-ssl.c b/examples/example-ssl.c index a90b78a..156f524 100644 --- a/examples/example-ssl.c +++ b/examples/example-ssl.c @@ -18,8 +18,12 @@ int main(int argc, char **argv) { const char *key = argv[4]; const char *ca = argc > 4 ? argv[5] : NULL; - struct timeval timeout = { 1, 500000 }; // 1.5 seconds - c = redisConnectWithTimeout(hostname, port, timeout); + struct timeval tv = { 1, 500000 }; // 1.5 seconds + redisOptions options = {0}; + REDIS_OPTIONS_SET_TCP(&options, hostname, port); + options.timeout = &tv; + c = redisConnectWithOptions(&options); + if (c == NULL || c->err) { if (c) { printf("Connection error: %s\n", c->errstr); -- cgit v1.2.3 From 5aa2397f9ec494380774fb9155446f14ebbf3d1f Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Thu, 11 Apr 2019 15:08:27 -0400 Subject: fix blocking timeouts on SSL reads/writes --- sslio.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/sslio.c b/sslio.c index efbf06e..25cb5c2 100644 --- a/sslio.c +++ b/sslio.c @@ -4,6 +4,7 @@ #include #ifdef HIREDIS_SSL #include +#include void __redisSetError(redisContext *c, int type, const char *str); @@ -184,6 +185,28 @@ int redisSslRead(redisContext *c, char *buf, size_t bufcap) { return -1; } else { int err = SSL_get_error(c->ssl->ssl, nread); + if (c->flags & REDIS_BLOCK) { + /** + * In blocking mode, we should never end up in a situation where + * we get an error without it being an actual error, except + * in the case of EINTR, which can be spuriously received from + * debuggers or whatever. + */ + if (errno == EINTR) { + return 0; + } else { + const char *msg = NULL; + if (errno == EAGAIN) { + msg = "Timed out"; + } + __redisSetError(c, REDIS_ERR_IO, msg); + return -1; + } + } + + /** + * We can very well get an EWOULDBLOCK/EAGAIN, however + */ if (maybeCheckWant(c->ssl, err)) { return 0; } else { @@ -203,7 +226,7 @@ int redisSslWrite(redisContext *c) { c->ssl->lastLen = len; int err = SSL_get_error(c->ssl->ssl, rv); - if (maybeCheckWant(c->ssl, err)) { + if ((c->flags & REDIS_BLOCK) == 0 && maybeCheckWant(c->ssl, err)) { return 0; } else { __redisSetError(c, REDIS_ERR_IO, NULL); -- cgit v1.2.3 From cdb836d5f845d2bf0fbe58671d073869f41a9936 Mon Sep 17 00:00:00 2001 From: jinjiazhang Date: Sat, 13 Apr 2019 10:38:34 +0800 Subject: Fix Compile Error On Windows (Visual Studio) --- async.c | 5 +++++ hiredis.h | 4 ++++ sds.h | 3 +++ sockcompat.h | 2 ++ 4 files changed, 14 insertions(+) diff --git a/async.c b/async.c index b6163f2..50eb2d9 100644 --- a/async.c +++ b/async.c @@ -32,7 +32,12 @@ #include "fmacros.h" #include #include +#ifndef _WIN32 #include +#else +#define strcasecmp stricmp +#define strncasecmp strnicmp +#endif #include #include #include diff --git a/hiredis.h b/hiredis.h index 6e56c5e..1ae1c0a 100644 --- a/hiredis.h +++ b/hiredis.h @@ -35,7 +35,11 @@ #define __HIREDIS_H #include "read.h" #include /* for va_list */ +#ifndef _WIN32 #include /* for struct timeval */ +#else +#include +#endif #include /* uintXX_t, etc */ #include "sds.h" /* for sds */ diff --git a/sds.h b/sds.h index 13be75a..eafe2cf 100644 --- a/sds.h +++ b/sds.h @@ -34,6 +34,9 @@ #define __SDS_H #define SDS_MAX_PREALLOC (1024*1024) +#ifdef _WIN32 +#define __attribute__(x) +#endif #include #include diff --git a/sockcompat.h b/sockcompat.h index 4965b7b..e0b2e5e 100644 --- a/sockcompat.h +++ b/sockcompat.h @@ -50,6 +50,8 @@ #include #include +typedef signed long ssize_t; + /* Emulate the parts of the BSD socket API that we need (override the winsock signatures). */ int win32_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res); const char *win32_gai_strerror(int errcode); -- cgit v1.2.3 From d8f814d48b25e202065b5a2b829cd0c2c20d29d3 Mon Sep 17 00:00:00 2001 From: Minun Dragonation Date: Sun, 5 May 2019 21:34:28 +0800 Subject: fix bugs of setsockopt diff in win compact implementation --- sockcompat.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/sockcompat.c b/sockcompat.c index b52cbc6..047f8b6 100644 --- a/sockcompat.c +++ b/sockcompat.c @@ -189,13 +189,31 @@ int win32_connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen) } int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, socklen_t *optlen) { - int ret = getsockopt(sockfd, level, optname, (char*)optval, optlen); + int ret = 0; + if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) { + struct timeval *tv = (struct timeval *)optval; + DWORD timeout = 0; + socklen_t dwlen = 0; + ret = getsockopt(sockfd, level, optname, (char *)timeout, &dwlen); + tv->tv_sec = timeout / 1000; + tv->tv_usec = timeout * 1000; + *optlen = sizeof (struct timeval); + } else { + ret = getsockopt(sockfd, level, optname, (char*)optval, optlen); + } _updateErrno(ret != SOCKET_ERROR); return ret != SOCKET_ERROR ? ret : -1; } int win32_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen) { - int ret = setsockopt(sockfd, level, optname, (const char*)optval, optlen); + int ret = 0; + if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) { + struct timeval *tv = (struct timeval *)optval; + DWORD timeout = tv->tv_sec * 1000 + tv->tv_usec / 1000; + ret = setsockopt(sockfd, level, optname, (const char*)&timeout, sizeof(DWORD)); + } else { + ret = setsockopt(sockfd, level, optname, (const char*)optval, optlen); + } _updateErrno(ret != SOCKET_ERROR); return ret != SOCKET_ERROR ? ret : -1; } -- cgit v1.2.3 From 82252440de09067d46772da727dbc69c2e83fcf1 Mon Sep 17 00:00:00 2001 From: Minun Dragonation Date: Sun, 5 May 2019 21:39:46 +0800 Subject: fix bugs on ref address incorrect on sockcompact with getsockopt --- sockcompat.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sockcompat.c b/sockcompat.c index 047f8b6..7e5b6a7 100644 --- a/sockcompat.c +++ b/sockcompat.c @@ -192,9 +192,8 @@ int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, sockle int ret = 0; if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) { struct timeval *tv = (struct timeval *)optval; - DWORD timeout = 0; - socklen_t dwlen = 0; - ret = getsockopt(sockfd, level, optname, (char *)timeout, &dwlen); + DWORD timeout = 0; socklen_t dwlen = 0; + ret = getsockopt(sockfd, level, optname, (char *)&timeout, &dwlen); tv->tv_sec = timeout / 1000; tv->tv_usec = timeout * 1000; *optlen = sizeof (struct timeval); -- cgit v1.2.3 From 4a94ce6326c51a2071025e929a3ab2a5263206f7 Mon Sep 17 00:00:00 2001 From: Minun Dragonation Date: Sun, 5 May 2019 21:46:34 +0800 Subject: fix bugs for optlen output on size not big enough for timeout events --- sockcompat.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/sockcompat.c b/sockcompat.c index 7e5b6a7..38cb9e5 100644 --- a/sockcompat.c +++ b/sockcompat.c @@ -191,11 +191,16 @@ int win32_connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen) int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, socklen_t *optlen) { int ret = 0; if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) { - struct timeval *tv = (struct timeval *)optval; - DWORD timeout = 0; socklen_t dwlen = 0; - ret = getsockopt(sockfd, level, optname, (char *)&timeout, &dwlen); - tv->tv_sec = timeout / 1000; - tv->tv_usec = timeout * 1000; + if (*optlen >= sizeof (struct timeval)) { + struct timeval *tv = (struct timeval *)optval; + DWORD timeout = 0; + socklen_t dwlen = 0; + ret = getsockopt(sockfd, level, optname, (char *)&timeout, &dwlen); + tv->tv_sec = timeout / 1000; + tv->tv_usec = timeout * 1000; + } else { + ret = WSAEFAULT; + } *optlen = sizeof (struct timeval); } else { ret = getsockopt(sockfd, level, optname, (char*)optval, optlen); -- cgit v1.2.3 From f5454d509f9eafef35714c4e5e23af066e80f4f6 Mon Sep 17 00:00:00 2001 From: Minun Dragonation Date: Sun, 5 May 2019 21:58:34 +0800 Subject: fix bugs on socket timeout tv usec calculation --- sockcompat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sockcompat.c b/sockcompat.c index 38cb9e5..c3b6f66 100644 --- a/sockcompat.c +++ b/sockcompat.c @@ -197,7 +197,7 @@ int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, sockle socklen_t dwlen = 0; ret = getsockopt(sockfd, level, optname, (char *)&timeout, &dwlen); tv->tv_sec = timeout / 1000; - tv->tv_usec = timeout * 1000; + tv->tv_usec = (timeout * 1000) % 1000000; } else { ret = WSAEFAULT; } -- cgit v1.2.3 From 76394f1be87359c88b13fc327f86da9e949ce3e3 Mon Sep 17 00:00:00 2001 From: Minun Dragonation Date: Mon, 13 May 2019 23:20:05 +0800 Subject: remove useless type casting --- sockcompat.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sockcompat.c b/sockcompat.c index c3b6f66..4cc2f41 100644 --- a/sockcompat.c +++ b/sockcompat.c @@ -192,7 +192,7 @@ int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, sockle int ret = 0; if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) { if (*optlen >= sizeof (struct timeval)) { - struct timeval *tv = (struct timeval *)optval; + struct timeval *tv = optval; DWORD timeout = 0; socklen_t dwlen = 0; ret = getsockopt(sockfd, level, optname, (char *)&timeout, &dwlen); @@ -212,7 +212,7 @@ int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, sockle int win32_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen) { int ret = 0; if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) { - struct timeval *tv = (struct timeval *)optval; + struct timeval *tv = optval; DWORD timeout = tv->tv_sec * 1000 + tv->tv_usec / 1000; ret = setsockopt(sockfd, level, optname, (const char*)&timeout, sizeof(DWORD)); } else { -- cgit v1.2.3 From 993af7710ef2f9e72b5bf2c27e15227bbcaf6a9c Mon Sep 17 00:00:00 2001 From: Eivind Uggedal Date: Tue, 14 May 2019 07:29:26 +0000 Subject: test: fix errstr matching for musl libc This makes the tests pass on musl[1] based distros like Alpine Linux. [1]: https://www.musl-libc.org/ --- test.c | 1 + 1 file changed, 1 insertion(+) diff --git a/test.c b/test.c index 4af99b2..05c5ee3 100644 --- a/test.c +++ b/test.c @@ -450,6 +450,7 @@ static void test_blocking_connection_errors(void) { c->err == REDIS_ERR_OTHER && (strcmp(c->errstr, "Name or service not known") == 0 || strcmp(c->errstr, "Can't resolve: " HIREDIS_BAD_DOMAIN) == 0 || + strcmp(c->errstr, "Name does not resolve") == 0 || strcmp(c->errstr, "nodename nor servname provided, or not known") == 0 || strcmp(c->errstr, "No address associated with hostname") == 0 || -- cgit v1.2.3 From 918e24c83b2a6cecea089d1c335acb98b5c593e3 Mon Sep 17 00:00:00 2001 From: "qi.yang" Date: Thu, 30 May 2019 15:03:38 +0800 Subject: redisReaderGetReply leak memory --- read.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/read.c b/read.c index cc21267..065b0b5 100644 --- a/read.c +++ b/read.c @@ -590,8 +590,11 @@ int redisReaderGetReply(redisReader *r, void **reply) { /* Emit a reply when there is one. */ if (r->ridx == -1) { - if (reply != NULL) + if (reply != NULL) { *reply = r->reply; + } else if (r->reply != NULL && r->fn && r->fn->freeObject) { + r->fn->freeObject(r->reply); + } r->reply = NULL; } return REDIS_OK; -- cgit v1.2.3 From 83d3c097efcc724177fae7b5f8c8daefd95e9a32 Mon Sep 17 00:00:00 2001 From: Quentin Young Date: Wed, 3 Jul 2019 21:35:54 +0000 Subject: Remove unnecessary null check before free --- net.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/net.c b/net.c index ac304b4..255f435 100644 --- a/net.c +++ b/net.c @@ -435,9 +435,7 @@ addrretry: } /* For repeat connection */ - if (c->saddr) { - free(c->saddr); - } + free(c->saddr); c->saddr = malloc(p->ai_addrlen); memcpy(c->saddr, p->ai_addr, p->ai_addrlen); c->addrlen = p->ai_addrlen; -- cgit v1.2.3 From 8249e67355e60246fb109f0d98d0b92834fd30e8 Mon Sep 17 00:00:00 2001 From: kevin1018 Date: Wed, 17 Jul 2019 19:05:06 +0800 Subject: Add install adapters header files --- CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c8c8071..30140bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,7 +47,10 @@ INSTALL(TARGETS hiredis INSTALL(FILES hiredis.h read.h sds.h async.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis) - + +INSTALL(DIRECTORY adapters + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis) + INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) -- cgit v1.2.3 From a1d4da63b87be5b99c96879105e47e8f3b83bd06 Mon Sep 17 00:00:00 2001 From: Odin Hultgren Van Der Horst Date: Mon, 22 Jul 2019 11:06:10 +0200 Subject: Removed whitespace before newline - Removed whitespace before newline - Removed win style newline --- README.md | 2 +- async.c | 8 ++++---- sds.c | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 01223ea..e757d07 100644 --- a/README.md +++ b/README.md @@ -406,6 +406,6 @@ as soon as possible in order to prevent allocation of useless memory. ## AUTHORS Hiredis was written by Salvatore Sanfilippo (antirez at gmail) and -Pieter Noordhuis (pcnoordhuis at gmail) and is released under the BSD license. +Pieter Noordhuis (pcnoordhuis at gmail) and is released under the BSD license. Hiredis is currently maintained by Matt Stancliff (matt at genges dot com) and Jan-Erik Rediger (janerik at fnordig dot com) diff --git a/async.c b/async.c index 50eb2d9..171fabd 100644 --- a/async.c +++ b/async.c @@ -34,8 +34,8 @@ #include #ifndef _WIN32 #include -#else -#define strcasecmp stricmp +#else +#define strcasecmp stricmp #define strncasecmp strnicmp #endif #include @@ -438,7 +438,7 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, assert(reply->element[2]->type == REDIS_REPLY_INTEGER); /* Unset subscribed flag only when no pipelined pending subscribe. */ - if (reply->element[2]->integer == 0 + if (reply->element[2]->integer == 0 && dictSize(ac->sub.channels) == 0 && dictSize(ac->sub.patterns) == 0) c->flags &= ~REDIS_SUBSCRIBED; @@ -557,7 +557,7 @@ static int __redisAsyncHandleConnect(redisAsyncContext *ac) { /** * Handle SSL when socket becomes available for reading. This also handles * read-while-write and write-while-read. - * + * * These functions will not work properly unless `HIREDIS_SSL` is defined * (however, they will compile) */ diff --git a/sds.c b/sds.c index 44777b1..6cf7584 100644 --- a/sds.c +++ b/sds.c @@ -1035,7 +1035,7 @@ sds *sdssplitargs(const char *line, int *argc) { s_free(vector); return NULL; } - + vector = new_vector; vector[*argc] = current; (*argc)++; -- cgit v1.2.3