Merge branch 'master' into createArray-size_t
This commit is contained in:
commit
f9bccfb7ba
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,3 +5,4 @@
|
|||||||
/*.dylib
|
/*.dylib
|
||||||
/*.a
|
/*.a
|
||||||
/*.pc
|
/*.pc
|
||||||
|
*.dSYM
|
||||||
|
51
.travis.yml
51
.travis.yml
@ -8,12 +8,6 @@ os:
|
|||||||
- linux
|
- linux
|
||||||
- osx
|
- osx
|
||||||
|
|
||||||
branches:
|
|
||||||
only:
|
|
||||||
- staging
|
|
||||||
- trying
|
|
||||||
- master
|
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- if [ "$TRAVIS_OS_NAME" == "osx" ] ; then brew update; brew install redis; fi
|
- if [ "$TRAVIS_OS_NAME" == "osx" ] ; then brew update; brew install redis; fi
|
||||||
|
|
||||||
@ -26,20 +20,39 @@ addons:
|
|||||||
- libc6-dev-i386
|
- libc6-dev-i386
|
||||||
- libc6-dbg:i386
|
- libc6-dbg:i386
|
||||||
- gcc-multilib
|
- gcc-multilib
|
||||||
|
- g++-multilib
|
||||||
- valgrind
|
- valgrind
|
||||||
|
|
||||||
env:
|
env:
|
||||||
- CFLAGS="-Werror"
|
- BITS="32"
|
||||||
- PRE="valgrind --track-origins=yes --leak-check=full"
|
- BITS="64"
|
||||||
- TARGET="32bit" TARGET_VARS="32bit-vars" CFLAGS="-Werror"
|
|
||||||
- TARGET="32bit" TARGET_VARS="32bit-vars" PRE="valgrind --track-origins=yes --leak-check=full"
|
|
||||||
|
|
||||||
matrix:
|
script:
|
||||||
exclude:
|
- EXTRA_CMAKE_OPTS="-DENABLE_EXAMPLES:BOOL=ON -DHIREDIS_SSL:BOOL=ON";
|
||||||
- os: osx
|
if [ "$TRAVIS_OS_NAME" == "osx" ]; then
|
||||||
env: PRE="valgrind --track-origins=yes --leak-check=full"
|
if [ "$BITS" == "32" ]; then
|
||||||
|
CFLAGS="-m32 -Werror";
|
||||||
- os: osx
|
CXXFLAGS="-m32 -Werror";
|
||||||
env: TARGET="32bit" TARGET_VARS="32bit-vars" PRE="valgrind --track-origins=yes --leak-check=full"
|
LDFLAGS="-m32";
|
||||||
|
EXTRA_CMAKE_OPTS=;
|
||||||
script: make $TARGET CFLAGS="$CFLAGS" && make check PRE="$PRE" && make $TARGET_VARS hiredis-example
|
else
|
||||||
|
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";
|
||||||
|
EXTRA_CMAKE_OPTS=;
|
||||||
|
else
|
||||||
|
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
|
||||||
|
55
CHANGELOG.md
55
CHANGELOG.md
@ -1,15 +1,18 @@
|
|||||||
### 1.0.0 (unreleased)
|
### 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
|
* 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
|
protocol errors. This is consistent with the RESP specification. On 32-bit
|
||||||
platforms, the upper bound is lowered to `SIZE_MAX`.
|
platforms, the upper bound is lowered to `SIZE_MAX`.
|
||||||
|
|
||||||
|
* 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.
|
||||||
|
|
||||||
|
### 0.x.x (unreleased)
|
||||||
**BREAKING CHANGES**:
|
**BREAKING CHANGES**:
|
||||||
|
|
||||||
* Change `redisReply.len` to `size_t`, as it denotes the the size of a string
|
* Change `redisReply.len` to `size_t`, as it denotes the the size of a string
|
||||||
@ -19,6 +22,48 @@ If it was used to compare to other values, casting might be necessary or can be
|
|||||||
|
|
||||||
* `redisReplyObjectFunctions.createArray` now takes `size_t` for its length parameter.
|
* `redisReplyObjectFunctions.createArray` now takes `size_t` for its length parameter.
|
||||||
|
|
||||||
|
### 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
|
||||||
|
* 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`.
|
||||||
|
|
||||||
* Remove backwards compatibility macro's
|
* Remove backwards compatibility macro's
|
||||||
|
|
||||||
This removes the following old function aliases, use the new name now:
|
This removes the following old function aliases, use the new name now:
|
||||||
|
80
CMakeLists.txt
Normal file
80
CMakeLists.txt
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
CMAKE_MINIMUM_REQUIRED(VERSION 3.4.0)
|
||||||
|
INCLUDE(GNUInstallDirs)
|
||||||
|
PROJECT(hiredis)
|
||||||
|
|
||||||
|
OPTION(HIREDIS_SSL "Link against OpenSSL" OFF)
|
||||||
|
|
||||||
|
MACRO(getVersionBit name)
|
||||||
|
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)
|
||||||
|
getVersionBit(HIREDIS_SONAME)
|
||||||
|
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")
|
||||||
|
|
||||||
|
ADD_LIBRARY(hiredis SHARED
|
||||||
|
async.c
|
||||||
|
dict.c
|
||||||
|
hiredis.c
|
||||||
|
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()
|
||||||
|
TARGET_INCLUDE_DIRECTORIES(hiredis PUBLIC .)
|
||||||
|
|
||||||
|
CONFIGURE_FILE(hiredis.pc.in hiredis.pc @ONLY)
|
||||||
|
|
||||||
|
INSTALL(TARGETS hiredis
|
||||||
|
DESTINATION "${CMAKE_INSTALL_LIBDIR}")
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
IF(HIREDIS_SSL)
|
||||||
|
IF (NOT OPENSSL_ROOT_DIR)
|
||||||
|
IF (APPLE)
|
||||||
|
SET(OPENSSL_ROOT_DIR "/usr/local/opt/openssl")
|
||||||
|
ENDIF()
|
||||||
|
ENDIF()
|
||||||
|
FIND_PACKAGE(OpenSSL REQUIRED)
|
||||||
|
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))
|
||||||
|
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)
|
||||||
|
ADD_SUBDIRECTORY(examples)
|
||||||
|
ENDIF(ENABLE_EXAMPLES)
|
68
Makefile
68
Makefile
@ -3,8 +3,9 @@
|
|||||||
# Copyright (C) 2010-2011 Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
# Copyright (C) 2010-2011 Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||||
# This file is released under the BSD license, see the COPYING file
|
# This file is released under the BSD license, see the COPYING file
|
||||||
|
|
||||||
OBJ=net.o hiredis.o sds.o async.o read.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
|
EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib \
|
||||||
|
hiredis-example-ssl hiredis-example-libevent-ssl
|
||||||
TESTS=hiredis-test
|
TESTS=hiredis-test
|
||||||
LIBNAME=libhiredis
|
LIBNAME=libhiredis
|
||||||
PKGCONFNAME=hiredis.pc
|
PKGCONFNAME=hiredis.pc
|
||||||
@ -39,9 +40,9 @@ export REDIS_TEST_CONFIG
|
|||||||
CC:=$(shell sh -c 'type $${CC%% *} >/dev/null 2>/dev/null && echo $(CC) || echo gcc')
|
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++')
|
CXX:=$(shell sh -c 'type $${CXX%% *} >/dev/null 2>/dev/null && echo $(CXX) || echo g++')
|
||||||
OPTIMIZATION?=-O3
|
OPTIMIZATION?=-O3
|
||||||
WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings
|
WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings -Wno-missing-field-initializers
|
||||||
DEBUG_FLAGS?= -g -ggdb
|
DEBUG_FLAGS?= -g -ggdb
|
||||||
REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS)
|
REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CPPFLAGS) $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS)
|
||||||
REAL_LDFLAGS=$(LDFLAGS)
|
REAL_LDFLAGS=$(LDFLAGS)
|
||||||
|
|
||||||
DYLIBSUFFIX=so
|
DYLIBSUFFIX=so
|
||||||
@ -49,12 +50,28 @@ STLIBSUFFIX=a
|
|||||||
DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME)
|
DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME)
|
||||||
DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR)
|
DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR)
|
||||||
DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX)
|
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)
|
STLIBNAME=$(LIBNAME).$(STLIBSUFFIX)
|
||||||
STLIB_MAKE_CMD=ar rcs $(STLIBNAME)
|
STLIB_MAKE_CMD=$(AR) rcs $(STLIBNAME)
|
||||||
|
|
||||||
# Platform-specific overrides
|
# Platform-specific overrides
|
||||||
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
|
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
|
||||||
|
|
||||||
|
USE_SSL?=0
|
||||||
|
|
||||||
|
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)
|
||||||
|
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
|
||||||
|
|
||||||
ifeq ($(uname_S),SunOS)
|
ifeq ($(uname_S),SunOS)
|
||||||
REAL_LDFLAGS+= -ldl -lnsl -lsocket
|
REAL_LDFLAGS+= -ldl -lnsl -lsocket
|
||||||
DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS)
|
DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS)
|
||||||
@ -62,7 +79,7 @@ endif
|
|||||||
ifeq ($(uname_S),Darwin)
|
ifeq ($(uname_S),Darwin)
|
||||||
DYLIBSUFFIX=dylib
|
DYLIBSUFFIX=dylib
|
||||||
DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_SONAME).$(DYLIBSUFFIX)
|
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
|
endif
|
||||||
|
|
||||||
all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME)
|
all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME)
|
||||||
@ -70,14 +87,16 @@ all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME)
|
|||||||
# Deps (use make dep to generate this)
|
# 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
|
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
|
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 win32.h
|
||||||
net.o: net.c fmacros.h net.h hiredis.h read.h sds.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
|
read.o: read.c fmacros.h read.h sds.h
|
||||||
sds.o: sds.c 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
|
test.o: test.c fmacros.h hiredis.h read.h sds.h
|
||||||
|
|
||||||
$(DYLIBNAME): $(OBJ)
|
$(DYLIBNAME): $(OBJ)
|
||||||
$(DYLIB_MAKE_CMD) $(OBJ)
|
$(DYLIB_MAKE_CMD) $(OBJ) $(REAL_LDFLAGS)
|
||||||
|
|
||||||
$(STLIBNAME): $(OBJ)
|
$(STLIBNAME): $(OBJ)
|
||||||
$(STLIB_MAKE_CMD) $(OBJ)
|
$(STLIB_MAKE_CMD) $(OBJ)
|
||||||
@ -87,19 +106,25 @@ static: $(STLIBNAME)
|
|||||||
|
|
||||||
# Binaries:
|
# Binaries:
|
||||||
hiredis-example-libevent: examples/example-libevent.c adapters/libevent.h $(STLIBNAME)
|
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) -I. $< -levent $(STLIBNAME) $(REAL_LDFLAGS)
|
||||||
|
|
||||||
hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME)
|
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)
|
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)
|
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)
|
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) -I. $< $(STLIBNAME) $(REAL_LDFLAGS)
|
||||||
|
|
||||||
ifndef AE_DIR
|
ifndef AE_DIR
|
||||||
hiredis-example-ae:
|
hiredis-example-ae:
|
||||||
@ -116,7 +141,7 @@ hiredis-example-libuv:
|
|||||||
@false
|
@false
|
||||||
else
|
else
|
||||||
hiredis-example-libuv: examples/example-libuv.c adapters/libuv.h $(STLIBNAME)
|
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
|
endif
|
||||||
|
|
||||||
ifeq ($(and $(QT_MOC),$(QT_INCLUDE_DIR),$(QT_LIBRARY_DIR)),)
|
ifeq ($(and $(QT_MOC),$(QT_INCLUDE_DIR),$(QT_LIBRARY_DIR)),)
|
||||||
@ -133,14 +158,14 @@ hiredis-example-qt: examples/example-qt.cpp adapters/qt.h $(STLIBNAME)
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
hiredis-example: examples/example.c $(STLIBNAME)
|
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)
|
examples: $(EXAMPLES)
|
||||||
|
|
||||||
hiredis-test: test.o $(STLIBNAME)
|
hiredis-test: test.o $(STLIBNAME)
|
||||||
|
|
||||||
hiredis-%: %.o $(STLIBNAME)
|
hiredis-%: %.o $(STLIBNAME)
|
||||||
$(CC) $(REAL_CFLAGS) -o $@ $(REAL_LDFLAGS) $< $(STLIBNAME)
|
$(CC) $(REAL_CFLAGS) -o $@ $< $(STLIBNAME) $(REAL_LDFLAGS)
|
||||||
|
|
||||||
test: hiredis-test
|
test: hiredis-test
|
||||||
./hiredis-test
|
./hiredis-test
|
||||||
@ -158,7 +183,7 @@ clean:
|
|||||||
rm -rf $(DYLIBNAME) $(STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov
|
rm -rf $(DYLIBNAME) $(STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov
|
||||||
|
|
||||||
dep:
|
dep:
|
||||||
$(CC) -MM *.c
|
$(CC) $(CPPFLAGS) $(CFLAGS) -MM *.c
|
||||||
|
|
||||||
INSTALL?= cp -pPR
|
INSTALL?= cp -pPR
|
||||||
|
|
||||||
@ -173,11 +198,14 @@ $(PKGCONFNAME): hiredis.h
|
|||||||
@echo Description: Minimalistic C client library for Redis. >> $@
|
@echo Description: Minimalistic C client library for Redis. >> $@
|
||||||
@echo Version: $(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(HIREDIS_PATCH) >> $@
|
@echo Version: $(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(HIREDIS_PATCH) >> $@
|
||||||
@echo Libs: -L\$${libdir} -lhiredis >> $@
|
@echo Libs: -L\$${libdir} -lhiredis >> $@
|
||||||
|
ifdef USE_SSL
|
||||||
|
@echo Libs.private: -lssl -lcrypto >> $@
|
||||||
|
endif
|
||||||
@echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@
|
@echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@
|
||||||
|
|
||||||
install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME)
|
install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME)
|
||||||
mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL_LIBRARY_PATH)
|
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) adapters/*.h $(INSTALL_INCLUDE_PATH)/adapters
|
||||||
$(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME)
|
$(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME)
|
||||||
cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIBNAME)
|
cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIBNAME)
|
||||||
|
@ -286,6 +286,7 @@ return `REDIS_ERR`. The function to set the disconnect callback has the followin
|
|||||||
```c
|
```c
|
||||||
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
|
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
|
### Sending commands and their callbacks
|
||||||
|
|
||||||
In an asynchronous context, commands are automatically pipelined due to the nature of an event loop.
|
In an asynchronous context, commands are automatically pipelined due to the nature of an event loop.
|
||||||
@ -406,6 +407,6 @@ as soon as possible in order to prevent allocation of useless memory.
|
|||||||
## AUTHORS
|
## AUTHORS
|
||||||
|
|
||||||
Hiredis was written by Salvatore Sanfilippo (antirez at gmail) and
|
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
|
Hiredis is currently maintained by Matt Stancliff (matt at genges dot com) and
|
||||||
Jan-Erik Rediger (janerik at fnordig dot com)
|
Jan-Erik Rediger (janerik at fnordig dot com)
|
||||||
|
@ -34,48 +34,113 @@
|
|||||||
#include "../hiredis.h"
|
#include "../hiredis.h"
|
||||||
#include "../async.h"
|
#include "../async.h"
|
||||||
|
|
||||||
|
#define REDIS_LIBEVENT_DELETED 0x01
|
||||||
|
#define REDIS_LIBEVENT_ENTERED 0x02
|
||||||
|
|
||||||
typedef struct redisLibeventEvents {
|
typedef struct redisLibeventEvents {
|
||||||
redisAsyncContext *context;
|
redisAsyncContext *context;
|
||||||
struct event *rev, *wev;
|
struct event *ev;
|
||||||
|
struct event_base *base;
|
||||||
|
struct timeval tv;
|
||||||
|
short flags;
|
||||||
|
short state;
|
||||||
} redisLibeventEvents;
|
} redisLibeventEvents;
|
||||||
|
|
||||||
static void redisLibeventReadEvent(int fd, short event, void *arg) {
|
static void redisLibeventDestroy(redisLibeventEvents *e) {
|
||||||
((void)fd); ((void)event);
|
free(e);
|
||||||
redisLibeventEvents *e = (redisLibeventEvents*)arg;
|
|
||||||
redisAsyncHandleRead(e->context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void redisLibeventWriteEvent(int fd, short event, void *arg) {
|
static void redisLibeventHandler(int fd, short event, void *arg) {
|
||||||
((void)fd); ((void)event);
|
((void)fd);
|
||||||
redisLibeventEvents *e = (redisLibeventEvents*)arg;
|
redisLibeventEvents *e = (redisLibeventEvents*)arg;
|
||||||
redisAsyncHandleWrite(e->context);
|
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 && (e->state & REDIS_LIBEVENT_DELETED) == 0) {
|
||||||
|
redisAsyncHandleRead(e->context);
|
||||||
|
CHECK_DELETED();
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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 | EV_PERSIST,
|
||||||
|
redisLibeventHandler, privdata);
|
||||||
|
event_add(e->ev, tv);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void redisLibeventAddRead(void *privdata) {
|
static void redisLibeventAddRead(void *privdata) {
|
||||||
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
|
redisLibeventUpdate(privdata, EV_READ, 0);
|
||||||
event_add(e->rev,NULL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void redisLibeventDelRead(void *privdata) {
|
static void redisLibeventDelRead(void *privdata) {
|
||||||
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
|
redisLibeventUpdate(privdata, EV_READ, 1);
|
||||||
event_del(e->rev);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void redisLibeventAddWrite(void *privdata) {
|
static void redisLibeventAddWrite(void *privdata) {
|
||||||
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
|
redisLibeventUpdate(privdata, EV_WRITE, 0);
|
||||||
event_add(e->wev,NULL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void redisLibeventDelWrite(void *privdata) {
|
static void redisLibeventDelWrite(void *privdata) {
|
||||||
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
|
redisLibeventUpdate(privdata, EV_WRITE, 1);
|
||||||
event_del(e->wev);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void redisLibeventCleanup(void *privdata) {
|
static void redisLibeventCleanup(void *privdata) {
|
||||||
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
|
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
|
||||||
event_free(e->rev);
|
if (!e) {
|
||||||
event_free(e->wev);
|
return;
|
||||||
free(e);
|
}
|
||||||
|
event_del(e->ev);
|
||||||
|
event_free(e->ev);
|
||||||
|
e->ev = NULL;
|
||||||
|
|
||||||
|
if (e->state & REDIS_LIBEVENT_ENTERED) {
|
||||||
|
e->state |= REDIS_LIBEVENT_DELETED;
|
||||||
|
} else {
|
||||||
|
redisLibeventDestroy(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisLibeventSetTimeout(void *privdata, struct timeval tv) {
|
||||||
|
redisLibeventEvents *e = (redisLibeventEvents *)privdata;
|
||||||
|
short flags = e->flags;
|
||||||
|
e->flags = 0;
|
||||||
|
e->tv = tv;
|
||||||
|
redisLibeventUpdate(e, flags, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) {
|
static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) {
|
||||||
@ -87,7 +152,7 @@ static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) {
|
|||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
|
|
||||||
/* Create container for context and r/w events */
|
/* Create container for context and r/w events */
|
||||||
e = (redisLibeventEvents*)malloc(sizeof(*e));
|
e = (redisLibeventEvents*)calloc(1, sizeof(*e));
|
||||||
e->context = ac;
|
e->context = ac;
|
||||||
|
|
||||||
/* Register functions to start/stop listening for events */
|
/* Register functions to start/stop listening for events */
|
||||||
@ -96,13 +161,12 @@ static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) {
|
|||||||
ac->ev.addWrite = redisLibeventAddWrite;
|
ac->ev.addWrite = redisLibeventAddWrite;
|
||||||
ac->ev.delWrite = redisLibeventDelWrite;
|
ac->ev.delWrite = redisLibeventDelWrite;
|
||||||
ac->ev.cleanup = redisLibeventCleanup;
|
ac->ev.cleanup = redisLibeventCleanup;
|
||||||
|
ac->ev.scheduleTimer = redisLibeventSetTimeout;
|
||||||
ac->ev.data = e;
|
ac->ev.data = e;
|
||||||
|
|
||||||
/* Initialize and install read/write events */
|
/* Initialize and install read/write events */
|
||||||
e->rev = event_new(base, c->fd, EV_READ, redisLibeventReadEvent, e);
|
e->ev = event_new(base, c->fd, EV_READ | EV_WRITE, redisLibeventHandler, e);
|
||||||
e->wev = event_new(base, c->fd, EV_WRITE, redisLibeventWriteEvent, e);
|
e->base = base;
|
||||||
event_add(e->rev, NULL);
|
|
||||||
event_add(e->wev, NULL);
|
|
||||||
return REDIS_OK;
|
return REDIS_OK;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -15,15 +15,12 @@ typedef struct redisLibuvEvents {
|
|||||||
|
|
||||||
static void redisLibuvPoll(uv_poll_t* handle, int status, int events) {
|
static void redisLibuvPoll(uv_poll_t* handle, int status, int events) {
|
||||||
redisLibuvEvents* p = (redisLibuvEvents*)handle->data;
|
redisLibuvEvents* p = (redisLibuvEvents*)handle->data;
|
||||||
|
int ev = (status ? p->events : events);
|
||||||
|
|
||||||
if (status != 0) {
|
if (p->context != NULL && (ev & UV_READABLE)) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (p->context != NULL && (events & UV_READABLE)) {
|
|
||||||
redisAsyncHandleRead(p->context);
|
redisAsyncHandleRead(p->context);
|
||||||
}
|
}
|
||||||
if (p->context != NULL && (events & UV_WRITABLE)) {
|
if (p->context != NULL && (ev & UV_WRITABLE)) {
|
||||||
redisAsyncHandleWrite(p->context);
|
redisAsyncHandleWrite(p->context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,9 @@ environment:
|
|||||||
CC: gcc
|
CC: gcc
|
||||||
- CYG_BASH: C:\cygwin\bin\bash
|
- CYG_BASH: C:\cygwin\bin\bash
|
||||||
CC: gcc
|
CC: gcc
|
||||||
TARGET: 32bit
|
CFLAGS: -m32
|
||||||
TARGET_VARS: 32bit-vars
|
CXXFLAGS: -m32
|
||||||
|
LDFLAGS: -m32
|
||||||
|
|
||||||
clone_depth: 1
|
clone_depth: 1
|
||||||
|
|
||||||
@ -20,4 +21,4 @@ install:
|
|||||||
|
|
||||||
build_script:
|
build_script:
|
||||||
- 'echo building...'
|
- 'echo building...'
|
||||||
- '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0</dev/null; make LDFLAGS=$LDFLAGS CC=$CC $TARGET CFLAGS=$CFLAGS && make LDFLAGS=$LDFLAGS CC=$CC $TARGET_VARS hiredis-example"'
|
- '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0</dev/null; mkdir build && cd build && cmake .. -G \"Unix Makefiles\" && make VERBOSE=1"'
|
||||||
|
243
async.c
243
async.c
@ -32,7 +32,12 @@
|
|||||||
#include "fmacros.h"
|
#include "fmacros.h"
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#ifndef _WIN32
|
||||||
#include <strings.h>
|
#include <strings.h>
|
||||||
|
#else
|
||||||
|
#define strcasecmp stricmp
|
||||||
|
#define strncasecmp strnicmp
|
||||||
|
#endif
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
@ -40,23 +45,42 @@
|
|||||||
#include "net.h"
|
#include "net.h"
|
||||||
#include "dict.c"
|
#include "dict.c"
|
||||||
#include "sds.h"
|
#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); \
|
if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \
|
||||||
} while(0)
|
} while (0)
|
||||||
#define _EL_DEL_READ(ctx) do { \
|
#define _EL_DEL_READ(ctx) do { \
|
||||||
if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \
|
if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \
|
||||||
} while(0)
|
} 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); \
|
if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \
|
||||||
} while(0)
|
} while (0)
|
||||||
#define _EL_DEL_WRITE(ctx) do { \
|
#define _EL_DEL_WRITE(ctx) do { \
|
||||||
if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \
|
if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \
|
||||||
} while(0)
|
} while(0)
|
||||||
#define _EL_CLEANUP(ctx) do { \
|
#define _EL_CLEANUP(ctx) do { \
|
||||||
if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \
|
if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \
|
||||||
|
ctx->ev.cleanup = NULL; \
|
||||||
} while(0);
|
} 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 */
|
/* Forward declaration of function in hiredis.c */
|
||||||
int __redisAppendCommand(redisContext *c, const char *cmd, size_t len);
|
int __redisAppendCommand(redisContext *c, const char *cmd, size_t len);
|
||||||
|
|
||||||
@ -126,6 +150,7 @@ static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
|
|||||||
ac->ev.addWrite = NULL;
|
ac->ev.addWrite = NULL;
|
||||||
ac->ev.delWrite = NULL;
|
ac->ev.delWrite = NULL;
|
||||||
ac->ev.cleanup = NULL;
|
ac->ev.cleanup = NULL;
|
||||||
|
ac->ev.scheduleTimer = NULL;
|
||||||
|
|
||||||
ac->onConnect = NULL;
|
ac->onConnect = NULL;
|
||||||
ac->onDisconnect = NULL;
|
ac->onDisconnect = NULL;
|
||||||
@ -150,56 +175,52 @@ static void __redisAsyncCopyError(redisAsyncContext *ac) {
|
|||||||
ac->errstr = c->errstr;
|
ac->errstr = c->errstr;
|
||||||
}
|
}
|
||||||
|
|
||||||
redisAsyncContext *redisAsyncConnect(const char *ip, int port) {
|
redisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options) {
|
||||||
|
redisOptions myOptions = *options;
|
||||||
redisContext *c;
|
redisContext *c;
|
||||||
redisAsyncContext *ac;
|
redisAsyncContext *ac;
|
||||||
|
|
||||||
c = redisConnectNonBlock(ip,port);
|
myOptions.options |= REDIS_OPT_NONBLOCK;
|
||||||
if (c == NULL)
|
c = redisConnectWithOptions(&myOptions);
|
||||||
|
if (c == NULL) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
}
|
||||||
ac = redisAsyncInitialize(c);
|
ac = redisAsyncInitialize(c);
|
||||||
if (ac == NULL) {
|
if (ac == NULL) {
|
||||||
redisFree(c);
|
redisFree(c);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
__redisAsyncCopyError(ac);
|
__redisAsyncCopyError(ac);
|
||||||
return 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,
|
redisAsyncContext *redisAsyncConnectBind(const char *ip, int port,
|
||||||
const char *source_addr) {
|
const char *source_addr) {
|
||||||
redisContext *c = redisConnectBindNonBlock(ip,port,source_addr);
|
redisOptions options = {0};
|
||||||
redisAsyncContext *ac = redisAsyncInitialize(c);
|
REDIS_OPTIONS_SET_TCP(&options, ip, port);
|
||||||
__redisAsyncCopyError(ac);
|
options.endpoint.tcp.source_addr = source_addr;
|
||||||
return ac;
|
return redisAsyncConnectWithOptions(&options);
|
||||||
}
|
}
|
||||||
|
|
||||||
redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
|
redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
|
||||||
const char *source_addr) {
|
const char *source_addr) {
|
||||||
redisContext *c = redisConnectBindNonBlockWithReuse(ip,port,source_addr);
|
redisOptions options = {0};
|
||||||
redisAsyncContext *ac = redisAsyncInitialize(c);
|
REDIS_OPTIONS_SET_TCP(&options, ip, port);
|
||||||
__redisAsyncCopyError(ac);
|
options.options |= REDIS_OPT_REUSEADDR;
|
||||||
return ac;
|
options.endpoint.tcp.source_addr = source_addr;
|
||||||
|
return redisAsyncConnectWithOptions(&options);
|
||||||
}
|
}
|
||||||
|
|
||||||
redisAsyncContext *redisAsyncConnectUnix(const char *path) {
|
redisAsyncContext *redisAsyncConnectUnix(const char *path) {
|
||||||
redisContext *c;
|
redisOptions options = {0};
|
||||||
redisAsyncContext *ac;
|
REDIS_OPTIONS_SET_UNIX(&options, path);
|
||||||
|
return redisAsyncConnectWithOptions(&options);
|
||||||
c = redisConnectUnixNonBlock(path);
|
|
||||||
if (c == NULL)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
ac = redisAsyncInitialize(c);
|
|
||||||
if (ac == NULL) {
|
|
||||||
redisFree(c);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
__redisAsyncCopyError(ac);
|
|
||||||
return ac;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) {
|
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) {
|
||||||
@ -344,9 +365,15 @@ static void __redisAsyncDisconnect(redisAsyncContext *ac) {
|
|||||||
c->flags |= REDIS_DISCONNECTING;
|
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
|
/* For non-clean disconnects, __redisAsyncFree() will execute pending
|
||||||
* callbacks with a NULL-reply. */
|
* 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
|
/* Tries to do a clean disconnect from Redis, meaning it stops new commands
|
||||||
@ -358,6 +385,9 @@ static void __redisAsyncDisconnect(redisAsyncContext *ac) {
|
|||||||
void redisAsyncDisconnect(redisAsyncContext *ac) {
|
void redisAsyncDisconnect(redisAsyncContext *ac) {
|
||||||
redisContext *c = &(ac->c);
|
redisContext *c = &(ac->c);
|
||||||
c->flags |= REDIS_DISCONNECTING;
|
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)
|
if (!(c->flags & REDIS_IN_CALLBACK) && ac->replies.head == NULL)
|
||||||
__redisAsyncDisconnect(ac);
|
__redisAsyncDisconnect(ac);
|
||||||
}
|
}
|
||||||
@ -408,7 +438,7 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply,
|
|||||||
assert(reply->element[2]->type == REDIS_REPLY_INTEGER);
|
assert(reply->element[2]->type == REDIS_REPLY_INTEGER);
|
||||||
|
|
||||||
/* Unset subscribed flag only when no pipelined pending subscribe. */
|
/* 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.channels) == 0
|
||||||
&& dictSize(ac->sub.patterns) == 0)
|
&& dictSize(ac->sub.patterns) == 0)
|
||||||
c->flags &= ~REDIS_SUBSCRIBED;
|
c->flags &= ~REDIS_SUBSCRIBED;
|
||||||
@ -506,22 +536,92 @@ void redisProcessCallbacks(redisAsyncContext *ac) {
|
|||||||
* write event fires. When connecting was not successful, the connect callback
|
* write event fires. When connecting was not successful, the connect callback
|
||||||
* is called with a REDIS_ERR status and the context is free'd. */
|
* is called with a REDIS_ERR status and the context is free'd. */
|
||||||
static int __redisAsyncHandleConnect(redisAsyncContext *ac) {
|
static int __redisAsyncHandleConnect(redisAsyncContext *ac) {
|
||||||
|
int completed = 0;
|
||||||
redisContext *c = &(ac->c);
|
redisContext *c = &(ac->c);
|
||||||
|
if (redisCheckConnectDone(c, &completed) == REDIS_ERR) {
|
||||||
if (redisCheckSocketError(c) == REDIS_ERR) {
|
/* Error! */
|
||||||
/* Try again later when connect(2) is still in progress. */
|
redisCheckSocketError(c);
|
||||||
if (errno == EINPROGRESS)
|
if (ac->onConnect) ac->onConnect(ac, REDIS_ERR);
|
||||||
return REDIS_OK;
|
|
||||||
|
|
||||||
if (ac->onConnect) ac->onConnect(ac,REDIS_ERR);
|
|
||||||
__redisAsyncDisconnect(ac);
|
__redisAsyncDisconnect(ac);
|
||||||
return REDIS_ERR;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Mark context as connected. */
|
rv = redisBufferRead(c);
|
||||||
c->flags |= REDIS_CONNECTED;
|
if (rv == REDIS_ERR) {
|
||||||
if (ac->onConnect) ac->onConnect(ac,REDIS_OK);
|
__redisAsyncDisconnect(ac);
|
||||||
return REDIS_OK;
|
} 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This function should be called when the socket is readable.
|
/* This function should be called when the socket is readable.
|
||||||
@ -539,6 +639,11 @@ void redisAsyncHandleRead(redisAsyncContext *ac) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (c->flags & REDIS_SSL) {
|
||||||
|
asyncSslRead(ac);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (redisBufferRead(c) == REDIS_ERR) {
|
if (redisBufferRead(c) == REDIS_ERR) {
|
||||||
__redisAsyncDisconnect(ac);
|
__redisAsyncDisconnect(ac);
|
||||||
} else {
|
} else {
|
||||||
@ -561,6 +666,11 @@ void redisAsyncHandleWrite(redisAsyncContext *ac) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (c->flags & REDIS_SSL) {
|
||||||
|
asyncSslWrite(ac);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (redisBufferWrite(c,&done) == REDIS_ERR) {
|
if (redisBufferWrite(c,&done) == REDIS_ERR) {
|
||||||
__redisAsyncDisconnect(ac);
|
__redisAsyncDisconnect(ac);
|
||||||
} else {
|
} else {
|
||||||
@ -575,6 +685,36 @@ 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: Don't automatically sever the connection,
|
||||||
|
* rather, allow to ignore <x> responses before the queue is clear
|
||||||
|
*/
|
||||||
|
__redisAsyncDisconnect(ac);
|
||||||
|
}
|
||||||
|
|
||||||
/* Sets a pointer to the first argument and its length starting at p. Returns
|
/* 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. */
|
* 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) {
|
static const char *nextArgument(const char *start, const char **str, size_t *len) {
|
||||||
@ -714,3 +854,16 @@ int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
|
|||||||
int status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
|
int status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
|
||||||
return status;
|
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;
|
||||||
|
}
|
||||||
|
10
async.h
10
async.h
@ -57,6 +57,7 @@ typedef struct redisCallbackList {
|
|||||||
/* Connection callback prototypes */
|
/* Connection callback prototypes */
|
||||||
typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status);
|
typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status);
|
||||||
typedef void (redisConnectCallback)(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 */
|
/* Context for an async connection to Redis */
|
||||||
typedef struct redisAsyncContext {
|
typedef struct redisAsyncContext {
|
||||||
@ -81,6 +82,7 @@ typedef struct redisAsyncContext {
|
|||||||
void (*addWrite)(void *privdata);
|
void (*addWrite)(void *privdata);
|
||||||
void (*delWrite)(void *privdata);
|
void (*delWrite)(void *privdata);
|
||||||
void (*cleanup)(void *privdata);
|
void (*cleanup)(void *privdata);
|
||||||
|
void (*scheduleTimer)(void *privdata, struct timeval tv);
|
||||||
} ev;
|
} ev;
|
||||||
|
|
||||||
/* Called when either the connection is terminated due to an error or per
|
/* Called when either the connection is terminated due to an error or per
|
||||||
@ -93,6 +95,10 @@ typedef struct redisAsyncContext {
|
|||||||
/* Regular command callbacks */
|
/* Regular command callbacks */
|
||||||
redisCallbackList replies;
|
redisCallbackList replies;
|
||||||
|
|
||||||
|
/* Address used for connect() */
|
||||||
|
struct sockaddr *saddr;
|
||||||
|
size_t addrlen;
|
||||||
|
|
||||||
/* Subscription callbacks */
|
/* Subscription callbacks */
|
||||||
struct {
|
struct {
|
||||||
redisCallbackList invalid;
|
redisCallbackList invalid;
|
||||||
@ -102,6 +108,7 @@ typedef struct redisAsyncContext {
|
|||||||
} redisAsyncContext;
|
} redisAsyncContext;
|
||||||
|
|
||||||
/* Functions that proxy to hiredis */
|
/* Functions that proxy to hiredis */
|
||||||
|
redisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options);
|
||||||
redisAsyncContext *redisAsyncConnect(const char *ip, int port);
|
redisAsyncContext *redisAsyncConnect(const char *ip, int port);
|
||||||
redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, const char *source_addr);
|
redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, const char *source_addr);
|
||||||
redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
|
redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
|
||||||
@ -109,12 +116,15 @@ redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
|
|||||||
redisAsyncContext *redisAsyncConnectUnix(const char *path);
|
redisAsyncContext *redisAsyncConnectUnix(const char *path);
|
||||||
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn);
|
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn);
|
||||||
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
|
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
|
||||||
|
|
||||||
|
void redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv);
|
||||||
void redisAsyncDisconnect(redisAsyncContext *ac);
|
void redisAsyncDisconnect(redisAsyncContext *ac);
|
||||||
void redisAsyncFree(redisAsyncContext *ac);
|
void redisAsyncFree(redisAsyncContext *ac);
|
||||||
|
|
||||||
/* Handle read/write events */
|
/* Handle read/write events */
|
||||||
void redisAsyncHandleRead(redisAsyncContext *ac);
|
void redisAsyncHandleRead(redisAsyncContext *ac);
|
||||||
void redisAsyncHandleWrite(redisAsyncContext *ac);
|
void redisAsyncHandleWrite(redisAsyncContext *ac);
|
||||||
|
void redisAsyncHandleTimeout(redisAsyncContext *ac);
|
||||||
|
|
||||||
/* Command functions for an async context. Write the command to the
|
/* Command functions for an async context. Write the command to the
|
||||||
* output buffer and register the provided callback. */
|
* output buffer and register the provided callback. */
|
||||||
|
46
examples/CMakeLists.txt
Normal file
46
examples/CMakeLists.txt
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
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()
|
||||||
|
|
||||||
|
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)
|
72
examples/example-libevent-ssl.c
Normal file
72
examples/example-libevent-ssl.c
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
|
#include <hiredis.h>
|
||||||
|
#include <async.h>
|
||||||
|
#include <adapters/libevent.h>
|
||||||
|
|
||||||
|
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 <key> <host> <port> <cert> <certKey> [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, "sni") != 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;
|
||||||
|
}
|
@ -9,7 +9,12 @@
|
|||||||
|
|
||||||
void getCallback(redisAsyncContext *c, void *r, void *privdata) {
|
void getCallback(redisAsyncContext *c, void *r, void *privdata) {
|
||||||
redisReply *reply = r;
|
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);
|
printf("argv[%s]: %s\n", (char*)privdata, reply->str);
|
||||||
|
|
||||||
/* Disconnect after receiving the reply to GET */
|
/* Disconnect after receiving the reply to GET */
|
||||||
@ -35,8 +40,14 @@ void disconnectCallback(const redisAsyncContext *c, int status) {
|
|||||||
int main (int argc, char **argv) {
|
int main (int argc, char **argv) {
|
||||||
signal(SIGPIPE, SIG_IGN);
|
signal(SIGPIPE, SIG_IGN);
|
||||||
struct event_base *base = event_base_new();
|
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) {
|
if (c->err) {
|
||||||
/* Let *c leak for now... */
|
/* Let *c leak for now... */
|
||||||
printf("Error: %s\n", c->errstr);
|
printf("Error: %s\n", c->errstr);
|
||||||
|
96
examples/example-ssl.c
Normal file
96
examples/example-ssl.c
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <hiredis.h>
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
unsigned int j;
|
||||||
|
redisContext *c;
|
||||||
|
redisReply *reply;
|
||||||
|
if (argc < 4) {
|
||||||
|
printf("Usage: %s <host> <port> <cert> <key> [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 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);
|
||||||
|
redisFree(c);
|
||||||
|
} else {
|
||||||
|
printf("Connection error: can't allocate redis context\n");
|
||||||
|
}
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (redisSecureConnection(c, ca, cert, key, "sni") != 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;
|
||||||
|
}
|
@ -5,14 +5,27 @@
|
|||||||
#include <hiredis.h>
|
#include <hiredis.h>
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
unsigned int j;
|
unsigned int j, isunix = 0;
|
||||||
redisContext *c;
|
redisContext *c;
|
||||||
redisReply *reply;
|
redisReply *reply;
|
||||||
const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1";
|
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;
|
int port = (argc > 2) ? atoi(argv[2]) : 6379;
|
||||||
|
|
||||||
struct timeval timeout = { 1, 500000 }; // 1.5 seconds
|
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 == NULL || c->err) {
|
||||||
if (c) {
|
if (c) {
|
||||||
printf("Connection error: %s\n", c->errstr);
|
printf("Connection error: %s\n", c->errstr);
|
||||||
|
215
hiredis.c
215
hiredis.c
@ -34,7 +34,6 @@
|
|||||||
#include "fmacros.h"
|
#include "fmacros.h"
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <unistd.h>
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
@ -42,6 +41,8 @@
|
|||||||
#include "hiredis.h"
|
#include "hiredis.h"
|
||||||
#include "net.h"
|
#include "net.h"
|
||||||
#include "sds.h"
|
#include "sds.h"
|
||||||
|
#include "sslio.h"
|
||||||
|
#include "win32.h"
|
||||||
|
|
||||||
static redisReply *createReplyObject(int type);
|
static redisReply *createReplyObject(int type);
|
||||||
static void *createStringObject(const redisReadTask *task, char *str, size_t len);
|
static void *createStringObject(const redisReadTask *task, char *str, size_t len);
|
||||||
@ -583,41 +584,47 @@ redisReader *redisReaderCreate(void) {
|
|||||||
return redisReaderCreateWithFunctions(&defaultFunctions);
|
return redisReaderCreateWithFunctions(&defaultFunctions);
|
||||||
}
|
}
|
||||||
|
|
||||||
static redisContext *redisContextInit(void) {
|
static redisContext *redisContextInit(const redisOptions *options) {
|
||||||
redisContext *c;
|
redisContext *c;
|
||||||
|
|
||||||
c = calloc(1,sizeof(redisContext));
|
c = calloc(1, sizeof(*c));
|
||||||
if (c == NULL)
|
if (c == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
c->obuf = sdsempty();
|
c->obuf = sdsempty();
|
||||||
c->reader = redisReaderCreate();
|
c->reader = redisReaderCreate();
|
||||||
|
c->fd = REDIS_INVALID_FD;
|
||||||
|
|
||||||
if (c->obuf == NULL || c->reader == NULL) {
|
if (c->obuf == NULL || c->reader == NULL) {
|
||||||
redisFree(c);
|
redisFree(c);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
(void)options; /* options are used in other functions */
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
void redisFree(redisContext *c) {
|
void redisFree(redisContext *c) {
|
||||||
if (c == NULL)
|
if (c == NULL)
|
||||||
return;
|
return;
|
||||||
if (c->fd > 0)
|
redisNetClose(c);
|
||||||
close(c->fd);
|
|
||||||
sdsfree(c->obuf);
|
sdsfree(c->obuf);
|
||||||
redisReaderFree(c->reader);
|
redisReaderFree(c->reader);
|
||||||
free(c->tcp.host);
|
free(c->tcp.host);
|
||||||
free(c->tcp.source_addr);
|
free(c->tcp.source_addr);
|
||||||
free(c->unix_sock.path);
|
free(c->unix_sock.path);
|
||||||
free(c->timeout);
|
free(c->timeout);
|
||||||
|
free(c->saddr);
|
||||||
|
if (c->ssl) {
|
||||||
|
redisFreeSsl(c->ssl);
|
||||||
|
}
|
||||||
|
memset(c, 0xff, sizeof(*c));
|
||||||
free(c);
|
free(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
int redisFreeKeepFd(redisContext *c) {
|
redisFD redisFreeKeepFd(redisContext *c) {
|
||||||
int fd = c->fd;
|
redisFD fd = c->fd;
|
||||||
c->fd = -1;
|
c->fd = REDIS_INVALID_FD;
|
||||||
redisFree(c);
|
redisFree(c);
|
||||||
return fd;
|
return fd;
|
||||||
}
|
}
|
||||||
@ -626,9 +633,7 @@ int redisReconnect(redisContext *c) {
|
|||||||
c->err = 0;
|
c->err = 0;
|
||||||
memset(c->errstr, '\0', strlen(c->errstr));
|
memset(c->errstr, '\0', strlen(c->errstr));
|
||||||
|
|
||||||
if (c->fd > 0) {
|
redisNetClose(c);
|
||||||
close(c->fd);
|
|
||||||
}
|
|
||||||
|
|
||||||
sdsfree(c->obuf);
|
sdsfree(c->obuf);
|
||||||
redisReaderFree(c->reader);
|
redisReaderFree(c->reader);
|
||||||
@ -650,112 +655,112 @@ int redisReconnect(redisContext *c) {
|
|||||||
return REDIS_ERR;
|
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->options & REDIS_OPT_NOAUTOFREE) {
|
||||||
|
c->flags |= REDIS_NO_AUTO_FREE;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
if (options->timeout != NULL && (c->flags & REDIS_BLOCK) && c->fd != REDIS_INVALID_FD) {
|
||||||
|
redisContextSetTimeout(c, *options->timeout);
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
/* Connect to a Redis instance. On error the field error in the returned
|
/* 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.
|
* 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. */
|
* When no set of reply functions is given, the default set will be used. */
|
||||||
redisContext *redisConnect(const char *ip, int port) {
|
redisContext *redisConnect(const char *ip, int port) {
|
||||||
redisContext *c;
|
redisOptions options = {0};
|
||||||
|
REDIS_OPTIONS_SET_TCP(&options, ip, port);
|
||||||
c = redisContextInit();
|
return redisConnectWithOptions(&options);
|
||||||
if (c == NULL)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
c->flags |= REDIS_BLOCK;
|
|
||||||
redisContextConnectTcp(c,ip,port,NULL);
|
|
||||||
return c;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv) {
|
redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv) {
|
||||||
redisContext *c;
|
redisOptions options = {0};
|
||||||
|
REDIS_OPTIONS_SET_TCP(&options, ip, port);
|
||||||
c = redisContextInit();
|
options.timeout = &tv;
|
||||||
if (c == NULL)
|
return redisConnectWithOptions(&options);
|
||||||
return NULL;
|
|
||||||
|
|
||||||
c->flags |= REDIS_BLOCK;
|
|
||||||
redisContextConnectTcp(c,ip,port,&tv);
|
|
||||||
return c;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
redisContext *redisConnectNonBlock(const char *ip, int port) {
|
redisContext *redisConnectNonBlock(const char *ip, int port) {
|
||||||
redisContext *c;
|
redisOptions options = {0};
|
||||||
|
REDIS_OPTIONS_SET_TCP(&options, ip, port);
|
||||||
c = redisContextInit();
|
options.options |= REDIS_OPT_NONBLOCK;
|
||||||
if (c == NULL)
|
return redisConnectWithOptions(&options);
|
||||||
return NULL;
|
|
||||||
|
|
||||||
c->flags &= ~REDIS_BLOCK;
|
|
||||||
redisContextConnectTcp(c,ip,port,NULL);
|
|
||||||
return c;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
redisContext *redisConnectBindNonBlock(const char *ip, int port,
|
redisContext *redisConnectBindNonBlock(const char *ip, int port,
|
||||||
const char *source_addr) {
|
const char *source_addr) {
|
||||||
redisContext *c = redisContextInit();
|
redisOptions options = {0};
|
||||||
if (c == NULL)
|
REDIS_OPTIONS_SET_TCP(&options, ip, port);
|
||||||
return NULL;
|
options.endpoint.tcp.source_addr = source_addr;
|
||||||
c->flags &= ~REDIS_BLOCK;
|
options.options |= REDIS_OPT_NONBLOCK;
|
||||||
redisContextConnectBindTcp(c,ip,port,NULL,source_addr);
|
return redisConnectWithOptions(&options);
|
||||||
return c;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port,
|
redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port,
|
||||||
const char *source_addr) {
|
const char *source_addr) {
|
||||||
redisContext *c = redisContextInit();
|
redisOptions options = {0};
|
||||||
if (c == NULL)
|
REDIS_OPTIONS_SET_TCP(&options, ip, port);
|
||||||
return NULL;
|
options.endpoint.tcp.source_addr = source_addr;
|
||||||
c->flags &= ~REDIS_BLOCK;
|
options.options |= REDIS_OPT_NONBLOCK|REDIS_OPT_REUSEADDR;
|
||||||
c->flags |= REDIS_REUSEADDR;
|
return redisConnectWithOptions(&options);
|
||||||
redisContextConnectBindTcp(c,ip,port,NULL,source_addr);
|
|
||||||
return c;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
redisContext *redisConnectUnix(const char *path) {
|
redisContext *redisConnectUnix(const char *path) {
|
||||||
redisContext *c;
|
redisOptions options = {0};
|
||||||
|
REDIS_OPTIONS_SET_UNIX(&options, path);
|
||||||
c = redisContextInit();
|
return redisConnectWithOptions(&options);
|
||||||
if (c == NULL)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
c->flags |= REDIS_BLOCK;
|
|
||||||
redisContextConnectUnix(c,path,NULL);
|
|
||||||
return c;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv) {
|
redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv) {
|
||||||
redisContext *c;
|
redisOptions options = {0};
|
||||||
|
REDIS_OPTIONS_SET_UNIX(&options, path);
|
||||||
c = redisContextInit();
|
options.timeout = &tv;
|
||||||
if (c == NULL)
|
return redisConnectWithOptions(&options);
|
||||||
return NULL;
|
|
||||||
|
|
||||||
c->flags |= REDIS_BLOCK;
|
|
||||||
redisContextConnectUnix(c,path,&tv);
|
|
||||||
return c;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
redisContext *redisConnectUnixNonBlock(const char *path) {
|
redisContext *redisConnectUnixNonBlock(const char *path) {
|
||||||
redisContext *c;
|
redisOptions options = {0};
|
||||||
|
REDIS_OPTIONS_SET_UNIX(&options, path);
|
||||||
c = redisContextInit();
|
options.options |= REDIS_OPT_NONBLOCK;
|
||||||
if (c == NULL)
|
return redisConnectWithOptions(&options);
|
||||||
return NULL;
|
|
||||||
|
|
||||||
c->flags &= ~REDIS_BLOCK;
|
|
||||||
redisContextConnectUnix(c,path,NULL);
|
|
||||||
return c;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
redisContext *redisConnectFd(int fd) {
|
redisContext *redisConnectFd(redisFD fd) {
|
||||||
redisContext *c;
|
redisOptions options = {0};
|
||||||
|
options.type = REDIS_CONN_USERFD;
|
||||||
|
options.endpoint.fd = fd;
|
||||||
|
return redisConnectWithOptions(&options);
|
||||||
|
}
|
||||||
|
|
||||||
c = redisContextInit();
|
int redisSecureConnection(redisContext *c, const char *caPath,
|
||||||
if (c == NULL)
|
const char *certPath, const char *keyPath, const char *servername) {
|
||||||
return NULL;
|
return redisSslCreate(c, caPath, certPath, keyPath, servername);
|
||||||
|
|
||||||
c->fd = fd;
|
|
||||||
c->flags |= REDIS_BLOCK | REDIS_CONNECTED;
|
|
||||||
return c;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Set read/write timeout on a blocking socket. */
|
/* Set read/write timeout on a blocking socket. */
|
||||||
@ -775,7 +780,7 @@ int redisEnableKeepAlive(redisContext *c) {
|
|||||||
/* Use this function to handle a read event on the descriptor. It will try
|
/* 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.
|
* 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. */
|
* see if there is a reply available. */
|
||||||
int redisBufferRead(redisContext *c) {
|
int redisBufferRead(redisContext *c) {
|
||||||
char buf[1024*16];
|
char buf[1024*16];
|
||||||
@ -785,22 +790,16 @@ int redisBufferRead(redisContext *c) {
|
|||||||
if (c->err)
|
if (c->err)
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
|
|
||||||
nread = read(c->fd,buf,sizeof(buf));
|
nread = c->flags & REDIS_SSL ?
|
||||||
if (nread == -1) {
|
redisSslRead(c, buf, sizeof(buf)) : redisNetRead(c, buf, sizeof(buf));
|
||||||
if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
|
if (nread > 0) {
|
||||||
/* Try again later */
|
if (redisReaderFeed(c->reader, buf, nread) != REDIS_OK) {
|
||||||
|
__redisSetError(c, c->reader->err, c->reader->errstr);
|
||||||
|
return REDIS_ERR;
|
||||||
} else {
|
} else {
|
||||||
__redisSetError(c,REDIS_ERR_IO,NULL);
|
|
||||||
return REDIS_ERR;
|
|
||||||
}
|
}
|
||||||
} else if (nread == 0) {
|
} else if (nread < 0) {
|
||||||
__redisSetError(c,REDIS_ERR_EOF,"Server closed the connection");
|
|
||||||
return REDIS_ERR;
|
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;
|
return REDIS_OK;
|
||||||
}
|
}
|
||||||
@ -815,21 +814,15 @@ int redisBufferRead(redisContext *c) {
|
|||||||
* c->errstr to hold the appropriate error string.
|
* c->errstr to hold the appropriate error string.
|
||||||
*/
|
*/
|
||||||
int redisBufferWrite(redisContext *c, int *done) {
|
int redisBufferWrite(redisContext *c, int *done) {
|
||||||
int nwritten;
|
|
||||||
|
|
||||||
/* Return early when the context has seen an error. */
|
/* Return early when the context has seen an error. */
|
||||||
if (c->err)
|
if (c->err)
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
|
|
||||||
if (sdslen(c->obuf) > 0) {
|
if (sdslen(c->obuf) > 0) {
|
||||||
nwritten = write(c->fd,c->obuf,sdslen(c->obuf));
|
int nwritten = (c->flags & REDIS_SSL) ? redisSslWrite(c) : redisNetWrite(c);
|
||||||
if (nwritten == -1) {
|
if (nwritten < 0) {
|
||||||
if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
|
return REDIS_ERR;
|
||||||
/* Try again later */
|
|
||||||
} else {
|
|
||||||
__redisSetError(c,REDIS_ERR_IO,NULL);
|
|
||||||
return REDIS_ERR;
|
|
||||||
}
|
|
||||||
} else if (nwritten > 0) {
|
} else if (nwritten > 0) {
|
||||||
if (nwritten == (signed)sdslen(c->obuf)) {
|
if (nwritten == (signed)sdslen(c->obuf)) {
|
||||||
sdsfree(c->obuf);
|
sdsfree(c->obuf);
|
||||||
|
107
hiredis.h
107
hiredis.h
@ -35,14 +35,18 @@
|
|||||||
#define __HIREDIS_H
|
#define __HIREDIS_H
|
||||||
#include "read.h"
|
#include "read.h"
|
||||||
#include <stdarg.h> /* for va_list */
|
#include <stdarg.h> /* for va_list */
|
||||||
|
#ifndef _WIN32
|
||||||
#include <sys/time.h> /* for struct timeval */
|
#include <sys/time.h> /* for struct timeval */
|
||||||
|
#else
|
||||||
|
#include <winsock2.h>
|
||||||
|
#endif
|
||||||
#include <stdint.h> /* uintXX_t, etc */
|
#include <stdint.h> /* uintXX_t, etc */
|
||||||
#include "sds.h" /* for sds */
|
#include "sds.h" /* for sds */
|
||||||
|
|
||||||
#define HIREDIS_MAJOR 0
|
#define HIREDIS_MAJOR 0
|
||||||
#define HIREDIS_MINOR 13
|
#define HIREDIS_MINOR 14
|
||||||
#define HIREDIS_PATCH 3
|
#define HIREDIS_PATCH 0
|
||||||
#define HIREDIS_SONAME 0.13
|
#define HIREDIS_SONAME 0.14
|
||||||
|
|
||||||
/* Connection type can be blocking or non-blocking and is set in the
|
/* Connection type can be blocking or non-blocking and is set in the
|
||||||
* least significant bit of the flags field in redisContext. */
|
* least significant bit of the flags field in redisContext. */
|
||||||
@ -74,6 +78,15 @@
|
|||||||
/* Flag that is set when we should set SO_REUSEADDR before calling bind() */
|
/* Flag that is set when we should set SO_REUSEADDR before calling bind() */
|
||||||
#define REDIS_REUSEADDR 0x80
|
#define REDIS_REUSEADDR 0x80
|
||||||
|
|
||||||
|
/* Flag that is set when 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 */
|
#define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */
|
||||||
|
|
||||||
/* number of times we retry to connect in the case of EADDRNOTAVAIL and
|
/* number of times we retry to connect in the case of EADDRNOTAVAIL and
|
||||||
@ -109,14 +122,80 @@ void redisFreeSdsCommand(sds cmd);
|
|||||||
|
|
||||||
enum redisConnectionType {
|
enum redisConnectionType {
|
||||||
REDIS_CONN_TCP,
|
REDIS_CONN_TCP,
|
||||||
REDIS_CONN_UNIX
|
REDIS_CONN_UNIX,
|
||||||
|
REDIS_CONN_USERFD
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
/* 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 {
|
||||||
|
/*
|
||||||
|
* 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 */
|
||||||
|
redisFD 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 */
|
/* Context for a connection to Redis */
|
||||||
typedef struct redisContext {
|
typedef struct redisContext {
|
||||||
int err; /* Error flags, 0 when there is no error */
|
int err; /* Error flags, 0 when there is no error */
|
||||||
char errstr[128]; /* String representation of error when applicable */
|
char errstr[128]; /* String representation of error when applicable */
|
||||||
int fd;
|
redisFD fd;
|
||||||
int flags;
|
int flags;
|
||||||
char *obuf; /* Write buffer */
|
char *obuf; /* Write buffer */
|
||||||
redisReader *reader; /* Protocol reader */
|
redisReader *reader; /* Protocol reader */
|
||||||
@ -134,8 +213,15 @@ typedef struct redisContext {
|
|||||||
char *path;
|
char *path;
|
||||||
} unix_sock;
|
} unix_sock;
|
||||||
|
|
||||||
|
/* For non-blocking connect */
|
||||||
|
struct sockadr *saddr;
|
||||||
|
size_t addrlen;
|
||||||
|
/* For SSL communication */
|
||||||
|
struct redisSsl *ssl;
|
||||||
|
|
||||||
} redisContext;
|
} redisContext;
|
||||||
|
|
||||||
|
redisContext *redisConnectWithOptions(const redisOptions *options);
|
||||||
redisContext *redisConnect(const char *ip, int port);
|
redisContext *redisConnect(const char *ip, int port);
|
||||||
redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv);
|
redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv);
|
||||||
redisContext *redisConnectNonBlock(const char *ip, int port);
|
redisContext *redisConnectNonBlock(const char *ip, int port);
|
||||||
@ -146,7 +232,14 @@ redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port,
|
|||||||
redisContext *redisConnectUnix(const char *path);
|
redisContext *redisConnectUnix(const char *path);
|
||||||
redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv);
|
redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv);
|
||||||
redisContext *redisConnectUnixNonBlock(const char *path);
|
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
|
||||||
|
* executed on the connection.
|
||||||
|
*/
|
||||||
|
int redisSecureConnection(redisContext *c, const char *capath, const char *certpath,
|
||||||
|
const char *keypath, const char *servername);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reconnect the given context using the saved information.
|
* Reconnect the given context using the saved information.
|
||||||
@ -162,7 +255,7 @@ int redisReconnect(redisContext *c);
|
|||||||
int redisSetTimeout(redisContext *c, const struct timeval tv);
|
int redisSetTimeout(redisContext *c, const struct timeval tv);
|
||||||
int redisEnableKeepAlive(redisContext *c);
|
int redisEnableKeepAlive(redisContext *c);
|
||||||
void redisFree(redisContext *c);
|
void redisFree(redisContext *c);
|
||||||
int redisFreeKeepFd(redisContext *c);
|
redisFD redisFreeKeepFd(redisContext *c);
|
||||||
int redisBufferRead(redisContext *c);
|
int redisBufferRead(redisContext *c);
|
||||||
int redisBufferWrite(redisContext *c, int *done);
|
int redisBufferWrite(redisContext *c, int *done);
|
||||||
|
|
||||||
|
11
hiredis.pc.in
Normal file
11
hiredis.pc.in
Normal file
@ -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: @PROJECT_VERSION@
|
||||||
|
Libs: -L${libdir} -lhiredis
|
||||||
|
Cflags: -I${pkgincludedir} -D_FILE_OFFSET_BITS=64
|
151
net.c
151
net.c
@ -34,36 +34,60 @@
|
|||||||
|
|
||||||
#include "fmacros.h"
|
#include "fmacros.h"
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <sys/select.h>
|
|
||||||
#include <sys/un.h>
|
|
||||||
#include <netinet/in.h>
|
|
||||||
#include <netinet/tcp.h>
|
|
||||||
#include <arpa/inet.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <netdb.h>
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <poll.h>
|
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
#include "net.h"
|
#include "net.h"
|
||||||
#include "sds.h"
|
#include "sds.h"
|
||||||
|
#include "sockcompat.h"
|
||||||
|
#include "win32.h"
|
||||||
|
|
||||||
/* Defined in hiredis.c */
|
/* Defined in hiredis.c */
|
||||||
void __redisSetError(redisContext *c, int type, const char *str);
|
void __redisSetError(redisContext *c, int type, const char *str);
|
||||||
|
|
||||||
static void redisContextCloseFd(redisContext *c) {
|
void redisNetClose(redisContext *c) {
|
||||||
if (c && c->fd >= 0) {
|
if (c && c->fd != REDIS_INVALID_FD) {
|
||||||
close(c->fd);
|
close(c->fd);
|
||||||
c->fd = -1;
|
c->fd = REDIS_INVALID_FD;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int redisNetRead(redisContext *c, char *buf, size_t bufcap) {
|
||||||
|
int nread = recv(c->fd, buf, bufcap, 0);
|
||||||
|
if (nread == -1) {
|
||||||
|
if ((errno == EWOULDBLOCK && !(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 = send(c->fd, c->obuf, sdslen(c->obuf), 0);
|
||||||
|
if (nwritten < 0) {
|
||||||
|
if ((errno == EWOULDBLOCK && !(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) {
|
static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) {
|
||||||
int errorno = errno; /* snprintf() may change errno */
|
int errorno = errno; /* snprintf() may change errno */
|
||||||
char buf[128] = { 0 };
|
char buf[128] = { 0 };
|
||||||
@ -79,15 +103,15 @@ static int redisSetReuseAddr(redisContext *c) {
|
|||||||
int on = 1;
|
int on = 1;
|
||||||
if (setsockopt(c->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
|
if (setsockopt(c->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
|
||||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
|
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
|
||||||
redisContextCloseFd(c);
|
redisNetClose(c);
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
}
|
}
|
||||||
return REDIS_OK;
|
return REDIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int redisCreateSocket(redisContext *c, int type) {
|
static int redisCreateSocket(redisContext *c, int type) {
|
||||||
int s;
|
redisFD s;
|
||||||
if ((s = socket(type, SOCK_STREAM, 0)) == -1) {
|
if ((s = socket(type, SOCK_STREAM, 0)) == REDIS_INVALID_FD) {
|
||||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
|
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
}
|
}
|
||||||
@ -101,6 +125,7 @@ static int redisCreateSocket(redisContext *c, int type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int redisSetBlocking(redisContext *c, int blocking) {
|
static int redisSetBlocking(redisContext *c, int blocking) {
|
||||||
|
#ifndef _WIN32
|
||||||
int flags;
|
int flags;
|
||||||
|
|
||||||
/* Set the socket nonblocking.
|
/* Set the socket nonblocking.
|
||||||
@ -108,7 +133,7 @@ static int redisSetBlocking(redisContext *c, int blocking) {
|
|||||||
* interrupted by a signal. */
|
* interrupted by a signal. */
|
||||||
if ((flags = fcntl(c->fd, F_GETFL)) == -1) {
|
if ((flags = fcntl(c->fd, F_GETFL)) == -1) {
|
||||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)");
|
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)");
|
||||||
redisContextCloseFd(c);
|
redisNetClose(c);
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,15 +144,23 @@ static int redisSetBlocking(redisContext *c, int blocking) {
|
|||||||
|
|
||||||
if (fcntl(c->fd, F_SETFL, flags) == -1) {
|
if (fcntl(c->fd, F_SETFL, flags) == -1) {
|
||||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)");
|
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)");
|
||||||
redisContextCloseFd(c);
|
redisNetClose(c);
|
||||||
return REDIS_ERR;
|
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;
|
return REDIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int redisKeepAlive(redisContext *c, int interval) {
|
int redisKeepAlive(redisContext *c, int interval) {
|
||||||
int val = 1;
|
int val = 1;
|
||||||
int fd = c->fd;
|
redisFD fd = c->fd;
|
||||||
|
|
||||||
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){
|
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){
|
||||||
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
|
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
|
||||||
@ -170,7 +203,7 @@ static int redisSetTcpNoDelay(redisContext *c) {
|
|||||||
int yes = 1;
|
int yes = 1;
|
||||||
if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) {
|
if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) {
|
||||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)");
|
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)");
|
||||||
redisContextCloseFd(c);
|
redisNetClose(c);
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
}
|
}
|
||||||
return REDIS_OK;
|
return REDIS_OK;
|
||||||
@ -212,28 +245,50 @@ static int redisContextWaitReady(redisContext *c, long msec) {
|
|||||||
|
|
||||||
if ((res = poll(wfd, 1, msec)) == -1) {
|
if ((res = poll(wfd, 1, msec)) == -1) {
|
||||||
__redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)");
|
__redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)");
|
||||||
redisContextCloseFd(c);
|
redisNetClose(c);
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
} else if (res == 0) {
|
} else if (res == 0) {
|
||||||
errno = ETIMEDOUT;
|
errno = ETIMEDOUT;
|
||||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
|
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
|
||||||
redisContextCloseFd(c);
|
redisNetClose(c);
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (redisCheckSocketError(c) != REDIS_OK)
|
if (redisCheckConnectDone(c, &res) != REDIS_OK || res == 0) {
|
||||||
|
redisCheckSocketError(c);
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
return REDIS_OK;
|
return REDIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
|
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
|
||||||
redisContextCloseFd(c);
|
redisNetClose(c);
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int redisCheckConnectDone(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 redisCheckSocketError(redisContext *c) {
|
||||||
int err = 0;
|
int err = 0, errno_saved = errno;
|
||||||
socklen_t errlen = sizeof(err);
|
socklen_t errlen = sizeof(err);
|
||||||
|
|
||||||
if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) {
|
if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) {
|
||||||
@ -241,6 +296,10 @@ int redisCheckSocketError(redisContext *c) {
|
|||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (err == 0) {
|
||||||
|
err = errno_saved;
|
||||||
|
}
|
||||||
|
|
||||||
if (err) {
|
if (err) {
|
||||||
errno = err;
|
errno = err;
|
||||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
|
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
|
||||||
@ -265,7 +324,8 @@ int redisContextSetTimeout(redisContext *c, const struct timeval tv) {
|
|||||||
static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
|
static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
|
||||||
const struct timeval *timeout,
|
const struct timeval *timeout,
|
||||||
const char *source_addr) {
|
const char *source_addr) {
|
||||||
int s, rv, n;
|
redisFD s;
|
||||||
|
int rv, n;
|
||||||
char _port[6]; /* strlen("65535"); */
|
char _port[6]; /* strlen("65535"); */
|
||||||
struct addrinfo hints, *servinfo, *bservinfo, *p, *b;
|
struct addrinfo hints, *servinfo, *bservinfo, *p, *b;
|
||||||
int blocking = (c->flags & REDIS_BLOCK);
|
int blocking = (c->flags & REDIS_BLOCK);
|
||||||
@ -334,7 +394,7 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
|
|||||||
}
|
}
|
||||||
for (p = servinfo; p != NULL; p = p->ai_next) {
|
for (p = servinfo; p != NULL; p = p->ai_next) {
|
||||||
addrretry:
|
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;
|
continue;
|
||||||
|
|
||||||
c->fd = s;
|
c->fd = s;
|
||||||
@ -373,20 +433,34 @@ addrretry:
|
|||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* For repeat connection */
|
||||||
|
free(c->saddr);
|
||||||
|
c->saddr = malloc(p->ai_addrlen);
|
||||||
|
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 (connect(s,p->ai_addr,p->ai_addrlen) == -1) {
|
||||||
if (errno == EHOSTUNREACH) {
|
if (errno == EHOSTUNREACH) {
|
||||||
redisContextCloseFd(c);
|
redisNetClose(c);
|
||||||
continue;
|
continue;
|
||||||
} else if (errno == EINPROGRESS && !blocking) {
|
} else if (errno == EINPROGRESS) {
|
||||||
/* This is ok. */
|
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) {
|
} else if (errno == EADDRNOTAVAIL && reuseaddr) {
|
||||||
if (++reuses >= REDIS_CONNECT_RETRIES) {
|
if (++reuses >= REDIS_CONNECT_RETRIES) {
|
||||||
goto error;
|
goto error;
|
||||||
} else {
|
} else {
|
||||||
redisContextCloseFd(c);
|
redisNetClose(c);
|
||||||
goto addrretry;
|
goto addrretry;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
wait_for_ready:
|
||||||
if (redisContextWaitReady(c,timeout_msec) != REDIS_OK)
|
if (redisContextWaitReady(c,timeout_msec) != REDIS_OK)
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
@ -429,8 +503,9 @@ int redisContextConnectBindTcp(redisContext *c, const char *addr, int port,
|
|||||||
}
|
}
|
||||||
|
|
||||||
int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) {
|
int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) {
|
||||||
|
#ifndef _WIN32
|
||||||
int blocking = (c->flags & REDIS_BLOCK);
|
int blocking = (c->flags & REDIS_BLOCK);
|
||||||
struct sockaddr_un sa;
|
struct sockaddr_un *sa;
|
||||||
long timeout_msec = -1;
|
long timeout_msec = -1;
|
||||||
|
|
||||||
if (redisCreateSocket(c,AF_UNIX) < 0)
|
if (redisCreateSocket(c,AF_UNIX) < 0)
|
||||||
@ -457,9 +532,11 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time
|
|||||||
if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK)
|
if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK)
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
|
|
||||||
sa.sun_family = AF_UNIX;
|
sa = (struct sockaddr_un*)(c->saddr = malloc(sizeof(struct sockaddr_un)));
|
||||||
strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1);
|
c->addrlen = sizeof(struct sockaddr_un);
|
||||||
if (connect(c->fd, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
|
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) {
|
if (errno == EINPROGRESS && !blocking) {
|
||||||
/* This is ok. */
|
/* This is ok. */
|
||||||
} else {
|
} else {
|
||||||
@ -474,4 +551,10 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time
|
|||||||
|
|
||||||
c->flags |= REDIS_CONNECTED;
|
c->flags |= REDIS_CONNECTED;
|
||||||
return REDIS_OK;
|
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 */
|
||||||
}
|
}
|
||||||
|
5
net.h
5
net.h
@ -37,6 +37,10 @@
|
|||||||
|
|
||||||
#include "hiredis.h"
|
#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 redisCheckSocketError(redisContext *c);
|
||||||
int redisContextSetTimeout(redisContext *c, const struct timeval tv);
|
int redisContextSetTimeout(redisContext *c, const struct timeval tv);
|
||||||
int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout);
|
int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout);
|
||||||
@ -45,5 +49,6 @@ int redisContextConnectBindTcp(redisContext *c, const char *addr, int port,
|
|||||||
const char *source_addr);
|
const char *source_addr);
|
||||||
int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout);
|
int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout);
|
||||||
int redisKeepAlive(redisContext *c, int interval);
|
int redisKeepAlive(redisContext *c, int interval);
|
||||||
|
int redisCheckConnectDone(redisContext *c, int *completed);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
7
read.c
7
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
|
* 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
|
* you can convert a string into a long long, and obtain back the string
|
||||||
* from the number without any loss in the string representation. */
|
* 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;
|
const char *p = s;
|
||||||
size_t plen = 0;
|
size_t plen = 0;
|
||||||
int negative = 0;
|
int negative = 0;
|
||||||
@ -590,8 +590,11 @@ int redisReaderGetReply(redisReader *r, void **reply) {
|
|||||||
|
|
||||||
/* Emit a reply when there is one. */
|
/* Emit a reply when there is one. */
|
||||||
if (r->ridx == -1) {
|
if (r->ridx == -1) {
|
||||||
if (reply != NULL)
|
if (reply != NULL) {
|
||||||
*reply = r->reply;
|
*reply = r->reply;
|
||||||
|
} else if (r->reply != NULL && r->fn && r->fn->freeObject) {
|
||||||
|
r->fn->freeObject(r->reply);
|
||||||
|
}
|
||||||
r->reply = NULL;
|
r->reply = NULL;
|
||||||
}
|
}
|
||||||
return REDIS_OK;
|
return REDIS_OK;
|
||||||
|
1
read.h
1
read.h
@ -45,6 +45,7 @@
|
|||||||
#define REDIS_ERR_EOF 3 /* End of file */
|
#define REDIS_ERR_EOF 3 /* End of file */
|
||||||
#define REDIS_ERR_PROTOCOL 4 /* Protocol error */
|
#define REDIS_ERR_PROTOCOL 4 /* Protocol error */
|
||||||
#define REDIS_ERR_OOM 5 /* Out of memory */
|
#define REDIS_ERR_OOM 5 /* Out of memory */
|
||||||
|
#define REDIS_ERR_TIMEOUT 6 /* Timed out */
|
||||||
#define REDIS_ERR_OTHER 2 /* Everything else... */
|
#define REDIS_ERR_OTHER 2 /* Everything else... */
|
||||||
|
|
||||||
#define REDIS_REPLY_STRING 1
|
#define REDIS_REPLY_STRING 1
|
||||||
|
29
sds.c
29
sds.c
@ -219,7 +219,10 @@ sds sdsMakeRoomFor(sds s, size_t addlen) {
|
|||||||
hdrlen = sdsHdrSize(type);
|
hdrlen = sdsHdrSize(type);
|
||||||
if (oldtype==type) {
|
if (oldtype==type) {
|
||||||
newsh = s_realloc(sh, hdrlen+newlen+1);
|
newsh = s_realloc(sh, hdrlen+newlen+1);
|
||||||
if (newsh == NULL) return NULL;
|
if (newsh == NULL) {
|
||||||
|
s_free(sh);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
s = (char*)newsh+hdrlen;
|
s = (char*)newsh+hdrlen;
|
||||||
} else {
|
} else {
|
||||||
/* Since the header size changes, need to move the string forward,
|
/* 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. */
|
/* Make sure there is always space for at least 1 char. */
|
||||||
if (sdsavail(s)==0) {
|
if (sdsavail(s)==0) {
|
||||||
s = sdsMakeRoomFor(s,1);
|
s = sdsMakeRoomFor(s,1);
|
||||||
|
if (s == NULL) goto fmt_error;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch(*f) {
|
switch(*f) {
|
||||||
@ -605,6 +609,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
|
|||||||
l = (next == 's') ? strlen(str) : sdslen(str);
|
l = (next == 's') ? strlen(str) : sdslen(str);
|
||||||
if (sdsavail(s) < l) {
|
if (sdsavail(s) < l) {
|
||||||
s = sdsMakeRoomFor(s,l);
|
s = sdsMakeRoomFor(s,l);
|
||||||
|
if (s == NULL) goto fmt_error;
|
||||||
}
|
}
|
||||||
memcpy(s+i,str,l);
|
memcpy(s+i,str,l);
|
||||||
sdsinclen(s,l);
|
sdsinclen(s,l);
|
||||||
@ -621,6 +626,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
|
|||||||
l = sdsll2str(buf,num);
|
l = sdsll2str(buf,num);
|
||||||
if (sdsavail(s) < l) {
|
if (sdsavail(s) < l) {
|
||||||
s = sdsMakeRoomFor(s,l);
|
s = sdsMakeRoomFor(s,l);
|
||||||
|
if (s == NULL) goto fmt_error;
|
||||||
}
|
}
|
||||||
memcpy(s+i,buf,l);
|
memcpy(s+i,buf,l);
|
||||||
sdsinclen(s,l);
|
sdsinclen(s,l);
|
||||||
@ -638,6 +644,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
|
|||||||
l = sdsull2str(buf,unum);
|
l = sdsull2str(buf,unum);
|
||||||
if (sdsavail(s) < l) {
|
if (sdsavail(s) < l) {
|
||||||
s = sdsMakeRoomFor(s,l);
|
s = sdsMakeRoomFor(s,l);
|
||||||
|
if (s == NULL) goto fmt_error;
|
||||||
}
|
}
|
||||||
memcpy(s+i,buf,l);
|
memcpy(s+i,buf,l);
|
||||||
sdsinclen(s,l);
|
sdsinclen(s,l);
|
||||||
@ -662,6 +669,10 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
|
|||||||
/* Add null-term */
|
/* Add null-term */
|
||||||
s[i] = '\0';
|
s[i] = '\0';
|
||||||
return s;
|
return s;
|
||||||
|
|
||||||
|
fmt_error:
|
||||||
|
va_end(ap);
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Remove the part of the string from left and from right composed just of
|
/* 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++;
|
if (*p) p++;
|
||||||
}
|
}
|
||||||
/* add the token to the vector */
|
/* add the token to the vector */
|
||||||
vector = s_realloc(vector,((*argc)+1)*sizeof(char*));
|
{
|
||||||
vector[*argc] = current;
|
char **new_vector = s_realloc(vector,((*argc)+1)*sizeof(char*));
|
||||||
(*argc)++;
|
if (new_vector == NULL) {
|
||||||
current = NULL;
|
s_free(vector);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
vector = new_vector;
|
||||||
|
vector[*argc] = current;
|
||||||
|
(*argc)++;
|
||||||
|
current = NULL;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
/* Even on empty input string return something not NULL. */
|
/* Even on empty input string return something not NULL. */
|
||||||
if (vector == NULL) vector = s_malloc(sizeof(void*));
|
if (vector == NULL) vector = s_malloc(sizeof(void*));
|
||||||
|
3
sds.h
3
sds.h
@ -34,6 +34,9 @@
|
|||||||
#define __SDS_H
|
#define __SDS_H
|
||||||
|
|
||||||
#define SDS_MAX_PREALLOC (1024*1024)
|
#define SDS_MAX_PREALLOC (1024*1024)
|
||||||
|
#ifdef _WIN32
|
||||||
|
#define __attribute__(x)
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
|
248
sockcompat.c
Normal file
248
sockcompat.c
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019, Marcus Geelnard <m at bitsnbites dot eu>
|
||||||
|
*
|
||||||
|
* 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 = 0;
|
||||||
|
if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) {
|
||||||
|
if (*optlen >= sizeof (struct timeval)) {
|
||||||
|
struct timeval *tv = 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) % 1000000;
|
||||||
|
} else {
|
||||||
|
ret = WSAEFAULT;
|
||||||
|
}
|
||||||
|
*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 = 0;
|
||||||
|
if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) {
|
||||||
|
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 {
|
||||||
|
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 */
|
89
sockcompat.h
Normal file
89
sockcompat.h
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019, Marcus Geelnard <m at bitsnbites dot eu>
|
||||||
|
*
|
||||||
|
* 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 <unistd.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/select.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <netinet/tcp.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#else
|
||||||
|
/* For Windows we use winsock. */
|
||||||
|
#undef _WIN32_WINNT
|
||||||
|
#define _WIN32_WINNT 0x0600 /* To get WSAPoll etc. */
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
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);
|
||||||
|
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 */
|
239
sslio.c
Normal file
239
sslio.c
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
#include "hiredis.h"
|
||||||
|
#include "sslio.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#ifdef HIREDIS_SSL
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
/* 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, const char *servername) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s->ssl = SSL_new(s->ctx);
|
||||||
|
if (!s->ssl) {
|
||||||
|
__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);
|
||||||
|
|
||||||
|
c->flags |= REDIS_SSL;
|
||||||
|
int rv = SSL_connect(c->ssl->ssl);
|
||||||
|
if (rv == 1) {
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
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 (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 {
|
||||||
|
__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 ((c->flags & REDIS_BLOCK) == 0 && maybeCheckWant(c->ssl, err)) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
__redisSetError(c, REDIS_ERR_IO, NULL);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
64
sslio.h
Normal file
64
sslio.h
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
#ifndef REDIS_SSLIO_H
|
||||||
|
#define REDIS_SSLIO_H
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef HIREDIS_SSL
|
||||||
|
typedef struct redisSsl {
|
||||||
|
size_t lastLen;
|
||||||
|
int wantRead;
|
||||||
|
int pendingWrite;
|
||||||
|
} redisSsl;
|
||||||
|
static inline void redisFreeSsl(redisSsl *ssl) {
|
||||||
|
(void)ssl;
|
||||||
|
}
|
||||||
|
static inline int redisSslCreate(struct redisContext *c, const char *ca,
|
||||||
|
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) {
|
||||||
|
(void)c;(void)s;(void)n;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
static inline int redisSslWrite(struct redisContext *c) {
|
||||||
|
(void)c;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/** 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;
|
||||||
|
|
||||||
|
void redisFreeSsl(redisSsl *);
|
||||||
|
int redisSslCreate(struct redisContext *c, const char *caPath,
|
||||||
|
const char *certPath, const char *keyPath, const char *servername);
|
||||||
|
|
||||||
|
int redisSslRead(struct redisContext *c, char *buf, size_t bufcap);
|
||||||
|
int redisSslWrite(struct redisContext *c);
|
||||||
|
|
||||||
|
#endif /* HIREDIS_SSL */
|
||||||
|
#endif /* HIREDIS_SSLIO_H */
|
55
test.c
55
test.c
@ -3,7 +3,9 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <strings.h>
|
#include <strings.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
|
#include <netdb.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
@ -91,7 +93,7 @@ static int disconnect(redisContext *c, int keep_fd) {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static redisContext *connect(struct config config) {
|
static redisContext *do_connect(struct config config) {
|
||||||
redisContext *c = NULL;
|
redisContext *c = NULL;
|
||||||
|
|
||||||
if (config.type == CONN_TCP) {
|
if (config.type == CONN_TCP) {
|
||||||
@ -248,7 +250,7 @@ static void test_append_formatted_commands(struct config config) {
|
|||||||
char *cmd;
|
char *cmd;
|
||||||
int len;
|
int len;
|
||||||
|
|
||||||
c = connect(config);
|
c = do_connect(config);
|
||||||
|
|
||||||
test("Append format command: ");
|
test("Append format command: ");
|
||||||
|
|
||||||
@ -432,20 +434,35 @@ static void test_free_null(void) {
|
|||||||
test_cond(reply == NULL);
|
test_cond(reply == NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define HIREDIS_BAD_DOMAIN "idontexist-noreally.com"
|
||||||
static void test_blocking_connection_errors(void) {
|
static void test_blocking_connection_errors(void) {
|
||||||
redisContext *c;
|
redisContext *c;
|
||||||
|
struct addrinfo hints = {.ai_family = AF_INET};
|
||||||
|
struct addrinfo *ai_tmp = NULL;
|
||||||
|
|
||||||
test("Returns error when host cannot be resolved: ");
|
int rv = getaddrinfo(HIREDIS_BAD_DOMAIN, "6379", &hints, &ai_tmp);
|
||||||
c = redisConnect((char*)"idontexist.test", 6379);
|
if (rv != 0) {
|
||||||
test_cond(c->err == REDIS_ERR_OTHER &&
|
// Address does *not* exist
|
||||||
(strcmp(c->errstr,"Name or service not known") == 0 ||
|
test("Returns error when host cannot be resolved: ");
|
||||||
strcmp(c->errstr,"Can't resolve: idontexist.test") == 0 ||
|
// First see if this domain name *actually* resolves to NXDOMAIN
|
||||||
strcmp(c->errstr,"nodename nor servname provided, or not known") == 0 ||
|
c = redisConnect(HIREDIS_BAD_DOMAIN, 6379);
|
||||||
strcmp(c->errstr,"No address associated with hostname") == 0 ||
|
test_cond(
|
||||||
strcmp(c->errstr,"Temporary failure in name resolution") == 0 ||
|
c->err == REDIS_ERR_OTHER &&
|
||||||
strcmp(c->errstr,"hostname nor servname provided, or not known") == 0 ||
|
(strcmp(c->errstr, "Name or service not known") == 0 ||
|
||||||
strcmp(c->errstr,"no address associated with name") == 0));
|
strcmp(c->errstr, "Can't resolve: " HIREDIS_BAD_DOMAIN) == 0 ||
|
||||||
redisFree(c);
|
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 ||
|
||||||
|
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: ");
|
test("Returns error when the port is not open: ");
|
||||||
c = redisConnect((char*)"localhost", 1);
|
c = redisConnect((char*)"localhost", 1);
|
||||||
@ -463,7 +480,7 @@ static void test_blocking_connection(struct config config) {
|
|||||||
redisContext *c;
|
redisContext *c;
|
||||||
redisReply *reply;
|
redisReply *reply;
|
||||||
|
|
||||||
c = connect(config);
|
c = do_connect(config);
|
||||||
|
|
||||||
test("Is able to deliver commands: ");
|
test("Is able to deliver commands: ");
|
||||||
reply = redisCommand(c,"PING");
|
reply = redisCommand(c,"PING");
|
||||||
@ -544,7 +561,7 @@ static void test_blocking_connection_timeouts(struct config config) {
|
|||||||
const char *cmd = "DEBUG SLEEP 3\r\n";
|
const char *cmd = "DEBUG SLEEP 3\r\n";
|
||||||
struct timeval tv;
|
struct timeval tv;
|
||||||
|
|
||||||
c = connect(config);
|
c = do_connect(config);
|
||||||
test("Successfully completes a command when the timeout is not exceeded: ");
|
test("Successfully completes a command when the timeout is not exceeded: ");
|
||||||
reply = redisCommand(c,"SET foo fast");
|
reply = redisCommand(c,"SET foo fast");
|
||||||
freeReplyObject(reply);
|
freeReplyObject(reply);
|
||||||
@ -556,7 +573,7 @@ static void test_blocking_connection_timeouts(struct config config) {
|
|||||||
freeReplyObject(reply);
|
freeReplyObject(reply);
|
||||||
disconnect(c, 0);
|
disconnect(c, 0);
|
||||||
|
|
||||||
c = connect(config);
|
c = do_connect(config);
|
||||||
test("Does not return a reply when the command times out: ");
|
test("Does not return a reply when the command times out: ");
|
||||||
s = write(c->fd, cmd, strlen(cmd));
|
s = write(c->fd, cmd, strlen(cmd));
|
||||||
tv.tv_sec = 0;
|
tv.tv_sec = 0;
|
||||||
@ -590,7 +607,7 @@ static void test_blocking_io_errors(struct config config) {
|
|||||||
int major, minor;
|
int major, minor;
|
||||||
|
|
||||||
/* Connect to target given by config. */
|
/* 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 */
|
/* Find out Redis version to determine the path for the next test */
|
||||||
const char *field = "redis_version:";
|
const char *field = "redis_version:";
|
||||||
@ -625,7 +642,7 @@ static void test_blocking_io_errors(struct config config) {
|
|||||||
strcmp(c->errstr,"Server closed the connection") == 0);
|
strcmp(c->errstr,"Server closed the connection") == 0);
|
||||||
redisFree(c);
|
redisFree(c);
|
||||||
|
|
||||||
c = connect(config);
|
c = do_connect(config);
|
||||||
test("Returns I/O error on socket timeout: ");
|
test("Returns I/O error on socket timeout: ");
|
||||||
struct timeval tv = { 0, 1000 };
|
struct timeval tv = { 0, 1000 };
|
||||||
assert(redisSetTimeout(c,tv) == REDIS_OK);
|
assert(redisSetTimeout(c,tv) == REDIS_OK);
|
||||||
@ -659,7 +676,7 @@ static void test_invalid_timeout_errors(struct config config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void test_throughput(struct config config) {
|
static void test_throughput(struct config config) {
|
||||||
redisContext *c = connect(config);
|
redisContext *c = do_connect(config);
|
||||||
redisReply **replies;
|
redisReply **replies;
|
||||||
int i, num;
|
int i, num;
|
||||||
long long t1, t2;
|
long long t1, t2;
|
||||||
|
25
test.sh
Executable file
25
test.sh
Executable file
@ -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} - <<EOF
|
||||||
|
daemonize yes
|
||||||
|
pidfile ${PID_FILE}
|
||||||
|
port ${REDIS_PORT}
|
||||||
|
bind 127.0.0.1
|
||||||
|
unixsocket ${SOCK_FILE}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
${TEST_PREFIX:-} ./hiredis-test -h 127.0.0.1 -p ${REDIS_PORT} -s ${SOCK_FILE}
|
8
win32.h
8
win32.h
@ -37,6 +37,10 @@ __inline int c99_snprintf(char* str, size_t size, const char* format, ...)
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
#endif /* _MSC_VER */
|
||||||
|
|
||||||
#endif
|
#ifdef _WIN32
|
||||||
#endif
|
#define strerror_r(errno,buf,len) strerror_s(buf,len,errno)
|
||||||
|
#endif /* _WIN32 */
|
||||||
|
|
||||||
|
#endif /* _WIN32_HELPER_INCLUDE */
|
||||||
|
Loading…
Reference in New Issue
Block a user