Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 31dce76b3e | |||
| 660ba53438 | |||
| 61ffde5dae |
+18
@@ -0,0 +1,18 @@
|
|||||||
|
# /
|
||||||
|
/revision.h
|
||||||
|
/*.aps
|
||||||
|
/*.ncb
|
||||||
|
/*.suo
|
||||||
|
/*.user
|
||||||
|
/*.sdf
|
||||||
|
/*.opensdf
|
||||||
|
/Debug32
|
||||||
|
/Release32
|
||||||
|
/Trial32
|
||||||
|
/Debug64
|
||||||
|
/Release64
|
||||||
|
/ipch
|
||||||
|
/bin
|
||||||
|
/obj
|
||||||
|
/.vs
|
||||||
|
/out
|
||||||
+125
@@ -0,0 +1,125 @@
|
|||||||
|
language: c
|
||||||
|
compiler:
|
||||||
|
- gcc
|
||||||
|
- clang
|
||||||
|
|
||||||
|
os:
|
||||||
|
- linux
|
||||||
|
- osx
|
||||||
|
|
||||||
|
dist: bionic
|
||||||
|
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- staging
|
||||||
|
- trying
|
||||||
|
- master
|
||||||
|
- /^release\/.*$/
|
||||||
|
|
||||||
|
install:
|
||||||
|
- if [ "$TRAVIS_COMPILER" != "mingw" ]; then
|
||||||
|
wget https://github.com/redis/redis/archive/6.0.6.tar.gz;
|
||||||
|
tar -xzvf 6.0.6.tar.gz;
|
||||||
|
pushd redis-6.0.6 && BUILD_TLS=yes make && export PATH=$PWD/src:$PATH && popd;
|
||||||
|
fi;
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- if [ "$TRAVIS_OS_NAME" == "osx" ]; then
|
||||||
|
curl -O https://distfiles.macports.org/MacPorts/MacPorts-2.6.2-10.13-HighSierra.pkg;
|
||||||
|
sudo installer -pkg MacPorts-2.6.2-10.13-HighSierra.pkg -target /;
|
||||||
|
export PATH=$PATH:/opt/local/bin && sudo port -v selfupdate;
|
||||||
|
sudo port -N install openssl redis;
|
||||||
|
fi;
|
||||||
|
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
packages:
|
||||||
|
- libc6-dbg
|
||||||
|
- libc6-dev
|
||||||
|
- libc6:i386
|
||||||
|
- libc6-dev-i386
|
||||||
|
- libc6-dbg:i386
|
||||||
|
- gcc-multilib
|
||||||
|
- g++-multilib
|
||||||
|
- libssl-dev
|
||||||
|
- libssl-dev:i386
|
||||||
|
- valgrind
|
||||||
|
|
||||||
|
env:
|
||||||
|
- BITS="32"
|
||||||
|
- BITS="64"
|
||||||
|
|
||||||
|
script:
|
||||||
|
- EXTRA_CMAKE_OPTS="-DENABLE_EXAMPLES:BOOL=ON -DENABLE_SSL:BOOL=ON -DENABLE_SSL_TESTS:BOOL=ON";
|
||||||
|
if [ "$TRAVIS_OS_NAME" == "osx" ]; then
|
||||||
|
if [ "$BITS" == "32" ]; then
|
||||||
|
CFLAGS="-m32 -Werror";
|
||||||
|
CXXFLAGS="-m32 -Werror";
|
||||||
|
LDFLAGS="-m32";
|
||||||
|
EXTRA_CMAKE_OPTS=;
|
||||||
|
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
|
||||||
|
- make && make clean;
|
||||||
|
if [ "$TRAVIS_OS_NAME" == "osx" ]; then
|
||||||
|
if [ "$BITS" == "64" ]; then
|
||||||
|
OPENSSL_PREFIX="$(ls -d /usr/local/Cellar/openssl@1.1/*)" USE_SSL=1 make;
|
||||||
|
fi;
|
||||||
|
else
|
||||||
|
USE_SSL=1 make;
|
||||||
|
fi;
|
||||||
|
- mkdir build/ && cd build/
|
||||||
|
- cmake .. ${EXTRA_CMAKE_OPTS}
|
||||||
|
- make VERBOSE=1
|
||||||
|
- if [ "$BITS" == "64" ]; then
|
||||||
|
TEST_SSL=1 SKIPS_AS_FAILS=1 ctest -V;
|
||||||
|
else
|
||||||
|
SKIPS_AS_FAILS=1 ctest -V;
|
||||||
|
fi;
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
include:
|
||||||
|
# Windows MinGW cross compile on Linux
|
||||||
|
- os: linux
|
||||||
|
dist: xenial
|
||||||
|
compiler: mingw
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
packages:
|
||||||
|
- ninja-build
|
||||||
|
- gcc-mingw-w64-x86-64
|
||||||
|
- g++-mingw-w64-x86-64
|
||||||
|
script:
|
||||||
|
- mkdir build && cd build
|
||||||
|
- CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_BUILD_WITH_INSTALL_RPATH=on
|
||||||
|
- ninja -v
|
||||||
|
|
||||||
|
# Windows MSVC 2017
|
||||||
|
- os: windows
|
||||||
|
compiler: msvc
|
||||||
|
env:
|
||||||
|
- MATRIX_EVAL="CC=cl.exe && CXX=cl.exe"
|
||||||
|
before_install:
|
||||||
|
- eval "${MATRIX_EVAL}"
|
||||||
|
install:
|
||||||
|
- choco install ninja
|
||||||
|
- choco install -y memurai-developer
|
||||||
|
script:
|
||||||
|
- mkdir build && cd build
|
||||||
|
- cmd.exe //C 'C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvarsall.bat' amd64 '&&'
|
||||||
|
cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DENABLE_EXAMPLES=ON '&&' ninja -v
|
||||||
|
- ./hiredis-test.exe
|
||||||
+300
@@ -0,0 +1,300 @@
|
|||||||
|
CMAKE_MINIMUM_REQUIRED(VERSION 3.7.0)
|
||||||
|
|
||||||
|
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 LANGUAGES "C" VERSION "${VERSION}")
|
||||||
|
INCLUDE(GNUInstallDirs)
|
||||||
|
|
||||||
|
OPTION(BUILD_SHARED_LIBS "Build shared libraries" ON)
|
||||||
|
OPTION(ENABLE_SSL "Build hiredis_ssl for SSL support" OFF)
|
||||||
|
OPTION(DISABLE_TESTS "If tests should be compiled or not" OFF)
|
||||||
|
OPTION(ENABLE_SSL_TESTS "Should we test SSL connections" OFF)
|
||||||
|
OPTION(ENABLE_EXAMPLES "Enable building hiredis examples" OFF)
|
||||||
|
OPTION(ENABLE_ASYNC_TESTS "Should we run all asynchronous API tests" OFF)
|
||||||
|
# Historically, the NuGet file was always install; default
|
||||||
|
# to ON for those who rely on that historical behaviour.
|
||||||
|
OPTION(ENABLE_NUGET "Install NuGET packaging details" ON)
|
||||||
|
|
||||||
|
# Hiredis requires C99
|
||||||
|
SET(CMAKE_C_STANDARD 99)
|
||||||
|
#SET(CMAKE_DEBUG_POSTFIX d) --- rimozione ---
|
||||||
|
|
||||||
|
include_directories("${CMAKE_SOURCE_DIR}/../Extern/libevent/Include")
|
||||||
|
|
||||||
|
SET(hiredis_sources
|
||||||
|
alloc.c
|
||||||
|
async.c
|
||||||
|
hiredis.c
|
||||||
|
net.c
|
||||||
|
read.c
|
||||||
|
sds.c
|
||||||
|
sockcompat.c)
|
||||||
|
|
||||||
|
SET(hiredis_sources ${hiredis_sources})
|
||||||
|
|
||||||
|
IF(WIN32)
|
||||||
|
ADD_DEFINITIONS(-D_CRT_SECURE_NO_WARNINGS -DWIN32_LEAN_AND_MEAN)
|
||||||
|
ENDIF()
|
||||||
|
|
||||||
|
ADD_LIBRARY(hiredis ${hiredis_sources})
|
||||||
|
|
||||||
|
#Disattiva generazione di .pdb e .ilk
|
||||||
|
if( MSVC)
|
||||||
|
set_target_properties( hiredis PROPERTIES
|
||||||
|
LINK_FLAGS "/INCREMENTAL:NO /DEBUG:NONE"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Determino architettura 64bit/32bit
|
||||||
|
if ( CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||||
|
set( ARCH_SUFFIX "64")
|
||||||
|
else()
|
||||||
|
set( ARCH_SUFFIX "32")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_sources( hiredis PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/version.rc)
|
||||||
|
|
||||||
|
set( HIREDIS_EGTDEV_DIR "C:/EgtDev/Extern/hiredis")
|
||||||
|
set( HIREDIS_EGTPROG_DIR "C:/EgtProg")
|
||||||
|
|
||||||
|
# per .rc
|
||||||
|
if ( WIN32 AND CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||||
|
target_compile_definitions( hiredis PRIVATE _WIN64)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Imposta proprietà per tutte le configurazioni
|
||||||
|
set_target_properties(hiredis PROPERTIES
|
||||||
|
# Direttori
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY_DEBUG "${HIREDIS_EGTPROG_DIR}/DllD${ARCH_SUFFIX}" # --- Debug
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY_RELEASE "${HIREDIS_EGTPROG_DIR}/Dll${ARCH_SUFFIX}" # --- Release
|
||||||
|
ARCHIVE_OUTPUT_DIRECTORY_DEBUG "${HIREDIS_EGTDEV_DIR}/Lib" # --- Debug
|
||||||
|
ARCHIVE_OUTPUT_DIRECTORY_RELEASE "${HIREDIS_EGTDEV_DIR}/Lib" # --- Release
|
||||||
|
# Nomi
|
||||||
|
OUTPUT_NAME_DEBUG "hiredisD${ARCH_SUFFIX}" # --- Debug
|
||||||
|
OUTPUT_NAME_RELEASE "hiredisR${ARCH_SUFFIX}" # --- Release
|
||||||
|
)
|
||||||
|
|
||||||
|
# Copia dei file .h ( nal caso siano differenti da quelli presenti )
|
||||||
|
ADD_CUSTOM_COMMAND( TARGET hiredis POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/hiredis.h # main API for synchronous Redis commands
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/read.h # reply parsing logic
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/sds.h # string handling (used internally and exposed)
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/async.h # asynchronous API
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/alloc.h # memory allocation hooks
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/sockcompat.h # socket compatibility layer
|
||||||
|
${HIREDIS_EGTDEV_DIR}/Include)
|
||||||
|
|
||||||
|
# Copia dei file.h ( nel caso siano differenti da quelli presenti ) -> per ora non servono !
|
||||||
|
#ADD_CUSTOM_COMMAND(TARGET hiredis POST_BUILD
|
||||||
|
# COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
# ${CMAKE_CURRENT_SOURCE_DIR}/adapters/libevent.h
|
||||||
|
# ${HIREDIS_EGTDEV_DIR}/Include/adapters)
|
||||||
|
|
||||||
|
ADD_LIBRARY(hiredis::hiredis ALIAS hiredis)
|
||||||
|
set(hiredis_export_name hiredis CACHE STRING "Name of the exported target")
|
||||||
|
set_target_properties(hiredis PROPERTIES EXPORT_NAME ${hiredis_export_name})
|
||||||
|
|
||||||
|
SET_TARGET_PROPERTIES(hiredis
|
||||||
|
PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE
|
||||||
|
VERSION "${VERSION}"
|
||||||
|
SOVERSION "${HIREDIS_MAJOR}")
|
||||||
|
IF(MSVC)
|
||||||
|
SET_TARGET_PROPERTIES(hiredis
|
||||||
|
PROPERTIES COMPILE_FLAGS /Z7)
|
||||||
|
ENDIF()
|
||||||
|
IF(WIN32)
|
||||||
|
TARGET_LINK_LIBRARIES(hiredis PUBLIC ws2_32 crypt32)
|
||||||
|
ELSEIF(CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
|
||||||
|
TARGET_LINK_LIBRARIES(hiredis PUBLIC m)
|
||||||
|
ELSEIF(CMAKE_SYSTEM_NAME MATCHES "SunOS")
|
||||||
|
TARGET_LINK_LIBRARIES(hiredis PUBLIC socket)
|
||||||
|
ENDIF()
|
||||||
|
|
||||||
|
TARGET_INCLUDE_DIRECTORIES(hiredis PUBLIC $<INSTALL_INTERFACE:include> $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
|
||||||
|
|
||||||
|
CONFIGURE_FILE(hiredis.pc.in hiredis.pc @ONLY)
|
||||||
|
|
||||||
|
set(CPACK_PACKAGE_VENDOR "Redis")
|
||||||
|
set(CPACK_PACKAGE_DESCRIPTION "\
|
||||||
|
Hiredis is a minimalistic C client library for the Redis database.
|
||||||
|
|
||||||
|
It is minimalistic because it just adds minimal support for the protocol, \
|
||||||
|
but at the same time it uses a high level printf-alike API in order to make \
|
||||||
|
it much higher level than otherwise suggested by its minimal code base and the \
|
||||||
|
lack of explicit bindings for every Redis command.
|
||||||
|
|
||||||
|
Apart from supporting sending commands and receiving replies, it comes with a \
|
||||||
|
reply parser that is decoupled from the I/O layer. It is a stream parser designed \
|
||||||
|
for easy reusability, which can for instance be used in higher level language bindings \
|
||||||
|
for efficient reply parsing.
|
||||||
|
|
||||||
|
Hiredis only supports the binary-safe Redis protocol, so you can use it with any Redis \
|
||||||
|
version >= 1.2.0.
|
||||||
|
|
||||||
|
The library comes with multiple APIs. There is the synchronous API, the asynchronous API \
|
||||||
|
and the reply parsing API.")
|
||||||
|
set(CPACK_PACKAGE_HOMEPAGE_URL "https://github.com/redis/hiredis")
|
||||||
|
set(CPACK_PACKAGE_CONTACT "michael dot grunder at gmail dot com")
|
||||||
|
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
|
||||||
|
set(CPACK_RPM_PACKAGE_AUTOREQPROV ON)
|
||||||
|
|
||||||
|
include(CPack)
|
||||||
|
|
||||||
|
INSTALL(TARGETS hiredis
|
||||||
|
EXPORT hiredis-targets
|
||||||
|
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||||
|
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||||
|
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||||
|
|
||||||
|
if (MSVC AND BUILD_SHARED_LIBS)
|
||||||
|
INSTALL(FILES $<TARGET_PDB_FILE:hiredis>
|
||||||
|
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||||
|
CONFIGURATIONS Debug RelWithDebInfo)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (ENABLE_NUGET)
|
||||||
|
# For NuGet packages
|
||||||
|
INSTALL(FILES hiredis.targets
|
||||||
|
DESTINATION build/native)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
INSTALL(FILES hiredis.h read.h sds.h async.h alloc.h sockcompat.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)
|
||||||
|
|
||||||
|
export(EXPORT hiredis-targets
|
||||||
|
FILE "${CMAKE_CURRENT_BINARY_DIR}/hiredis-targets.cmake"
|
||||||
|
NAMESPACE hiredis::)
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
SET(CMAKE_CONF_INSTALL_DIR share/hiredis)
|
||||||
|
else()
|
||||||
|
SET(CMAKE_CONF_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/hiredis)
|
||||||
|
endif()
|
||||||
|
SET(INCLUDE_INSTALL_DIR include)
|
||||||
|
include(CMakePackageConfigHelpers)
|
||||||
|
write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/hiredis-config-version.cmake"
|
||||||
|
COMPATIBILITY SameMajorVersion)
|
||||||
|
configure_package_config_file(hiredis-config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/hiredis-config.cmake
|
||||||
|
INSTALL_DESTINATION ${CMAKE_CONF_INSTALL_DIR}
|
||||||
|
PATH_VARS INCLUDE_INSTALL_DIR)
|
||||||
|
|
||||||
|
INSTALL(EXPORT hiredis-targets
|
||||||
|
FILE hiredis-targets.cmake
|
||||||
|
NAMESPACE hiredis::
|
||||||
|
DESTINATION ${CMAKE_CONF_INSTALL_DIR})
|
||||||
|
|
||||||
|
INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis-config.cmake
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/hiredis-config-version.cmake
|
||||||
|
DESTINATION ${CMAKE_CONF_INSTALL_DIR})
|
||||||
|
|
||||||
|
|
||||||
|
IF(ENABLE_SSL)
|
||||||
|
IF (NOT OPENSSL_ROOT_DIR)
|
||||||
|
IF (APPLE)
|
||||||
|
SET(OPENSSL_ROOT_DIR "/usr/local/opt/openssl")
|
||||||
|
ENDIF()
|
||||||
|
ENDIF()
|
||||||
|
FIND_PACKAGE(OpenSSL REQUIRED)
|
||||||
|
SET(hiredis_ssl_sources
|
||||||
|
ssl.c)
|
||||||
|
ADD_LIBRARY(hiredis_ssl ${hiredis_ssl_sources})
|
||||||
|
ADD_LIBRARY(hiredis::hiredis_ssl ALIAS hiredis_ssl)
|
||||||
|
|
||||||
|
IF (APPLE AND BUILD_SHARED_LIBS)
|
||||||
|
SET_PROPERTY(TARGET hiredis_ssl PROPERTY LINK_FLAGS "-Wl,-undefined -Wl,dynamic_lookup")
|
||||||
|
ENDIF()
|
||||||
|
|
||||||
|
SET_TARGET_PROPERTIES(hiredis_ssl
|
||||||
|
PROPERTIES
|
||||||
|
WINDOWS_EXPORT_ALL_SYMBOLS TRUE
|
||||||
|
VERSION "${VERSION}"
|
||||||
|
SOVERSION "${HIREDIS_MAJOR}")
|
||||||
|
IF(MSVC)
|
||||||
|
SET_TARGET_PROPERTIES(hiredis_ssl
|
||||||
|
PROPERTIES COMPILE_FLAGS /Z7)
|
||||||
|
ENDIF()
|
||||||
|
TARGET_LINK_LIBRARIES(hiredis_ssl PRIVATE OpenSSL::SSL)
|
||||||
|
IF(WIN32)
|
||||||
|
TARGET_LINK_LIBRARIES(hiredis_ssl PRIVATE hiredis)
|
||||||
|
ENDIF()
|
||||||
|
CONFIGURE_FILE(hiredis_ssl.pc.in hiredis_ssl.pc @ONLY)
|
||||||
|
|
||||||
|
INSTALL(TARGETS hiredis_ssl
|
||||||
|
EXPORT hiredis_ssl-targets
|
||||||
|
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||||
|
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||||
|
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||||
|
|
||||||
|
if (MSVC AND BUILD_SHARED_LIBS)
|
||||||
|
INSTALL(FILES $<TARGET_PDB_FILE:hiredis_ssl>
|
||||||
|
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||||
|
CONFIGURATIONS Debug RelWithDebInfo)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
INSTALL(FILES hiredis_ssl.h
|
||||||
|
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis)
|
||||||
|
|
||||||
|
INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl.pc
|
||||||
|
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
|
||||||
|
|
||||||
|
export(EXPORT hiredis_ssl-targets
|
||||||
|
FILE "${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl-targets.cmake"
|
||||||
|
NAMESPACE hiredis::)
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
SET(CMAKE_CONF_INSTALL_DIR share/hiredis_ssl)
|
||||||
|
else()
|
||||||
|
SET(CMAKE_CONF_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/hiredis_ssl)
|
||||||
|
endif()
|
||||||
|
configure_package_config_file(hiredis_ssl-config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl-config.cmake
|
||||||
|
INSTALL_DESTINATION ${CMAKE_CONF_INSTALL_DIR}
|
||||||
|
PATH_VARS INCLUDE_INSTALL_DIR)
|
||||||
|
|
||||||
|
INSTALL(EXPORT hiredis_ssl-targets
|
||||||
|
FILE hiredis_ssl-targets.cmake
|
||||||
|
NAMESPACE hiredis::
|
||||||
|
DESTINATION ${CMAKE_CONF_INSTALL_DIR})
|
||||||
|
|
||||||
|
INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl-config.cmake
|
||||||
|
DESTINATION ${CMAKE_CONF_INSTALL_DIR})
|
||||||
|
ENDIF()
|
||||||
|
|
||||||
|
IF(NOT DISABLE_TESTS)
|
||||||
|
ENABLE_TESTING()
|
||||||
|
ADD_EXECUTABLE(hiredis-test test.c)
|
||||||
|
TARGET_LINK_LIBRARIES(hiredis-test hiredis)
|
||||||
|
IF(ENABLE_SSL_TESTS)
|
||||||
|
ADD_DEFINITIONS(-DHIREDIS_TEST_SSL=1)
|
||||||
|
TARGET_LINK_LIBRARIES(hiredis-test hiredis_ssl)
|
||||||
|
ENDIF()
|
||||||
|
IF(ENABLE_ASYNC_TESTS)
|
||||||
|
ADD_DEFINITIONS(-DHIREDIS_TEST_ASYNC=1)
|
||||||
|
TARGET_LINK_LIBRARIES(hiredis-test event)
|
||||||
|
ENDIF()
|
||||||
|
ADD_TEST(NAME hiredis-test
|
||||||
|
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test.sh)
|
||||||
|
ENDIF()
|
||||||
|
|
||||||
|
# Add examples
|
||||||
|
IF(ENABLE_EXAMPLES)
|
||||||
|
ADD_SUBDIRECTORY(examples)
|
||||||
|
ENDIF(ENABLE_EXAMPLES)
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "x64-Debug",
|
||||||
|
"generator": "Visual Studio 17 2022 Win64",
|
||||||
|
"configurationType": "Debug",
|
||||||
|
"inheritEnvironments": [ "msvc_x64_x64" ],
|
||||||
|
"buildRoot": "${projectDir}\\out\\build\\${name}",
|
||||||
|
"installRoot": "${projectDir}\\out\\install\\${name}",
|
||||||
|
"cmakeCommandArgs": "",
|
||||||
|
"buildCommandArgs": "",
|
||||||
|
"ctestCommandArgs": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x64-Release",
|
||||||
|
"generator": "Visual Studio 17 2022 Win64",
|
||||||
|
"configurationType": "Release",
|
||||||
|
"buildRoot": "${projectDir}\\out\\build\\${name}",
|
||||||
|
"installRoot": "${projectDir}\\out\\install\\${name}",
|
||||||
|
"cmakeCommandArgs": "",
|
||||||
|
"buildCommandArgs": "",
|
||||||
|
"ctestCommandArgs": "",
|
||||||
|
"inheritEnvironments": [ "msvc_x64_x64" ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x86-Debug",
|
||||||
|
"generator": "Visual Studio 17 2022",
|
||||||
|
"configurationType": "Debug",
|
||||||
|
"buildRoot": "${projectDir}\\out\\build\\${name}",
|
||||||
|
"installRoot": "${projectDir}\\out\\install\\${name}",
|
||||||
|
"cmakeCommandArgs": "",
|
||||||
|
"buildCommandArgs": "",
|
||||||
|
"ctestCommandArgs": "",
|
||||||
|
"inheritEnvironments": [ "msvc_x86" ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x86-Release",
|
||||||
|
"generator": "Visual Studio 17 2022",
|
||||||
|
"configurationType": "Release",
|
||||||
|
"buildRoot": "${projectDir}\\out\\build\\${name}",
|
||||||
|
"installRoot": "${projectDir}\\out\\install\\${name}",
|
||||||
|
"cmakeCommandArgs": "",
|
||||||
|
"buildCommandArgs": "",
|
||||||
|
"ctestCommandArgs": "",
|
||||||
|
"inheritEnvironments": [ "msvc_x86" ]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,361 @@
|
|||||||
|
# Hiredis Makefile
|
||||||
|
# Copyright (C) 2010-2011 Salvatore Sanfilippo <antirez 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
|
||||||
|
|
||||||
|
OBJ=alloc.o net.o hiredis.o sds.o async.o read.o sockcompat.o
|
||||||
|
EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib hiredis-example-push hiredis-example-poll
|
||||||
|
TESTS=hiredis-test
|
||||||
|
LIBNAME=libhiredis
|
||||||
|
PKGCONFNAME=hiredis.pc
|
||||||
|
|
||||||
|
HIREDIS_MAJOR=$(shell grep HIREDIS_MAJOR hiredis.h | awk '{print $$3}')
|
||||||
|
HIREDIS_MINOR=$(shell grep HIREDIS_MINOR hiredis.h | awk '{print $$3}')
|
||||||
|
HIREDIS_PATCH=$(shell grep HIREDIS_PATCH hiredis.h | awk '{print $$3}')
|
||||||
|
HIREDIS_SONAME=$(shell grep HIREDIS_SONAME hiredis.h | awk '{print $$3}')
|
||||||
|
|
||||||
|
# Installation related variables and target
|
||||||
|
PREFIX?=/usr/local
|
||||||
|
INCLUDE_PATH?=include/hiredis
|
||||||
|
LIBRARY_PATH?=lib
|
||||||
|
PKGCONF_PATH?=pkgconfig
|
||||||
|
INSTALL_INCLUDE_PATH= $(DESTDIR)$(PREFIX)/$(INCLUDE_PATH)
|
||||||
|
INSTALL_LIBRARY_PATH= $(DESTDIR)$(PREFIX)/$(LIBRARY_PATH)
|
||||||
|
INSTALL_PKGCONF_PATH= $(INSTALL_LIBRARY_PATH)/$(PKGCONF_PATH)
|
||||||
|
|
||||||
|
# redis-server configuration used for testing
|
||||||
|
REDIS_PORT=56379
|
||||||
|
REDIS_SERVER=redis-server
|
||||||
|
define REDIS_TEST_CONFIG
|
||||||
|
daemonize yes
|
||||||
|
pidfile /tmp/hiredis-test-redis.pid
|
||||||
|
port $(REDIS_PORT)
|
||||||
|
bind 127.0.0.1
|
||||||
|
unixsocket /tmp/hiredis-test-redis.sock
|
||||||
|
endef
|
||||||
|
export REDIS_TEST_CONFIG
|
||||||
|
|
||||||
|
# Fallback to gcc when $CC is not in $PATH.
|
||||||
|
CC:=$(shell sh -c 'type $${CC%% *} >/dev/null 2>/dev/null && echo $(CC) || echo gcc')
|
||||||
|
CXX:=$(shell sh -c 'type $${CXX%% *} >/dev/null 2>/dev/null && echo $(CXX) || echo g++')
|
||||||
|
OPTIMIZATION?=-O3
|
||||||
|
WARNINGS=-Wall -Wextra -Wstrict-prototypes -Wwrite-strings -Wno-missing-field-initializers
|
||||||
|
USE_WERROR?=1
|
||||||
|
ifeq ($(USE_WERROR),1)
|
||||||
|
WARNINGS+=-Werror
|
||||||
|
endif
|
||||||
|
DEBUG_FLAGS?= -g -ggdb
|
||||||
|
REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CPPFLAGS) $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS) $(PLATFORM_FLAGS)
|
||||||
|
REAL_LDFLAGS=$(LDFLAGS)
|
||||||
|
|
||||||
|
DYLIBSUFFIX=so
|
||||||
|
STLIBSUFFIX=a
|
||||||
|
DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME)
|
||||||
|
DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR)
|
||||||
|
DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX)
|
||||||
|
|
||||||
|
DYLIB_MAKE_CMD=$(CC) $(PLATFORM_FLAGS) -shared -Wl,-soname,$(DYLIB_MINOR_NAME)
|
||||||
|
STLIBNAME=$(LIBNAME).$(STLIBSUFFIX)
|
||||||
|
STLIB_MAKE_CMD=$(AR) rcs
|
||||||
|
|
||||||
|
#################### SSL variables start ####################
|
||||||
|
SSL_OBJ=ssl.o
|
||||||
|
SSL_LIBNAME=libhiredis_ssl
|
||||||
|
SSL_PKGCONFNAME=hiredis_ssl.pc
|
||||||
|
SSL_INSTALLNAME=install-ssl
|
||||||
|
SSL_DYLIB_MINOR_NAME=$(SSL_LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME)
|
||||||
|
SSL_DYLIB_MAJOR_NAME=$(SSL_LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR)
|
||||||
|
SSL_DYLIBNAME=$(SSL_LIBNAME).$(DYLIBSUFFIX)
|
||||||
|
SSL_STLIBNAME=$(SSL_LIBNAME).$(STLIBSUFFIX)
|
||||||
|
SSL_DYLIB_MAKE_CMD=$(CC) $(PLATFORM_FLAGS) -shared -Wl,-soname,$(SSL_DYLIB_MINOR_NAME)
|
||||||
|
|
||||||
|
USE_SSL?=0
|
||||||
|
ifeq ($(USE_SSL),1)
|
||||||
|
# This is required for test.c only
|
||||||
|
CFLAGS+=-DHIREDIS_TEST_SSL
|
||||||
|
EXAMPLES+=hiredis-example-ssl hiredis-example-libevent-ssl
|
||||||
|
SSL_STLIB=$(SSL_STLIBNAME)
|
||||||
|
SSL_DYLIB=$(SSL_DYLIBNAME)
|
||||||
|
SSL_PKGCONF=$(SSL_PKGCONFNAME)
|
||||||
|
SSL_INSTALL=$(SSL_INSTALLNAME)
|
||||||
|
else
|
||||||
|
SSL_STLIB=
|
||||||
|
SSL_DYLIB=
|
||||||
|
SSL_PKGCONF=
|
||||||
|
SSL_INSTALL=
|
||||||
|
endif
|
||||||
|
##################### SSL variables end #####################
|
||||||
|
|
||||||
|
|
||||||
|
# Platform-specific overrides
|
||||||
|
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
|
||||||
|
|
||||||
|
# This is required for test.c only
|
||||||
|
ifeq ($(TEST_ASYNC),1)
|
||||||
|
export CFLAGS+=-DHIREDIS_TEST_ASYNC
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(USE_SSL),1)
|
||||||
|
ifndef OPENSSL_PREFIX
|
||||||
|
ifeq ($(uname_S),Darwin)
|
||||||
|
SEARCH_PATH1=/opt/homebrew/opt/openssl
|
||||||
|
SEARCH_PATH2=/usr/local/opt/openssl
|
||||||
|
|
||||||
|
ifneq ($(wildcard $(SEARCH_PATH1)),)
|
||||||
|
OPENSSL_PREFIX=$(SEARCH_PATH1)
|
||||||
|
else ifneq ($(wildcard $(SEARCH_PATH2)),)
|
||||||
|
OPENSSL_PREFIX=$(SEARCH_PATH2)
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifdef OPENSSL_PREFIX
|
||||||
|
CFLAGS+=-I$(OPENSSL_PREFIX)/include
|
||||||
|
SSL_LDFLAGS+=-L$(OPENSSL_PREFIX)/lib
|
||||||
|
endif
|
||||||
|
|
||||||
|
SSL_LDFLAGS+=-lssl -lcrypto
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(uname_S),FreeBSD)
|
||||||
|
LDFLAGS+=-lm
|
||||||
|
IS_GCC=$(shell sh -c '$(CC) --version 2>/dev/null |egrep -i -c "gcc"')
|
||||||
|
ifeq ($(IS_GCC),1)
|
||||||
|
REAL_CFLAGS+=-pedantic
|
||||||
|
endif
|
||||||
|
else
|
||||||
|
REAL_CFLAGS+=-pedantic
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(uname_S),SunOS)
|
||||||
|
IS_SUN_CC=$(shell sh -c '$(CC) -V 2>&1 |egrep -i -c "sun|studio"')
|
||||||
|
ifeq ($(IS_SUN_CC),1)
|
||||||
|
SUN_SHARED_FLAG=-G
|
||||||
|
else
|
||||||
|
SUN_SHARED_FLAG=-shared
|
||||||
|
endif
|
||||||
|
REAL_LDFLAGS+= -ldl -lnsl -lsocket
|
||||||
|
DYLIB_MAKE_CMD=$(CC) $(SUN_SHARED_FLAG) -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS)
|
||||||
|
SSL_DYLIB_MAKE_CMD=$(CC) $(SUN_SHARED_FLAG) -o $(SSL_DYLIBNAME) -h $(SSL_DYLIB_MINOR_NAME) $(LDFLAGS) $(SSL_LDFLAGS)
|
||||||
|
endif
|
||||||
|
ifeq ($(uname_S),Darwin)
|
||||||
|
DYLIBSUFFIX=dylib
|
||||||
|
DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_SONAME).$(DYLIBSUFFIX)
|
||||||
|
DYLIB_MAJOR_NAME=$(LIBNAME).$(HIREDIS_MAJOR).$(DYLIBSUFFIX)
|
||||||
|
DYLIB_MAKE_CMD=$(CC) -dynamiclib -Wl,-install_name,$(PREFIX)/$(LIBRARY_PATH)/$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
|
||||||
|
SSL_DYLIB_MINOR_NAME=$(SSL_LIBNAME).$(HIREDIS_SONAME).$(DYLIBSUFFIX)
|
||||||
|
SSL_DYLIB_MAJOR_NAME=$(SSL_LIBNAME).$(HIREDIS_MAJOR).$(DYLIBSUFFIX)
|
||||||
|
SSL_DYLIB_MAKE_CMD=$(CC) -dynamiclib -Wl,-install_name,$(PREFIX)/$(LIBRARY_PATH)/$(SSL_DYLIB_MINOR_NAME) -o $(SSL_DYLIBNAME) $(LDFLAGS) $(SSL_LDFLAGS)
|
||||||
|
DYLIB_PLUGIN=-Wl,-undefined -Wl,dynamic_lookup
|
||||||
|
endif
|
||||||
|
|
||||||
|
all: dynamic static hiredis-test pkgconfig
|
||||||
|
|
||||||
|
dynamic: $(DYLIBNAME) $(SSL_DYLIB)
|
||||||
|
|
||||||
|
static: $(STLIBNAME) $(SSL_STLIB)
|
||||||
|
|
||||||
|
pkgconfig: $(PKGCONFNAME) $(SSL_PKGCONF)
|
||||||
|
|
||||||
|
# Deps (use make dep to generate this)
|
||||||
|
alloc.o: alloc.c fmacros.h alloc.h
|
||||||
|
async.o: async.c fmacros.h alloc.h async.h hiredis.h read.h sds.h net.h dict.c dict.h win32.h async_private.h
|
||||||
|
dict.o: dict.c fmacros.h alloc.h dict.h
|
||||||
|
hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h alloc.h net.h async.h win32.h
|
||||||
|
net.o: net.c fmacros.h net.h hiredis.h read.h sds.h alloc.h sockcompat.h win32.h
|
||||||
|
read.o: read.c fmacros.h alloc.h read.h sds.h win32.h
|
||||||
|
sds.o: sds.c sds.h sdsalloc.h alloc.h
|
||||||
|
sockcompat.o: sockcompat.c sockcompat.h
|
||||||
|
test.o: test.c fmacros.h hiredis.h read.h sds.h alloc.h net.h sockcompat.h win32.h
|
||||||
|
|
||||||
|
$(DYLIBNAME): $(OBJ)
|
||||||
|
$(DYLIB_MAKE_CMD) -o $(DYLIBNAME) $(OBJ) $(REAL_LDFLAGS)
|
||||||
|
|
||||||
|
$(STLIBNAME): $(OBJ)
|
||||||
|
$(STLIB_MAKE_CMD) $(STLIBNAME) $(OBJ)
|
||||||
|
|
||||||
|
#################### SSL building rules start ####################
|
||||||
|
$(SSL_DYLIBNAME): $(SSL_OBJ)
|
||||||
|
$(SSL_DYLIB_MAKE_CMD) $(DYLIB_PLUGIN) -o $(SSL_DYLIBNAME) $(SSL_OBJ) $(REAL_LDFLAGS) $(LDFLAGS) $(SSL_LDFLAGS)
|
||||||
|
|
||||||
|
$(SSL_STLIBNAME): $(SSL_OBJ)
|
||||||
|
$(STLIB_MAKE_CMD) $(SSL_STLIBNAME) $(SSL_OBJ)
|
||||||
|
|
||||||
|
$(SSL_OBJ): ssl.c hiredis.h read.h sds.h alloc.h async.h win32.h async_private.h
|
||||||
|
#################### SSL building rules end ####################
|
||||||
|
|
||||||
|
# Binaries:
|
||||||
|
hiredis-example-libevent: examples/example-libevent.c adapters/libevent.h $(STLIBNAME)
|
||||||
|
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -levent $(STLIBNAME) $(REAL_LDFLAGS)
|
||||||
|
|
||||||
|
hiredis-example-libevent-ssl: examples/example-libevent-ssl.c adapters/libevent.h $(STLIBNAME) $(SSL_STLIBNAME)
|
||||||
|
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -levent $(STLIBNAME) $(SSL_STLIBNAME) $(REAL_LDFLAGS) $(SSL_LDFLAGS)
|
||||||
|
|
||||||
|
hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME)
|
||||||
|
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -lev $(STLIBNAME) $(REAL_LDFLAGS)
|
||||||
|
|
||||||
|
hiredis-example-libhv: examples/example-libhv.c adapters/libhv.h $(STLIBNAME)
|
||||||
|
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -lhv $(STLIBNAME) $(REAL_LDFLAGS)
|
||||||
|
|
||||||
|
hiredis-example-glib: examples/example-glib.c adapters/glib.h $(STLIBNAME)
|
||||||
|
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(shell pkg-config --cflags --libs glib-2.0) $(STLIBNAME) $(REAL_LDFLAGS)
|
||||||
|
|
||||||
|
hiredis-example-ivykis: examples/example-ivykis.c adapters/ivykis.h $(STLIBNAME)
|
||||||
|
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -livykis $(STLIBNAME) $(REAL_LDFLAGS)
|
||||||
|
|
||||||
|
hiredis-example-macosx: examples/example-macosx.c adapters/macosx.h $(STLIBNAME)
|
||||||
|
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -framework CoreFoundation $(STLIBNAME) $(REAL_LDFLAGS)
|
||||||
|
|
||||||
|
hiredis-example-ssl: examples/example-ssl.c $(STLIBNAME) $(SSL_STLIBNAME)
|
||||||
|
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(SSL_STLIBNAME) $(REAL_LDFLAGS) $(SSL_LDFLAGS)
|
||||||
|
|
||||||
|
hiredis-example-poll: examples/example-poll.c adapters/poll.h $(STLIBNAME)
|
||||||
|
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(REAL_LDFLAGS)
|
||||||
|
|
||||||
|
ifndef AE_DIR
|
||||||
|
hiredis-example-ae:
|
||||||
|
@echo "Please specify AE_DIR (e.g. <redis repository>/src)"
|
||||||
|
@false
|
||||||
|
else
|
||||||
|
hiredis-example-ae: examples/example-ae.c adapters/ae.h $(STLIBNAME)
|
||||||
|
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(AE_DIR) $< $(AE_DIR)/ae.o $(AE_DIR)/zmalloc.o $(AE_DIR)/../deps/jemalloc/lib/libjemalloc.a -pthread $(STLIBNAME)
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifndef LIBUV_DIR
|
||||||
|
# dynamic link libuv.so
|
||||||
|
hiredis-example-libuv: examples/example-libuv.c adapters/libuv.h $(STLIBNAME)
|
||||||
|
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. -I$(LIBUV_DIR)/include $< -luv -lpthread -lrt $(STLIBNAME) $(REAL_LDFLAGS)
|
||||||
|
else
|
||||||
|
# use user provided static lib
|
||||||
|
hiredis-example-libuv: examples/example-libuv.c adapters/libuv.h $(STLIBNAME)
|
||||||
|
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. -I$(LIBUV_DIR)/include $< $(LIBUV_DIR)/.libs/libuv.a -lpthread -lrt $(STLIBNAME) $(REAL_LDFLAGS)
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(and $(QT_MOC),$(QT_INCLUDE_DIR),$(QT_LIBRARY_DIR)),)
|
||||||
|
hiredis-example-qt:
|
||||||
|
@echo "Please specify QT_MOC, QT_INCLUDE_DIR AND QT_LIBRARY_DIR"
|
||||||
|
@false
|
||||||
|
else
|
||||||
|
hiredis-example-qt: examples/example-qt.cpp adapters/qt.h $(STLIBNAME)
|
||||||
|
$(QT_MOC) adapters/qt.h -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore | \
|
||||||
|
$(CXX) -x c++ -o qt-adapter-moc.o -c - $(REAL_CFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore
|
||||||
|
$(QT_MOC) examples/example-qt.h -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore | \
|
||||||
|
$(CXX) -x c++ -o qt-example-moc.o -c - $(REAL_CFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore
|
||||||
|
$(CXX) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore -L$(QT_LIBRARY_DIR) qt-adapter-moc.o qt-example-moc.o $< -pthread $(STLIBNAME) -lQtCore
|
||||||
|
endif
|
||||||
|
|
||||||
|
hiredis-example: examples/example.c $(STLIBNAME)
|
||||||
|
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(REAL_LDFLAGS)
|
||||||
|
|
||||||
|
hiredis-example-push: examples/example-push.c $(STLIBNAME)
|
||||||
|
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(REAL_LDFLAGS)
|
||||||
|
|
||||||
|
examples: $(EXAMPLES)
|
||||||
|
|
||||||
|
TEST_LIBS = $(STLIBNAME) $(SSL_STLIB)
|
||||||
|
TEST_LDFLAGS = $(SSL_LDFLAGS)
|
||||||
|
ifeq ($(USE_SSL),1)
|
||||||
|
TEST_LDFLAGS += -pthread
|
||||||
|
endif
|
||||||
|
ifeq ($(TEST_ASYNC),1)
|
||||||
|
TEST_LDFLAGS += -levent
|
||||||
|
endif
|
||||||
|
|
||||||
|
hiredis-test: test.o $(TEST_LIBS)
|
||||||
|
$(CC) -o $@ $(REAL_CFLAGS) -I. $^ $(REAL_LDFLAGS) $(TEST_LDFLAGS)
|
||||||
|
|
||||||
|
hiredis-%: %.o $(STLIBNAME)
|
||||||
|
$(CC) $(REAL_CFLAGS) -o $@ $< $(TEST_LIBS) $(REAL_LDFLAGS)
|
||||||
|
|
||||||
|
test: hiredis-test
|
||||||
|
./hiredis-test
|
||||||
|
|
||||||
|
check: hiredis-test
|
||||||
|
TEST_SSL=$(USE_SSL) ./test.sh
|
||||||
|
|
||||||
|
.c.o:
|
||||||
|
$(CC) -std=c99 -c $(REAL_CFLAGS) $<
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf $(DYLIBNAME) $(STLIBNAME) $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov
|
||||||
|
|
||||||
|
dep:
|
||||||
|
$(CC) $(CPPFLAGS) $(CFLAGS) -MM *.c
|
||||||
|
|
||||||
|
INSTALL?= cp -pPR
|
||||||
|
|
||||||
|
$(PKGCONFNAME): hiredis.h
|
||||||
|
@echo "Generating $@ for pkgconfig..."
|
||||||
|
@echo prefix=$(PREFIX) > $@
|
||||||
|
@echo exec_prefix=\$${prefix} >> $@
|
||||||
|
@echo libdir=$(PREFIX)/$(LIBRARY_PATH) >> $@
|
||||||
|
@echo includedir=$(PREFIX)/include >> $@
|
||||||
|
@echo pkgincludedir=$(PREFIX)/$(INCLUDE_PATH) >> $@
|
||||||
|
@echo >> $@
|
||||||
|
@echo Name: hiredis >> $@
|
||||||
|
@echo Description: Minimalistic C client library for Redis. >> $@
|
||||||
|
@echo Version: $(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(HIREDIS_PATCH) >> $@
|
||||||
|
@echo Libs: -L\$${libdir} -lhiredis >> $@
|
||||||
|
@echo Cflags: -I\$${pkgincludedir} -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@
|
||||||
|
|
||||||
|
$(SSL_PKGCONFNAME): hiredis_ssl.h
|
||||||
|
@echo "Generating $@ for pkgconfig..."
|
||||||
|
@echo prefix=$(PREFIX) > $@
|
||||||
|
@echo exec_prefix=\$${prefix} >> $@
|
||||||
|
@echo libdir=$(PREFIX)/$(LIBRARY_PATH) >> $@
|
||||||
|
@echo includedir=$(PREFIX)/include >> $@
|
||||||
|
@echo pkgincludedir=$(PREFIX)/$(INCLUDE_PATH) >> $@
|
||||||
|
@echo >> $@
|
||||||
|
@echo Name: hiredis_ssl >> $@
|
||||||
|
@echo Description: SSL Support for hiredis. >> $@
|
||||||
|
@echo Version: $(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(HIREDIS_PATCH) >> $@
|
||||||
|
@echo Requires: hiredis >> $@
|
||||||
|
@echo Libs: -L\$${libdir} -lhiredis_ssl >> $@
|
||||||
|
@echo Libs.private: -lssl -lcrypto >> $@
|
||||||
|
|
||||||
|
install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME) $(SSL_INSTALL)
|
||||||
|
mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL_LIBRARY_PATH)
|
||||||
|
$(INSTALL) hiredis.h async.h read.h sds.h alloc.h sockcompat.h $(INSTALL_INCLUDE_PATH)
|
||||||
|
$(INSTALL) adapters/*.h $(INSTALL_INCLUDE_PATH)/adapters
|
||||||
|
$(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME)
|
||||||
|
cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIBNAME) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIB_MAJOR_NAME)
|
||||||
|
$(INSTALL) $(STLIBNAME) $(INSTALL_LIBRARY_PATH)
|
||||||
|
mkdir -p $(INSTALL_PKGCONF_PATH)
|
||||||
|
$(INSTALL) $(PKGCONFNAME) $(INSTALL_PKGCONF_PATH)
|
||||||
|
|
||||||
|
install-ssl: $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(SSL_PKGCONFNAME)
|
||||||
|
mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_LIBRARY_PATH)
|
||||||
|
$(INSTALL) hiredis_ssl.h $(INSTALL_INCLUDE_PATH)
|
||||||
|
$(INSTALL) $(SSL_DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(SSL_DYLIB_MINOR_NAME)
|
||||||
|
cd $(INSTALL_LIBRARY_PATH) && ln -sf $(SSL_DYLIB_MINOR_NAME) $(SSL_DYLIBNAME) && ln -sf $(SSL_DYLIB_MINOR_NAME) $(SSL_DYLIB_MAJOR_NAME)
|
||||||
|
$(INSTALL) $(SSL_STLIBNAME) $(INSTALL_LIBRARY_PATH)
|
||||||
|
mkdir -p $(INSTALL_PKGCONF_PATH)
|
||||||
|
$(INSTALL) $(SSL_PKGCONFNAME) $(INSTALL_PKGCONF_PATH)
|
||||||
|
|
||||||
|
32bit:
|
||||||
|
@echo ""
|
||||||
|
@echo "WARNING: if this fails under Linux you probably need to install libc6-dev-i386"
|
||||||
|
@echo ""
|
||||||
|
$(MAKE) CFLAGS="-m32" LDFLAGS="-m32"
|
||||||
|
|
||||||
|
32bit-vars:
|
||||||
|
$(eval CFLAGS=-m32)
|
||||||
|
$(eval LDFLAGS=-m32)
|
||||||
|
|
||||||
|
gprof:
|
||||||
|
$(MAKE) CFLAGS="-pg" LDFLAGS="-pg"
|
||||||
|
|
||||||
|
gcov:
|
||||||
|
$(MAKE) CFLAGS+="-fprofile-arcs -ftest-coverage" LDFLAGS="-fprofile-arcs"
|
||||||
|
|
||||||
|
coverage: gcov
|
||||||
|
make check
|
||||||
|
mkdir -p tmp/lcov
|
||||||
|
lcov -d . -c --exclude '/usr*' -o tmp/lcov/hiredis.info
|
||||||
|
lcov -q -l tmp/lcov/hiredis.info
|
||||||
|
genhtml --legend -q -o tmp/lcov/report tmp/lcov/hiredis.info
|
||||||
|
|
||||||
|
noopt:
|
||||||
|
$(MAKE) OPTIMIZATION=""
|
||||||
|
|
||||||
|
.PHONY: all test check clean dep install 32bit 32bit-vars gprof gcov noopt
|
||||||
+130
@@ -0,0 +1,130 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||||
|
*
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Redis nor the names of its contributors may be used
|
||||||
|
* to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __HIREDIS_AE_H__
|
||||||
|
#define __HIREDIS_AE_H__
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <ae.h>
|
||||||
|
#include "../hiredis.h"
|
||||||
|
#include "../async.h"
|
||||||
|
|
||||||
|
typedef struct redisAeEvents {
|
||||||
|
redisAsyncContext *context;
|
||||||
|
aeEventLoop *loop;
|
||||||
|
int fd;
|
||||||
|
int reading, writing;
|
||||||
|
} redisAeEvents;
|
||||||
|
|
||||||
|
static void redisAeReadEvent(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||||
|
((void)el); ((void)fd); ((void)mask);
|
||||||
|
|
||||||
|
redisAeEvents *e = (redisAeEvents*)privdata;
|
||||||
|
redisAsyncHandleRead(e->context);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisAeWriteEvent(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||||
|
((void)el); ((void)fd); ((void)mask);
|
||||||
|
|
||||||
|
redisAeEvents *e = (redisAeEvents*)privdata;
|
||||||
|
redisAsyncHandleWrite(e->context);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisAeAddRead(void *privdata) {
|
||||||
|
redisAeEvents *e = (redisAeEvents*)privdata;
|
||||||
|
aeEventLoop *loop = e->loop;
|
||||||
|
if (!e->reading) {
|
||||||
|
e->reading = 1;
|
||||||
|
aeCreateFileEvent(loop,e->fd,AE_READABLE,redisAeReadEvent,e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisAeDelRead(void *privdata) {
|
||||||
|
redisAeEvents *e = (redisAeEvents*)privdata;
|
||||||
|
aeEventLoop *loop = e->loop;
|
||||||
|
if (e->reading) {
|
||||||
|
e->reading = 0;
|
||||||
|
aeDeleteFileEvent(loop,e->fd,AE_READABLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisAeAddWrite(void *privdata) {
|
||||||
|
redisAeEvents *e = (redisAeEvents*)privdata;
|
||||||
|
aeEventLoop *loop = e->loop;
|
||||||
|
if (!e->writing) {
|
||||||
|
e->writing = 1;
|
||||||
|
aeCreateFileEvent(loop,e->fd,AE_WRITABLE,redisAeWriteEvent,e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisAeDelWrite(void *privdata) {
|
||||||
|
redisAeEvents *e = (redisAeEvents*)privdata;
|
||||||
|
aeEventLoop *loop = e->loop;
|
||||||
|
if (e->writing) {
|
||||||
|
e->writing = 0;
|
||||||
|
aeDeleteFileEvent(loop,e->fd,AE_WRITABLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisAeCleanup(void *privdata) {
|
||||||
|
redisAeEvents *e = (redisAeEvents*)privdata;
|
||||||
|
redisAeDelRead(privdata);
|
||||||
|
redisAeDelWrite(privdata);
|
||||||
|
hi_free(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int redisAeAttach(aeEventLoop *loop, redisAsyncContext *ac) {
|
||||||
|
redisContext *c = &(ac->c);
|
||||||
|
redisAeEvents *e;
|
||||||
|
|
||||||
|
/* Nothing should be attached when something is already attached */
|
||||||
|
if (ac->ev.data != NULL)
|
||||||
|
return REDIS_ERR;
|
||||||
|
|
||||||
|
/* Create container for context and r/w events */
|
||||||
|
e = (redisAeEvents*)hi_malloc(sizeof(*e));
|
||||||
|
if (e == NULL)
|
||||||
|
return REDIS_ERR;
|
||||||
|
|
||||||
|
e->context = ac;
|
||||||
|
e->loop = loop;
|
||||||
|
e->fd = c->fd;
|
||||||
|
e->reading = e->writing = 0;
|
||||||
|
|
||||||
|
/* Register functions to start/stop listening for events */
|
||||||
|
ac->ev.addRead = redisAeAddRead;
|
||||||
|
ac->ev.delRead = redisAeDelRead;
|
||||||
|
ac->ev.addWrite = redisAeAddWrite;
|
||||||
|
ac->ev.delWrite = redisAeDelWrite;
|
||||||
|
ac->ev.cleanup = redisAeCleanup;
|
||||||
|
ac->ev.data = e;
|
||||||
|
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
+156
@@ -0,0 +1,156 @@
|
|||||||
|
#ifndef __HIREDIS_GLIB_H__
|
||||||
|
#define __HIREDIS_GLIB_H__
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
|
#include "../hiredis.h"
|
||||||
|
#include "../async.h"
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
GSource source;
|
||||||
|
redisAsyncContext *ac;
|
||||||
|
GPollFD poll_fd;
|
||||||
|
} RedisSource;
|
||||||
|
|
||||||
|
static void
|
||||||
|
redis_source_add_read (gpointer data)
|
||||||
|
{
|
||||||
|
RedisSource *source = (RedisSource *)data;
|
||||||
|
g_return_if_fail(source);
|
||||||
|
source->poll_fd.events |= G_IO_IN;
|
||||||
|
g_main_context_wakeup(g_source_get_context((GSource *)data));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
redis_source_del_read (gpointer data)
|
||||||
|
{
|
||||||
|
RedisSource *source = (RedisSource *)data;
|
||||||
|
g_return_if_fail(source);
|
||||||
|
source->poll_fd.events &= ~G_IO_IN;
|
||||||
|
g_main_context_wakeup(g_source_get_context((GSource *)data));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
redis_source_add_write (gpointer data)
|
||||||
|
{
|
||||||
|
RedisSource *source = (RedisSource *)data;
|
||||||
|
g_return_if_fail(source);
|
||||||
|
source->poll_fd.events |= G_IO_OUT;
|
||||||
|
g_main_context_wakeup(g_source_get_context((GSource *)data));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
redis_source_del_write (gpointer data)
|
||||||
|
{
|
||||||
|
RedisSource *source = (RedisSource *)data;
|
||||||
|
g_return_if_fail(source);
|
||||||
|
source->poll_fd.events &= ~G_IO_OUT;
|
||||||
|
g_main_context_wakeup(g_source_get_context((GSource *)data));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
redis_source_cleanup (gpointer data)
|
||||||
|
{
|
||||||
|
RedisSource *source = (RedisSource *)data;
|
||||||
|
|
||||||
|
g_return_if_fail(source);
|
||||||
|
|
||||||
|
redis_source_del_read(source);
|
||||||
|
redis_source_del_write(source);
|
||||||
|
/*
|
||||||
|
* It is not our responsibility to remove ourself from the
|
||||||
|
* current main loop. However, we will remove the GPollFD.
|
||||||
|
*/
|
||||||
|
if (source->poll_fd.fd >= 0) {
|
||||||
|
g_source_remove_poll((GSource *)data, &source->poll_fd);
|
||||||
|
source->poll_fd.fd = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
redis_source_prepare (GSource *source,
|
||||||
|
gint *timeout_)
|
||||||
|
{
|
||||||
|
RedisSource *redis = (RedisSource *)source;
|
||||||
|
*timeout_ = -1;
|
||||||
|
return !!(redis->poll_fd.events & redis->poll_fd.revents);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
redis_source_check (GSource *source)
|
||||||
|
{
|
||||||
|
RedisSource *redis = (RedisSource *)source;
|
||||||
|
return !!(redis->poll_fd.events & redis->poll_fd.revents);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
redis_source_dispatch (GSource *source,
|
||||||
|
GSourceFunc callback,
|
||||||
|
gpointer user_data)
|
||||||
|
{
|
||||||
|
RedisSource *redis = (RedisSource *)source;
|
||||||
|
|
||||||
|
if ((redis->poll_fd.revents & G_IO_OUT)) {
|
||||||
|
redisAsyncHandleWrite(redis->ac);
|
||||||
|
redis->poll_fd.revents &= ~G_IO_OUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((redis->poll_fd.revents & G_IO_IN)) {
|
||||||
|
redisAsyncHandleRead(redis->ac);
|
||||||
|
redis->poll_fd.revents &= ~G_IO_IN;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callback) {
|
||||||
|
return callback(user_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
redis_source_finalize (GSource *source)
|
||||||
|
{
|
||||||
|
RedisSource *redis = (RedisSource *)source;
|
||||||
|
|
||||||
|
if (redis->poll_fd.fd >= 0) {
|
||||||
|
g_source_remove_poll(source, &redis->poll_fd);
|
||||||
|
redis->poll_fd.fd = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static GSource *
|
||||||
|
redis_source_new (redisAsyncContext *ac)
|
||||||
|
{
|
||||||
|
static GSourceFuncs source_funcs = {
|
||||||
|
.prepare = redis_source_prepare,
|
||||||
|
.check = redis_source_check,
|
||||||
|
.dispatch = redis_source_dispatch,
|
||||||
|
.finalize = redis_source_finalize,
|
||||||
|
};
|
||||||
|
redisContext *c = &ac->c;
|
||||||
|
RedisSource *source;
|
||||||
|
|
||||||
|
g_return_val_if_fail(ac != NULL, NULL);
|
||||||
|
|
||||||
|
source = (RedisSource *)g_source_new(&source_funcs, sizeof *source);
|
||||||
|
if (source == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
source->ac = ac;
|
||||||
|
source->poll_fd.fd = c->fd;
|
||||||
|
source->poll_fd.events = 0;
|
||||||
|
source->poll_fd.revents = 0;
|
||||||
|
g_source_add_poll((GSource *)source, &source->poll_fd);
|
||||||
|
|
||||||
|
ac->ev.addRead = redis_source_add_read;
|
||||||
|
ac->ev.delRead = redis_source_del_read;
|
||||||
|
ac->ev.addWrite = redis_source_add_write;
|
||||||
|
ac->ev.delWrite = redis_source_del_write;
|
||||||
|
ac->ev.cleanup = redis_source_cleanup;
|
||||||
|
ac->ev.data = source;
|
||||||
|
|
||||||
|
return (GSource *)source;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* __HIREDIS_GLIB_H__ */
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
#ifndef __HIREDIS_IVYKIS_H__
|
||||||
|
#define __HIREDIS_IVYKIS_H__
|
||||||
|
#include <iv.h>
|
||||||
|
#include "../hiredis.h"
|
||||||
|
#include "../async.h"
|
||||||
|
|
||||||
|
typedef struct redisIvykisEvents {
|
||||||
|
redisAsyncContext *context;
|
||||||
|
struct iv_fd fd;
|
||||||
|
} redisIvykisEvents;
|
||||||
|
|
||||||
|
static void redisIvykisReadEvent(void *arg) {
|
||||||
|
redisAsyncContext *context = (redisAsyncContext *)arg;
|
||||||
|
redisAsyncHandleRead(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisIvykisWriteEvent(void *arg) {
|
||||||
|
redisAsyncContext *context = (redisAsyncContext *)arg;
|
||||||
|
redisAsyncHandleWrite(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisIvykisAddRead(void *privdata) {
|
||||||
|
redisIvykisEvents *e = (redisIvykisEvents*)privdata;
|
||||||
|
iv_fd_set_handler_in(&e->fd, redisIvykisReadEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisIvykisDelRead(void *privdata) {
|
||||||
|
redisIvykisEvents *e = (redisIvykisEvents*)privdata;
|
||||||
|
iv_fd_set_handler_in(&e->fd, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisIvykisAddWrite(void *privdata) {
|
||||||
|
redisIvykisEvents *e = (redisIvykisEvents*)privdata;
|
||||||
|
iv_fd_set_handler_out(&e->fd, redisIvykisWriteEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisIvykisDelWrite(void *privdata) {
|
||||||
|
redisIvykisEvents *e = (redisIvykisEvents*)privdata;
|
||||||
|
iv_fd_set_handler_out(&e->fd, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisIvykisCleanup(void *privdata) {
|
||||||
|
redisIvykisEvents *e = (redisIvykisEvents*)privdata;
|
||||||
|
|
||||||
|
iv_fd_unregister(&e->fd);
|
||||||
|
hi_free(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int redisIvykisAttach(redisAsyncContext *ac) {
|
||||||
|
redisContext *c = &(ac->c);
|
||||||
|
redisIvykisEvents *e;
|
||||||
|
|
||||||
|
/* Nothing should be attached when something is already attached */
|
||||||
|
if (ac->ev.data != NULL)
|
||||||
|
return REDIS_ERR;
|
||||||
|
|
||||||
|
/* Create container for context and r/w events */
|
||||||
|
e = (redisIvykisEvents*)hi_malloc(sizeof(*e));
|
||||||
|
if (e == NULL)
|
||||||
|
return REDIS_ERR;
|
||||||
|
|
||||||
|
e->context = ac;
|
||||||
|
|
||||||
|
/* Register functions to start/stop listening for events */
|
||||||
|
ac->ev.addRead = redisIvykisAddRead;
|
||||||
|
ac->ev.delRead = redisIvykisDelRead;
|
||||||
|
ac->ev.addWrite = redisIvykisAddWrite;
|
||||||
|
ac->ev.delWrite = redisIvykisDelWrite;
|
||||||
|
ac->ev.cleanup = redisIvykisCleanup;
|
||||||
|
ac->ev.data = e;
|
||||||
|
|
||||||
|
/* Initialize and install read/write events */
|
||||||
|
IV_FD_INIT(&e->fd);
|
||||||
|
e->fd.fd = c->fd;
|
||||||
|
e->fd.handler_in = redisIvykisReadEvent;
|
||||||
|
e->fd.handler_out = redisIvykisWriteEvent;
|
||||||
|
e->fd.handler_err = NULL;
|
||||||
|
e->fd.cookie = e->context;
|
||||||
|
|
||||||
|
iv_fd_register(&e->fd);
|
||||||
|
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||||
|
*
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Redis nor the names of its contributors may be used
|
||||||
|
* to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __HIREDIS_LIBEV_H__
|
||||||
|
#define __HIREDIS_LIBEV_H__
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <ev.h>
|
||||||
|
#include "../hiredis.h"
|
||||||
|
#include "../async.h"
|
||||||
|
|
||||||
|
typedef struct redisLibevEvents {
|
||||||
|
redisAsyncContext *context;
|
||||||
|
struct ev_loop *loop;
|
||||||
|
int reading, writing;
|
||||||
|
ev_io rev, wev;
|
||||||
|
ev_timer timer;
|
||||||
|
} redisLibevEvents;
|
||||||
|
|
||||||
|
static void redisLibevReadEvent(EV_P_ ev_io *watcher, int revents) {
|
||||||
|
#if EV_MULTIPLICITY
|
||||||
|
((void)EV_A);
|
||||||
|
#endif
|
||||||
|
((void)revents);
|
||||||
|
|
||||||
|
redisLibevEvents *e = (redisLibevEvents*)watcher->data;
|
||||||
|
redisAsyncHandleRead(e->context);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisLibevWriteEvent(EV_P_ ev_io *watcher, int revents) {
|
||||||
|
#if EV_MULTIPLICITY
|
||||||
|
((void)EV_A);
|
||||||
|
#endif
|
||||||
|
((void)revents);
|
||||||
|
|
||||||
|
redisLibevEvents *e = (redisLibevEvents*)watcher->data;
|
||||||
|
redisAsyncHandleWrite(e->context);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisLibevAddRead(void *privdata) {
|
||||||
|
redisLibevEvents *e = (redisLibevEvents*)privdata;
|
||||||
|
#if EV_MULTIPLICITY
|
||||||
|
struct ev_loop *loop = e->loop;
|
||||||
|
#endif
|
||||||
|
if (!e->reading) {
|
||||||
|
e->reading = 1;
|
||||||
|
ev_io_start(EV_A_ &e->rev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisLibevDelRead(void *privdata) {
|
||||||
|
redisLibevEvents *e = (redisLibevEvents*)privdata;
|
||||||
|
#if EV_MULTIPLICITY
|
||||||
|
struct ev_loop *loop = e->loop;
|
||||||
|
#endif
|
||||||
|
if (e->reading) {
|
||||||
|
e->reading = 0;
|
||||||
|
ev_io_stop(EV_A_ &e->rev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisLibevAddWrite(void *privdata) {
|
||||||
|
redisLibevEvents *e = (redisLibevEvents*)privdata;
|
||||||
|
#if EV_MULTIPLICITY
|
||||||
|
struct ev_loop *loop = e->loop;
|
||||||
|
#endif
|
||||||
|
if (!e->writing) {
|
||||||
|
e->writing = 1;
|
||||||
|
ev_io_start(EV_A_ &e->wev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisLibevDelWrite(void *privdata) {
|
||||||
|
redisLibevEvents *e = (redisLibevEvents*)privdata;
|
||||||
|
#if EV_MULTIPLICITY
|
||||||
|
struct ev_loop *loop = e->loop;
|
||||||
|
#endif
|
||||||
|
if (e->writing) {
|
||||||
|
e->writing = 0;
|
||||||
|
ev_io_stop(EV_A_ &e->wev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisLibevStopTimer(void *privdata) {
|
||||||
|
redisLibevEvents *e = (redisLibevEvents*)privdata;
|
||||||
|
#if EV_MULTIPLICITY
|
||||||
|
struct ev_loop *loop = e->loop;
|
||||||
|
#endif
|
||||||
|
ev_timer_stop(EV_A_ &e->timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisLibevCleanup(void *privdata) {
|
||||||
|
redisLibevEvents *e = (redisLibevEvents*)privdata;
|
||||||
|
redisLibevDelRead(privdata);
|
||||||
|
redisLibevDelWrite(privdata);
|
||||||
|
redisLibevStopTimer(privdata);
|
||||||
|
hi_free(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisLibevTimeout(EV_P_ ev_timer *timer, int revents) {
|
||||||
|
#if EV_MULTIPLICITY
|
||||||
|
((void)EV_A);
|
||||||
|
#endif
|
||||||
|
((void)revents);
|
||||||
|
redisLibevEvents *e = (redisLibevEvents*)timer->data;
|
||||||
|
redisAsyncHandleTimeout(e->context);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisLibevSetTimeout(void *privdata, struct timeval tv) {
|
||||||
|
redisLibevEvents *e = (redisLibevEvents*)privdata;
|
||||||
|
#if EV_MULTIPLICITY
|
||||||
|
struct ev_loop *loop = e->loop;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!ev_is_active(&e->timer)) {
|
||||||
|
ev_init(&e->timer, redisLibevTimeout);
|
||||||
|
e->timer.data = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
e->timer.repeat = tv.tv_sec + tv.tv_usec / 1000000.00;
|
||||||
|
ev_timer_again(EV_A_ &e->timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int redisLibevAttach(EV_P_ redisAsyncContext *ac) {
|
||||||
|
redisContext *c = &(ac->c);
|
||||||
|
redisLibevEvents *e;
|
||||||
|
|
||||||
|
/* Nothing should be attached when something is already attached */
|
||||||
|
if (ac->ev.data != NULL)
|
||||||
|
return REDIS_ERR;
|
||||||
|
|
||||||
|
/* Create container for context and r/w events */
|
||||||
|
e = (redisLibevEvents*)hi_calloc(1, sizeof(*e));
|
||||||
|
if (e == NULL)
|
||||||
|
return REDIS_ERR;
|
||||||
|
|
||||||
|
e->context = ac;
|
||||||
|
#if EV_MULTIPLICITY
|
||||||
|
e->loop = EV_A;
|
||||||
|
#else
|
||||||
|
e->loop = NULL;
|
||||||
|
#endif
|
||||||
|
e->rev.data = e;
|
||||||
|
e->wev.data = e;
|
||||||
|
|
||||||
|
/* Register functions to start/stop listening for events */
|
||||||
|
ac->ev.addRead = redisLibevAddRead;
|
||||||
|
ac->ev.delRead = redisLibevDelRead;
|
||||||
|
ac->ev.addWrite = redisLibevAddWrite;
|
||||||
|
ac->ev.delWrite = redisLibevDelWrite;
|
||||||
|
ac->ev.cleanup = redisLibevCleanup;
|
||||||
|
ac->ev.scheduleTimer = redisLibevSetTimeout;
|
||||||
|
ac->ev.data = e;
|
||||||
|
|
||||||
|
/* Initialize read/write events */
|
||||||
|
ev_io_init(&e->rev,redisLibevReadEvent,c->fd,EV_READ);
|
||||||
|
ev_io_init(&e->wev,redisLibevWriteEvent,c->fd,EV_WRITE);
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||||
|
*
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Redis nor the names of its contributors may be used
|
||||||
|
* to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __HIREDIS_LIBEVENT_H__
|
||||||
|
#define __HIREDIS_LIBEVENT_H__
|
||||||
|
#include <event2/event.h>
|
||||||
|
#include "../hiredis.h"
|
||||||
|
#include "../async.h"
|
||||||
|
|
||||||
|
#define REDIS_LIBEVENT_DELETED 0x01
|
||||||
|
#define REDIS_LIBEVENT_ENTERED 0x02
|
||||||
|
|
||||||
|
typedef struct redisLibeventEvents {
|
||||||
|
redisAsyncContext *context;
|
||||||
|
struct event *ev;
|
||||||
|
struct event_base *base;
|
||||||
|
struct timeval tv;
|
||||||
|
short flags;
|
||||||
|
short state;
|
||||||
|
} redisLibeventEvents;
|
||||||
|
|
||||||
|
static void redisLibeventDestroy(redisLibeventEvents *e) {
|
||||||
|
hi_free(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisLibeventHandler(evutil_socket_t fd, short event, void *arg) {
|
||||||
|
((void)fd);
|
||||||
|
redisLibeventEvents *e = (redisLibeventEvents*)arg;
|
||||||
|
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) {
|
||||||
|
redisLibeventUpdate(privdata, EV_READ, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisLibeventDelRead(void *privdata) {
|
||||||
|
redisLibeventUpdate(privdata, EV_READ, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisLibeventAddWrite(void *privdata) {
|
||||||
|
redisLibeventUpdate(privdata, EV_WRITE, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisLibeventDelWrite(void *privdata) {
|
||||||
|
redisLibeventUpdate(privdata, EV_WRITE, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisLibeventCleanup(void *privdata) {
|
||||||
|
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
|
||||||
|
if (!e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
redisContext *c = &(ac->c);
|
||||||
|
redisLibeventEvents *e;
|
||||||
|
|
||||||
|
/* Nothing should be attached when something is already attached */
|
||||||
|
if (ac->ev.data != NULL)
|
||||||
|
return REDIS_ERR;
|
||||||
|
|
||||||
|
/* Create container for context and r/w events */
|
||||||
|
e = (redisLibeventEvents*)hi_calloc(1, sizeof(*e));
|
||||||
|
if (e == NULL)
|
||||||
|
return REDIS_ERR;
|
||||||
|
|
||||||
|
e->context = ac;
|
||||||
|
|
||||||
|
/* Register functions to start/stop listening for events */
|
||||||
|
ac->ev.addRead = redisLibeventAddRead;
|
||||||
|
ac->ev.delRead = redisLibeventDelRead;
|
||||||
|
ac->ev.addWrite = redisLibeventAddWrite;
|
||||||
|
ac->ev.delWrite = redisLibeventDelWrite;
|
||||||
|
ac->ev.cleanup = redisLibeventCleanup;
|
||||||
|
ac->ev.scheduleTimer = redisLibeventSetTimeout;
|
||||||
|
ac->ev.data = e;
|
||||||
|
|
||||||
|
/* Initialize and install read/write events */
|
||||||
|
e->ev = event_new(base, c->fd, EV_READ | EV_WRITE, redisLibeventHandler, e);
|
||||||
|
e->base = base;
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
#ifndef __HIREDIS_LIBHV_H__
|
||||||
|
#define __HIREDIS_LIBHV_H__
|
||||||
|
|
||||||
|
#include <hv/hloop.h>
|
||||||
|
#include "../hiredis.h"
|
||||||
|
#include "../async.h"
|
||||||
|
|
||||||
|
typedef struct redisLibhvEvents {
|
||||||
|
hio_t *io;
|
||||||
|
htimer_t *timer;
|
||||||
|
} redisLibhvEvents;
|
||||||
|
|
||||||
|
static void redisLibhvHandleEvents(hio_t* io) {
|
||||||
|
redisAsyncContext* context = (redisAsyncContext*)hevent_userdata(io);
|
||||||
|
int events = hio_events(io);
|
||||||
|
int revents = hio_revents(io);
|
||||||
|
if (context && (events & HV_READ) && (revents & HV_READ)) {
|
||||||
|
redisAsyncHandleRead(context);
|
||||||
|
}
|
||||||
|
if (context && (events & HV_WRITE) && (revents & HV_WRITE)) {
|
||||||
|
redisAsyncHandleWrite(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisLibhvAddRead(void *privdata) {
|
||||||
|
redisLibhvEvents* events = (redisLibhvEvents*)privdata;
|
||||||
|
hio_add(events->io, redisLibhvHandleEvents, HV_READ);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisLibhvDelRead(void *privdata) {
|
||||||
|
redisLibhvEvents* events = (redisLibhvEvents*)privdata;
|
||||||
|
hio_del(events->io, HV_READ);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisLibhvAddWrite(void *privdata) {
|
||||||
|
redisLibhvEvents* events = (redisLibhvEvents*)privdata;
|
||||||
|
hio_add(events->io, redisLibhvHandleEvents, HV_WRITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisLibhvDelWrite(void *privdata) {
|
||||||
|
redisLibhvEvents* events = (redisLibhvEvents*)privdata;
|
||||||
|
hio_del(events->io, HV_WRITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisLibhvCleanup(void *privdata) {
|
||||||
|
redisLibhvEvents* events = (redisLibhvEvents*)privdata;
|
||||||
|
|
||||||
|
if (events->timer)
|
||||||
|
htimer_del(events->timer);
|
||||||
|
|
||||||
|
hio_close(events->io);
|
||||||
|
hevent_set_userdata(events->io, NULL);
|
||||||
|
|
||||||
|
hi_free(events);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisLibhvTimeout(htimer_t* timer) {
|
||||||
|
hio_t* io = (hio_t*)hevent_userdata(timer);
|
||||||
|
redisAsyncHandleTimeout((redisAsyncContext*)hevent_userdata(io));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisLibhvSetTimeout(void *privdata, struct timeval tv) {
|
||||||
|
redisLibhvEvents* events;
|
||||||
|
uint32_t millis;
|
||||||
|
hloop_t* loop;
|
||||||
|
|
||||||
|
events = (redisLibhvEvents*)privdata;
|
||||||
|
millis = tv.tv_sec * 1000 + tv.tv_usec / 1000;
|
||||||
|
|
||||||
|
if (millis == 0) {
|
||||||
|
/* Libhv disallows zero'd timers so treat this as a delete or NO OP */
|
||||||
|
if (events->timer) {
|
||||||
|
htimer_del(events->timer);
|
||||||
|
events->timer = NULL;
|
||||||
|
}
|
||||||
|
} else if (events->timer == NULL) {
|
||||||
|
/* Add new timer */
|
||||||
|
loop = hevent_loop(events->io);
|
||||||
|
events->timer = htimer_add(loop, redisLibhvTimeout, millis, 1);
|
||||||
|
hevent_set_userdata(events->timer, events->io);
|
||||||
|
} else {
|
||||||
|
/* Update existing timer */
|
||||||
|
htimer_reset(events->timer, millis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int redisLibhvAttach(redisAsyncContext* ac, hloop_t* loop) {
|
||||||
|
redisContext *c = &(ac->c);
|
||||||
|
redisLibhvEvents *events;
|
||||||
|
hio_t* io = NULL;
|
||||||
|
|
||||||
|
if (ac->ev.data != NULL) {
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create container struct to keep track of our io and any timer */
|
||||||
|
events = (redisLibhvEvents*)hi_malloc(sizeof(*events));
|
||||||
|
if (events == NULL) {
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
io = hio_get(loop, c->fd);
|
||||||
|
if (io == NULL) {
|
||||||
|
hi_free(events);
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
hevent_set_userdata(io, ac);
|
||||||
|
|
||||||
|
events->io = io;
|
||||||
|
events->timer = NULL;
|
||||||
|
|
||||||
|
ac->ev.addRead = redisLibhvAddRead;
|
||||||
|
ac->ev.delRead = redisLibhvDelRead;
|
||||||
|
ac->ev.addWrite = redisLibhvAddWrite;
|
||||||
|
ac->ev.delWrite = redisLibhvDelWrite;
|
||||||
|
ac->ev.cleanup = redisLibhvCleanup;
|
||||||
|
ac->ev.scheduleTimer = redisLibhvSetTimeout;
|
||||||
|
ac->ev.data = events;
|
||||||
|
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
#ifndef HIREDIS_LIBSDEVENT_H
|
||||||
|
#define HIREDIS_LIBSDEVENT_H
|
||||||
|
#include <systemd/sd-event.h>
|
||||||
|
#include "../hiredis.h"
|
||||||
|
#include "../async.h"
|
||||||
|
|
||||||
|
#define REDIS_LIBSDEVENT_DELETED 0x01
|
||||||
|
#define REDIS_LIBSDEVENT_ENTERED 0x02
|
||||||
|
|
||||||
|
typedef struct redisLibsdeventEvents {
|
||||||
|
redisAsyncContext *context;
|
||||||
|
struct sd_event *event;
|
||||||
|
struct sd_event_source *fdSource;
|
||||||
|
struct sd_event_source *timerSource;
|
||||||
|
int fd;
|
||||||
|
short flags;
|
||||||
|
short state;
|
||||||
|
} redisLibsdeventEvents;
|
||||||
|
|
||||||
|
static void redisLibsdeventDestroy(redisLibsdeventEvents *e) {
|
||||||
|
if (e->fdSource) {
|
||||||
|
e->fdSource = sd_event_source_disable_unref(e->fdSource);
|
||||||
|
}
|
||||||
|
if (e->timerSource) {
|
||||||
|
e->timerSource = sd_event_source_disable_unref(e->timerSource);
|
||||||
|
}
|
||||||
|
sd_event_unref(e->event);
|
||||||
|
hi_free(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int redisLibsdeventTimeoutHandler(sd_event_source *s, uint64_t usec, void *userdata) {
|
||||||
|
((void)s);
|
||||||
|
((void)usec);
|
||||||
|
redisLibsdeventEvents *e = (redisLibsdeventEvents*)userdata;
|
||||||
|
redisAsyncHandleTimeout(e->context);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int redisLibsdeventHandler(sd_event_source *s, int fd, uint32_t event, void *userdata) {
|
||||||
|
((void)s);
|
||||||
|
((void)fd);
|
||||||
|
redisLibsdeventEvents *e = (redisLibsdeventEvents*)userdata;
|
||||||
|
e->state |= REDIS_LIBSDEVENT_ENTERED;
|
||||||
|
|
||||||
|
#define CHECK_DELETED() if (e->state & REDIS_LIBSDEVENT_DELETED) {\
|
||||||
|
redisLibsdeventDestroy(e);\
|
||||||
|
return 0; \
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((event & EPOLLIN) && e->context && (e->state & REDIS_LIBSDEVENT_DELETED) == 0) {
|
||||||
|
redisAsyncHandleRead(e->context);
|
||||||
|
CHECK_DELETED();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((event & EPOLLOUT) && e->context && (e->state & REDIS_LIBSDEVENT_DELETED) == 0) {
|
||||||
|
redisAsyncHandleWrite(e->context);
|
||||||
|
CHECK_DELETED();
|
||||||
|
}
|
||||||
|
|
||||||
|
e->state &= ~REDIS_LIBSDEVENT_ENTERED;
|
||||||
|
#undef CHECK_DELETED
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisLibsdeventAddRead(void *userdata) {
|
||||||
|
redisLibsdeventEvents *e = (redisLibsdeventEvents*)userdata;
|
||||||
|
|
||||||
|
if (e->flags & EPOLLIN) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
e->flags |= EPOLLIN;
|
||||||
|
|
||||||
|
if (e->flags & EPOLLOUT) {
|
||||||
|
sd_event_source_set_io_events(e->fdSource, e->flags);
|
||||||
|
} else {
|
||||||
|
sd_event_add_io(e->event, &e->fdSource, e->fd, e->flags, redisLibsdeventHandler, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisLibsdeventDelRead(void *userdata) {
|
||||||
|
redisLibsdeventEvents *e = (redisLibsdeventEvents*)userdata;
|
||||||
|
|
||||||
|
e->flags &= ~EPOLLIN;
|
||||||
|
|
||||||
|
if (e->flags) {
|
||||||
|
sd_event_source_set_io_events(e->fdSource, e->flags);
|
||||||
|
} else {
|
||||||
|
e->fdSource = sd_event_source_disable_unref(e->fdSource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisLibsdeventAddWrite(void *userdata) {
|
||||||
|
redisLibsdeventEvents *e = (redisLibsdeventEvents*)userdata;
|
||||||
|
|
||||||
|
if (e->flags & EPOLLOUT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
e->flags |= EPOLLOUT;
|
||||||
|
|
||||||
|
if (e->flags & EPOLLIN) {
|
||||||
|
sd_event_source_set_io_events(e->fdSource, e->flags);
|
||||||
|
} else {
|
||||||
|
sd_event_add_io(e->event, &e->fdSource, e->fd, e->flags, redisLibsdeventHandler, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisLibsdeventDelWrite(void *userdata) {
|
||||||
|
redisLibsdeventEvents *e = (redisLibsdeventEvents*)userdata;
|
||||||
|
|
||||||
|
e->flags &= ~EPOLLOUT;
|
||||||
|
|
||||||
|
if (e->flags) {
|
||||||
|
sd_event_source_set_io_events(e->fdSource, e->flags);
|
||||||
|
} else {
|
||||||
|
e->fdSource = sd_event_source_disable_unref(e->fdSource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisLibsdeventCleanup(void *userdata) {
|
||||||
|
redisLibsdeventEvents *e = (redisLibsdeventEvents*)userdata;
|
||||||
|
|
||||||
|
if (!e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e->state & REDIS_LIBSDEVENT_ENTERED) {
|
||||||
|
e->state |= REDIS_LIBSDEVENT_DELETED;
|
||||||
|
} else {
|
||||||
|
redisLibsdeventDestroy(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisLibsdeventSetTimeout(void *userdata, struct timeval tv) {
|
||||||
|
redisLibsdeventEvents *e = (redisLibsdeventEvents *)userdata;
|
||||||
|
|
||||||
|
uint64_t usec = tv.tv_sec * 1000000 + tv.tv_usec;
|
||||||
|
if (!e->timerSource) {
|
||||||
|
sd_event_add_time_relative(e->event, &e->timerSource, CLOCK_MONOTONIC, usec, 1, redisLibsdeventTimeoutHandler, e);
|
||||||
|
} else {
|
||||||
|
sd_event_source_set_time_relative(e->timerSource, usec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int redisLibsdeventAttach(redisAsyncContext *ac, struct sd_event *event) {
|
||||||
|
redisContext *c = &(ac->c);
|
||||||
|
redisLibsdeventEvents *e;
|
||||||
|
|
||||||
|
/* Nothing should be attached when something is already attached */
|
||||||
|
if (ac->ev.data != NULL)
|
||||||
|
return REDIS_ERR;
|
||||||
|
|
||||||
|
/* Create container for context and r/w events */
|
||||||
|
e = (redisLibsdeventEvents*)hi_calloc(1, sizeof(*e));
|
||||||
|
if (e == NULL)
|
||||||
|
return REDIS_ERR;
|
||||||
|
|
||||||
|
/* Initialize and increase event refcount */
|
||||||
|
e->context = ac;
|
||||||
|
e->event = event;
|
||||||
|
e->fd = c->fd;
|
||||||
|
sd_event_ref(event);
|
||||||
|
|
||||||
|
/* Register functions to start/stop listening for events */
|
||||||
|
ac->ev.addRead = redisLibsdeventAddRead;
|
||||||
|
ac->ev.delRead = redisLibsdeventDelRead;
|
||||||
|
ac->ev.addWrite = redisLibsdeventAddWrite;
|
||||||
|
ac->ev.delWrite = redisLibsdeventDelWrite;
|
||||||
|
ac->ev.cleanup = redisLibsdeventCleanup;
|
||||||
|
ac->ev.scheduleTimer = redisLibsdeventSetTimeout;
|
||||||
|
ac->ev.data = e;
|
||||||
|
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
#ifndef __HIREDIS_LIBUV_H__
|
||||||
|
#define __HIREDIS_LIBUV_H__
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <uv.h>
|
||||||
|
#include "../hiredis.h"
|
||||||
|
#include "../async.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
typedef struct redisLibuvEvents {
|
||||||
|
redisAsyncContext* context;
|
||||||
|
uv_poll_t handle;
|
||||||
|
uv_timer_t timer;
|
||||||
|
int events;
|
||||||
|
} redisLibuvEvents;
|
||||||
|
|
||||||
|
|
||||||
|
static void redisLibuvPoll(uv_poll_t* handle, int status, int events) {
|
||||||
|
redisLibuvEvents* p = (redisLibuvEvents*)handle->data;
|
||||||
|
int ev = (status ? p->events : events);
|
||||||
|
|
||||||
|
if (p->context != NULL && (ev & UV_READABLE)) {
|
||||||
|
redisAsyncHandleRead(p->context);
|
||||||
|
}
|
||||||
|
if (p->context != NULL && (ev & UV_WRITABLE)) {
|
||||||
|
redisAsyncHandleWrite(p->context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void redisLibuvAddRead(void *privdata) {
|
||||||
|
redisLibuvEvents* p = (redisLibuvEvents*)privdata;
|
||||||
|
|
||||||
|
if (p->events & UV_READABLE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
p->events |= UV_READABLE;
|
||||||
|
|
||||||
|
uv_poll_start(&p->handle, p->events, redisLibuvPoll);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void redisLibuvDelRead(void *privdata) {
|
||||||
|
redisLibuvEvents* p = (redisLibuvEvents*)privdata;
|
||||||
|
|
||||||
|
p->events &= ~UV_READABLE;
|
||||||
|
|
||||||
|
if (p->events) {
|
||||||
|
uv_poll_start(&p->handle, p->events, redisLibuvPoll);
|
||||||
|
} else {
|
||||||
|
uv_poll_stop(&p->handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void redisLibuvAddWrite(void *privdata) {
|
||||||
|
redisLibuvEvents* p = (redisLibuvEvents*)privdata;
|
||||||
|
|
||||||
|
if (p->events & UV_WRITABLE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
p->events |= UV_WRITABLE;
|
||||||
|
|
||||||
|
uv_poll_start(&p->handle, p->events, redisLibuvPoll);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void redisLibuvDelWrite(void *privdata) {
|
||||||
|
redisLibuvEvents* p = (redisLibuvEvents*)privdata;
|
||||||
|
|
||||||
|
p->events &= ~UV_WRITABLE;
|
||||||
|
|
||||||
|
if (p->events) {
|
||||||
|
uv_poll_start(&p->handle, p->events, redisLibuvPoll);
|
||||||
|
} else {
|
||||||
|
uv_poll_stop(&p->handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_timer_close(uv_handle_t *handle) {
|
||||||
|
redisLibuvEvents* p = (redisLibuvEvents*)handle->data;
|
||||||
|
p->timer.data = NULL;
|
||||||
|
if (!p->handle.data) {
|
||||||
|
// both timer and handle are closed
|
||||||
|
hi_free(p);
|
||||||
|
}
|
||||||
|
// else, wait for `on_handle_close`
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_handle_close(uv_handle_t *handle) {
|
||||||
|
redisLibuvEvents* p = (redisLibuvEvents*)handle->data;
|
||||||
|
p->handle.data = NULL;
|
||||||
|
if (!p->timer.data) {
|
||||||
|
// timer never started, or timer already destroyed
|
||||||
|
hi_free(p);
|
||||||
|
}
|
||||||
|
// else, wait for `on_timer_close`
|
||||||
|
}
|
||||||
|
|
||||||
|
// libuv removed `status` parameter since v0.11.23
|
||||||
|
// see: https://github.com/libuv/libuv/blob/v0.11.23/include/uv.h
|
||||||
|
#if (UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR < 11) || \
|
||||||
|
(UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR == 11 && UV_VERSION_PATCH < 23)
|
||||||
|
static void redisLibuvTimeout(uv_timer_t *timer, int status) {
|
||||||
|
(void)status; // unused
|
||||||
|
#else
|
||||||
|
static void redisLibuvTimeout(uv_timer_t *timer) {
|
||||||
|
#endif
|
||||||
|
redisLibuvEvents *e = (redisLibuvEvents*)timer->data;
|
||||||
|
redisAsyncHandleTimeout(e->context);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisLibuvSetTimeout(void *privdata, struct timeval tv) {
|
||||||
|
redisLibuvEvents* p = (redisLibuvEvents*)privdata;
|
||||||
|
|
||||||
|
uint64_t millsec = tv.tv_sec * 1000 + tv.tv_usec / 1000.0;
|
||||||
|
if (!p->timer.data) {
|
||||||
|
// timer is uninitialized
|
||||||
|
if (uv_timer_init(p->handle.loop, &p->timer) != 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
p->timer.data = p;
|
||||||
|
}
|
||||||
|
// updates the timeout if the timer has already started
|
||||||
|
// or start the timer
|
||||||
|
uv_timer_start(&p->timer, redisLibuvTimeout, millsec, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisLibuvCleanup(void *privdata) {
|
||||||
|
redisLibuvEvents* p = (redisLibuvEvents*)privdata;
|
||||||
|
|
||||||
|
p->context = NULL; // indicate that context might no longer exist
|
||||||
|
if (p->timer.data) {
|
||||||
|
uv_close((uv_handle_t*)&p->timer, on_timer_close);
|
||||||
|
}
|
||||||
|
uv_close((uv_handle_t*)&p->handle, on_handle_close);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int redisLibuvAttach(redisAsyncContext* ac, uv_loop_t* loop) {
|
||||||
|
redisContext *c = &(ac->c);
|
||||||
|
|
||||||
|
if (ac->ev.data != NULL) {
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
ac->ev.addRead = redisLibuvAddRead;
|
||||||
|
ac->ev.delRead = redisLibuvDelRead;
|
||||||
|
ac->ev.addWrite = redisLibuvAddWrite;
|
||||||
|
ac->ev.delWrite = redisLibuvDelWrite;
|
||||||
|
ac->ev.cleanup = redisLibuvCleanup;
|
||||||
|
ac->ev.scheduleTimer = redisLibuvSetTimeout;
|
||||||
|
|
||||||
|
redisLibuvEvents* p = (redisLibuvEvents*)hi_malloc(sizeof(*p));
|
||||||
|
if (p == NULL)
|
||||||
|
return REDIS_ERR;
|
||||||
|
|
||||||
|
memset(p, 0, sizeof(*p));
|
||||||
|
|
||||||
|
if (uv_poll_init_socket(loop, &p->handle, c->fd) != 0) {
|
||||||
|
hi_free(p);
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
ac->ev.data = p;
|
||||||
|
p->handle.data = p;
|
||||||
|
p->context = ac;
|
||||||
|
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015 Дмитрий Бахвалов (Dmitry Bakhvalov)
|
||||||
|
*
|
||||||
|
* Permission for license update:
|
||||||
|
* https://github.com/redis/hiredis/issues/1271#issuecomment-2258225227
|
||||||
|
*
|
||||||
|
* 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 __HIREDIS_MACOSX_H__
|
||||||
|
#define __HIREDIS_MACOSX_H__
|
||||||
|
|
||||||
|
#include <CoreFoundation/CoreFoundation.h>
|
||||||
|
|
||||||
|
#include "../hiredis.h"
|
||||||
|
#include "../async.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
redisAsyncContext *context;
|
||||||
|
CFSocketRef socketRef;
|
||||||
|
CFRunLoopSourceRef sourceRef;
|
||||||
|
} RedisRunLoop;
|
||||||
|
|
||||||
|
static int freeRedisRunLoop(RedisRunLoop* redisRunLoop) {
|
||||||
|
if( redisRunLoop != NULL ) {
|
||||||
|
if( redisRunLoop->sourceRef != NULL ) {
|
||||||
|
CFRunLoopSourceInvalidate(redisRunLoop->sourceRef);
|
||||||
|
CFRelease(redisRunLoop->sourceRef);
|
||||||
|
}
|
||||||
|
if( redisRunLoop->socketRef != NULL ) {
|
||||||
|
CFSocketInvalidate(redisRunLoop->socketRef);
|
||||||
|
CFRelease(redisRunLoop->socketRef);
|
||||||
|
}
|
||||||
|
hi_free(redisRunLoop);
|
||||||
|
}
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisMacOSAddRead(void *privdata) {
|
||||||
|
RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata;
|
||||||
|
CFSocketEnableCallBacks(redisRunLoop->socketRef, kCFSocketReadCallBack);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisMacOSDelRead(void *privdata) {
|
||||||
|
RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata;
|
||||||
|
CFSocketDisableCallBacks(redisRunLoop->socketRef, kCFSocketReadCallBack);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisMacOSAddWrite(void *privdata) {
|
||||||
|
RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata;
|
||||||
|
CFSocketEnableCallBacks(redisRunLoop->socketRef, kCFSocketWriteCallBack);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisMacOSDelWrite(void *privdata) {
|
||||||
|
RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata;
|
||||||
|
CFSocketDisableCallBacks(redisRunLoop->socketRef, kCFSocketWriteCallBack);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisMacOSCleanup(void *privdata) {
|
||||||
|
RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata;
|
||||||
|
freeRedisRunLoop(redisRunLoop);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisMacOSAsyncCallback(CFSocketRef __unused s, CFSocketCallBackType callbackType, CFDataRef __unused address, const void __unused *data, void *info) {
|
||||||
|
redisAsyncContext* context = (redisAsyncContext*) info;
|
||||||
|
|
||||||
|
switch (callbackType) {
|
||||||
|
case kCFSocketReadCallBack:
|
||||||
|
redisAsyncHandleRead(context);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case kCFSocketWriteCallBack:
|
||||||
|
redisAsyncHandleWrite(context);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int redisMacOSAttach(redisAsyncContext *redisAsyncCtx, CFRunLoopRef runLoop) {
|
||||||
|
redisContext *redisCtx = &(redisAsyncCtx->c);
|
||||||
|
|
||||||
|
/* Nothing should be attached when something is already attached */
|
||||||
|
if( redisAsyncCtx->ev.data != NULL ) return REDIS_ERR;
|
||||||
|
|
||||||
|
RedisRunLoop* redisRunLoop = (RedisRunLoop*) hi_calloc(1, sizeof(RedisRunLoop));
|
||||||
|
if (redisRunLoop == NULL)
|
||||||
|
return REDIS_ERR;
|
||||||
|
|
||||||
|
/* Setup redis stuff */
|
||||||
|
redisRunLoop->context = redisAsyncCtx;
|
||||||
|
|
||||||
|
redisAsyncCtx->ev.addRead = redisMacOSAddRead;
|
||||||
|
redisAsyncCtx->ev.delRead = redisMacOSDelRead;
|
||||||
|
redisAsyncCtx->ev.addWrite = redisMacOSAddWrite;
|
||||||
|
redisAsyncCtx->ev.delWrite = redisMacOSDelWrite;
|
||||||
|
redisAsyncCtx->ev.cleanup = redisMacOSCleanup;
|
||||||
|
redisAsyncCtx->ev.data = redisRunLoop;
|
||||||
|
|
||||||
|
/* Initialize and install read/write events */
|
||||||
|
CFSocketContext socketCtx = { 0, redisAsyncCtx, NULL, NULL, NULL };
|
||||||
|
|
||||||
|
redisRunLoop->socketRef = CFSocketCreateWithNative(NULL, redisCtx->fd,
|
||||||
|
kCFSocketReadCallBack | kCFSocketWriteCallBack,
|
||||||
|
redisMacOSAsyncCallback,
|
||||||
|
&socketCtx);
|
||||||
|
if( !redisRunLoop->socketRef ) return freeRedisRunLoop(redisRunLoop);
|
||||||
|
|
||||||
|
redisRunLoop->sourceRef = CFSocketCreateRunLoopSource(NULL, redisRunLoop->socketRef, 0);
|
||||||
|
if( !redisRunLoop->sourceRef ) return freeRedisRunLoop(redisRunLoop);
|
||||||
|
|
||||||
|
CFRunLoopAddSource(runLoop, redisRunLoop->sourceRef, kCFRunLoopDefaultMode);
|
||||||
|
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
+197
@@ -0,0 +1,197 @@
|
|||||||
|
|
||||||
|
#ifndef HIREDIS_POLL_H
|
||||||
|
#define HIREDIS_POLL_H
|
||||||
|
|
||||||
|
#include "../async.h"
|
||||||
|
#include "../sockcompat.h"
|
||||||
|
#include <string.h> // for memset
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
/* Values to return from redisPollTick */
|
||||||
|
#define REDIS_POLL_HANDLED_READ 1
|
||||||
|
#define REDIS_POLL_HANDLED_WRITE 2
|
||||||
|
#define REDIS_POLL_HANDLED_TIMEOUT 4
|
||||||
|
|
||||||
|
/* An adapter to allow manual polling of the async context by checking the state
|
||||||
|
* of the underlying file descriptor. Useful in cases where there is no formal
|
||||||
|
* IO event loop but regular ticking can be used, such as in game engines. */
|
||||||
|
|
||||||
|
typedef struct redisPollEvents {
|
||||||
|
redisAsyncContext *context;
|
||||||
|
redisFD fd;
|
||||||
|
char reading, writing;
|
||||||
|
char in_tick;
|
||||||
|
char deleted;
|
||||||
|
double deadline;
|
||||||
|
} redisPollEvents;
|
||||||
|
|
||||||
|
static double redisPollTimevalToDouble(struct timeval *tv) {
|
||||||
|
if (tv == NULL)
|
||||||
|
return 0.0;
|
||||||
|
return tv->tv_sec + tv->tv_usec / 1000000.00;
|
||||||
|
}
|
||||||
|
|
||||||
|
static double redisPollGetNow(void) {
|
||||||
|
#ifndef _MSC_VER
|
||||||
|
struct timeval tv;
|
||||||
|
gettimeofday(&tv,NULL);
|
||||||
|
return redisPollTimevalToDouble(&tv);
|
||||||
|
#else
|
||||||
|
FILETIME ft;
|
||||||
|
ULARGE_INTEGER li;
|
||||||
|
GetSystemTimeAsFileTime(&ft);
|
||||||
|
li.HighPart = ft.dwHighDateTime;
|
||||||
|
li.LowPart = ft.dwLowDateTime;
|
||||||
|
return (double)li.QuadPart * 1e-7;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Poll for io, handling any pending callbacks. The timeout argument can be
|
||||||
|
* positive to wait for a maximum given time for IO, zero to poll, or negative
|
||||||
|
* to wait forever */
|
||||||
|
static int redisPollTick(redisAsyncContext *ac, double timeout) {
|
||||||
|
int reading, writing;
|
||||||
|
struct pollfd pfd;
|
||||||
|
int handled;
|
||||||
|
int ns;
|
||||||
|
int itimeout;
|
||||||
|
|
||||||
|
redisPollEvents *e = (redisPollEvents*)ac->ev.data;
|
||||||
|
if (!e)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* local flags, won't get changed during callbacks */
|
||||||
|
reading = e->reading;
|
||||||
|
writing = e->writing;
|
||||||
|
if (!reading && !writing)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
pfd.fd = e->fd;
|
||||||
|
pfd.events = 0;
|
||||||
|
if (reading)
|
||||||
|
pfd.events = POLLIN;
|
||||||
|
if (writing)
|
||||||
|
pfd.events |= POLLOUT;
|
||||||
|
|
||||||
|
if (timeout >= 0.0) {
|
||||||
|
itimeout = (int)(timeout * 1000.0);
|
||||||
|
} else {
|
||||||
|
itimeout = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ns = poll(&pfd, 1, itimeout);
|
||||||
|
if (ns < 0) {
|
||||||
|
/* ignore the EINTR error */
|
||||||
|
if (errno != EINTR)
|
||||||
|
return ns;
|
||||||
|
ns = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
handled = 0;
|
||||||
|
e->in_tick = 1;
|
||||||
|
if (ns) {
|
||||||
|
if (reading && (pfd.revents & POLLIN)) {
|
||||||
|
redisAsyncHandleRead(ac);
|
||||||
|
handled |= REDIS_POLL_HANDLED_READ;
|
||||||
|
}
|
||||||
|
/* on Windows, connection failure is indicated with the Exception fdset.
|
||||||
|
* handle it the same as writable. */
|
||||||
|
if (writing && (pfd.revents & (POLLOUT | POLLERR))) {
|
||||||
|
/* context Read callback may have caused context to be deleted, e.g.
|
||||||
|
by doing an redisAsyncDisconnect() */
|
||||||
|
if (!e->deleted) {
|
||||||
|
redisAsyncHandleWrite(ac);
|
||||||
|
handled |= REDIS_POLL_HANDLED_WRITE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* perform timeouts */
|
||||||
|
if (!e->deleted && e->deadline != 0.0) {
|
||||||
|
double now = redisPollGetNow();
|
||||||
|
if (now >= e->deadline) {
|
||||||
|
/* deadline has passed. disable timeout and perform callback */
|
||||||
|
e->deadline = 0.0;
|
||||||
|
redisAsyncHandleTimeout(ac);
|
||||||
|
handled |= REDIS_POLL_HANDLED_TIMEOUT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* do a delayed cleanup if required */
|
||||||
|
if (e->deleted)
|
||||||
|
hi_free(e);
|
||||||
|
else
|
||||||
|
e->in_tick = 0;
|
||||||
|
|
||||||
|
return handled;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisPollAddRead(void *data) {
|
||||||
|
redisPollEvents *e = (redisPollEvents*)data;
|
||||||
|
e->reading = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisPollDelRead(void *data) {
|
||||||
|
redisPollEvents *e = (redisPollEvents*)data;
|
||||||
|
e->reading = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisPollAddWrite(void *data) {
|
||||||
|
redisPollEvents *e = (redisPollEvents*)data;
|
||||||
|
e->writing = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisPollDelWrite(void *data) {
|
||||||
|
redisPollEvents *e = (redisPollEvents*)data;
|
||||||
|
e->writing = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisPollCleanup(void *data) {
|
||||||
|
redisPollEvents *e = (redisPollEvents*)data;
|
||||||
|
|
||||||
|
/* if we are currently processing a tick, postpone deletion */
|
||||||
|
if (e->in_tick)
|
||||||
|
e->deleted = 1;
|
||||||
|
else
|
||||||
|
hi_free(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisPollScheduleTimer(void *data, struct timeval tv)
|
||||||
|
{
|
||||||
|
redisPollEvents *e = (redisPollEvents*)data;
|
||||||
|
double now = redisPollGetNow();
|
||||||
|
e->deadline = now + redisPollTimevalToDouble(&tv);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int redisPollAttach(redisAsyncContext *ac) {
|
||||||
|
redisContext *c = &(ac->c);
|
||||||
|
redisPollEvents *e;
|
||||||
|
|
||||||
|
/* Nothing should be attached when something is already attached */
|
||||||
|
if (ac->ev.data != NULL)
|
||||||
|
return REDIS_ERR;
|
||||||
|
|
||||||
|
/* Create container for context and r/w events */
|
||||||
|
e = (redisPollEvents*)hi_malloc(sizeof(*e));
|
||||||
|
if (e == NULL)
|
||||||
|
return REDIS_ERR;
|
||||||
|
memset(e, 0, sizeof(*e));
|
||||||
|
|
||||||
|
e->context = ac;
|
||||||
|
e->fd = c->fd;
|
||||||
|
e->reading = e->writing = 0;
|
||||||
|
e->in_tick = e->deleted = 0;
|
||||||
|
e->deadline = 0.0;
|
||||||
|
|
||||||
|
/* Register functions to start/stop listening for events */
|
||||||
|
ac->ev.addRead = redisPollAddRead;
|
||||||
|
ac->ev.delRead = redisPollDelRead;
|
||||||
|
ac->ev.addWrite = redisPollAddWrite;
|
||||||
|
ac->ev.delWrite = redisPollDelWrite;
|
||||||
|
ac->ev.scheduleTimer = redisPollScheduleTimer;
|
||||||
|
ac->ev.cleanup = redisPollCleanup;
|
||||||
|
ac->ev.data = e;
|
||||||
|
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
||||||
|
#endif /* HIREDIS_POLL_H */
|
||||||
+135
@@ -0,0 +1,135 @@
|
|||||||
|
/*-
|
||||||
|
* Copyright (C) 2014 Pietro Cerutti <gahr@gahr.ch>
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. 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.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY AUTHOR 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 AUTHOR 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 __HIREDIS_QT_H__
|
||||||
|
#define __HIREDIS_QT_H__
|
||||||
|
#include <QSocketNotifier>
|
||||||
|
#include "../async.h"
|
||||||
|
|
||||||
|
static void RedisQtAddRead(void *);
|
||||||
|
static void RedisQtDelRead(void *);
|
||||||
|
static void RedisQtAddWrite(void *);
|
||||||
|
static void RedisQtDelWrite(void *);
|
||||||
|
static void RedisQtCleanup(void *);
|
||||||
|
|
||||||
|
class RedisQtAdapter : public QObject {
|
||||||
|
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
friend
|
||||||
|
void RedisQtAddRead(void * adapter) {
|
||||||
|
RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter);
|
||||||
|
a->addRead();
|
||||||
|
}
|
||||||
|
|
||||||
|
friend
|
||||||
|
void RedisQtDelRead(void * adapter) {
|
||||||
|
RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter);
|
||||||
|
a->delRead();
|
||||||
|
}
|
||||||
|
|
||||||
|
friend
|
||||||
|
void RedisQtAddWrite(void * adapter) {
|
||||||
|
RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter);
|
||||||
|
a->addWrite();
|
||||||
|
}
|
||||||
|
|
||||||
|
friend
|
||||||
|
void RedisQtDelWrite(void * adapter) {
|
||||||
|
RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter);
|
||||||
|
a->delWrite();
|
||||||
|
}
|
||||||
|
|
||||||
|
friend
|
||||||
|
void RedisQtCleanup(void * adapter) {
|
||||||
|
RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter);
|
||||||
|
a->cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
RedisQtAdapter(QObject * parent = 0)
|
||||||
|
: QObject(parent), m_ctx(0), m_read(0), m_write(0) { }
|
||||||
|
|
||||||
|
~RedisQtAdapter() {
|
||||||
|
if (m_ctx != 0) {
|
||||||
|
m_ctx->ev.data = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int setContext(redisAsyncContext * ac) {
|
||||||
|
if (ac->ev.data != NULL) {
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
m_ctx = ac;
|
||||||
|
m_ctx->ev.data = this;
|
||||||
|
m_ctx->ev.addRead = RedisQtAddRead;
|
||||||
|
m_ctx->ev.delRead = RedisQtDelRead;
|
||||||
|
m_ctx->ev.addWrite = RedisQtAddWrite;
|
||||||
|
m_ctx->ev.delWrite = RedisQtDelWrite;
|
||||||
|
m_ctx->ev.cleanup = RedisQtCleanup;
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void addRead() {
|
||||||
|
if (m_read) return;
|
||||||
|
m_read = new QSocketNotifier(m_ctx->c.fd, QSocketNotifier::Read, 0);
|
||||||
|
connect(m_read, SIGNAL(activated(int)), this, SLOT(read()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void delRead() {
|
||||||
|
if (!m_read) return;
|
||||||
|
delete m_read;
|
||||||
|
m_read = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void addWrite() {
|
||||||
|
if (m_write) return;
|
||||||
|
m_write = new QSocketNotifier(m_ctx->c.fd, QSocketNotifier::Write, 0);
|
||||||
|
connect(m_write, SIGNAL(activated(int)), this, SLOT(write()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void delWrite() {
|
||||||
|
if (!m_write) return;
|
||||||
|
delete m_write;
|
||||||
|
m_write = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cleanup() {
|
||||||
|
delRead();
|
||||||
|
delWrite();
|
||||||
|
}
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void read() { redisAsyncHandleRead(m_ctx); }
|
||||||
|
void write() { redisAsyncHandleWrite(m_ctx); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
redisAsyncContext * m_ctx;
|
||||||
|
QSocketNotifier * m_read;
|
||||||
|
QSocketNotifier * m_write;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* !__HIREDIS_QT_H__ */
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
#ifndef __HIREDIS_REDISMODULEAPI_H__
|
||||||
|
#define __HIREDIS_REDISMODULEAPI_H__
|
||||||
|
|
||||||
|
#include "redismodule.h"
|
||||||
|
|
||||||
|
#include "../async.h"
|
||||||
|
#include "../hiredis.h"
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
typedef struct redisModuleEvents {
|
||||||
|
redisAsyncContext *context;
|
||||||
|
RedisModuleCtx *module_ctx;
|
||||||
|
int fd;
|
||||||
|
int reading, writing;
|
||||||
|
int timer_active;
|
||||||
|
RedisModuleTimerID timer_id;
|
||||||
|
} redisModuleEvents;
|
||||||
|
|
||||||
|
static inline void redisModuleReadEvent(int fd, void *privdata, int mask) {
|
||||||
|
(void) fd;
|
||||||
|
(void) mask;
|
||||||
|
|
||||||
|
redisModuleEvents *e = (redisModuleEvents*)privdata;
|
||||||
|
redisAsyncHandleRead(e->context);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void redisModuleWriteEvent(int fd, void *privdata, int mask) {
|
||||||
|
(void) fd;
|
||||||
|
(void) mask;
|
||||||
|
|
||||||
|
redisModuleEvents *e = (redisModuleEvents*)privdata;
|
||||||
|
redisAsyncHandleWrite(e->context);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void redisModuleAddRead(void *privdata) {
|
||||||
|
redisModuleEvents *e = (redisModuleEvents*)privdata;
|
||||||
|
if (!e->reading) {
|
||||||
|
e->reading = 1;
|
||||||
|
RedisModule_EventLoopAdd(e->fd, REDISMODULE_EVENTLOOP_READABLE, redisModuleReadEvent, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void redisModuleDelRead(void *privdata) {
|
||||||
|
redisModuleEvents *e = (redisModuleEvents*)privdata;
|
||||||
|
if (e->reading) {
|
||||||
|
e->reading = 0;
|
||||||
|
RedisModule_EventLoopDel(e->fd, REDISMODULE_EVENTLOOP_READABLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void redisModuleAddWrite(void *privdata) {
|
||||||
|
redisModuleEvents *e = (redisModuleEvents*)privdata;
|
||||||
|
if (!e->writing) {
|
||||||
|
e->writing = 1;
|
||||||
|
RedisModule_EventLoopAdd(e->fd, REDISMODULE_EVENTLOOP_WRITABLE, redisModuleWriteEvent, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void redisModuleDelWrite(void *privdata) {
|
||||||
|
redisModuleEvents *e = (redisModuleEvents*)privdata;
|
||||||
|
if (e->writing) {
|
||||||
|
e->writing = 0;
|
||||||
|
RedisModule_EventLoopDel(e->fd, REDISMODULE_EVENTLOOP_WRITABLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void redisModuleStopTimer(void *privdata) {
|
||||||
|
redisModuleEvents *e = (redisModuleEvents*)privdata;
|
||||||
|
if (e->timer_active) {
|
||||||
|
RedisModule_StopTimer(e->module_ctx, e->timer_id, NULL);
|
||||||
|
}
|
||||||
|
e->timer_active = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void redisModuleCleanup(void *privdata) {
|
||||||
|
redisModuleEvents *e = (redisModuleEvents*)privdata;
|
||||||
|
redisModuleDelRead(privdata);
|
||||||
|
redisModuleDelWrite(privdata);
|
||||||
|
redisModuleStopTimer(privdata);
|
||||||
|
hi_free(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void redisModuleTimeout(RedisModuleCtx *ctx, void *privdata) {
|
||||||
|
(void) ctx;
|
||||||
|
|
||||||
|
redisModuleEvents *e = (redisModuleEvents*)privdata;
|
||||||
|
e->timer_active = 0;
|
||||||
|
redisAsyncHandleTimeout(e->context);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void redisModuleSetTimeout(void *privdata, struct timeval tv) {
|
||||||
|
redisModuleEvents* e = (redisModuleEvents*)privdata;
|
||||||
|
|
||||||
|
redisModuleStopTimer(privdata);
|
||||||
|
|
||||||
|
mstime_t millis = tv.tv_sec * 1000 + tv.tv_usec / 1000.0;
|
||||||
|
e->timer_id = RedisModule_CreateTimer(e->module_ctx, millis, redisModuleTimeout, e);
|
||||||
|
e->timer_active = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if Redis version is compatible with the adapter. */
|
||||||
|
static inline int redisModuleCompatibilityCheck(void) {
|
||||||
|
if (!RedisModule_EventLoopAdd ||
|
||||||
|
!RedisModule_EventLoopDel ||
|
||||||
|
!RedisModule_CreateTimer ||
|
||||||
|
!RedisModule_StopTimer) {
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int redisModuleAttach(redisAsyncContext *ac, RedisModuleCtx *module_ctx) {
|
||||||
|
redisContext *c = &(ac->c);
|
||||||
|
redisModuleEvents *e;
|
||||||
|
|
||||||
|
/* Nothing should be attached when something is already attached */
|
||||||
|
if (ac->ev.data != NULL)
|
||||||
|
return REDIS_ERR;
|
||||||
|
|
||||||
|
/* Create container for context and r/w events */
|
||||||
|
e = (redisModuleEvents*)hi_malloc(sizeof(*e));
|
||||||
|
if (e == NULL)
|
||||||
|
return REDIS_ERR;
|
||||||
|
|
||||||
|
e->context = ac;
|
||||||
|
e->module_ctx = module_ctx;
|
||||||
|
e->fd = c->fd;
|
||||||
|
e->reading = e->writing = 0;
|
||||||
|
e->timer_active = 0;
|
||||||
|
|
||||||
|
/* Register functions to start/stop listening for events */
|
||||||
|
ac->ev.addRead = redisModuleAddRead;
|
||||||
|
ac->ev.delRead = redisModuleDelRead;
|
||||||
|
ac->ev.addWrite = redisModuleAddWrite;
|
||||||
|
ac->ev.delWrite = redisModuleDelWrite;
|
||||||
|
ac->ev.cleanup = redisModuleCleanup;
|
||||||
|
ac->ev.scheduleTimer = redisModuleSetTimeout;
|
||||||
|
ac->ev.data = e;
|
||||||
|
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020, Michael Grunder <michael dot grunder at gmail dot com>
|
||||||
|
*
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Redis nor the names of its contributors may be used
|
||||||
|
* to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "fmacros.h"
|
||||||
|
#include "alloc.h"
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
hiredisAllocFuncs hiredisAllocFns = {
|
||||||
|
.mallocFn = malloc,
|
||||||
|
.callocFn = calloc,
|
||||||
|
.reallocFn = realloc,
|
||||||
|
.strdupFn = strdup,
|
||||||
|
.freeFn = free,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Override hiredis' allocators with ones supplied by the user */
|
||||||
|
hiredisAllocFuncs hiredisSetAllocators(hiredisAllocFuncs *override) {
|
||||||
|
hiredisAllocFuncs orig = hiredisAllocFns;
|
||||||
|
|
||||||
|
hiredisAllocFns = *override;
|
||||||
|
|
||||||
|
return orig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reset allocators to use libc defaults */
|
||||||
|
void hiredisResetAllocators(void) {
|
||||||
|
hiredisAllocFns = (hiredisAllocFuncs) {
|
||||||
|
.mallocFn = malloc,
|
||||||
|
.callocFn = calloc,
|
||||||
|
.reallocFn = realloc,
|
||||||
|
.strdupFn = strdup,
|
||||||
|
.freeFn = free,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
|
||||||
|
void *hi_malloc(size_t size) {
|
||||||
|
return hiredisAllocFns.mallocFn(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *hi_calloc(size_t nmemb, size_t size) {
|
||||||
|
/* Overflow check as the user can specify any arbitrary allocator */
|
||||||
|
if (SIZE_MAX / size < nmemb)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return hiredisAllocFns.callocFn(nmemb, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *hi_realloc(void *ptr, size_t size) {
|
||||||
|
return hiredisAllocFns.reallocFn(ptr, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *hi_strdup(const char *str) {
|
||||||
|
return hiredisAllocFns.strdupFn(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hi_free(void *ptr) {
|
||||||
|
hiredisAllocFns.freeFn(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020, Michael Grunder <michael dot grunder at gmail dot com>
|
||||||
|
*
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Redis nor the names of its contributors may be used
|
||||||
|
* to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef HIREDIS_ALLOC_H
|
||||||
|
#define HIREDIS_ALLOC_H
|
||||||
|
|
||||||
|
#include <stddef.h> /* for size_t */
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Structure pointing to our actually configured allocators */
|
||||||
|
typedef struct hiredisAllocFuncs {
|
||||||
|
void *(*mallocFn)(size_t);
|
||||||
|
void *(*callocFn)(size_t,size_t);
|
||||||
|
void *(*reallocFn)(void*,size_t);
|
||||||
|
char *(*strdupFn)(const char*);
|
||||||
|
void (*freeFn)(void*);
|
||||||
|
} hiredisAllocFuncs;
|
||||||
|
|
||||||
|
hiredisAllocFuncs hiredisSetAllocators(hiredisAllocFuncs *ha);
|
||||||
|
void hiredisResetAllocators(void);
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
|
||||||
|
/* Hiredis' configured allocator function pointer struct */
|
||||||
|
extern hiredisAllocFuncs hiredisAllocFns;
|
||||||
|
|
||||||
|
static inline void *hi_malloc(size_t size) {
|
||||||
|
return hiredisAllocFns.mallocFn(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void *hi_calloc(size_t nmemb, size_t size) {
|
||||||
|
/* Overflow check as the user can specify any arbitrary allocator */
|
||||||
|
if (SIZE_MAX / size < nmemb)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return hiredisAllocFns.callocFn(nmemb, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void *hi_realloc(void *ptr, size_t size) {
|
||||||
|
return hiredisAllocFns.reallocFn(ptr, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline char *hi_strdup(const char *str) {
|
||||||
|
return hiredisAllocFns.strdupFn(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void hi_free(void *ptr) {
|
||||||
|
hiredisAllocFns.freeFn(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
void *hi_malloc(size_t size);
|
||||||
|
void *hi_calloc(size_t nmemb, size_t size);
|
||||||
|
void *hi_realloc(void *ptr, size_t size);
|
||||||
|
char *hi_strdup(const char *str);
|
||||||
|
void hi_free(void *ptr);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* HIREDIS_ALLOC_H */
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# Appveyor configuration file for CI build of hiredis on Windows (under Cygwin)
|
||||||
|
environment:
|
||||||
|
matrix:
|
||||||
|
- CYG_BASH: C:\cygwin64\bin\bash
|
||||||
|
CC: gcc
|
||||||
|
- CYG_BASH: C:\cygwin\bin\bash
|
||||||
|
CC: gcc
|
||||||
|
CFLAGS: -m32
|
||||||
|
CXXFLAGS: -m32
|
||||||
|
LDFLAGS: -m32
|
||||||
|
|
||||||
|
clone_depth: 1
|
||||||
|
|
||||||
|
# Attempt to ensure we don't try to convert line endings to Win32 CRLF as this will cause build to fail
|
||||||
|
init:
|
||||||
|
- git config --global core.autocrlf input
|
||||||
|
|
||||||
|
# Install needed build dependencies
|
||||||
|
install:
|
||||||
|
- '%CYG_BASH% -lc "cygcheck -dc cygwin"'
|
||||||
|
|
||||||
|
build_script:
|
||||||
|
- 'echo building...'
|
||||||
|
- '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0</dev/null; mkdir build && cd build && cmake .. -G \"Unix Makefiles\" && make VERBOSE=1"'
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||||
|
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||||
|
*
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Redis nor the names of its contributors may be used
|
||||||
|
* to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __HIREDIS_ASYNC_H
|
||||||
|
#define __HIREDIS_ASYNC_H
|
||||||
|
#include "hiredis.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct redisAsyncContext; /* need forward declaration of redisAsyncContext */
|
||||||
|
struct dict; /* dictionary header is included in async.c */
|
||||||
|
|
||||||
|
/* Reply callback prototype and container */
|
||||||
|
typedef void (redisCallbackFn)(struct redisAsyncContext*, void*, void*);
|
||||||
|
typedef struct redisCallback {
|
||||||
|
struct redisCallback *next; /* simple singly linked list */
|
||||||
|
redisCallbackFn *fn;
|
||||||
|
int pending_subs;
|
||||||
|
int unsubscribe_sent;
|
||||||
|
void *privdata;
|
||||||
|
} redisCallback;
|
||||||
|
|
||||||
|
/* List of callbacks for either regular replies or pub/sub */
|
||||||
|
typedef struct redisCallbackList {
|
||||||
|
redisCallback *head, *tail;
|
||||||
|
} redisCallbackList;
|
||||||
|
|
||||||
|
/* Connection callback prototypes */
|
||||||
|
typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status);
|
||||||
|
typedef void (redisConnectCallback)(const struct redisAsyncContext*, int status);
|
||||||
|
typedef void (redisConnectCallbackNC)(struct redisAsyncContext *, int status);
|
||||||
|
typedef void(redisTimerCallback)(void *timer, void *privdata);
|
||||||
|
|
||||||
|
/* Context for an async connection to Redis */
|
||||||
|
typedef struct redisAsyncContext {
|
||||||
|
/* Hold the regular context, so it can be realloc'ed. */
|
||||||
|
redisContext c;
|
||||||
|
|
||||||
|
/* Setup error flags so they can be used directly. */
|
||||||
|
int err;
|
||||||
|
char *errstr;
|
||||||
|
|
||||||
|
/* Not used by hiredis */
|
||||||
|
void *data;
|
||||||
|
void (*dataCleanup)(void *privdata);
|
||||||
|
|
||||||
|
/* Event library data and hooks */
|
||||||
|
struct {
|
||||||
|
void *data;
|
||||||
|
|
||||||
|
/* Hooks that are called when the library expects to start
|
||||||
|
* reading/writing. These functions should be idempotent. */
|
||||||
|
void (*addRead)(void *privdata);
|
||||||
|
void (*delRead)(void *privdata);
|
||||||
|
void (*addWrite)(void *privdata);
|
||||||
|
void (*delWrite)(void *privdata);
|
||||||
|
void (*cleanup)(void *privdata);
|
||||||
|
void (*scheduleTimer)(void *privdata, struct timeval tv);
|
||||||
|
} ev;
|
||||||
|
|
||||||
|
/* Called when either the connection is terminated due to an error or per
|
||||||
|
* user request. The status is set accordingly (REDIS_OK, REDIS_ERR). */
|
||||||
|
redisDisconnectCallback *onDisconnect;
|
||||||
|
|
||||||
|
/* Called when the first write event was received. */
|
||||||
|
redisConnectCallback *onConnect;
|
||||||
|
redisConnectCallbackNC *onConnectNC;
|
||||||
|
|
||||||
|
/* Regular command callbacks */
|
||||||
|
redisCallbackList replies;
|
||||||
|
|
||||||
|
/* Address used for connect() */
|
||||||
|
struct sockaddr *saddr;
|
||||||
|
size_t addrlen;
|
||||||
|
|
||||||
|
/* Subscription callbacks */
|
||||||
|
struct {
|
||||||
|
redisCallbackList replies;
|
||||||
|
struct dict *channels;
|
||||||
|
struct dict *patterns;
|
||||||
|
int pending_unsubs;
|
||||||
|
} sub;
|
||||||
|
|
||||||
|
/* Any configured RESP3 PUSH handler */
|
||||||
|
redisAsyncPushFn *push_cb;
|
||||||
|
} redisAsyncContext;
|
||||||
|
|
||||||
|
/* Functions that proxy to hiredis */
|
||||||
|
redisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options);
|
||||||
|
redisAsyncContext *redisAsyncConnect(const char *ip, int port);
|
||||||
|
redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, const char *source_addr);
|
||||||
|
redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
|
||||||
|
const char *source_addr);
|
||||||
|
redisAsyncContext *redisAsyncConnectUnix(const char *path);
|
||||||
|
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn);
|
||||||
|
int redisAsyncSetConnectCallbackNC(redisAsyncContext *ac, redisConnectCallbackNC *fn);
|
||||||
|
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
|
||||||
|
|
||||||
|
redisAsyncPushFn *redisAsyncSetPushCallback(redisAsyncContext *ac, redisAsyncPushFn *fn);
|
||||||
|
int redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv);
|
||||||
|
void redisAsyncDisconnect(redisAsyncContext *ac);
|
||||||
|
void redisAsyncFree(redisAsyncContext *ac);
|
||||||
|
|
||||||
|
/* Handle read/write events */
|
||||||
|
void redisAsyncHandleRead(redisAsyncContext *ac);
|
||||||
|
void redisAsyncHandleWrite(redisAsyncContext *ac);
|
||||||
|
void redisAsyncHandleTimeout(redisAsyncContext *ac);
|
||||||
|
void redisAsyncRead(redisAsyncContext *ac);
|
||||||
|
void redisAsyncWrite(redisAsyncContext *ac);
|
||||||
|
|
||||||
|
/* Command functions for an async context. Write the command to the
|
||||||
|
* output buffer and register the provided callback. */
|
||||||
|
int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap);
|
||||||
|
int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...);
|
||||||
|
int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen);
|
||||||
|
int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||||
|
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||||
|
*
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Redis nor the names of its contributors may be used
|
||||||
|
* to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __HIREDIS_ASYNC_PRIVATE_H
|
||||||
|
#define __HIREDIS_ASYNC_PRIVATE_H
|
||||||
|
|
||||||
|
#define _EL_ADD_READ(ctx) \
|
||||||
|
do { \
|
||||||
|
refreshTimeout(ctx); \
|
||||||
|
if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \
|
||||||
|
} while (0)
|
||||||
|
#define _EL_DEL_READ(ctx) do { \
|
||||||
|
if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \
|
||||||
|
} while(0)
|
||||||
|
#define _EL_ADD_WRITE(ctx) \
|
||||||
|
do { \
|
||||||
|
refreshTimeout(ctx); \
|
||||||
|
if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \
|
||||||
|
} while (0)
|
||||||
|
#define _EL_DEL_WRITE(ctx) do { \
|
||||||
|
if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \
|
||||||
|
} while(0)
|
||||||
|
#define _EL_CLEANUP(ctx) do { \
|
||||||
|
if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \
|
||||||
|
ctx->ev.cleanup = NULL; \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
static inline void refreshTimeout(redisAsyncContext *ctx) {
|
||||||
|
#define REDIS_TIMER_ISSET(tvp) \
|
||||||
|
(tvp && ((tvp)->tv_sec || (tvp)->tv_usec))
|
||||||
|
|
||||||
|
#define REDIS_EL_TIMER(ac, tvp) \
|
||||||
|
if ((ac)->ev.scheduleTimer && REDIS_TIMER_ISSET(tvp)) { \
|
||||||
|
(ac)->ev.scheduleTimer((ac)->ev.data, *(tvp)); \
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->c.flags & REDIS_CONNECTED) {
|
||||||
|
REDIS_EL_TIMER(ctx, ctx->c.command_timeout);
|
||||||
|
} else {
|
||||||
|
REDIS_EL_TIMER(ctx, ctx->c.connect_timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void __redisAsyncDisconnect(redisAsyncContext *ac);
|
||||||
|
void redisProcessCallbacks(redisAsyncContext *ac);
|
||||||
|
|
||||||
|
#endif /* __HIREDIS_ASYNC_PRIVATE_H */
|
||||||
@@ -0,0 +1,343 @@
|
|||||||
|
/* Hash table implementation.
|
||||||
|
*
|
||||||
|
* This file implements in memory hash tables with insert/del/replace/find/
|
||||||
|
* get-random-element operations. Hash tables will auto resize if needed
|
||||||
|
* tables of power of two in size are used, collisions are handled by
|
||||||
|
* chaining. See the source code for more information... :)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Redis nor the names of its contributors may be used
|
||||||
|
* to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "fmacros.h"
|
||||||
|
#include "alloc.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include "dict.h"
|
||||||
|
|
||||||
|
/* -------------------------- private prototypes ---------------------------- */
|
||||||
|
|
||||||
|
static int _dictExpandIfNeeded(dict *ht);
|
||||||
|
static unsigned long _dictNextPower(unsigned long size);
|
||||||
|
static int _dictKeyIndex(dict *ht, const void *key);
|
||||||
|
static int _dictInit(dict *ht, dictType *type, void *privDataPtr);
|
||||||
|
|
||||||
|
/* -------------------------- hash functions -------------------------------- */
|
||||||
|
|
||||||
|
/* Generic hash function (a popular one from Bernstein).
|
||||||
|
* I tested a few and this was the best. */
|
||||||
|
static unsigned int dictGenHashFunction(const unsigned char *buf, int len) {
|
||||||
|
unsigned int hash = 5381;
|
||||||
|
|
||||||
|
while (len--)
|
||||||
|
hash = ((hash << 5) + hash) + (*buf++); /* hash * 33 + c */
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------- API implementation ------------------------- */
|
||||||
|
|
||||||
|
/* Reset an hashtable already initialized with ht_init().
|
||||||
|
* NOTE: This function should only called by ht_destroy(). */
|
||||||
|
static void _dictReset(dict *ht) {
|
||||||
|
ht->table = NULL;
|
||||||
|
ht->size = 0;
|
||||||
|
ht->sizemask = 0;
|
||||||
|
ht->used = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create a new hash table */
|
||||||
|
static dict *dictCreate(dictType *type, void *privDataPtr) {
|
||||||
|
dict *ht = hi_malloc(sizeof(*ht));
|
||||||
|
if (ht == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
_dictInit(ht,type,privDataPtr);
|
||||||
|
return ht;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Initialize the hash table */
|
||||||
|
static int _dictInit(dict *ht, dictType *type, void *privDataPtr) {
|
||||||
|
_dictReset(ht);
|
||||||
|
ht->type = type;
|
||||||
|
ht->privdata = privDataPtr;
|
||||||
|
return DICT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Expand or create the hashtable */
|
||||||
|
static int dictExpand(dict *ht, unsigned long size) {
|
||||||
|
dict n; /* the new hashtable */
|
||||||
|
unsigned long realsize = _dictNextPower(size), i;
|
||||||
|
|
||||||
|
/* the size is invalid if it is smaller than the number of
|
||||||
|
* elements already inside the hashtable */
|
||||||
|
if (ht->used > size)
|
||||||
|
return DICT_ERR;
|
||||||
|
|
||||||
|
_dictInit(&n, ht->type, ht->privdata);
|
||||||
|
n.size = realsize;
|
||||||
|
n.sizemask = realsize-1;
|
||||||
|
n.table = hi_calloc(realsize,sizeof(dictEntry*));
|
||||||
|
if (n.table == NULL)
|
||||||
|
return DICT_ERR;
|
||||||
|
|
||||||
|
/* Copy all the elements from the old to the new table:
|
||||||
|
* note that if the old hash table is empty ht->size is zero,
|
||||||
|
* so dictExpand just creates an hash table. */
|
||||||
|
n.used = ht->used;
|
||||||
|
for (i = 0; i < ht->size && ht->used > 0; i++) {
|
||||||
|
dictEntry *he, *nextHe;
|
||||||
|
|
||||||
|
if (ht->table[i] == NULL) continue;
|
||||||
|
|
||||||
|
/* For each hash entry on this slot... */
|
||||||
|
he = ht->table[i];
|
||||||
|
while(he) {
|
||||||
|
unsigned int h;
|
||||||
|
|
||||||
|
nextHe = he->next;
|
||||||
|
/* Get the new element index */
|
||||||
|
h = dictHashKey(ht, he->key) & n.sizemask;
|
||||||
|
he->next = n.table[h];
|
||||||
|
n.table[h] = he;
|
||||||
|
ht->used--;
|
||||||
|
/* Pass to the next element */
|
||||||
|
he = nextHe;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(ht->used == 0);
|
||||||
|
hi_free(ht->table);
|
||||||
|
|
||||||
|
/* Remap the new hashtable in the old */
|
||||||
|
*ht = n;
|
||||||
|
return DICT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add an element to the target hash table */
|
||||||
|
static int dictAdd(dict *ht, void *key, void *val) {
|
||||||
|
int index;
|
||||||
|
dictEntry *entry;
|
||||||
|
|
||||||
|
/* Get the index of the new element, or -1 if
|
||||||
|
* the element already exists. */
|
||||||
|
if ((index = _dictKeyIndex(ht, key)) == -1)
|
||||||
|
return DICT_ERR;
|
||||||
|
|
||||||
|
/* Allocates the memory and stores key */
|
||||||
|
entry = hi_malloc(sizeof(*entry));
|
||||||
|
if (entry == NULL)
|
||||||
|
return DICT_ERR;
|
||||||
|
|
||||||
|
entry->next = ht->table[index];
|
||||||
|
ht->table[index] = entry;
|
||||||
|
|
||||||
|
/* Set the hash entry fields. */
|
||||||
|
dictSetHashKey(ht, entry, key);
|
||||||
|
dictSetHashVal(ht, entry, val);
|
||||||
|
ht->used++;
|
||||||
|
return DICT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add an element, discarding the old if the key already exists.
|
||||||
|
* Return 1 if the key was added from scratch, 0 if there was already an
|
||||||
|
* element with such key and dictReplace() just performed a value update
|
||||||
|
* operation. */
|
||||||
|
static int dictReplace(dict *ht, void *key, void *val) {
|
||||||
|
dictEntry *entry, auxentry;
|
||||||
|
|
||||||
|
/* Try to add the element. If the key
|
||||||
|
* does not exists dictAdd will succeed. */
|
||||||
|
if (dictAdd(ht, key, val) == DICT_OK)
|
||||||
|
return 1;
|
||||||
|
/* It already exists, get the entry */
|
||||||
|
entry = dictFind(ht, key);
|
||||||
|
if (entry == NULL)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* Free the old value and set the new one */
|
||||||
|
/* Set the new value and free the old one. Note that it is important
|
||||||
|
* to do that in this order, as the value may just be exactly the same
|
||||||
|
* as the previous one. In this context, think to reference counting,
|
||||||
|
* you want to increment (set), and then decrement (free), and not the
|
||||||
|
* reverse. */
|
||||||
|
auxentry = *entry;
|
||||||
|
dictSetHashVal(ht, entry, val);
|
||||||
|
dictFreeEntryVal(ht, &auxentry);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Search and remove an element */
|
||||||
|
static int dictDelete(dict *ht, const void *key) {
|
||||||
|
unsigned int h;
|
||||||
|
dictEntry *de, *prevde;
|
||||||
|
|
||||||
|
if (ht->size == 0)
|
||||||
|
return DICT_ERR;
|
||||||
|
h = dictHashKey(ht, key) & ht->sizemask;
|
||||||
|
de = ht->table[h];
|
||||||
|
|
||||||
|
prevde = NULL;
|
||||||
|
while(de) {
|
||||||
|
if (dictCompareHashKeys(ht,key,de->key)) {
|
||||||
|
/* Unlink the element from the list */
|
||||||
|
if (prevde)
|
||||||
|
prevde->next = de->next;
|
||||||
|
else
|
||||||
|
ht->table[h] = de->next;
|
||||||
|
|
||||||
|
dictFreeEntryKey(ht,de);
|
||||||
|
dictFreeEntryVal(ht,de);
|
||||||
|
hi_free(de);
|
||||||
|
ht->used--;
|
||||||
|
return DICT_OK;
|
||||||
|
}
|
||||||
|
prevde = de;
|
||||||
|
de = de->next;
|
||||||
|
}
|
||||||
|
return DICT_ERR; /* not found */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Destroy an entire hash table */
|
||||||
|
static int _dictClear(dict *ht) {
|
||||||
|
unsigned long i;
|
||||||
|
|
||||||
|
/* Free all the elements */
|
||||||
|
for (i = 0; i < ht->size && ht->used > 0; i++) {
|
||||||
|
dictEntry *he, *nextHe;
|
||||||
|
|
||||||
|
if ((he = ht->table[i]) == NULL) continue;
|
||||||
|
while(he) {
|
||||||
|
nextHe = he->next;
|
||||||
|
dictFreeEntryKey(ht, he);
|
||||||
|
dictFreeEntryVal(ht, he);
|
||||||
|
hi_free(he);
|
||||||
|
ht->used--;
|
||||||
|
he = nextHe;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Free the table and the allocated cache structure */
|
||||||
|
hi_free(ht->table);
|
||||||
|
/* Re-initialize the table */
|
||||||
|
_dictReset(ht);
|
||||||
|
return DICT_OK; /* never fails */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Clear & Release the hash table */
|
||||||
|
static void dictRelease(dict *ht) {
|
||||||
|
_dictClear(ht);
|
||||||
|
hi_free(ht);
|
||||||
|
}
|
||||||
|
|
||||||
|
static dictEntry *dictFind(dict *ht, const void *key) {
|
||||||
|
dictEntry *he;
|
||||||
|
unsigned int h;
|
||||||
|
|
||||||
|
if (ht->size == 0) return NULL;
|
||||||
|
h = dictHashKey(ht, key) & ht->sizemask;
|
||||||
|
he = ht->table[h];
|
||||||
|
while(he) {
|
||||||
|
if (dictCompareHashKeys(ht, key, he->key))
|
||||||
|
return he;
|
||||||
|
he = he->next;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dictInitIterator(dictIterator *iter, dict *ht) {
|
||||||
|
iter->ht = ht;
|
||||||
|
iter->index = -1;
|
||||||
|
iter->entry = NULL;
|
||||||
|
iter->nextEntry = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static dictEntry *dictNext(dictIterator *iter) {
|
||||||
|
while (1) {
|
||||||
|
if (iter->entry == NULL) {
|
||||||
|
iter->index++;
|
||||||
|
if (iter->index >=
|
||||||
|
(signed)iter->ht->size) break;
|
||||||
|
iter->entry = iter->ht->table[iter->index];
|
||||||
|
} else {
|
||||||
|
iter->entry = iter->nextEntry;
|
||||||
|
}
|
||||||
|
if (iter->entry) {
|
||||||
|
/* We need to save the 'next' here, the iterator user
|
||||||
|
* may delete the entry we are returning. */
|
||||||
|
iter->nextEntry = iter->entry->next;
|
||||||
|
return iter->entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------- private functions ------------------------------ */
|
||||||
|
|
||||||
|
/* Expand the hash table if needed */
|
||||||
|
static int _dictExpandIfNeeded(dict *ht) {
|
||||||
|
/* If the hash table is empty expand it to the initial size,
|
||||||
|
* if the table is "full" double its size. */
|
||||||
|
if (ht->size == 0)
|
||||||
|
return dictExpand(ht, DICT_HT_INITIAL_SIZE);
|
||||||
|
if (ht->used == ht->size)
|
||||||
|
return dictExpand(ht, ht->size*2);
|
||||||
|
return DICT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Our hash table capability is a power of two */
|
||||||
|
static unsigned long _dictNextPower(unsigned long size) {
|
||||||
|
unsigned long i = DICT_HT_INITIAL_SIZE;
|
||||||
|
|
||||||
|
if (size >= LONG_MAX) return LONG_MAX;
|
||||||
|
while(1) {
|
||||||
|
if (i >= size)
|
||||||
|
return i;
|
||||||
|
i *= 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Returns the index of a free slot that can be populated with
|
||||||
|
* an hash entry for the given 'key'.
|
||||||
|
* If the key already exists, -1 is returned. */
|
||||||
|
static int _dictKeyIndex(dict *ht, const void *key) {
|
||||||
|
unsigned int h;
|
||||||
|
dictEntry *he;
|
||||||
|
|
||||||
|
/* Expand the hashtable if needed */
|
||||||
|
if (_dictExpandIfNeeded(ht) == DICT_ERR)
|
||||||
|
return -1;
|
||||||
|
/* Compute the key hash value */
|
||||||
|
h = dictHashKey(ht, key) & ht->sizemask;
|
||||||
|
/* Search if this slot does not already contain the given key */
|
||||||
|
he = ht->table[h];
|
||||||
|
while(he) {
|
||||||
|
if (dictCompareHashKeys(ht, key, he->key))
|
||||||
|
return -1;
|
||||||
|
he = he->next;
|
||||||
|
}
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
/* Hash table implementation.
|
||||||
|
*
|
||||||
|
* This file implements in memory hash tables with insert/del/replace/find/
|
||||||
|
* get-random-element operations. Hash tables will auto resize if needed
|
||||||
|
* tables of power of two in size are used, collisions are handled by
|
||||||
|
* chaining. See the source code for more information... :)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Redis nor the names of its contributors may be used
|
||||||
|
* to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __DICT_H
|
||||||
|
#define __DICT_H
|
||||||
|
|
||||||
|
#define DICT_OK 0
|
||||||
|
#define DICT_ERR 1
|
||||||
|
|
||||||
|
/* Unused arguments generate annoying warnings... */
|
||||||
|
#define DICT_NOTUSED(V) ((void) V)
|
||||||
|
|
||||||
|
typedef struct dictEntry {
|
||||||
|
void *key;
|
||||||
|
void *val;
|
||||||
|
struct dictEntry *next;
|
||||||
|
} dictEntry;
|
||||||
|
|
||||||
|
typedef struct dictType {
|
||||||
|
unsigned int (*hashFunction)(const void *key);
|
||||||
|
void *(*keyDup)(void *privdata, const void *key);
|
||||||
|
void *(*valDup)(void *privdata, const void *obj);
|
||||||
|
int (*keyCompare)(void *privdata, const void *key1, const void *key2);
|
||||||
|
void (*keyDestructor)(void *privdata, void *key);
|
||||||
|
void (*valDestructor)(void *privdata, void *obj);
|
||||||
|
} dictType;
|
||||||
|
|
||||||
|
typedef struct dict {
|
||||||
|
dictEntry **table;
|
||||||
|
dictType *type;
|
||||||
|
unsigned long size;
|
||||||
|
unsigned long sizemask;
|
||||||
|
unsigned long used;
|
||||||
|
void *privdata;
|
||||||
|
} dict;
|
||||||
|
|
||||||
|
typedef struct dictIterator {
|
||||||
|
dict *ht;
|
||||||
|
int index;
|
||||||
|
dictEntry *entry, *nextEntry;
|
||||||
|
} dictIterator;
|
||||||
|
|
||||||
|
/* This is the initial size of every hash table */
|
||||||
|
#define DICT_HT_INITIAL_SIZE 4
|
||||||
|
|
||||||
|
/* ------------------------------- Macros ------------------------------------*/
|
||||||
|
#define dictFreeEntryVal(ht, entry) \
|
||||||
|
if ((ht)->type->valDestructor) \
|
||||||
|
(ht)->type->valDestructor((ht)->privdata, (entry)->val)
|
||||||
|
|
||||||
|
#define dictSetHashVal(ht, entry, _val_) do { \
|
||||||
|
if ((ht)->type->valDup) \
|
||||||
|
entry->val = (ht)->type->valDup((ht)->privdata, _val_); \
|
||||||
|
else \
|
||||||
|
entry->val = (_val_); \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
#define dictFreeEntryKey(ht, entry) \
|
||||||
|
if ((ht)->type->keyDestructor) \
|
||||||
|
(ht)->type->keyDestructor((ht)->privdata, (entry)->key)
|
||||||
|
|
||||||
|
#define dictSetHashKey(ht, entry, _key_) do { \
|
||||||
|
if ((ht)->type->keyDup) \
|
||||||
|
entry->key = (ht)->type->keyDup((ht)->privdata, _key_); \
|
||||||
|
else \
|
||||||
|
entry->key = (_key_); \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
#define dictCompareHashKeys(ht, key1, key2) \
|
||||||
|
(((ht)->type->keyCompare) ? \
|
||||||
|
(ht)->type->keyCompare((ht)->privdata, key1, key2) : \
|
||||||
|
(key1) == (key2))
|
||||||
|
|
||||||
|
#define dictHashKey(ht, key) (ht)->type->hashFunction(key)
|
||||||
|
|
||||||
|
#define dictGetEntryKey(he) ((he)->key)
|
||||||
|
#define dictGetEntryVal(he) ((he)->val)
|
||||||
|
#define dictSlots(ht) ((ht)->size)
|
||||||
|
#define dictSize(ht) ((ht)->used)
|
||||||
|
|
||||||
|
/* API */
|
||||||
|
static unsigned int dictGenHashFunction(const unsigned char *buf, int len);
|
||||||
|
static dict *dictCreate(dictType *type, void *privDataPtr);
|
||||||
|
static int dictExpand(dict *ht, unsigned long size);
|
||||||
|
static int dictAdd(dict *ht, void *key, void *val);
|
||||||
|
static int dictReplace(dict *ht, void *key, void *val);
|
||||||
|
static int dictDelete(dict *ht, const void *key);
|
||||||
|
static void dictRelease(dict *ht);
|
||||||
|
static dictEntry * dictFind(dict *ht, const void *key);
|
||||||
|
static void dictInitIterator(dictIterator *iter, dict *ht);
|
||||||
|
static dictEntry *dictNext(dictIterator *iter);
|
||||||
|
|
||||||
|
#endif /* __DICT_H */
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
#ifndef __HIREDIS_FMACRO_H
|
||||||
|
#define __HIREDIS_FMACRO_H
|
||||||
|
|
||||||
|
#ifndef _AIX
|
||||||
|
#define _XOPEN_SOURCE 600
|
||||||
|
#define _POSIX_C_SOURCE 200112L
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__APPLE__) && defined(__MACH__)
|
||||||
|
/* Enable TCP_KEEPALIVE */
|
||||||
|
#define _DARWIN_C_SOURCE
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1,335 +0,0 @@
|
|||||||
#--------------------------------
|
|
||||||
# area generale
|
|
||||||
#--------------------------------
|
|
||||||
*.pdb
|
|
||||||
.vs/*
|
|
||||||
|
|
||||||
# ---> VisualStudio
|
|
||||||
## Ignore Visual Studio temporary files, build results, and
|
|
||||||
## files generated by popular Visual Studio add-ons.
|
|
||||||
|
|
||||||
# User-specific files
|
|
||||||
*.suo
|
|
||||||
*.user
|
|
||||||
*.userosscache
|
|
||||||
*.sln.docstates
|
|
||||||
|
|
||||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
|
||||||
*.userprefs
|
|
||||||
|
|
||||||
# Build results
|
|
||||||
[Dd]ebug/
|
|
||||||
[Dd]ebugPublic/
|
|
||||||
[Rr]elease/
|
|
||||||
[Rr]eleases/
|
|
||||||
x64/
|
|
||||||
x86/
|
|
||||||
build/
|
|
||||||
bld/
|
|
||||||
[Bb]in/
|
|
||||||
[Oo]bj/
|
|
||||||
|
|
||||||
# Visual Studio 2015 cache/options directory
|
|
||||||
.vs/
|
|
||||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
|
||||||
#wwwroot/
|
|
||||||
|
|
||||||
# MSTest test Results
|
|
||||||
[Tt]est[Rr]esult*/
|
|
||||||
[Bb]uild[Ll]og.*
|
|
||||||
|
|
||||||
# NUNIT
|
|
||||||
*.VisualState.xml
|
|
||||||
TestResult.xml
|
|
||||||
|
|
||||||
# Build Results of an ATL Project
|
|
||||||
[Dd]ebugPS/
|
|
||||||
[Rr]eleasePS/
|
|
||||||
dlldata.c
|
|
||||||
|
|
||||||
# DNX
|
|
||||||
project.lock.json
|
|
||||||
artifacts/
|
|
||||||
|
|
||||||
*_i.c
|
|
||||||
*_p.c
|
|
||||||
*_i.h
|
|
||||||
*.ilk
|
|
||||||
*.meta
|
|
||||||
*.obj
|
|
||||||
*.pch
|
|
||||||
*.pdb
|
|
||||||
*.pgc
|
|
||||||
*.pgd
|
|
||||||
*.rsp
|
|
||||||
*.sbr
|
|
||||||
*.tlb
|
|
||||||
*.tli
|
|
||||||
*.tlh
|
|
||||||
*.tmp
|
|
||||||
*.tmp_proj
|
|
||||||
*.log
|
|
||||||
*.vspscc
|
|
||||||
*.vssscc
|
|
||||||
.builds
|
|
||||||
*.pidb
|
|
||||||
*.svclog
|
|
||||||
*.scc
|
|
||||||
|
|
||||||
# Chutzpah Test files
|
|
||||||
_Chutzpah*
|
|
||||||
|
|
||||||
# Visual C++ cache files
|
|
||||||
ipch/
|
|
||||||
*.aps
|
|
||||||
*.ncb
|
|
||||||
*.opensdf
|
|
||||||
*.sdf
|
|
||||||
*.cachefile
|
|
||||||
|
|
||||||
# Visual Studio profiler
|
|
||||||
*.psess
|
|
||||||
*.vsp
|
|
||||||
*.vspx
|
|
||||||
*.sap
|
|
||||||
|
|
||||||
# TFS 2012 Local Workspace
|
|
||||||
$tf/
|
|
||||||
|
|
||||||
# Guidance Automation Toolkit
|
|
||||||
*.gpState
|
|
||||||
|
|
||||||
# ReSharper is a .NET coding add-in
|
|
||||||
_ReSharper*/
|
|
||||||
*.[Rr]e[Ss]harper
|
|
||||||
*.DotSettings.user
|
|
||||||
|
|
||||||
# JustCode is a .NET coding add-in
|
|
||||||
.JustCode
|
|
||||||
|
|
||||||
# TeamCity is a build add-in
|
|
||||||
_TeamCity*
|
|
||||||
|
|
||||||
# DotCover is a Code Coverage Tool
|
|
||||||
*.dotCover
|
|
||||||
|
|
||||||
# NCrunch
|
|
||||||
_NCrunch_*
|
|
||||||
.*crunch*.local.xml
|
|
||||||
nCrunchTemp_*
|
|
||||||
|
|
||||||
# MightyMoose
|
|
||||||
*.mm.*
|
|
||||||
AutoTest.Net/
|
|
||||||
|
|
||||||
# Web workbench (sass)
|
|
||||||
.sass-cache/
|
|
||||||
|
|
||||||
# Installshield output folder
|
|
||||||
[Ee]xpress/
|
|
||||||
|
|
||||||
# DocProject is a documentation generator add-in
|
|
||||||
DocProject/buildhelp/
|
|
||||||
DocProject/Help/*.HxT
|
|
||||||
DocProject/Help/*.HxC
|
|
||||||
DocProject/Help/*.hhc
|
|
||||||
DocProject/Help/*.hhk
|
|
||||||
DocProject/Help/*.hhp
|
|
||||||
DocProject/Help/Html2
|
|
||||||
DocProject/Help/html
|
|
||||||
|
|
||||||
# Click-Once directory
|
|
||||||
publish/
|
|
||||||
|
|
||||||
# Publish Web Output
|
|
||||||
*.[Pp]ublish.xml
|
|
||||||
*.azurePubxml
|
|
||||||
# TODO: Comment the next line if you want to checkin your web deploy settings
|
|
||||||
# but database connection strings (with potential passwords) will be unencrypted
|
|
||||||
*.pubxml
|
|
||||||
*.publishproj
|
|
||||||
|
|
||||||
# NuGet Packages
|
|
||||||
*.nupkg
|
|
||||||
# The packages folder can be ignored because of Package Restore
|
|
||||||
**/packages/*
|
|
||||||
# except build/, which is used as an MSBuild target.
|
|
||||||
!**/packages/build/
|
|
||||||
# Uncomment if necessary however generally it will be regenerated when needed
|
|
||||||
#!**/packages/repositories.config
|
|
||||||
|
|
||||||
# Windows Azure Build Output
|
|
||||||
csx/
|
|
||||||
*.build.csdef
|
|
||||||
|
|
||||||
# Windows Store app package directory
|
|
||||||
AppPackages/
|
|
||||||
|
|
||||||
# Visual Studio cache files
|
|
||||||
# files ending in .cache can be ignored
|
|
||||||
*.[Cc]ache
|
|
||||||
# but keep track of directories ending in .cache
|
|
||||||
!*.[Cc]ache/
|
|
||||||
|
|
||||||
# Others
|
|
||||||
ClientBin/
|
|
||||||
[Ss]tyle[Cc]op.*
|
|
||||||
~$*
|
|
||||||
*~
|
|
||||||
*.dbmdl
|
|
||||||
*.dbproj.schemaview
|
|
||||||
*.pfx
|
|
||||||
*.publishsettings
|
|
||||||
node_modules/
|
|
||||||
orleans.codegen.cs
|
|
||||||
|
|
||||||
# RIA/Silverlight projects
|
|
||||||
Generated_Code/
|
|
||||||
|
|
||||||
# Backup & report files from converting an old project file
|
|
||||||
# to a newer Visual Studio version. Backup files are not needed,
|
|
||||||
# because we have git ;-)
|
|
||||||
_UpgradeReport_Files/
|
|
||||||
Backup*/
|
|
||||||
UpgradeLog*.XML
|
|
||||||
UpgradeLog*.htm
|
|
||||||
|
|
||||||
# SQL Server files
|
|
||||||
*.mdf
|
|
||||||
*.ldf
|
|
||||||
|
|
||||||
# Business Intelligence projects
|
|
||||||
*.rdl.data
|
|
||||||
*.bim.layout
|
|
||||||
*.bim_*.settings
|
|
||||||
|
|
||||||
# Microsoft Fakes
|
|
||||||
FakesAssemblies/
|
|
||||||
|
|
||||||
# Node.js Tools for Visual Studio
|
|
||||||
.ntvs_analysis.dat
|
|
||||||
|
|
||||||
# Visual Studio 6 build log
|
|
||||||
*.plg
|
|
||||||
|
|
||||||
# Visual Studio 6 workspace options file
|
|
||||||
*.opt
|
|
||||||
|
|
||||||
# Visual Studio LightSwitch build output
|
|
||||||
**/*.HTMLClient/GeneratedArtifacts
|
|
||||||
**/*.DesktopClient/GeneratedArtifacts
|
|
||||||
**/*.DesktopClient/ModelManifest.xml
|
|
||||||
**/*.Server/GeneratedArtifacts
|
|
||||||
**/*.Server/ModelManifest.xml
|
|
||||||
_Pvt_Extensions
|
|
||||||
|
|
||||||
# ---> C Sharp
|
|
||||||
# Build Folders (you can keep bin if you'd like, to store dlls and pdbs)
|
|
||||||
[Bb]in/
|
|
||||||
[Oo]bj/
|
|
||||||
|
|
||||||
# mstest test results
|
|
||||||
TestResults
|
|
||||||
|
|
||||||
## Ignore Visual Studio temporary files, build results, and
|
|
||||||
## files generated by popular Visual Studio add-ons.
|
|
||||||
|
|
||||||
# User-specific files
|
|
||||||
*.suo
|
|
||||||
*.user
|
|
||||||
*.sln.docstates
|
|
||||||
|
|
||||||
# Build results
|
|
||||||
[Dd]ebug/
|
|
||||||
[Rr]elease/
|
|
||||||
x64/
|
|
||||||
*_i.c
|
|
||||||
*_p.c
|
|
||||||
*.ilk
|
|
||||||
*.meta
|
|
||||||
*.obj
|
|
||||||
*.pch
|
|
||||||
*.pdb
|
|
||||||
*.pgc
|
|
||||||
*.pgd
|
|
||||||
*.rsp
|
|
||||||
*.sbr
|
|
||||||
*.tlb
|
|
||||||
*.tli
|
|
||||||
*.tlh
|
|
||||||
*.tmp
|
|
||||||
*.log
|
|
||||||
*.vspscc
|
|
||||||
*.vssscc
|
|
||||||
.builds
|
|
||||||
|
|
||||||
# Visual C++ cache files
|
|
||||||
ipch/
|
|
||||||
*.aps
|
|
||||||
*.ncb
|
|
||||||
*.opensdf
|
|
||||||
*.sdf
|
|
||||||
|
|
||||||
# Visual Studio profiler
|
|
||||||
*.psess
|
|
||||||
*.vsp
|
|
||||||
*.vspx
|
|
||||||
|
|
||||||
# Guidance Automation Toolkit
|
|
||||||
*.gpState
|
|
||||||
|
|
||||||
# ReSharper is a .NET coding add-in
|
|
||||||
_ReSharper*
|
|
||||||
|
|
||||||
# NCrunch
|
|
||||||
*.ncrunch*
|
|
||||||
.*crunch*.local.xml
|
|
||||||
|
|
||||||
# Installshield output folder
|
|
||||||
[Ee]xpress
|
|
||||||
|
|
||||||
# DocProject is a documentation generator add-in
|
|
||||||
DocProject/buildhelp/
|
|
||||||
DocProject/Help/*.HxT
|
|
||||||
DocProject/Help/*.HxC
|
|
||||||
DocProject/Help/*.hhc
|
|
||||||
DocProject/Help/*.hhk
|
|
||||||
DocProject/Help/*.hhp
|
|
||||||
DocProject/Help/Html2
|
|
||||||
DocProject/Help/html
|
|
||||||
|
|
||||||
# Click-Once directory
|
|
||||||
publish
|
|
||||||
|
|
||||||
# Publish Web Output
|
|
||||||
*.Publish.xml
|
|
||||||
|
|
||||||
# NuGet Packages Directory
|
|
||||||
packages
|
|
||||||
|
|
||||||
# Windows Azure Build Output
|
|
||||||
csx
|
|
||||||
*.build.csdef
|
|
||||||
|
|
||||||
# Windows Store app package directory
|
|
||||||
AppPackages/
|
|
||||||
|
|
||||||
# Others
|
|
||||||
[Bb]in
|
|
||||||
[Oo]bj
|
|
||||||
sql
|
|
||||||
TestResults
|
|
||||||
[Tt]est[Rr]esult*
|
|
||||||
*.Cache
|
|
||||||
ClientBin
|
|
||||||
[Ss]tyle[Cc]op.*
|
|
||||||
~$*
|
|
||||||
*.dbmdl
|
|
||||||
Generated_Code #added for RIA/Silverlight projects
|
|
||||||
|
|
||||||
# Backup & report files from converting an old project file to a newer
|
|
||||||
# Visual Studio version. Backup files are not needed, because we have git ;-)
|
|
||||||
_UpgradeReport_Files/
|
|
||||||
Backup*/
|
|
||||||
UpgradeLog*.XML
|
|
||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
@PACKAGE_INIT@
|
||||||
|
|
||||||
|
set_and_check(hiredis_INCLUDEDIR "@PACKAGE_INCLUDE_INSTALL_DIR@")
|
||||||
|
|
||||||
|
IF (NOT TARGET hiredis::@hiredis_export_name@)
|
||||||
|
INCLUDE(${CMAKE_CURRENT_LIST_DIR}/hiredis-targets.cmake)
|
||||||
|
ENDIF()
|
||||||
|
|
||||||
|
SET(hiredis_LIBRARIES hiredis::@hiredis_export_name@)
|
||||||
|
SET(hiredis_INCLUDE_DIRS ${hiredis_INCLUDEDIR})
|
||||||
|
|
||||||
|
check_required_components(hiredis)
|
||||||
|
|
||||||
@@ -0,0 +1,363 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||||
|
* Copyright (c) 2010-2014, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||||
|
* Copyright (c) 2015, Matt Stancliff <matt at genges dot com>,
|
||||||
|
* Jan-Erik Rediger <janerik at fnordig dot com>
|
||||||
|
*
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Redis nor the names of its contributors may be used
|
||||||
|
* to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __HIREDIS_H
|
||||||
|
#define __HIREDIS_H
|
||||||
|
#include "read.h"
|
||||||
|
#include <stdarg.h> /* for va_list */
|
||||||
|
#ifndef _MSC_VER
|
||||||
|
#include <sys/time.h> /* for struct timeval */
|
||||||
|
#else
|
||||||
|
struct timeval; /* forward declaration */
|
||||||
|
typedef intptr_t ssize_t;
|
||||||
|
#endif
|
||||||
|
#include <stdint.h> /* uintXX_t, etc */
|
||||||
|
#include "sds.h" /* for sds */
|
||||||
|
#include "alloc.h" /* for allocation wrappers */
|
||||||
|
|
||||||
|
#define HIREDIS_MAJOR 1
|
||||||
|
#define HIREDIS_MINOR 3
|
||||||
|
#define HIREDIS_PATCH 0
|
||||||
|
#define HIREDIS_SONAME 1.3.0
|
||||||
|
|
||||||
|
/* Connection type can be blocking or non-blocking and is set in the
|
||||||
|
* least significant bit of the flags field in redisContext. */
|
||||||
|
#define REDIS_BLOCK 0x1
|
||||||
|
|
||||||
|
/* Connection may be disconnected before being free'd. The second bit
|
||||||
|
* in the flags field is set when the context is connected. */
|
||||||
|
#define REDIS_CONNECTED 0x2
|
||||||
|
|
||||||
|
/* The async API might try to disconnect cleanly and flush the output
|
||||||
|
* buffer and read all subsequent replies before disconnecting.
|
||||||
|
* This flag means no new commands can come in and the connection
|
||||||
|
* should be terminated once all replies have been read. */
|
||||||
|
#define REDIS_DISCONNECTING 0x4
|
||||||
|
|
||||||
|
/* Flag specific to the async API which means that the context should be clean
|
||||||
|
* up as soon as possible. */
|
||||||
|
#define REDIS_FREEING 0x8
|
||||||
|
|
||||||
|
/* Flag that is set when an async callback is executed. */
|
||||||
|
#define REDIS_IN_CALLBACK 0x10
|
||||||
|
|
||||||
|
/* Flag that is set when the async context has one or more subscriptions. */
|
||||||
|
#define REDIS_SUBSCRIBED 0x20
|
||||||
|
|
||||||
|
/* Flag that is set when monitor mode is active */
|
||||||
|
#define REDIS_MONITORING 0x40
|
||||||
|
|
||||||
|
/* Flag that is set when we should set SO_REUSEADDR before calling bind() */
|
||||||
|
#define REDIS_REUSEADDR 0x80
|
||||||
|
|
||||||
|
/* Flag that is set when the async connection supports push replies. */
|
||||||
|
#define REDIS_SUPPORTS_PUSH 0x100
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag that indicates the user does not want the context to
|
||||||
|
* be automatically freed upon error
|
||||||
|
*/
|
||||||
|
#define REDIS_NO_AUTO_FREE 0x200
|
||||||
|
|
||||||
|
/* Flag that indicates the user does not want replies to be automatically freed */
|
||||||
|
#define REDIS_NO_AUTO_FREE_REPLIES 0x400
|
||||||
|
|
||||||
|
/* Flags to prefer IPv6 or IPv4 when doing DNS lookup. (If both are set,
|
||||||
|
* AF_UNSPEC is used.) */
|
||||||
|
#define REDIS_PREFER_IPV4 0x800
|
||||||
|
#define REDIS_PREFER_IPV6 0x1000
|
||||||
|
|
||||||
|
#define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */
|
||||||
|
|
||||||
|
/* number of times we retry to connect in the case of EADDRNOTAVAIL and
|
||||||
|
* SO_REUSEADDR is being used. */
|
||||||
|
#define REDIS_CONNECT_RETRIES 10
|
||||||
|
|
||||||
|
/* Forward declarations for structs defined elsewhere */
|
||||||
|
struct redisAsyncContext;
|
||||||
|
struct redisContext;
|
||||||
|
|
||||||
|
/* RESP3 push helpers and callback prototypes */
|
||||||
|
#define redisIsPushReply(r) (((redisReply*)(r))->type == REDIS_REPLY_PUSH)
|
||||||
|
typedef void (redisPushFn)(void *, void *);
|
||||||
|
typedef void (redisAsyncPushFn)(struct redisAsyncContext *, void *);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* This is the reply object returned by redisCommand() */
|
||||||
|
typedef struct redisReply {
|
||||||
|
int type; /* REDIS_REPLY_* */
|
||||||
|
long long integer; /* The integer when type is REDIS_REPLY_INTEGER */
|
||||||
|
double dval; /* The double when type is REDIS_REPLY_DOUBLE */
|
||||||
|
size_t len; /* Length of string */
|
||||||
|
char *str; /* Used for REDIS_REPLY_ERROR, REDIS_REPLY_STRING
|
||||||
|
REDIS_REPLY_VERB, REDIS_REPLY_DOUBLE (in additional to dval),
|
||||||
|
and REDIS_REPLY_BIGNUM. */
|
||||||
|
char vtype[4]; /* Used for REDIS_REPLY_VERB, contains the null
|
||||||
|
terminated 3 character content type, such as "txt". */
|
||||||
|
size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */
|
||||||
|
struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */
|
||||||
|
} redisReply;
|
||||||
|
|
||||||
|
redisReader *redisReaderCreate(void);
|
||||||
|
|
||||||
|
/* Function to free the reply objects hiredis returns by default. */
|
||||||
|
void freeReplyObject(void *reply);
|
||||||
|
|
||||||
|
/* Functions to format a command according to the protocol. */
|
||||||
|
int redisvFormatCommand(char **target, const char *format, va_list ap);
|
||||||
|
int redisFormatCommand(char **target, const char *format, ...);
|
||||||
|
long long redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen);
|
||||||
|
long long redisFormatSdsCommandArgv(sds *target, int argc, const char ** argv, const size_t *argvlen);
|
||||||
|
void redisFreeCommand(char *cmd);
|
||||||
|
void redisFreeSdsCommand(sds cmd);
|
||||||
|
|
||||||
|
enum redisConnectionType {
|
||||||
|
REDIS_CONN_TCP,
|
||||||
|
REDIS_CONN_UNIX,
|
||||||
|
REDIS_CONN_USERFD
|
||||||
|
};
|
||||||
|
|
||||||
|
struct redisSsl;
|
||||||
|
|
||||||
|
#define REDIS_OPT_NONBLOCK 0x01
|
||||||
|
#define REDIS_OPT_REUSEADDR 0x02
|
||||||
|
#define REDIS_OPT_NOAUTOFREE 0x04 /* 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_NO_PUSH_AUTOFREE 0x08 /* Don't automatically intercept and
|
||||||
|
* free RESP3 PUSH replies. */
|
||||||
|
#define REDIS_OPT_NOAUTOFREEREPLIES 0x10 /* Don't automatically free replies. */
|
||||||
|
#define REDIS_OPT_PREFER_IPV4 0x20 /* Prefer IPv4 in DNS lookups. */
|
||||||
|
#define REDIS_OPT_PREFER_IPV6 0x40 /* Prefer IPv6 in DNS lookups. */
|
||||||
|
#define REDIS_OPT_PREFER_IP_UNSPEC (REDIS_OPT_PREFER_IPV4 | REDIS_OPT_PREFER_IPV6)
|
||||||
|
#define REDIS_OPT_SET_SOCK_CLOEXEC 0x80 /* Set SOCK_CLOEXEC on socket file descriptor. */
|
||||||
|
|
||||||
|
/* 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 for connect operation. If NULL, no timeout is used */
|
||||||
|
const struct timeval *connect_timeout;
|
||||||
|
/* timeout value for commands. If NULL, no timeout is used. This can be
|
||||||
|
* updated at runtime with redisSetTimeout/redisAsyncSetTimeout. */
|
||||||
|
const struct timeval *command_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;
|
||||||
|
|
||||||
|
/* Optional user defined data/destructor */
|
||||||
|
void *privdata;
|
||||||
|
void (*free_privdata)(void *);
|
||||||
|
|
||||||
|
/* A user defined PUSH message callback */
|
||||||
|
redisPushFn *push_cb;
|
||||||
|
redisAsyncPushFn *async_push_cb;
|
||||||
|
} redisOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper macros to initialize options to their specified fields.
|
||||||
|
*/
|
||||||
|
#define REDIS_OPTIONS_SET_TCP(opts, ip_, port_) do { \
|
||||||
|
(opts)->type = REDIS_CONN_TCP; \
|
||||||
|
(opts)->endpoint.tcp.ip = ip_; \
|
||||||
|
(opts)->endpoint.tcp.port = port_; \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
#define REDIS_OPTIONS_SET_UNIX(opts, path) do { \
|
||||||
|
(opts)->type = REDIS_CONN_UNIX; \
|
||||||
|
(opts)->endpoint.unix_socket = path; \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
#define REDIS_OPTIONS_SET_PRIVDATA(opts, data, dtor) do { \
|
||||||
|
(opts)->privdata = data; \
|
||||||
|
(opts)->free_privdata = dtor; \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
typedef struct redisContextFuncs {
|
||||||
|
void (*close)(struct redisContext *);
|
||||||
|
void (*free_privctx)(void *);
|
||||||
|
void (*async_read)(struct redisAsyncContext *);
|
||||||
|
void (*async_write)(struct redisAsyncContext *);
|
||||||
|
|
||||||
|
/* Read/Write data to the underlying communication stream, returning the
|
||||||
|
* number of bytes read/written. In the event of an unrecoverable error
|
||||||
|
* these functions shall return a value < 0. In the event of a
|
||||||
|
* recoverable error, they should return 0. */
|
||||||
|
ssize_t (*read)(struct redisContext *, char *, size_t);
|
||||||
|
ssize_t (*write)(struct redisContext *);
|
||||||
|
} redisContextFuncs;
|
||||||
|
|
||||||
|
|
||||||
|
/* Context for a connection to Redis */
|
||||||
|
typedef struct redisContext {
|
||||||
|
const redisContextFuncs *funcs; /* Function table */
|
||||||
|
|
||||||
|
int err; /* Error flags, 0 when there is no error */
|
||||||
|
char errstr[128]; /* String representation of error when applicable */
|
||||||
|
redisFD fd;
|
||||||
|
int flags;
|
||||||
|
char *obuf; /* Write buffer */
|
||||||
|
redisReader *reader; /* Protocol reader */
|
||||||
|
|
||||||
|
enum redisConnectionType connection_type;
|
||||||
|
struct timeval *connect_timeout;
|
||||||
|
struct timeval *command_timeout;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
char *host;
|
||||||
|
char *source_addr;
|
||||||
|
int port;
|
||||||
|
} tcp;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
char *path;
|
||||||
|
} unix_sock;
|
||||||
|
|
||||||
|
/* For non-blocking connect */
|
||||||
|
struct sockaddr *saddr;
|
||||||
|
size_t addrlen;
|
||||||
|
|
||||||
|
/* Optional data and corresponding destructor users can use to provide
|
||||||
|
* context to a given redisContext. Not used by hiredis. */
|
||||||
|
void *privdata;
|
||||||
|
void (*free_privdata)(void *);
|
||||||
|
|
||||||
|
/* Internal context pointer presently used by hiredis to manage
|
||||||
|
* SSL connections. */
|
||||||
|
void *privctx;
|
||||||
|
|
||||||
|
/* An optional RESP3 PUSH handler */
|
||||||
|
redisPushFn *push_cb;
|
||||||
|
} redisContext;
|
||||||
|
|
||||||
|
redisContext *redisConnectWithOptions(const redisOptions *options);
|
||||||
|
redisContext *redisConnect(const char *ip, int port);
|
||||||
|
redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv);
|
||||||
|
redisContext *redisConnectNonBlock(const char *ip, int port);
|
||||||
|
redisContext *redisConnectBindNonBlock(const char *ip, int port,
|
||||||
|
const char *source_addr);
|
||||||
|
redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port,
|
||||||
|
const char *source_addr);
|
||||||
|
redisContext *redisConnectUnix(const char *path);
|
||||||
|
redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv);
|
||||||
|
redisContext *redisConnectUnixNonBlock(const char *path);
|
||||||
|
redisContext *redisConnectFd(redisFD fd);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reconnect the given context using the saved information.
|
||||||
|
*
|
||||||
|
* This re-uses the exact same connect options as in the initial connection.
|
||||||
|
* host, ip (or path), timeout and bind address are reused,
|
||||||
|
* flags are used unmodified from the existing context.
|
||||||
|
*
|
||||||
|
* Returns REDIS_OK on successful connect or REDIS_ERR otherwise.
|
||||||
|
*/
|
||||||
|
int redisReconnect(redisContext *c);
|
||||||
|
|
||||||
|
redisPushFn *redisSetPushCallback(redisContext *c, redisPushFn *fn);
|
||||||
|
int redisSetTimeout(redisContext *c, const struct timeval tv);
|
||||||
|
int redisEnableKeepAlive(redisContext *c);
|
||||||
|
int redisEnableKeepAliveWithInterval(redisContext *c, int interval);
|
||||||
|
int redisSetTcpUserTimeout(redisContext *c, unsigned int timeout);
|
||||||
|
void redisFree(redisContext *c);
|
||||||
|
redisFD redisFreeKeepFd(redisContext *c);
|
||||||
|
int redisBufferRead(redisContext *c);
|
||||||
|
int redisBufferWrite(redisContext *c, int *done);
|
||||||
|
|
||||||
|
/* In a blocking context, this function first checks if there are unconsumed
|
||||||
|
* replies to return and returns one if so. Otherwise, it flushes the output
|
||||||
|
* buffer to the socket and reads until it has a reply. In a non-blocking
|
||||||
|
* context, it will return unconsumed replies until there are no more. */
|
||||||
|
int redisGetReply(redisContext *c, void **reply);
|
||||||
|
int redisGetReplyFromReader(redisContext *c, void **reply);
|
||||||
|
|
||||||
|
/* Write a formatted command to the output buffer. Use these functions in blocking mode
|
||||||
|
* to get a pipeline of commands. */
|
||||||
|
int redisAppendFormattedCommand(redisContext *c, const char *cmd, size_t len);
|
||||||
|
|
||||||
|
/* Write a command to the output buffer. Use these functions in blocking mode
|
||||||
|
* to get a pipeline of commands. */
|
||||||
|
int redisvAppendCommand(redisContext *c, const char *format, va_list ap);
|
||||||
|
int redisAppendCommand(redisContext *c, const char *format, ...);
|
||||||
|
int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
|
||||||
|
|
||||||
|
/* Issue a command to Redis. In a blocking context, it is identical to calling
|
||||||
|
* redisAppendCommand, followed by redisGetReply. The function will return
|
||||||
|
* NULL if there was an error in performing the request, otherwise it will
|
||||||
|
* return the reply. In a non-blocking context, it is identical to calling
|
||||||
|
* only redisAppendCommand and will always return NULL. */
|
||||||
|
void *redisvCommand(redisContext *c, const char *format, va_list ap);
|
||||||
|
void *redisCommand(redisContext *c, const char *format, ...);
|
||||||
|
void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
prefix=@CMAKE_INSTALL_PREFIX@
|
||||||
|
install_libdir=@CMAKE_INSTALL_LIBDIR@
|
||||||
|
exec_prefix=${prefix}
|
||||||
|
libdir=${exec_prefix}/${install_libdir}
|
||||||
|
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} -I${includedir} -D_FILE_OFFSET_BITS=64
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<ItemDefinitionGroup>
|
||||||
|
<ClCompile>
|
||||||
|
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)\..\..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<AdditionalLibraryDirectories>$(MSBuildThisFileDirectory)\..\..\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
@PACKAGE_INIT@
|
||||||
|
|
||||||
|
set_and_check(hiredis_ssl_INCLUDEDIR "@PACKAGE_INCLUDE_INSTALL_DIR@")
|
||||||
|
|
||||||
|
include(CMakeFindDependencyMacro)
|
||||||
|
find_dependency(OpenSSL)
|
||||||
|
|
||||||
|
IF (NOT TARGET hiredis::hiredis_ssl)
|
||||||
|
INCLUDE(${CMAKE_CURRENT_LIST_DIR}/hiredis_ssl-targets.cmake)
|
||||||
|
ENDIF()
|
||||||
|
|
||||||
|
SET(hiredis_ssl_LIBRARIES hiredis::hiredis_ssl)
|
||||||
|
SET(hiredis_ssl_INCLUDE_DIRS ${hiredis_ssl_INCLUDEDIR})
|
||||||
|
|
||||||
|
check_required_components(hiredis_ssl)
|
||||||
|
|
||||||
+163
@@ -0,0 +1,163 @@
|
|||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019, Redis Labs
|
||||||
|
*
|
||||||
|
* 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 __HIREDIS_SSL_H
|
||||||
|
#define __HIREDIS_SSL_H
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* This is the underlying struct for SSL in ssl.h, which is not included to
|
||||||
|
* keep build dependencies short here.
|
||||||
|
*/
|
||||||
|
struct ssl_st;
|
||||||
|
|
||||||
|
/* A wrapper around OpenSSL SSL_CTX to allow easy SSL use without directly
|
||||||
|
* calling OpenSSL.
|
||||||
|
*/
|
||||||
|
typedef struct redisSSLContext redisSSLContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialization errors that redisCreateSSLContext() may return.
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
REDIS_SSL_CTX_NONE = 0, /* No Error */
|
||||||
|
REDIS_SSL_CTX_CREATE_FAILED, /* Failed to create OpenSSL SSL_CTX */
|
||||||
|
REDIS_SSL_CTX_CERT_KEY_REQUIRED, /* Client cert and key must both be specified or skipped */
|
||||||
|
REDIS_SSL_CTX_CA_CERT_LOAD_FAILED, /* Failed to load CA Certificate or CA Path */
|
||||||
|
REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED, /* Failed to load client certificate */
|
||||||
|
REDIS_SSL_CTX_CLIENT_DEFAULT_CERT_FAILED, /* Failed to set client default certificate directory */
|
||||||
|
REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED, /* Failed to load private key */
|
||||||
|
REDIS_SSL_CTX_OS_CERTSTORE_OPEN_FAILED, /* Failed to open system certificate store */
|
||||||
|
REDIS_SSL_CTX_OS_CERT_ADD_FAILED /* Failed to add CA certificates obtained from system to the SSL context */
|
||||||
|
} redisSSLContextError;
|
||||||
|
|
||||||
|
/* Constants that mirror OpenSSL's verify modes. By default,
|
||||||
|
* REDIS_SSL_VERIFY_PEER is used with redisCreateSSLContext().
|
||||||
|
* Some Redis clients disable peer verification if there are no
|
||||||
|
* certificates specified.
|
||||||
|
*/
|
||||||
|
#define REDIS_SSL_VERIFY_NONE 0x00
|
||||||
|
#define REDIS_SSL_VERIFY_PEER 0x01
|
||||||
|
#define REDIS_SSL_VERIFY_FAIL_IF_NO_PEER_CERT 0x02
|
||||||
|
#define REDIS_SSL_VERIFY_CLIENT_ONCE 0x04
|
||||||
|
#define REDIS_SSL_VERIFY_POST_HANDSHAKE 0x08
|
||||||
|
|
||||||
|
/* Options to create an OpenSSL context. */
|
||||||
|
typedef struct {
|
||||||
|
const char *cacert_filename;
|
||||||
|
const char *capath;
|
||||||
|
const char *cert_filename;
|
||||||
|
const char *private_key_filename;
|
||||||
|
const char *server_name;
|
||||||
|
int verify_mode;
|
||||||
|
} redisSSLOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the error message corresponding with the specified error code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const char *redisSSLContextGetError(redisSSLContextError error);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to initialize the OpenSSL library.
|
||||||
|
*
|
||||||
|
* OpenSSL requires one-time initialization before it can be used. Callers should
|
||||||
|
* call this function only once, and only if OpenSSL is not directly initialized
|
||||||
|
* elsewhere.
|
||||||
|
*/
|
||||||
|
int redisInitOpenSSL(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to initialize an OpenSSL context that can be used
|
||||||
|
* to initiate SSL connections.
|
||||||
|
*
|
||||||
|
* cacert_filename is an optional name of a CA certificate/bundle file to load
|
||||||
|
* and use for validation.
|
||||||
|
*
|
||||||
|
* capath is an optional directory path where trusted CA certificate files are
|
||||||
|
* stored in an OpenSSL-compatible structure.
|
||||||
|
*
|
||||||
|
* cert_filename and private_key_filename are optional names of a client side
|
||||||
|
* certificate and private key files to use for authentication. They need to
|
||||||
|
* be both specified or omitted.
|
||||||
|
*
|
||||||
|
* server_name is an optional and will be used as a server name indication
|
||||||
|
* (SNI) TLS extension.
|
||||||
|
*
|
||||||
|
* If error is non-null, it will be populated in case the context creation fails
|
||||||
|
* (returning a NULL).
|
||||||
|
*/
|
||||||
|
|
||||||
|
redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char *capath,
|
||||||
|
const char *cert_filename, const char *private_key_filename,
|
||||||
|
const char *server_name, redisSSLContextError *error);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to initialize an OpenSSL context that can be used
|
||||||
|
* to initiate SSL connections. This is a more extensible version of redisCreateSSLContext().
|
||||||
|
*
|
||||||
|
* options contains a structure of SSL options to use.
|
||||||
|
*
|
||||||
|
* If error is non-null, it will be populated in case the context creation fails
|
||||||
|
* (returning a NULL).
|
||||||
|
*/
|
||||||
|
redisSSLContext *redisCreateSSLContextWithOptions(redisSSLOptions *options,
|
||||||
|
redisSSLContextError *error);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Free a previously created OpenSSL context.
|
||||||
|
*/
|
||||||
|
void redisFreeSSLContext(redisSSLContext *redis_ssl_ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiate SSL on an existing redisContext.
|
||||||
|
*
|
||||||
|
* This is similar to redisInitiateSSL() but does not require the caller
|
||||||
|
* to directly interact with OpenSSL, and instead uses a redisSSLContext
|
||||||
|
* previously created using redisCreateSSLContext().
|
||||||
|
*/
|
||||||
|
|
||||||
|
int redisInitiateSSLWithContext(redisContext *c, redisSSLContext *redis_ssl_ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiate SSL/TLS negotiation on a provided OpenSSL SSL object.
|
||||||
|
*/
|
||||||
|
|
||||||
|
int redisInitiateSSL(redisContext *c, struct ssl_st *ssl);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* __HIREDIS_SSL_H */
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
prefix=@CMAKE_INSTALL_PREFIX@
|
||||||
|
install_libdir=@CMAKE_INSTALL_LIBDIR@
|
||||||
|
exec_prefix=${prefix}
|
||||||
|
libdir=${exec_prefix}/${install_libdir}
|
||||||
|
includedir=${prefix}/include
|
||||||
|
pkgincludedir=${includedir}/hiredis
|
||||||
|
|
||||||
|
Name: hiredis_ssl
|
||||||
|
Description: SSL Support for hiredis.
|
||||||
|
Version: @PROJECT_VERSION@
|
||||||
|
Requires: hiredis
|
||||||
|
Libs: -L${libdir} -lhiredis_ssl
|
||||||
|
Libs.private: -lssl -lcrypto
|
||||||
@@ -0,0 +1,713 @@
|
|||||||
|
/* Extracted from anet.c to work properly with Hiredis error reporting.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||||
|
* Copyright (c) 2010-2014, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||||
|
* Copyright (c) 2015, Matt Stancliff <matt at genges dot com>,
|
||||||
|
* Jan-Erik Rediger <janerik at fnordig dot com>
|
||||||
|
*
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Redis nor the names of its contributors may be used
|
||||||
|
* to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "fmacros.h"
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
#include "net.h"
|
||||||
|
#include "sds.h"
|
||||||
|
#include "sockcompat.h"
|
||||||
|
#include "win32.h"
|
||||||
|
|
||||||
|
/* Defined in hiredis.c */
|
||||||
|
void __redisSetError(redisContext *c, int type, const char *str);
|
||||||
|
|
||||||
|
int redisContextUpdateCommandTimeout(redisContext *c, const struct timeval *timeout);
|
||||||
|
|
||||||
|
void redisNetClose(redisContext *c) {
|
||||||
|
if (c && c->fd != REDIS_INVALID_FD) {
|
||||||
|
close(c->fd);
|
||||||
|
c->fd = REDIS_INVALID_FD;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t redisNetRead(redisContext *c, char *buf, size_t bufcap) {
|
||||||
|
ssize_t 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 if(errno == ETIMEDOUT && (c->flags & REDIS_BLOCK)) {
|
||||||
|
/* especially in windows */
|
||||||
|
__redisSetError(c, REDIS_ERR_TIMEOUT, "recv timeout");
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
__redisSetError(c, REDIS_ERR_IO, strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} else if (nread == 0) {
|
||||||
|
__redisSetError(c, REDIS_ERR_EOF, "Server closed the connection");
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
return nread;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t redisNetWrite(redisContext *c) {
|
||||||
|
ssize_t nwritten;
|
||||||
|
|
||||||
|
nwritten = send(c->fd, c->obuf, sdslen(c->obuf), 0);
|
||||||
|
if (nwritten < 0) {
|
||||||
|
if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
|
||||||
|
/* Try again */
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
__redisSetError(c, REDIS_ERR_IO, strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nwritten;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) {
|
||||||
|
int errorno = errno; /* snprintf() may change errno */
|
||||||
|
char buf[128] = { 0 };
|
||||||
|
size_t len = 0;
|
||||||
|
|
||||||
|
if (prefix != NULL)
|
||||||
|
len = snprintf(buf,sizeof(buf),"%s: ",prefix);
|
||||||
|
strerror_r(errorno, (char *)(buf + len), sizeof(buf) - len);
|
||||||
|
__redisSetError(c,type,buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int redisSetReuseAddr(redisContext *c) {
|
||||||
|
int on = 1;
|
||||||
|
if (setsockopt(c->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
|
||||||
|
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
|
||||||
|
redisNetClose(c);
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int redisCreateSocket(redisContext *c, int type) {
|
||||||
|
redisFD s;
|
||||||
|
int flags = SOCK_STREAM;
|
||||||
|
|
||||||
|
#ifdef SOCK_CLOEXEC
|
||||||
|
if (c->flags & REDIS_OPT_SET_SOCK_CLOEXEC) {
|
||||||
|
flags |= SOCK_CLOEXEC;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if ((s = socket(type, flags, 0)) == REDIS_INVALID_FD) {
|
||||||
|
__redisSetErrorFromErrno(c, REDIS_ERR_IO, NULL);
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
c->fd = s;
|
||||||
|
|
||||||
|
if (type == AF_INET) {
|
||||||
|
if (redisSetReuseAddr(c) == REDIS_ERR) {
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int redisSetBlocking(redisContext *c, int blocking) {
|
||||||
|
#ifndef _WIN32
|
||||||
|
int flags;
|
||||||
|
|
||||||
|
/* Set the socket nonblocking.
|
||||||
|
* Note that fcntl(2) for F_GETFL and F_SETFL can't be
|
||||||
|
* interrupted by a signal. */
|
||||||
|
if ((flags = fcntl(c->fd, F_GETFL)) == -1) {
|
||||||
|
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)");
|
||||||
|
redisNetClose(c);
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blocking)
|
||||||
|
flags &= ~O_NONBLOCK;
|
||||||
|
else
|
||||||
|
flags |= O_NONBLOCK;
|
||||||
|
|
||||||
|
if (fcntl(c->fd, F_SETFL, flags) == -1) {
|
||||||
|
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)");
|
||||||
|
redisNetClose(c);
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
u_long mode = blocking ? 0 : 1;
|
||||||
|
if (ioctl(c->fd, FIONBIO, &mode) == -1) {
|
||||||
|
__redisSetErrorFromErrno(c, REDIS_ERR_IO, "ioctl(FIONBIO)");
|
||||||
|
redisNetClose(c);
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
#endif /* _WIN32 */
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int redisKeepAlive(redisContext *c, int interval) {
|
||||||
|
int val = 1;
|
||||||
|
redisFD fd = c->fd;
|
||||||
|
|
||||||
|
/* TCP_KEEPALIVE makes no sense with AF_UNIX connections */
|
||||||
|
if (c->connection_type == REDIS_CONN_UNIX)
|
||||||
|
return REDIS_ERR;
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){
|
||||||
|
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
val = interval;
|
||||||
|
|
||||||
|
#if defined(__APPLE__) && defined(__MACH__)
|
||||||
|
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0) {
|
||||||
|
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
#if defined(__GLIBC__) && !defined(__FreeBSD_kernel__)
|
||||||
|
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) {
|
||||||
|
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
val = interval/3;
|
||||||
|
if (val == 0) val = 1;
|
||||||
|
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &val, sizeof(val)) < 0) {
|
||||||
|
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
val = 3;
|
||||||
|
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &val, sizeof(val)) < 0) {
|
||||||
|
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
int res;
|
||||||
|
|
||||||
|
res = win32_redisKeepAlive(fd, interval * 1000);
|
||||||
|
if (res != 0) {
|
||||||
|
__redisSetError(c, REDIS_ERR_OTHER, strerror(res));
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int redisSetTcpNoDelay(redisContext *c) {
|
||||||
|
int yes = 1;
|
||||||
|
if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) {
|
||||||
|
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)");
|
||||||
|
redisNetClose(c);
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int redisContextSetTcpUserTimeout(redisContext *c, unsigned int timeout) {
|
||||||
|
int res;
|
||||||
|
#ifdef TCP_USER_TIMEOUT
|
||||||
|
res = setsockopt(c->fd, IPPROTO_TCP, TCP_USER_TIMEOUT, &timeout, sizeof(timeout));
|
||||||
|
#else
|
||||||
|
res = -1;
|
||||||
|
errno = ENOTSUP;
|
||||||
|
(void)timeout;
|
||||||
|
#endif
|
||||||
|
if (res == -1) {
|
||||||
|
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_USER_TIMEOUT)");
|
||||||
|
redisNetClose(c);
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define __MAX_MSEC (((LONG_MAX) - 999) / 1000)
|
||||||
|
|
||||||
|
static int redisContextTimeoutMsec(redisContext *c, long *result)
|
||||||
|
{
|
||||||
|
const struct timeval *timeout = c->connect_timeout;
|
||||||
|
long msec = -1;
|
||||||
|
|
||||||
|
/* Only use timeout when not NULL. */
|
||||||
|
if (timeout != NULL) {
|
||||||
|
if (timeout->tv_usec > 1000000 || timeout->tv_sec > __MAX_MSEC) {
|
||||||
|
__redisSetError(c, REDIS_ERR_IO, "Invalid timeout specified");
|
||||||
|
*result = msec;
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
msec = (timeout->tv_sec * 1000) + ((timeout->tv_usec + 999) / 1000);
|
||||||
|
|
||||||
|
if (msec < 0 || msec > INT_MAX) {
|
||||||
|
msec = INT_MAX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*result = msec;
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static long redisPollMillis(void) {
|
||||||
|
#ifndef _MSC_VER
|
||||||
|
struct timespec now;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||||
|
return (now.tv_sec * 1000) + now.tv_nsec / 1000000;
|
||||||
|
#else
|
||||||
|
FILETIME ft;
|
||||||
|
GetSystemTimeAsFileTime(&ft);
|
||||||
|
return (((long long)ft.dwHighDateTime << 32) | ft.dwLowDateTime) / 10;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static int redisContextWaitReady(redisContext *c, long msec) {
|
||||||
|
struct pollfd wfd;
|
||||||
|
long end;
|
||||||
|
int res;
|
||||||
|
|
||||||
|
if (errno != EINPROGRESS) {
|
||||||
|
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
|
||||||
|
redisNetClose(c);
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
wfd.fd = c->fd;
|
||||||
|
wfd.events = POLLOUT;
|
||||||
|
end = msec >= 0 ? redisPollMillis() + msec : 0;
|
||||||
|
|
||||||
|
while ((res = poll(&wfd, 1, msec)) <= 0) {
|
||||||
|
if (res < 0 && errno != EINTR) {
|
||||||
|
__redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)");
|
||||||
|
redisNetClose(c);
|
||||||
|
return REDIS_ERR;
|
||||||
|
} else if (res == 0 || (msec >= 0 && redisPollMillis() >= end)) {
|
||||||
|
errno = ETIMEDOUT;
|
||||||
|
__redisSetErrorFromErrno(c, REDIS_ERR_IO, NULL);
|
||||||
|
redisNetClose(c);
|
||||||
|
return REDIS_ERR;
|
||||||
|
} else {
|
||||||
|
/* res < 0 && errno == EINTR, try again */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (redisCheckConnectDone(c, &res) != REDIS_OK || res == 0) {
|
||||||
|
redisCheckSocketError(c);
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
int error = errno;
|
||||||
|
if (error == EINPROGRESS) {
|
||||||
|
/* must check error to see if connect failed. Get the socket error */
|
||||||
|
int fail, so_error;
|
||||||
|
socklen_t optlen = sizeof(so_error);
|
||||||
|
fail = getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &so_error, &optlen);
|
||||||
|
if (fail == 0) {
|
||||||
|
if (so_error == 0) {
|
||||||
|
/* Socket is connected! */
|
||||||
|
*completed = 1;
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
||||||
|
/* connection error; */
|
||||||
|
errno = so_error;
|
||||||
|
error = so_error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch (error) {
|
||||||
|
case EISCONN:
|
||||||
|
*completed = 1;
|
||||||
|
return REDIS_OK;
|
||||||
|
case EALREADY:
|
||||||
|
case EWOULDBLOCK:
|
||||||
|
*completed = 0;
|
||||||
|
return REDIS_OK;
|
||||||
|
default:
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int redisCheckSocketError(redisContext *c) {
|
||||||
|
int err = 0, errno_saved = errno;
|
||||||
|
socklen_t errlen = sizeof(err);
|
||||||
|
|
||||||
|
if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) {
|
||||||
|
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"getsockopt(SO_ERROR)");
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err == 0) {
|
||||||
|
err = errno_saved;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
errno = err;
|
||||||
|
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int redisContextSetTimeout(redisContext *c, const struct timeval tv) {
|
||||||
|
const void *to_ptr = &tv;
|
||||||
|
size_t to_sz = sizeof(tv);
|
||||||
|
|
||||||
|
if (redisContextUpdateCommandTimeout(c, &tv) != REDIS_OK) {
|
||||||
|
__redisSetError(c, REDIS_ERR_OOM, "Out of memory");
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,to_ptr,to_sz) == -1) {
|
||||||
|
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)");
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,to_ptr,to_sz) == -1) {
|
||||||
|
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_SNDTIMEO)");
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int redisContextUpdateConnectTimeout(redisContext *c, const struct timeval *timeout) {
|
||||||
|
/* Same timeval struct, short circuit */
|
||||||
|
if (c->connect_timeout == timeout)
|
||||||
|
return REDIS_OK;
|
||||||
|
|
||||||
|
/* Allocate context timeval if we need to */
|
||||||
|
if (c->connect_timeout == NULL) {
|
||||||
|
c->connect_timeout = hi_malloc(sizeof(*c->connect_timeout));
|
||||||
|
if (c->connect_timeout == NULL)
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(c->connect_timeout, timeout, sizeof(*c->connect_timeout));
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int redisContextUpdateCommandTimeout(redisContext *c, const struct timeval *timeout) {
|
||||||
|
/* Same timeval struct, short circuit */
|
||||||
|
if (c->command_timeout == timeout)
|
||||||
|
return REDIS_OK;
|
||||||
|
|
||||||
|
/* Allocate context timeval if we need to */
|
||||||
|
if (c->command_timeout == NULL) {
|
||||||
|
c->command_timeout = hi_malloc(sizeof(*c->command_timeout));
|
||||||
|
if (c->command_timeout == NULL)
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(c->command_timeout, timeout, sizeof(*c->command_timeout));
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
|
||||||
|
const struct timeval *timeout,
|
||||||
|
const char *source_addr) {
|
||||||
|
redisFD s;
|
||||||
|
int rv, n;
|
||||||
|
char _port[6]; /* strlen("65535"); */
|
||||||
|
struct addrinfo hints, *servinfo, *bservinfo, *p, *b;
|
||||||
|
int blocking = (c->flags & REDIS_BLOCK);
|
||||||
|
int reuseaddr = (c->flags & REDIS_REUSEADDR);
|
||||||
|
int reuses = 0;
|
||||||
|
long timeout_msec = -1;
|
||||||
|
|
||||||
|
servinfo = NULL;
|
||||||
|
c->connection_type = REDIS_CONN_TCP;
|
||||||
|
c->tcp.port = port;
|
||||||
|
|
||||||
|
/* We need to take possession of the passed parameters
|
||||||
|
* to make them reusable for a reconnect.
|
||||||
|
* We also carefully check we don't free data we already own,
|
||||||
|
* as in the case of the reconnect method.
|
||||||
|
*
|
||||||
|
* This is a bit ugly, but atleast it works and doesn't leak memory.
|
||||||
|
**/
|
||||||
|
if (c->tcp.host != addr) {
|
||||||
|
hi_free(c->tcp.host);
|
||||||
|
|
||||||
|
c->tcp.host = hi_strdup(addr);
|
||||||
|
if (c->tcp.host == NULL)
|
||||||
|
goto oom;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeout) {
|
||||||
|
if (redisContextUpdateConnectTimeout(c, timeout) == REDIS_ERR)
|
||||||
|
goto oom;
|
||||||
|
} else {
|
||||||
|
hi_free(c->connect_timeout);
|
||||||
|
c->connect_timeout = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (redisContextTimeoutMsec(c, &timeout_msec) != REDIS_OK) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source_addr == NULL) {
|
||||||
|
hi_free(c->tcp.source_addr);
|
||||||
|
c->tcp.source_addr = NULL;
|
||||||
|
} else if (c->tcp.source_addr != source_addr) {
|
||||||
|
hi_free(c->tcp.source_addr);
|
||||||
|
c->tcp.source_addr = hi_strdup(source_addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
snprintf(_port, 6, "%d", port);
|
||||||
|
memset(&hints,0,sizeof(hints));
|
||||||
|
hints.ai_family = AF_INET;
|
||||||
|
hints.ai_socktype = SOCK_STREAM;
|
||||||
|
|
||||||
|
/* DNS lookup. To use dual stack, set both flags to prefer both IPv4 and
|
||||||
|
* IPv6. By default, for historical reasons, we try IPv4 first and then we
|
||||||
|
* try IPv6 only if no IPv4 address was found. */
|
||||||
|
if (c->flags & REDIS_PREFER_IPV6 && c->flags & REDIS_PREFER_IPV4)
|
||||||
|
hints.ai_family = AF_UNSPEC;
|
||||||
|
else if (c->flags & REDIS_PREFER_IPV6)
|
||||||
|
hints.ai_family = AF_INET6;
|
||||||
|
else
|
||||||
|
hints.ai_family = AF_INET;
|
||||||
|
|
||||||
|
rv = getaddrinfo(c->tcp.host, _port, &hints, &servinfo);
|
||||||
|
if (rv != 0 && hints.ai_family != AF_UNSPEC) {
|
||||||
|
/* Try again with the other IP version. */
|
||||||
|
hints.ai_family = (hints.ai_family == AF_INET) ? AF_INET6 : AF_INET;
|
||||||
|
rv = getaddrinfo(c->tcp.host, _port, &hints, &servinfo);
|
||||||
|
}
|
||||||
|
if (rv != 0) {
|
||||||
|
__redisSetError(c, REDIS_ERR_OTHER, gai_strerror(rv));
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
for (p = servinfo; p != NULL; p = p->ai_next) {
|
||||||
|
addrretry: {
|
||||||
|
int sock_type = p->ai_socktype;
|
||||||
|
|
||||||
|
#ifdef SOCK_CLOEXEC
|
||||||
|
if (c->flags & REDIS_OPT_SET_SOCK_CLOEXEC) {
|
||||||
|
sock_type |= SOCK_CLOEXEC;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if ((s = socket(p->ai_family, sock_type, p->ai_protocol)) == REDIS_INVALID_FD)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
c->fd = s;
|
||||||
|
|
||||||
|
if (redisSetBlocking(c,0) != REDIS_OK)
|
||||||
|
goto error;
|
||||||
|
if (c->tcp.source_addr) {
|
||||||
|
int bound = 0;
|
||||||
|
/* Using getaddrinfo saves us from self-determining IPv4 vs IPv6 */
|
||||||
|
if ((rv = getaddrinfo(c->tcp.source_addr, NULL, &hints, &bservinfo)) != 0) {
|
||||||
|
char buf[128];
|
||||||
|
snprintf(buf,sizeof(buf),"Can't get addr: %s",gai_strerror(rv));
|
||||||
|
__redisSetError(c,REDIS_ERR_OTHER,buf);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reuseaddr) {
|
||||||
|
n = 1;
|
||||||
|
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*) &n,
|
||||||
|
sizeof(n)) < 0) {
|
||||||
|
freeaddrinfo(bservinfo);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (b = bservinfo; b != NULL; b = b->ai_next) {
|
||||||
|
if (bind(s,b->ai_addr,b->ai_addrlen) != -1) {
|
||||||
|
bound = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
freeaddrinfo(bservinfo);
|
||||||
|
if (!bound) {
|
||||||
|
char buf[128];
|
||||||
|
snprintf(buf,sizeof(buf),"Can't bind socket: %s",strerror(errno));
|
||||||
|
__redisSetError(c,REDIS_ERR_OTHER,buf);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* For repeat connection */
|
||||||
|
hi_free(c->saddr);
|
||||||
|
c->saddr = hi_malloc(p->ai_addrlen);
|
||||||
|
if (c->saddr == NULL)
|
||||||
|
goto oom;
|
||||||
|
|
||||||
|
memcpy(c->saddr, p->ai_addr, p->ai_addrlen);
|
||||||
|
c->addrlen = p->ai_addrlen;
|
||||||
|
|
||||||
|
if (connect(s,p->ai_addr,p->ai_addrlen) == -1) {
|
||||||
|
if (errno == EHOSTUNREACH) {
|
||||||
|
redisNetClose(c);
|
||||||
|
continue;
|
||||||
|
} else if (errno == EINPROGRESS) {
|
||||||
|
if (blocking) {
|
||||||
|
goto wait_for_ready;
|
||||||
|
}
|
||||||
|
/* This is ok.
|
||||||
|
* Note that even when it's in blocking mode, we unset blocking
|
||||||
|
* for `connect()`
|
||||||
|
*/
|
||||||
|
} else if (errno == EADDRNOTAVAIL && reuseaddr) {
|
||||||
|
if (++reuses >= REDIS_CONNECT_RETRIES) {
|
||||||
|
goto error;
|
||||||
|
} else {
|
||||||
|
redisNetClose(c);
|
||||||
|
goto addrretry;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
wait_for_ready:
|
||||||
|
if (redisContextWaitReady(c,timeout_msec) != REDIS_OK)
|
||||||
|
goto error;
|
||||||
|
if (redisSetTcpNoDelay(c) != REDIS_OK)
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (blocking && redisSetBlocking(c,1) != REDIS_OK)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
c->flags |= REDIS_CONNECTED;
|
||||||
|
rv = REDIS_OK;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
if (p == NULL) {
|
||||||
|
char buf[128];
|
||||||
|
snprintf(buf,sizeof(buf),"Can't create socket: %s",strerror(errno));
|
||||||
|
__redisSetError(c,REDIS_ERR_OTHER,buf);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
oom:
|
||||||
|
__redisSetError(c, REDIS_ERR_OOM, "Out of memory");
|
||||||
|
error:
|
||||||
|
rv = REDIS_ERR;
|
||||||
|
end:
|
||||||
|
if(servinfo) {
|
||||||
|
freeaddrinfo(servinfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rv; // Need to return REDIS_OK if alright
|
||||||
|
}
|
||||||
|
|
||||||
|
int redisContextConnectTcp(redisContext *c, const char *addr, int port,
|
||||||
|
const struct timeval *timeout) {
|
||||||
|
return _redisContextConnectTcp(c, addr, port, timeout, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
int redisContextConnectBindTcp(redisContext *c, const char *addr, int port,
|
||||||
|
const struct timeval *timeout,
|
||||||
|
const char *source_addr) {
|
||||||
|
return _redisContextConnectTcp(c, addr, port, timeout, source_addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) {
|
||||||
|
#ifndef _WIN32
|
||||||
|
int blocking = (c->flags & REDIS_BLOCK);
|
||||||
|
struct sockaddr_un *sa;
|
||||||
|
long timeout_msec = -1;
|
||||||
|
|
||||||
|
if (redisCreateSocket(c,AF_UNIX) < 0)
|
||||||
|
return REDIS_ERR;
|
||||||
|
if (redisSetBlocking(c,0) != REDIS_OK)
|
||||||
|
return REDIS_ERR;
|
||||||
|
|
||||||
|
c->connection_type = REDIS_CONN_UNIX;
|
||||||
|
if (c->unix_sock.path != path) {
|
||||||
|
hi_free(c->unix_sock.path);
|
||||||
|
|
||||||
|
c->unix_sock.path = hi_strdup(path);
|
||||||
|
if (c->unix_sock.path == NULL)
|
||||||
|
goto oom;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeout) {
|
||||||
|
if (redisContextUpdateConnectTimeout(c, timeout) == REDIS_ERR)
|
||||||
|
goto oom;
|
||||||
|
} else {
|
||||||
|
hi_free(c->connect_timeout);
|
||||||
|
c->connect_timeout = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK)
|
||||||
|
return REDIS_ERR;
|
||||||
|
|
||||||
|
/* Don't leak sockaddr if we're reconnecting */
|
||||||
|
if (c->saddr) hi_free(c->saddr);
|
||||||
|
|
||||||
|
sa = (struct sockaddr_un*)(c->saddr = hi_malloc(sizeof(struct sockaddr_un)));
|
||||||
|
if (sa == NULL)
|
||||||
|
goto oom;
|
||||||
|
|
||||||
|
c->addrlen = sizeof(struct sockaddr_un);
|
||||||
|
sa->sun_family = AF_UNIX;
|
||||||
|
strncpy(sa->sun_path, path, sizeof(sa->sun_path) - 1);
|
||||||
|
if (connect(c->fd, (struct sockaddr*)sa, sizeof(*sa)) == -1) {
|
||||||
|
if ((errno == EAGAIN || errno == EINPROGRESS) && !blocking) {
|
||||||
|
/* This is ok. */
|
||||||
|
} else {
|
||||||
|
if (redisContextWaitReady(c,timeout_msec) != REDIS_OK)
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reset socket to be blocking after connect(2). */
|
||||||
|
if (blocking && redisSetBlocking(c,1) != REDIS_OK)
|
||||||
|
return REDIS_ERR;
|
||||||
|
|
||||||
|
c->flags |= REDIS_CONNECTED;
|
||||||
|
return REDIS_OK;
|
||||||
|
#else
|
||||||
|
/* We currently do not support Unix sockets for Windows. */
|
||||||
|
/* TODO(m): https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/ */
|
||||||
|
errno = EPROTONOSUPPORT;
|
||||||
|
return REDIS_ERR;
|
||||||
|
#endif /* _WIN32 */
|
||||||
|
oom:
|
||||||
|
__redisSetError(c, REDIS_ERR_OOM, "Out of memory");
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
/* Extracted from anet.c to work properly with Hiredis error reporting.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||||
|
* Copyright (c) 2010-2014, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||||
|
* Copyright (c) 2015, Matt Stancliff <matt at genges dot com>,
|
||||||
|
* Jan-Erik Rediger <janerik at fnordig dot com>
|
||||||
|
*
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Redis nor the names of its contributors may be used
|
||||||
|
* to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __NET_H
|
||||||
|
#define __NET_H
|
||||||
|
|
||||||
|
#include "hiredis.h"
|
||||||
|
|
||||||
|
void redisNetClose(redisContext *c);
|
||||||
|
ssize_t redisNetRead(redisContext *c, char *buf, size_t bufcap);
|
||||||
|
ssize_t redisNetWrite(redisContext *c);
|
||||||
|
|
||||||
|
int redisCheckSocketError(redisContext *c);
|
||||||
|
int redisContextSetTimeout(redisContext *c, const struct timeval tv);
|
||||||
|
int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout);
|
||||||
|
int redisContextConnectBindTcp(redisContext *c, const char *addr, int port,
|
||||||
|
const struct timeval *timeout,
|
||||||
|
const char *source_addr);
|
||||||
|
int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout);
|
||||||
|
int redisKeepAlive(redisContext *c, int interval);
|
||||||
|
int redisCheckConnectDone(redisContext *c, int *completed);
|
||||||
|
|
||||||
|
int redisSetTcpNoDelay(redisContext *c);
|
||||||
|
int redisContextSetTcpUserTimeout(redisContext *c, unsigned int timeout);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,793 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||||
|
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||||
|
*
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Redis nor the names of its contributors may be used
|
||||||
|
* to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "fmacros.h"
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#ifndef _MSC_VER
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <strings.h>
|
||||||
|
#endif
|
||||||
|
#include <assert.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include "alloc.h"
|
||||||
|
#include "read.h"
|
||||||
|
#include "sds.h"
|
||||||
|
#include "win32.h"
|
||||||
|
|
||||||
|
/* Initial size of our nested reply stack and how much we grow it when needd */
|
||||||
|
#define REDIS_READER_STACK_SIZE 9
|
||||||
|
|
||||||
|
static void __redisReaderSetError(redisReader *r, int type, const char *str) {
|
||||||
|
size_t len;
|
||||||
|
|
||||||
|
if (r->reply != NULL && r->fn && r->fn->freeObject) {
|
||||||
|
r->fn->freeObject(r->reply);
|
||||||
|
r->reply = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Clear input buffer on errors. */
|
||||||
|
sdsfree(r->buf);
|
||||||
|
r->buf = NULL;
|
||||||
|
r->pos = r->len = 0;
|
||||||
|
|
||||||
|
/* Reset task stack. */
|
||||||
|
r->ridx = -1;
|
||||||
|
|
||||||
|
/* Set error. */
|
||||||
|
r->err = type;
|
||||||
|
len = strlen(str);
|
||||||
|
len = len < (sizeof(r->errstr)-1) ? len : (sizeof(r->errstr)-1);
|
||||||
|
memcpy(r->errstr,str,len);
|
||||||
|
r->errstr[len] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t chrtos(char *buf, size_t size, char byte) {
|
||||||
|
size_t len = 0;
|
||||||
|
|
||||||
|
switch(byte) {
|
||||||
|
case '\\':
|
||||||
|
case '"':
|
||||||
|
len = snprintf(buf,size,"\"\\%c\"",byte);
|
||||||
|
break;
|
||||||
|
case '\n': len = snprintf(buf,size,"\"\\n\""); break;
|
||||||
|
case '\r': len = snprintf(buf,size,"\"\\r\""); break;
|
||||||
|
case '\t': len = snprintf(buf,size,"\"\\t\""); break;
|
||||||
|
case '\a': len = snprintf(buf,size,"\"\\a\""); break;
|
||||||
|
case '\b': len = snprintf(buf,size,"\"\\b\""); break;
|
||||||
|
default:
|
||||||
|
if (isprint(byte))
|
||||||
|
len = snprintf(buf,size,"\"%c\"",byte);
|
||||||
|
else
|
||||||
|
len = snprintf(buf,size,"\"\\x%02x\"",(unsigned char)byte);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __redisReaderSetErrorProtocolByte(redisReader *r, char byte) {
|
||||||
|
char cbuf[8], sbuf[128];
|
||||||
|
|
||||||
|
chrtos(cbuf,sizeof(cbuf),byte);
|
||||||
|
snprintf(sbuf,sizeof(sbuf),
|
||||||
|
"Protocol error, got %s as reply type byte", cbuf);
|
||||||
|
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,sbuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __redisReaderSetErrorOOM(redisReader *r) {
|
||||||
|
__redisReaderSetError(r,REDIS_ERR_OOM,"Out of memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *readBytes(redisReader *r, unsigned int bytes) {
|
||||||
|
char *p;
|
||||||
|
if (r->len-r->pos >= bytes) {
|
||||||
|
p = r->buf+r->pos;
|
||||||
|
r->pos += bytes;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Find pointer to \r\n. */
|
||||||
|
static char *seekNewline(char *s, size_t len) {
|
||||||
|
char *ret;
|
||||||
|
|
||||||
|
/* We cannot match with fewer than 2 bytes */
|
||||||
|
if (len < 2)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
/* Search up to len - 1 characters */
|
||||||
|
len--;
|
||||||
|
|
||||||
|
/* Look for the \r */
|
||||||
|
while ((ret = memchr(s, '\r', len)) != NULL) {
|
||||||
|
if (ret[1] == '\n') {
|
||||||
|
/* Found. */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* Continue searching. */
|
||||||
|
ret++;
|
||||||
|
len -= ret - s;
|
||||||
|
s = ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Convert a string into a long long. Returns REDIS_OK if the string could be
|
||||||
|
* parsed into a (non-overflowing) long long, REDIS_ERR otherwise. The value
|
||||||
|
* will be set to the parsed value when appropriate.
|
||||||
|
*
|
||||||
|
* Note that this function demands that the string strictly represents
|
||||||
|
* a long long: no spaces or other characters before or after the string
|
||||||
|
* representing the number are accepted, nor zeroes at the start if not
|
||||||
|
* for the string "0" representing the zero number.
|
||||||
|
*
|
||||||
|
* Because of its strictness, it is safe to use this function to check if
|
||||||
|
* you can convert a string into a long long, and obtain back the string
|
||||||
|
* from the number without any loss in the string representation. */
|
||||||
|
static int string2ll(const char *s, size_t slen, long long *value) {
|
||||||
|
const char *p = s;
|
||||||
|
size_t plen = 0;
|
||||||
|
int negative = 0;
|
||||||
|
unsigned long long v;
|
||||||
|
|
||||||
|
if (plen == slen)
|
||||||
|
return REDIS_ERR;
|
||||||
|
|
||||||
|
/* Special case: first and only digit is 0. */
|
||||||
|
if (slen == 1 && p[0] == '0') {
|
||||||
|
if (value != NULL) *value = 0;
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p[0] == '-') {
|
||||||
|
negative = 1;
|
||||||
|
p++; plen++;
|
||||||
|
|
||||||
|
/* Abort on only a negative sign. */
|
||||||
|
if (plen == slen)
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* First digit should be 1-9, otherwise the string should just be 0. */
|
||||||
|
if (p[0] >= '1' && p[0] <= '9') {
|
||||||
|
v = p[0]-'0';
|
||||||
|
p++; plen++;
|
||||||
|
} else if (p[0] == '0' && slen == 1) {
|
||||||
|
*value = 0;
|
||||||
|
return REDIS_OK;
|
||||||
|
} else {
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (plen < slen && p[0] >= '0' && p[0] <= '9') {
|
||||||
|
if (v > (ULLONG_MAX / 10)) /* Overflow. */
|
||||||
|
return REDIS_ERR;
|
||||||
|
v *= 10;
|
||||||
|
|
||||||
|
if (v > (ULLONG_MAX - (p[0]-'0'))) /* Overflow. */
|
||||||
|
return REDIS_ERR;
|
||||||
|
v += p[0]-'0';
|
||||||
|
|
||||||
|
p++; plen++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return if not all bytes were used. */
|
||||||
|
if (plen < slen)
|
||||||
|
return REDIS_ERR;
|
||||||
|
|
||||||
|
if (negative) {
|
||||||
|
if (v > ((unsigned long long)(-(LLONG_MIN+1))+1)) /* Overflow. */
|
||||||
|
return REDIS_ERR;
|
||||||
|
if (value != NULL) *value = -v;
|
||||||
|
} else {
|
||||||
|
if (v > LLONG_MAX) /* Overflow. */
|
||||||
|
return REDIS_ERR;
|
||||||
|
if (value != NULL) *value = v;
|
||||||
|
}
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *readLine(redisReader *r, int *_len) {
|
||||||
|
char *p, *s;
|
||||||
|
int len;
|
||||||
|
|
||||||
|
p = r->buf+r->pos;
|
||||||
|
s = seekNewline(p,(r->len-r->pos));
|
||||||
|
if (s != NULL) {
|
||||||
|
len = s-(r->buf+r->pos);
|
||||||
|
r->pos += len+2; /* skip \r\n */
|
||||||
|
if (_len) *_len = len;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void moveToNextTask(redisReader *r) {
|
||||||
|
redisReadTask *cur, *prv;
|
||||||
|
while (r->ridx >= 0) {
|
||||||
|
/* Return a.s.a.p. when the stack is now empty. */
|
||||||
|
if (r->ridx == 0) {
|
||||||
|
r->ridx--;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cur = r->task[r->ridx];
|
||||||
|
prv = r->task[r->ridx-1];
|
||||||
|
assert(prv->type == REDIS_REPLY_ARRAY ||
|
||||||
|
prv->type == REDIS_REPLY_MAP ||
|
||||||
|
prv->type == REDIS_REPLY_ATTR ||
|
||||||
|
prv->type == REDIS_REPLY_SET ||
|
||||||
|
prv->type == REDIS_REPLY_PUSH);
|
||||||
|
if (cur->idx == prv->elements-1) {
|
||||||
|
r->ridx--;
|
||||||
|
} else {
|
||||||
|
/* Reset the type because the next item can be anything */
|
||||||
|
assert(cur->idx < prv->elements);
|
||||||
|
cur->type = -1;
|
||||||
|
cur->elements = -1;
|
||||||
|
cur->idx++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int processLineItem(redisReader *r) {
|
||||||
|
redisReadTask *cur = r->task[r->ridx];
|
||||||
|
void *obj;
|
||||||
|
char *p;
|
||||||
|
int len;
|
||||||
|
|
||||||
|
if ((p = readLine(r,&len)) != NULL) {
|
||||||
|
if (cur->type == REDIS_REPLY_INTEGER) {
|
||||||
|
long long v;
|
||||||
|
|
||||||
|
if (string2ll(p, len, &v) == REDIS_ERR) {
|
||||||
|
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||||
|
"Bad integer value");
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r->fn && r->fn->createInteger) {
|
||||||
|
obj = r->fn->createInteger(cur,v);
|
||||||
|
} else {
|
||||||
|
obj = (void*)REDIS_REPLY_INTEGER;
|
||||||
|
}
|
||||||
|
} else if (cur->type == REDIS_REPLY_DOUBLE) {
|
||||||
|
char buf[326], *eptr;
|
||||||
|
double d;
|
||||||
|
|
||||||
|
if ((size_t)len >= sizeof(buf)) {
|
||||||
|
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||||
|
"Double value is too large");
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(buf,p,len);
|
||||||
|
buf[len] = '\0';
|
||||||
|
|
||||||
|
if (len == 3 && strcasecmp(buf,"inf") == 0) {
|
||||||
|
d = INFINITY; /* Positive infinite. */
|
||||||
|
} else if (len == 4 && strcasecmp(buf,"-inf") == 0) {
|
||||||
|
d = -INFINITY; /* Negative infinite. */
|
||||||
|
} else if ((len == 3 && strcasecmp(buf,"nan") == 0) ||
|
||||||
|
(len == 4 && strcasecmp(buf, "-nan") == 0)) {
|
||||||
|
d = NAN; /* nan. */
|
||||||
|
} else {
|
||||||
|
d = strtod((char*)buf,&eptr);
|
||||||
|
/* RESP3 only allows "inf", "-inf", and finite values, while
|
||||||
|
* strtod() allows other variations on infinity,
|
||||||
|
* etc. We explicity handle our two allowed infinite cases and NaN
|
||||||
|
* above, so strtod() should only result in finite values. */
|
||||||
|
if (buf[0] == '\0' || eptr != &buf[len] || !isfinite(d)) {
|
||||||
|
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||||
|
"Bad double value");
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r->fn && r->fn->createDouble) {
|
||||||
|
obj = r->fn->createDouble(cur,d,buf,len);
|
||||||
|
} else {
|
||||||
|
obj = (void*)REDIS_REPLY_DOUBLE;
|
||||||
|
}
|
||||||
|
} else if (cur->type == REDIS_REPLY_NIL) {
|
||||||
|
if (len != 0) {
|
||||||
|
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||||
|
"Bad nil value");
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r->fn && r->fn->createNil)
|
||||||
|
obj = r->fn->createNil(cur);
|
||||||
|
else
|
||||||
|
obj = (void*)REDIS_REPLY_NIL;
|
||||||
|
} else if (cur->type == REDIS_REPLY_BOOL) {
|
||||||
|
int bval;
|
||||||
|
|
||||||
|
if (len != 1 || !strchr("tTfF", p[0])) {
|
||||||
|
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||||
|
"Bad bool value");
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
bval = p[0] == 't' || p[0] == 'T';
|
||||||
|
if (r->fn && r->fn->createBool)
|
||||||
|
obj = r->fn->createBool(cur,bval);
|
||||||
|
else
|
||||||
|
obj = (void*)REDIS_REPLY_BOOL;
|
||||||
|
} else if (cur->type == REDIS_REPLY_BIGNUM) {
|
||||||
|
/* Ensure all characters are decimal digits (with possible leading
|
||||||
|
* minus sign). */
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
/* XXX Consider: Allow leading '+'? Error on leading '0's? */
|
||||||
|
if (i == 0 && p[0] == '-') continue;
|
||||||
|
if (p[i] < '0' || p[i] > '9') {
|
||||||
|
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||||
|
"Bad bignum value");
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (r->fn && r->fn->createString)
|
||||||
|
obj = r->fn->createString(cur,p,len);
|
||||||
|
else
|
||||||
|
obj = (void*)REDIS_REPLY_BIGNUM;
|
||||||
|
} else {
|
||||||
|
/* Type will be error or status. */
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
if (p[i] == '\r' || p[i] == '\n') {
|
||||||
|
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||||
|
"Bad simple string value");
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (r->fn && r->fn->createString)
|
||||||
|
obj = r->fn->createString(cur,p,len);
|
||||||
|
else
|
||||||
|
obj = (void*)(uintptr_t)(cur->type);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj == NULL) {
|
||||||
|
__redisReaderSetErrorOOM(r);
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set reply if this is the root object. */
|
||||||
|
if (r->ridx == 0) r->reply = obj;
|
||||||
|
moveToNextTask(r);
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int processBulkItem(redisReader *r) {
|
||||||
|
redisReadTask *cur = r->task[r->ridx];
|
||||||
|
void *obj = NULL;
|
||||||
|
char *p, *s;
|
||||||
|
long long len;
|
||||||
|
unsigned long bytelen;
|
||||||
|
int success = 0;
|
||||||
|
|
||||||
|
p = r->buf+r->pos;
|
||||||
|
s = seekNewline(p,r->len-r->pos);
|
||||||
|
if (s != NULL) {
|
||||||
|
p = r->buf+r->pos;
|
||||||
|
bytelen = s-(r->buf+r->pos)+2; /* include \r\n */
|
||||||
|
|
||||||
|
if (string2ll(p, bytelen - 2, &len) == REDIS_ERR) {
|
||||||
|
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||||
|
"Bad bulk string length");
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len < -1 || (LLONG_MAX > SIZE_MAX && len > (long long)SIZE_MAX)) {
|
||||||
|
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||||
|
"Bulk string length out of range");
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len == -1) {
|
||||||
|
/* The nil object can always be created. */
|
||||||
|
if (r->fn && r->fn->createNil)
|
||||||
|
obj = r->fn->createNil(cur);
|
||||||
|
else
|
||||||
|
obj = (void*)REDIS_REPLY_NIL;
|
||||||
|
success = 1;
|
||||||
|
} else {
|
||||||
|
/* Only continue when the buffer contains the entire bulk item. */
|
||||||
|
bytelen += len+2; /* include \r\n */
|
||||||
|
if (r->pos+bytelen <= r->len) {
|
||||||
|
if ((cur->type == REDIS_REPLY_VERB && len < 4) ||
|
||||||
|
(cur->type == REDIS_REPLY_VERB && s[5] != ':'))
|
||||||
|
{
|
||||||
|
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||||
|
"Verbatim string 4 bytes of content type are "
|
||||||
|
"missing or incorrectly encoded.");
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
if (r->fn && r->fn->createString)
|
||||||
|
obj = r->fn->createString(cur,s+2,len);
|
||||||
|
else
|
||||||
|
obj = (void*)(uintptr_t)cur->type;
|
||||||
|
success = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Proceed when obj was created. */
|
||||||
|
if (success) {
|
||||||
|
if (obj == NULL) {
|
||||||
|
__redisReaderSetErrorOOM(r);
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
r->pos += bytelen;
|
||||||
|
|
||||||
|
/* Set reply if this is the root object. */
|
||||||
|
if (r->ridx == 0) r->reply = obj;
|
||||||
|
moveToNextTask(r);
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int redisReaderGrow(redisReader *r) {
|
||||||
|
redisReadTask **aux;
|
||||||
|
int newlen;
|
||||||
|
|
||||||
|
/* Grow our stack size */
|
||||||
|
newlen = r->tasks + REDIS_READER_STACK_SIZE;
|
||||||
|
aux = hi_realloc(r->task, sizeof(*r->task) * newlen);
|
||||||
|
if (aux == NULL)
|
||||||
|
goto oom;
|
||||||
|
|
||||||
|
r->task = aux;
|
||||||
|
|
||||||
|
/* Allocate new tasks */
|
||||||
|
for (; r->tasks < newlen; r->tasks++) {
|
||||||
|
r->task[r->tasks] = hi_calloc(1, sizeof(**r->task));
|
||||||
|
if (r->task[r->tasks] == NULL)
|
||||||
|
goto oom;
|
||||||
|
}
|
||||||
|
|
||||||
|
return REDIS_OK;
|
||||||
|
oom:
|
||||||
|
__redisReaderSetErrorOOM(r);
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Process the array, map and set types. */
|
||||||
|
static int processAggregateItem(redisReader *r) {
|
||||||
|
redisReadTask *cur = r->task[r->ridx];
|
||||||
|
void *obj;
|
||||||
|
char *p;
|
||||||
|
long long elements;
|
||||||
|
int root = 0, len;
|
||||||
|
|
||||||
|
if (r->ridx == r->tasks - 1) {
|
||||||
|
if (redisReaderGrow(r) == REDIS_ERR)
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((p = readLine(r,&len)) != NULL) {
|
||||||
|
if (string2ll(p, len, &elements) == REDIS_ERR) {
|
||||||
|
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||||
|
"Bad multi-bulk length");
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
root = (r->ridx == 0);
|
||||||
|
|
||||||
|
if (elements < -1 || (LLONG_MAX > SIZE_MAX && elements > SIZE_MAX) ||
|
||||||
|
(r->maxelements > 0 && elements > r->maxelements))
|
||||||
|
{
|
||||||
|
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||||
|
"Multi-bulk length out of range");
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elements == -1) {
|
||||||
|
if (r->fn && r->fn->createNil)
|
||||||
|
obj = r->fn->createNil(cur);
|
||||||
|
else
|
||||||
|
obj = (void*)REDIS_REPLY_NIL;
|
||||||
|
|
||||||
|
if (obj == NULL) {
|
||||||
|
__redisReaderSetErrorOOM(r);
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
moveToNextTask(r);
|
||||||
|
} else {
|
||||||
|
if (cur->type == REDIS_REPLY_MAP || cur->type == REDIS_REPLY_ATTR) elements *= 2;
|
||||||
|
|
||||||
|
if (r->fn && r->fn->createArray)
|
||||||
|
obj = r->fn->createArray(cur,elements);
|
||||||
|
else
|
||||||
|
obj = (void*)(uintptr_t)cur->type;
|
||||||
|
|
||||||
|
if (obj == NULL) {
|
||||||
|
__redisReaderSetErrorOOM(r);
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modify task stack when there are more than 0 elements. */
|
||||||
|
if (elements > 0) {
|
||||||
|
cur->elements = elements;
|
||||||
|
cur->obj = obj;
|
||||||
|
r->ridx++;
|
||||||
|
r->task[r->ridx]->type = -1;
|
||||||
|
r->task[r->ridx]->elements = -1;
|
||||||
|
r->task[r->ridx]->idx = 0;
|
||||||
|
r->task[r->ridx]->obj = NULL;
|
||||||
|
r->task[r->ridx]->parent = cur;
|
||||||
|
r->task[r->ridx]->privdata = r->privdata;
|
||||||
|
} else {
|
||||||
|
moveToNextTask(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set reply if this is the root object. */
|
||||||
|
if (root) r->reply = obj;
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int processItem(redisReader *r) {
|
||||||
|
redisReadTask *cur = r->task[r->ridx];
|
||||||
|
char *p;
|
||||||
|
|
||||||
|
/* check if we need to read type */
|
||||||
|
if (cur->type < 0) {
|
||||||
|
if ((p = readBytes(r,1)) != NULL) {
|
||||||
|
switch (p[0]) {
|
||||||
|
case '-':
|
||||||
|
cur->type = REDIS_REPLY_ERROR;
|
||||||
|
break;
|
||||||
|
case '+':
|
||||||
|
cur->type = REDIS_REPLY_STATUS;
|
||||||
|
break;
|
||||||
|
case ':':
|
||||||
|
cur->type = REDIS_REPLY_INTEGER;
|
||||||
|
break;
|
||||||
|
case ',':
|
||||||
|
cur->type = REDIS_REPLY_DOUBLE;
|
||||||
|
break;
|
||||||
|
case '_':
|
||||||
|
cur->type = REDIS_REPLY_NIL;
|
||||||
|
break;
|
||||||
|
case '$':
|
||||||
|
cur->type = REDIS_REPLY_STRING;
|
||||||
|
break;
|
||||||
|
case '*':
|
||||||
|
cur->type = REDIS_REPLY_ARRAY;
|
||||||
|
break;
|
||||||
|
case '%':
|
||||||
|
cur->type = REDIS_REPLY_MAP;
|
||||||
|
break;
|
||||||
|
case '|':
|
||||||
|
cur->type = REDIS_REPLY_ATTR;
|
||||||
|
break;
|
||||||
|
case '~':
|
||||||
|
cur->type = REDIS_REPLY_SET;
|
||||||
|
break;
|
||||||
|
case '#':
|
||||||
|
cur->type = REDIS_REPLY_BOOL;
|
||||||
|
break;
|
||||||
|
case '=':
|
||||||
|
cur->type = REDIS_REPLY_VERB;
|
||||||
|
break;
|
||||||
|
case '>':
|
||||||
|
cur->type = REDIS_REPLY_PUSH;
|
||||||
|
break;
|
||||||
|
case '(':
|
||||||
|
cur->type = REDIS_REPLY_BIGNUM;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
__redisReaderSetErrorProtocolByte(r,*p);
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* could not consume 1 byte */
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* process typed item */
|
||||||
|
switch(cur->type) {
|
||||||
|
case REDIS_REPLY_ERROR:
|
||||||
|
case REDIS_REPLY_STATUS:
|
||||||
|
case REDIS_REPLY_INTEGER:
|
||||||
|
case REDIS_REPLY_DOUBLE:
|
||||||
|
case REDIS_REPLY_NIL:
|
||||||
|
case REDIS_REPLY_BOOL:
|
||||||
|
case REDIS_REPLY_BIGNUM:
|
||||||
|
return processLineItem(r);
|
||||||
|
case REDIS_REPLY_STRING:
|
||||||
|
case REDIS_REPLY_VERB:
|
||||||
|
return processBulkItem(r);
|
||||||
|
case REDIS_REPLY_ARRAY:
|
||||||
|
case REDIS_REPLY_MAP:
|
||||||
|
case REDIS_REPLY_ATTR:
|
||||||
|
case REDIS_REPLY_SET:
|
||||||
|
case REDIS_REPLY_PUSH:
|
||||||
|
return processAggregateItem(r);
|
||||||
|
default:
|
||||||
|
assert(NULL);
|
||||||
|
return REDIS_ERR; /* Avoid warning. */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) {
|
||||||
|
redisReader *r;
|
||||||
|
|
||||||
|
r = hi_calloc(1,sizeof(redisReader));
|
||||||
|
if (r == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
r->buf = sdsempty();
|
||||||
|
if (r->buf == NULL)
|
||||||
|
goto oom;
|
||||||
|
|
||||||
|
r->task = hi_calloc(REDIS_READER_STACK_SIZE, sizeof(*r->task));
|
||||||
|
if (r->task == NULL)
|
||||||
|
goto oom;
|
||||||
|
|
||||||
|
for (; r->tasks < REDIS_READER_STACK_SIZE; r->tasks++) {
|
||||||
|
r->task[r->tasks] = hi_calloc(1, sizeof(**r->task));
|
||||||
|
if (r->task[r->tasks] == NULL)
|
||||||
|
goto oom;
|
||||||
|
}
|
||||||
|
|
||||||
|
r->fn = fn;
|
||||||
|
r->maxbuf = REDIS_READER_MAX_BUF;
|
||||||
|
r->maxelements = REDIS_READER_MAX_ARRAY_ELEMENTS;
|
||||||
|
r->ridx = -1;
|
||||||
|
|
||||||
|
return r;
|
||||||
|
oom:
|
||||||
|
redisReaderFree(r);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void redisReaderFree(redisReader *r) {
|
||||||
|
if (r == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (r->reply != NULL && r->fn && r->fn->freeObject)
|
||||||
|
r->fn->freeObject(r->reply);
|
||||||
|
|
||||||
|
if (r->task) {
|
||||||
|
/* We know r->task[i] is allocated if i < r->tasks */
|
||||||
|
for (int i = 0; i < r->tasks; i++) {
|
||||||
|
hi_free(r->task[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
hi_free(r->task);
|
||||||
|
}
|
||||||
|
|
||||||
|
sdsfree(r->buf);
|
||||||
|
hi_free(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
int redisReaderFeed(redisReader *r, const char *buf, size_t len) {
|
||||||
|
sds newbuf;
|
||||||
|
|
||||||
|
/* Return early when this reader is in an erroneous state. */
|
||||||
|
if (r->err)
|
||||||
|
return REDIS_ERR;
|
||||||
|
|
||||||
|
/* Copy the provided buffer. */
|
||||||
|
if (buf != NULL && len >= 1) {
|
||||||
|
/* Destroy internal buffer when it is empty and is quite large. */
|
||||||
|
if (r->len == 0 && r->maxbuf != 0 && sdsavail(r->buf) > r->maxbuf) {
|
||||||
|
sdsfree(r->buf);
|
||||||
|
r->buf = sdsempty();
|
||||||
|
if (r->buf == 0) goto oom;
|
||||||
|
|
||||||
|
r->pos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
newbuf = sdscatlen(r->buf,buf,len);
|
||||||
|
if (newbuf == NULL) goto oom;
|
||||||
|
|
||||||
|
r->buf = newbuf;
|
||||||
|
r->len = sdslen(r->buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
return REDIS_OK;
|
||||||
|
oom:
|
||||||
|
__redisReaderSetErrorOOM(r);
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
int redisReaderGetReply(redisReader *r, void **reply) {
|
||||||
|
/* Default target pointer to NULL. */
|
||||||
|
if (reply != NULL)
|
||||||
|
*reply = NULL;
|
||||||
|
|
||||||
|
/* Return early when this reader is in an erroneous state. */
|
||||||
|
if (r->err)
|
||||||
|
return REDIS_ERR;
|
||||||
|
|
||||||
|
/* When the buffer is empty, there will never be a reply. */
|
||||||
|
if (r->len == 0)
|
||||||
|
return REDIS_OK;
|
||||||
|
|
||||||
|
/* Set first item to process when the stack is empty. */
|
||||||
|
if (r->ridx == -1) {
|
||||||
|
r->task[0]->type = -1;
|
||||||
|
r->task[0]->elements = -1;
|
||||||
|
r->task[0]->idx = -1;
|
||||||
|
r->task[0]->obj = NULL;
|
||||||
|
r->task[0]->parent = NULL;
|
||||||
|
r->task[0]->privdata = r->privdata;
|
||||||
|
r->ridx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Process items in reply. */
|
||||||
|
while (r->ridx >= 0)
|
||||||
|
if (processItem(r) != REDIS_OK)
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* Return ASAP when an error occurred. */
|
||||||
|
if (r->err)
|
||||||
|
return REDIS_ERR;
|
||||||
|
|
||||||
|
/* Discard part of the buffer when we've consumed at least 1k, to avoid
|
||||||
|
* doing unnecessary calls to memmove() in sds.c. */
|
||||||
|
if (r->pos >= 1024) {
|
||||||
|
if (sdsrange(r->buf,r->pos,-1) < 0) return REDIS_ERR;
|
||||||
|
r->pos = 0;
|
||||||
|
r->len = sdslen(r->buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Emit a reply when there is one. */
|
||||||
|
if (r->ridx == -1) {
|
||||||
|
if (reply != NULL) {
|
||||||
|
*reply = r->reply;
|
||||||
|
} else if (r->reply != NULL && r->fn && r->fn->freeObject) {
|
||||||
|
r->fn->freeObject(r->reply);
|
||||||
|
}
|
||||||
|
r->reply = NULL;
|
||||||
|
}
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||||
|
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||||
|
*
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Redis nor the names of its contributors may be used
|
||||||
|
* to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef __HIREDIS_READ_H
|
||||||
|
#define __HIREDIS_READ_H
|
||||||
|
#include <stdio.h> /* for size_t */
|
||||||
|
|
||||||
|
#define REDIS_ERR -1
|
||||||
|
#define REDIS_OK 0
|
||||||
|
|
||||||
|
/* When an error occurs, the err flag in a context is set to hold the type of
|
||||||
|
* error that occurred. REDIS_ERR_IO means there was an I/O error and you
|
||||||
|
* should use the "errno" variable to find out what is wrong.
|
||||||
|
* For other values, the "errstr" field will hold a description. */
|
||||||
|
#define REDIS_ERR_IO 1 /* Error in read or write */
|
||||||
|
#define REDIS_ERR_EOF 3 /* End of file */
|
||||||
|
#define REDIS_ERR_PROTOCOL 4 /* Protocol error */
|
||||||
|
#define REDIS_ERR_OOM 5 /* Out of memory */
|
||||||
|
#define REDIS_ERR_TIMEOUT 6 /* Timed out */
|
||||||
|
#define REDIS_ERR_OTHER 2 /* Everything else... */
|
||||||
|
|
||||||
|
#define REDIS_REPLY_STRING 1
|
||||||
|
#define REDIS_REPLY_ARRAY 2
|
||||||
|
#define REDIS_REPLY_INTEGER 3
|
||||||
|
#define REDIS_REPLY_NIL 4
|
||||||
|
#define REDIS_REPLY_STATUS 5
|
||||||
|
#define REDIS_REPLY_ERROR 6
|
||||||
|
#define REDIS_REPLY_DOUBLE 7
|
||||||
|
#define REDIS_REPLY_BOOL 8
|
||||||
|
#define REDIS_REPLY_MAP 9
|
||||||
|
#define REDIS_REPLY_SET 10
|
||||||
|
#define REDIS_REPLY_ATTR 11
|
||||||
|
#define REDIS_REPLY_PUSH 12
|
||||||
|
#define REDIS_REPLY_BIGNUM 13
|
||||||
|
#define REDIS_REPLY_VERB 14
|
||||||
|
|
||||||
|
/* Default max unused reader buffer. */
|
||||||
|
#define REDIS_READER_MAX_BUF (1024*16)
|
||||||
|
|
||||||
|
/* Default multi-bulk element limit */
|
||||||
|
#define REDIS_READER_MAX_ARRAY_ELEMENTS ((1LL<<32) - 1)
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct redisReadTask {
|
||||||
|
int type;
|
||||||
|
long long elements; /* number of elements in multibulk container */
|
||||||
|
int idx; /* index in parent (array) object */
|
||||||
|
void *obj; /* holds user-generated value for a read task */
|
||||||
|
struct redisReadTask *parent; /* parent task */
|
||||||
|
void *privdata; /* user-settable arbitrary field */
|
||||||
|
} redisReadTask;
|
||||||
|
|
||||||
|
typedef struct redisReplyObjectFunctions {
|
||||||
|
void *(*createString)(const redisReadTask*, char*, size_t);
|
||||||
|
void *(*createArray)(const redisReadTask*, size_t);
|
||||||
|
void *(*createInteger)(const redisReadTask*, long long);
|
||||||
|
void *(*createDouble)(const redisReadTask*, double, char*, size_t);
|
||||||
|
void *(*createNil)(const redisReadTask*);
|
||||||
|
void *(*createBool)(const redisReadTask*, int);
|
||||||
|
void (*freeObject)(void*);
|
||||||
|
} redisReplyObjectFunctions;
|
||||||
|
|
||||||
|
typedef struct redisReader {
|
||||||
|
int err; /* Error flags, 0 when there is no error */
|
||||||
|
char errstr[128]; /* String representation of error when applicable */
|
||||||
|
|
||||||
|
char *buf; /* Read buffer */
|
||||||
|
size_t pos; /* Buffer cursor */
|
||||||
|
size_t len; /* Buffer length */
|
||||||
|
size_t maxbuf; /* Max length of unused buffer */
|
||||||
|
long long maxelements; /* Max multi-bulk elements */
|
||||||
|
|
||||||
|
redisReadTask **task;
|
||||||
|
int tasks;
|
||||||
|
|
||||||
|
int ridx; /* Index of current read task */
|
||||||
|
void *reply; /* Temporary reply pointer */
|
||||||
|
|
||||||
|
redisReplyObjectFunctions *fn;
|
||||||
|
void *privdata;
|
||||||
|
} redisReader;
|
||||||
|
|
||||||
|
/* Public API for the protocol parser. */
|
||||||
|
redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn);
|
||||||
|
void redisReaderFree(redisReader *r);
|
||||||
|
int redisReaderFeed(redisReader *r, const char *buf, size_t len);
|
||||||
|
int redisReaderGetReply(redisReader *r, void **reply);
|
||||||
|
|
||||||
|
#define redisReaderSetPrivdata(_r, _p) (int)(((redisReader*)(_r))->privdata = (_p))
|
||||||
|
#define redisReaderGetObject(_r) (((redisReader*)(_r))->reply)
|
||||||
|
#define redisReaderGetError(_r) (((redisReader*)(_r))->errstr)
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,280 @@
|
|||||||
|
/* SDSLib 2.0 -- A C dynamic strings library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2006-2015, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||||
|
* Copyright (c) 2015, Oran Agra
|
||||||
|
* Copyright (c) 2015, Redis Labs, Inc
|
||||||
|
* 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 __SDS_H
|
||||||
|
#define __SDS_H
|
||||||
|
|
||||||
|
#define SDS_MAX_PREALLOC (1024*1024)
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
typedef intptr_t ssize_t;
|
||||||
|
#define SSIZE_MAX INTPTR_MAX
|
||||||
|
#ifndef __clang__
|
||||||
|
#define __attribute__(x)
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
typedef char *sds;
|
||||||
|
|
||||||
|
/* Note: sdshdr5 is never used, we just access the flags byte directly.
|
||||||
|
* However is here to document the layout of type 5 SDS strings. */
|
||||||
|
struct __attribute__ ((__packed__)) sdshdr5 {
|
||||||
|
unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
|
||||||
|
char buf[];
|
||||||
|
};
|
||||||
|
struct __attribute__ ((__packed__)) sdshdr8 {
|
||||||
|
uint8_t len; /* used */
|
||||||
|
uint8_t alloc; /* excluding the header and null terminator */
|
||||||
|
unsigned char flags; /* 3 lsb of type, 5 unused bits */
|
||||||
|
char buf[];
|
||||||
|
};
|
||||||
|
struct __attribute__ ((__packed__)) sdshdr16 {
|
||||||
|
uint16_t len; /* used */
|
||||||
|
uint16_t alloc; /* excluding the header and null terminator */
|
||||||
|
unsigned char flags; /* 3 lsb of type, 5 unused bits */
|
||||||
|
char buf[];
|
||||||
|
};
|
||||||
|
struct __attribute__ ((__packed__)) sdshdr32 {
|
||||||
|
uint32_t len; /* used */
|
||||||
|
uint32_t alloc; /* excluding the header and null terminator */
|
||||||
|
unsigned char flags; /* 3 lsb of type, 5 unused bits */
|
||||||
|
char buf[];
|
||||||
|
};
|
||||||
|
struct __attribute__ ((__packed__)) sdshdr64 {
|
||||||
|
uint64_t len; /* used */
|
||||||
|
uint64_t alloc; /* excluding the header and null terminator */
|
||||||
|
unsigned char flags; /* 3 lsb of type, 5 unused bits */
|
||||||
|
char buf[];
|
||||||
|
};
|
||||||
|
|
||||||
|
#define SDS_TYPE_5 0
|
||||||
|
#define SDS_TYPE_8 1
|
||||||
|
#define SDS_TYPE_16 2
|
||||||
|
#define SDS_TYPE_32 3
|
||||||
|
#define SDS_TYPE_64 4
|
||||||
|
#define SDS_TYPE_MASK 7
|
||||||
|
#define SDS_TYPE_BITS 3
|
||||||
|
#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T)));
|
||||||
|
#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))
|
||||||
|
#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)
|
||||||
|
|
||||||
|
static inline size_t sdslen(const sds s) {
|
||||||
|
unsigned char flags = s[-1];
|
||||||
|
switch(flags&SDS_TYPE_MASK) {
|
||||||
|
case SDS_TYPE_5:
|
||||||
|
return SDS_TYPE_5_LEN(flags);
|
||||||
|
case SDS_TYPE_8:
|
||||||
|
return SDS_HDR(8,s)->len;
|
||||||
|
case SDS_TYPE_16:
|
||||||
|
return SDS_HDR(16,s)->len;
|
||||||
|
case SDS_TYPE_32:
|
||||||
|
return SDS_HDR(32,s)->len;
|
||||||
|
case SDS_TYPE_64:
|
||||||
|
return SDS_HDR(64,s)->len;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline size_t sdsavail(const sds s) {
|
||||||
|
unsigned char flags = s[-1];
|
||||||
|
switch(flags&SDS_TYPE_MASK) {
|
||||||
|
case SDS_TYPE_5: {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
case SDS_TYPE_8: {
|
||||||
|
SDS_HDR_VAR(8,s);
|
||||||
|
return sh->alloc - sh->len;
|
||||||
|
}
|
||||||
|
case SDS_TYPE_16: {
|
||||||
|
SDS_HDR_VAR(16,s);
|
||||||
|
return sh->alloc - sh->len;
|
||||||
|
}
|
||||||
|
case SDS_TYPE_32: {
|
||||||
|
SDS_HDR_VAR(32,s);
|
||||||
|
return sh->alloc - sh->len;
|
||||||
|
}
|
||||||
|
case SDS_TYPE_64: {
|
||||||
|
SDS_HDR_VAR(64,s);
|
||||||
|
return sh->alloc - sh->len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void sdssetlen(sds s, size_t newlen) {
|
||||||
|
unsigned char flags = s[-1];
|
||||||
|
switch(flags&SDS_TYPE_MASK) {
|
||||||
|
case SDS_TYPE_5:
|
||||||
|
{
|
||||||
|
unsigned char *fp = ((unsigned char*)s)-1;
|
||||||
|
*fp = (unsigned char)(SDS_TYPE_5 | (newlen << SDS_TYPE_BITS));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SDS_TYPE_8:
|
||||||
|
SDS_HDR(8,s)->len = (uint8_t)newlen;
|
||||||
|
break;
|
||||||
|
case SDS_TYPE_16:
|
||||||
|
SDS_HDR(16,s)->len = (uint16_t)newlen;
|
||||||
|
break;
|
||||||
|
case SDS_TYPE_32:
|
||||||
|
SDS_HDR(32,s)->len = (uint32_t)newlen;
|
||||||
|
break;
|
||||||
|
case SDS_TYPE_64:
|
||||||
|
SDS_HDR(64,s)->len = (uint64_t)newlen;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void sdsinclen(sds s, size_t inc) {
|
||||||
|
unsigned char flags = s[-1];
|
||||||
|
switch(flags&SDS_TYPE_MASK) {
|
||||||
|
case SDS_TYPE_5:
|
||||||
|
{
|
||||||
|
unsigned char *fp = ((unsigned char*)s)-1;
|
||||||
|
unsigned char newlen = SDS_TYPE_5_LEN(flags)+(unsigned char)inc;
|
||||||
|
*fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SDS_TYPE_8:
|
||||||
|
SDS_HDR(8,s)->len += (uint8_t)inc;
|
||||||
|
break;
|
||||||
|
case SDS_TYPE_16:
|
||||||
|
SDS_HDR(16,s)->len += (uint16_t)inc;
|
||||||
|
break;
|
||||||
|
case SDS_TYPE_32:
|
||||||
|
SDS_HDR(32,s)->len += (uint32_t)inc;
|
||||||
|
break;
|
||||||
|
case SDS_TYPE_64:
|
||||||
|
SDS_HDR(64,s)->len += (uint64_t)inc;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* sdsalloc() = sdsavail() + sdslen() */
|
||||||
|
static inline size_t sdsalloc(const sds s) {
|
||||||
|
unsigned char flags = s[-1];
|
||||||
|
switch(flags&SDS_TYPE_MASK) {
|
||||||
|
case SDS_TYPE_5:
|
||||||
|
return SDS_TYPE_5_LEN(flags);
|
||||||
|
case SDS_TYPE_8:
|
||||||
|
return SDS_HDR(8,s)->alloc;
|
||||||
|
case SDS_TYPE_16:
|
||||||
|
return SDS_HDR(16,s)->alloc;
|
||||||
|
case SDS_TYPE_32:
|
||||||
|
return SDS_HDR(32,s)->alloc;
|
||||||
|
case SDS_TYPE_64:
|
||||||
|
return SDS_HDR(64,s)->alloc;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void sdssetalloc(sds s, size_t newlen) {
|
||||||
|
unsigned char flags = s[-1];
|
||||||
|
switch(flags&SDS_TYPE_MASK) {
|
||||||
|
case SDS_TYPE_5:
|
||||||
|
/* Nothing to do, this type has no total allocation info. */
|
||||||
|
break;
|
||||||
|
case SDS_TYPE_8:
|
||||||
|
SDS_HDR(8,s)->alloc = (uint8_t)newlen;
|
||||||
|
break;
|
||||||
|
case SDS_TYPE_16:
|
||||||
|
SDS_HDR(16,s)->alloc = (uint16_t)newlen;
|
||||||
|
break;
|
||||||
|
case SDS_TYPE_32:
|
||||||
|
SDS_HDR(32,s)->alloc = (uint32_t)newlen;
|
||||||
|
break;
|
||||||
|
case SDS_TYPE_64:
|
||||||
|
SDS_HDR(64,s)->alloc = (uint64_t)newlen;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sds sdsnewlen(const void *init, size_t initlen);
|
||||||
|
sds sdsnew(const char *init);
|
||||||
|
sds sdsempty(void);
|
||||||
|
sds sdsdup(const sds s);
|
||||||
|
void sdsfree(sds s);
|
||||||
|
sds sdsgrowzero(sds s, size_t len);
|
||||||
|
sds sdscatlen(sds s, const void *t, size_t len);
|
||||||
|
sds sdscat(sds s, const char *t);
|
||||||
|
sds sdscatsds(sds s, const sds t);
|
||||||
|
sds sdscpylen(sds s, const char *t, size_t len);
|
||||||
|
sds sdscpy(sds s, const char *t);
|
||||||
|
|
||||||
|
sds sdscatvprintf(sds s, const char *fmt, va_list ap);
|
||||||
|
#ifdef __GNUC__
|
||||||
|
sds sdscatprintf(sds s, const char *fmt, ...)
|
||||||
|
__attribute__((format(printf, 2, 3)));
|
||||||
|
#else
|
||||||
|
sds sdscatprintf(sds s, const char *fmt, ...);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
sds sdscatfmt(sds s, char const *fmt, ...);
|
||||||
|
sds sdstrim(sds s, const char *cset);
|
||||||
|
int sdsrange(sds s, ssize_t start, ssize_t end);
|
||||||
|
void sdsupdatelen(sds s);
|
||||||
|
void sdsclear(sds s);
|
||||||
|
int sdscmp(const sds s1, const sds s2);
|
||||||
|
sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count);
|
||||||
|
void sdsfreesplitres(sds *tokens, int count);
|
||||||
|
void sdstolower(sds s);
|
||||||
|
void sdstoupper(sds s);
|
||||||
|
sds sdsfromlonglong(long long value);
|
||||||
|
sds sdscatrepr(sds s, const char *p, size_t len);
|
||||||
|
sds *sdssplitargs(const char *line, int *argc);
|
||||||
|
sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen);
|
||||||
|
sds sdsjoin(char **argv, int argc, char *sep);
|
||||||
|
sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen);
|
||||||
|
|
||||||
|
/* Low level functions exposed to the user API */
|
||||||
|
sds sdsMakeRoomFor(sds s, size_t addlen);
|
||||||
|
void sdsIncrLen(sds s, int incr);
|
||||||
|
sds sdsRemoveFreeSpace(sds s);
|
||||||
|
size_t sdsAllocSize(sds s);
|
||||||
|
void *sdsAllocPtr(sds s);
|
||||||
|
|
||||||
|
/* Export the allocator used by SDS to the program using SDS.
|
||||||
|
* Sometimes the program SDS is linked to, may use a different set of
|
||||||
|
* allocators, but may want to allocate or free things that SDS will
|
||||||
|
* respectively free or allocate. */
|
||||||
|
void *sds_malloc(size_t size);
|
||||||
|
void *sds_realloc(void *ptr, size_t size);
|
||||||
|
void sds_free(void *ptr);
|
||||||
|
|
||||||
|
#ifdef REDIS_TEST
|
||||||
|
int sdsTest(int argc, char *argv[]);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
/* SDSLib 2.0 -- A C dynamic strings library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2006-2015, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||||
|
* Copyright (c) 2015, Oran Agra
|
||||||
|
* Copyright (c) 2015, Redis Labs, Inc
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* SDS allocator selection.
|
||||||
|
*
|
||||||
|
* This file is used in order to change the SDS allocator at compile time.
|
||||||
|
* Just define the following defines to what you want to use. Also add
|
||||||
|
* the include of your alternate allocator if needed (not needed in order
|
||||||
|
* to use the default libc allocator). */
|
||||||
|
|
||||||
|
#include "alloc.h"
|
||||||
|
|
||||||
|
#define s_malloc hi_malloc
|
||||||
|
#define s_realloc hi_realloc
|
||||||
|
#define s_free hi_free
|
||||||
+280
@@ -0,0 +1,280 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
* Additionally, WSAALREADY is can be reported as WSAEINVAL to and this is
|
||||||
|
* translated to EIO. Convert appropriately
|
||||||
|
*/
|
||||||
|
int err = errno;
|
||||||
|
if (err == EWOULDBLOCK) {
|
||||||
|
errno = EINPROGRESS;
|
||||||
|
}
|
||||||
|
else if (err == EIO) {
|
||||||
|
errno = EALREADY;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
if (ret != SOCKET_ERROR && level == SOL_SOCKET && optname == SO_ERROR) {
|
||||||
|
/* translate SO_ERROR codes, if non-zero */
|
||||||
|
int err = *(int*)optval;
|
||||||
|
if (err != 0) {
|
||||||
|
err = _wsaErrorToErrno(err);
|
||||||
|
*(int*)optval = err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_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))) {
|
||||||
|
const 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
int win32_redisKeepAlive(SOCKET sockfd, int interval_ms) {
|
||||||
|
struct tcp_keepalive cfg;
|
||||||
|
DWORD bytes_in;
|
||||||
|
int res;
|
||||||
|
|
||||||
|
cfg.onoff = 1;
|
||||||
|
cfg.keepaliveinterval = interval_ms;
|
||||||
|
cfg.keepalivetime = interval_ms;
|
||||||
|
|
||||||
|
res = WSAIoctl(sockfd, SIO_KEEPALIVE_VALS, &cfg,
|
||||||
|
sizeof(struct tcp_keepalive), NULL, 0,
|
||||||
|
&bytes_in, NULL, NULL);
|
||||||
|
|
||||||
|
return res == 0 ? 0 : _wsaErrorToErrno(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* _WIN32 */
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* 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>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <mstcpip.h>
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
typedef intptr_t ssize_t;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* 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);
|
||||||
|
|
||||||
|
int win32_redisKeepAlive(SOCKET sockfd, int interval_ms);
|
||||||
|
|
||||||
|
#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 */
|
||||||
@@ -0,0 +1,620 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||||
|
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||||
|
* Copyright (c) 2019, Redis Labs
|
||||||
|
*
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Redis nor the names of its contributors may be used
|
||||||
|
* to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "hiredis.h"
|
||||||
|
#include "async.h"
|
||||||
|
#include "net.h"
|
||||||
|
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
#include <openssl/err.h>
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <string.h>
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#include <wincrypt.h>
|
||||||
|
#ifdef OPENSSL_IS_BORINGSSL
|
||||||
|
#undef X509_NAME
|
||||||
|
#undef X509_EXTENSIONS
|
||||||
|
#undef PKCS7_ISSUER_AND_SERIAL
|
||||||
|
#undef PKCS7_SIGNER_INFO
|
||||||
|
#undef OCSP_REQUEST
|
||||||
|
#undef OCSP_RESPONSE
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#include <pthread.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "win32.h"
|
||||||
|
#include "async_private.h"
|
||||||
|
#include "hiredis_ssl.h"
|
||||||
|
|
||||||
|
#define OPENSSL_1_1_0 0x10100000L
|
||||||
|
|
||||||
|
void __redisSetError(redisContext *c, int type, const char *str);
|
||||||
|
|
||||||
|
struct redisSSLContext {
|
||||||
|
/* Associated OpenSSL SSL_CTX as created by redisCreateSSLContext() */
|
||||||
|
SSL_CTX *ssl_ctx;
|
||||||
|
|
||||||
|
/* Requested SNI, or NULL */
|
||||||
|
char *server_name;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* The SSL connection context is attached to SSL/TLS connections as a privdata. */
|
||||||
|
typedef struct redisSSL {
|
||||||
|
/**
|
||||||
|
* OpenSSL SSL object.
|
||||||
|
*/
|
||||||
|
SSL *ssl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/* Forward declaration */
|
||||||
|
redisContextFuncs redisContextSSLFuncs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OpenSSL global initialization and locking handling callbacks.
|
||||||
|
* Note that this is only required for OpenSSL < 1.1.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if OPENSSL_VERSION_NUMBER < OPENSSL_1_1_0
|
||||||
|
#define HIREDIS_USE_CRYPTO_LOCKS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HIREDIS_USE_CRYPTO_LOCKS
|
||||||
|
#ifdef _WIN32
|
||||||
|
typedef CRITICAL_SECTION sslLockType;
|
||||||
|
static void sslLockInit(sslLockType* l) {
|
||||||
|
InitializeCriticalSection(l);
|
||||||
|
}
|
||||||
|
static void sslLockAcquire(sslLockType* l) {
|
||||||
|
EnterCriticalSection(l);
|
||||||
|
}
|
||||||
|
static void sslLockRelease(sslLockType* l) {
|
||||||
|
LeaveCriticalSection(l);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static sslLockType* 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 int initOpensslLocks(void) {
|
||||||
|
unsigned ii, nlocks;
|
||||||
|
if (CRYPTO_get_locking_callback() != NULL) {
|
||||||
|
/* Someone already set the callback before us. Don't destroy it! */
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
||||||
|
nlocks = CRYPTO_num_locks();
|
||||||
|
ossl_locks = hi_malloc(sizeof(*ossl_locks) * nlocks);
|
||||||
|
if (ossl_locks == NULL)
|
||||||
|
return REDIS_ERR;
|
||||||
|
|
||||||
|
for (ii = 0; ii < nlocks; ii++) {
|
||||||
|
sslLockInit(ossl_locks + ii);
|
||||||
|
}
|
||||||
|
CRYPTO_set_locking_callback(opensslDoLock);
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
||||||
|
#endif /* HIREDIS_USE_CRYPTO_LOCKS */
|
||||||
|
|
||||||
|
int redisInitOpenSSL(void)
|
||||||
|
{
|
||||||
|
#ifdef HIREDIS_USE_CRYPTO_LOCKS
|
||||||
|
SSL_library_init();
|
||||||
|
initOpensslLocks();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* redisSSLContext helper context destruction.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const char *redisSSLContextGetError(redisSSLContextError error)
|
||||||
|
{
|
||||||
|
switch (error) {
|
||||||
|
case REDIS_SSL_CTX_NONE:
|
||||||
|
return "No Error";
|
||||||
|
case REDIS_SSL_CTX_CREATE_FAILED:
|
||||||
|
return "Failed to create OpenSSL SSL_CTX";
|
||||||
|
case REDIS_SSL_CTX_CERT_KEY_REQUIRED:
|
||||||
|
return "Client cert and key must both be specified or skipped";
|
||||||
|
case REDIS_SSL_CTX_CA_CERT_LOAD_FAILED:
|
||||||
|
return "Failed to load CA Certificate or CA Path";
|
||||||
|
case REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED:
|
||||||
|
return "Failed to load client certificate";
|
||||||
|
case REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED:
|
||||||
|
return "Failed to load private key";
|
||||||
|
case REDIS_SSL_CTX_OS_CERTSTORE_OPEN_FAILED:
|
||||||
|
return "Failed to open system certificate store";
|
||||||
|
case REDIS_SSL_CTX_OS_CERT_ADD_FAILED:
|
||||||
|
return "Failed to add CA certificates obtained from system to the SSL context";
|
||||||
|
default:
|
||||||
|
return "Unknown error code";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void redisFreeSSLContext(redisSSLContext *ctx)
|
||||||
|
{
|
||||||
|
if (!ctx)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (ctx->server_name) {
|
||||||
|
hi_free(ctx->server_name);
|
||||||
|
ctx->server_name = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->ssl_ctx) {
|
||||||
|
SSL_CTX_free(ctx->ssl_ctx);
|
||||||
|
ctx->ssl_ctx = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
hi_free(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* redisSSLContext helper context initialization.
|
||||||
|
*/
|
||||||
|
|
||||||
|
redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char *capath,
|
||||||
|
const char *cert_filename, const char *private_key_filename,
|
||||||
|
const char *server_name, redisSSLContextError *error)
|
||||||
|
{
|
||||||
|
redisSSLOptions options = {
|
||||||
|
.cacert_filename = cacert_filename,
|
||||||
|
.capath = capath,
|
||||||
|
.cert_filename = cert_filename,
|
||||||
|
.private_key_filename = private_key_filename,
|
||||||
|
.server_name = server_name,
|
||||||
|
.verify_mode = REDIS_SSL_VERIFY_PEER,
|
||||||
|
};
|
||||||
|
|
||||||
|
return redisCreateSSLContextWithOptions(&options, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
redisSSLContext *redisCreateSSLContextWithOptions(redisSSLOptions *options, redisSSLContextError *error) {
|
||||||
|
const char *cacert_filename = options->cacert_filename;
|
||||||
|
const char *capath = options->capath;
|
||||||
|
const char *cert_filename = options->cert_filename;
|
||||||
|
const char *private_key_filename = options->private_key_filename;
|
||||||
|
const char *server_name = options->server_name;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
HCERTSTORE win_store = NULL;
|
||||||
|
PCCERT_CONTEXT win_ctx = NULL;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
redisSSLContext *ctx = hi_calloc(1, sizeof(redisSSLContext));
|
||||||
|
if (ctx == NULL)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
const SSL_METHOD *ssl_method;
|
||||||
|
#if OPENSSL_VERSION_NUMBER >= OPENSSL_1_1_0
|
||||||
|
ssl_method = TLS_client_method();
|
||||||
|
#else
|
||||||
|
ssl_method = SSLv23_client_method();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ctx->ssl_ctx = SSL_CTX_new(ssl_method);
|
||||||
|
if (!ctx->ssl_ctx) {
|
||||||
|
if (error) *error = REDIS_SSL_CTX_CREATE_FAILED;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if OPENSSL_VERSION_NUMBER >= OPENSSL_1_1_0
|
||||||
|
SSL_CTX_set_min_proto_version(ctx->ssl_ctx, TLS1_2_VERSION);
|
||||||
|
#else
|
||||||
|
SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
SSL_CTX_set_verify(ctx->ssl_ctx, options->verify_mode, NULL);
|
||||||
|
|
||||||
|
if ((cert_filename != NULL && private_key_filename == NULL) ||
|
||||||
|
(private_key_filename != NULL && cert_filename == NULL)) {
|
||||||
|
if (error) *error = REDIS_SSL_CTX_CERT_KEY_REQUIRED;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (capath || cacert_filename) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (0 == strcmp(cacert_filename, "wincert")) {
|
||||||
|
win_store = CertOpenSystemStore(NULL, "Root");
|
||||||
|
if (!win_store) {
|
||||||
|
if (error) *error = REDIS_SSL_CTX_OS_CERTSTORE_OPEN_FAILED;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
X509_STORE* store = SSL_CTX_get_cert_store(ctx->ssl_ctx);
|
||||||
|
while (win_ctx = CertEnumCertificatesInStore(win_store, win_ctx)) {
|
||||||
|
X509* x509 = NULL;
|
||||||
|
x509 = d2i_X509(NULL, (const unsigned char**)&win_ctx->pbCertEncoded, win_ctx->cbCertEncoded);
|
||||||
|
if (x509) {
|
||||||
|
if ((1 != X509_STORE_add_cert(store, x509)) ||
|
||||||
|
(1 != SSL_CTX_add_client_CA(ctx->ssl_ctx, x509)))
|
||||||
|
{
|
||||||
|
if (error) *error = REDIS_SSL_CTX_OS_CERT_ADD_FAILED;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
X509_free(x509);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CertFreeCertificateContext(win_ctx);
|
||||||
|
CertCloseStore(win_store, 0);
|
||||||
|
} else
|
||||||
|
#endif
|
||||||
|
if (!SSL_CTX_load_verify_locations(ctx->ssl_ctx, cacert_filename, capath)) {
|
||||||
|
if (error) *error = REDIS_SSL_CTX_CA_CERT_LOAD_FAILED;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!SSL_CTX_set_default_verify_paths(ctx->ssl_ctx)) {
|
||||||
|
if (error) *error = REDIS_SSL_CTX_CLIENT_DEFAULT_CERT_FAILED;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cert_filename) {
|
||||||
|
if (!SSL_CTX_use_certificate_chain_file(ctx->ssl_ctx, cert_filename)) {
|
||||||
|
if (error) *error = REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
if (!SSL_CTX_use_PrivateKey_file(ctx->ssl_ctx, private_key_filename, SSL_FILETYPE_PEM)) {
|
||||||
|
if (error) *error = REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (server_name)
|
||||||
|
ctx->server_name = hi_strdup(server_name);
|
||||||
|
|
||||||
|
return ctx;
|
||||||
|
|
||||||
|
error:
|
||||||
|
#ifdef _WIN32
|
||||||
|
CertFreeCertificateContext(win_ctx);
|
||||||
|
CertCloseStore(win_store, 0);
|
||||||
|
#endif
|
||||||
|
redisFreeSSLContext(ctx);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SSL Connection initialization.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
static int redisSSLConnect(redisContext *c, SSL *ssl) {
|
||||||
|
if (c->privctx) {
|
||||||
|
__redisSetError(c, REDIS_ERR_OTHER, "redisContext was already associated");
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
redisSSL *rssl = hi_calloc(1, sizeof(redisSSL));
|
||||||
|
if (rssl == NULL) {
|
||||||
|
__redisSetError(c, REDIS_ERR_OOM, "Out of memory");
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
rssl->ssl = ssl;
|
||||||
|
|
||||||
|
SSL_set_mode(rssl->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
|
||||||
|
SSL_set_fd(rssl->ssl, c->fd);
|
||||||
|
SSL_set_connect_state(rssl->ssl);
|
||||||
|
|
||||||
|
ERR_clear_error();
|
||||||
|
|
||||||
|
int rv = SSL_connect(rssl->ssl);
|
||||||
|
if (rv == 1) {
|
||||||
|
c->funcs = &redisContextSSLFuncs;
|
||||||
|
c->privctx = rssl;
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
rv = SSL_get_error(rssl->ssl, rv);
|
||||||
|
if (((c->flags & REDIS_BLOCK) == 0) &&
|
||||||
|
(rv == SSL_ERROR_WANT_READ || rv == SSL_ERROR_WANT_WRITE))
|
||||||
|
{
|
||||||
|
c->funcs = &redisContextSSLFuncs;
|
||||||
|
c->privctx = rssl;
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c->err == 0) {
|
||||||
|
char err[512];
|
||||||
|
if (rv == SSL_ERROR_SYSCALL)
|
||||||
|
snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",strerror(errno));
|
||||||
|
else {
|
||||||
|
unsigned long e = ERR_peek_last_error();
|
||||||
|
snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",
|
||||||
|
ERR_reason_error_string(e));
|
||||||
|
}
|
||||||
|
__redisSetError(c, REDIS_ERR_IO, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
hi_free(rssl);
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A wrapper around redisSSLConnect() for users who manage their own context and
|
||||||
|
* create their own SSL object.
|
||||||
|
*/
|
||||||
|
|
||||||
|
int redisInitiateSSL(redisContext *c, SSL *ssl) {
|
||||||
|
return redisSSLConnect(c, ssl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A wrapper around redisSSLConnect() for users who use redisSSLContext and don't
|
||||||
|
* manage their own SSL objects.
|
||||||
|
*/
|
||||||
|
|
||||||
|
int redisInitiateSSLWithContext(redisContext *c, redisSSLContext *redis_ssl_ctx)
|
||||||
|
{
|
||||||
|
if (!c || !redis_ssl_ctx)
|
||||||
|
return REDIS_ERR;
|
||||||
|
|
||||||
|
/* We want to verify that redisSSLConnect() won't fail on this, as it will
|
||||||
|
* not own the SSL object in that case and we'll end up leaking.
|
||||||
|
*/
|
||||||
|
if (c->privctx)
|
||||||
|
return REDIS_ERR;
|
||||||
|
|
||||||
|
SSL *ssl = SSL_new(redis_ssl_ctx->ssl_ctx);
|
||||||
|
if (!ssl) {
|
||||||
|
__redisSetError(c, REDIS_ERR_OTHER, "Couldn't create new SSL instance");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (redis_ssl_ctx->server_name) {
|
||||||
|
if (!SSL_set_tlsext_host_name(ssl, redis_ssl_ctx->server_name)) {
|
||||||
|
__redisSetError(c, REDIS_ERR_OTHER, "Failed to set server_name/SNI");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (redisSSLConnect(c, ssl) != REDIS_OK) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return REDIS_OK;
|
||||||
|
|
||||||
|
error:
|
||||||
|
if (ssl)
|
||||||
|
SSL_free(ssl);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of redisContextFuncs for SSL connections.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void redisSSLFree(void *privctx){
|
||||||
|
redisSSL *rsc = privctx;
|
||||||
|
|
||||||
|
if (!rsc) return;
|
||||||
|
if (rsc->ssl) {
|
||||||
|
SSL_free(rsc->ssl);
|
||||||
|
rsc->ssl = NULL;
|
||||||
|
}
|
||||||
|
hi_free(rsc);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t redisSSLRead(redisContext *c, char *buf, size_t bufcap) {
|
||||||
|
redisSSL *rssl = c->privctx;
|
||||||
|
|
||||||
|
int nread = SSL_read(rssl->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(rssl->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 = "Resource temporarily unavailable";
|
||||||
|
}
|
||||||
|
__redisSetError(c, REDIS_ERR_IO, msg);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We can very well get an EWOULDBLOCK/EAGAIN, however
|
||||||
|
*/
|
||||||
|
if (maybeCheckWant(rssl, err)) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
__redisSetError(c, REDIS_ERR_IO, NULL);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t redisSSLWrite(redisContext *c) {
|
||||||
|
redisSSL *rssl = c->privctx;
|
||||||
|
|
||||||
|
size_t len = rssl->lastLen ? rssl->lastLen : sdslen(c->obuf);
|
||||||
|
int rv = SSL_write(rssl->ssl, c->obuf, len);
|
||||||
|
|
||||||
|
if (rv > 0) {
|
||||||
|
rssl->lastLen = 0;
|
||||||
|
} else if (rv < 0) {
|
||||||
|
rssl->lastLen = len;
|
||||||
|
|
||||||
|
int err = SSL_get_error(rssl->ssl, rv);
|
||||||
|
if ((c->flags & REDIS_BLOCK) == 0 && maybeCheckWant(rssl, err)) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
__redisSetError(c, REDIS_ERR_IO, NULL);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisSSLAsyncRead(redisAsyncContext *ac) {
|
||||||
|
int rv;
|
||||||
|
redisSSL *rssl = ac->c.privctx;
|
||||||
|
redisContext *c = &ac->c;
|
||||||
|
|
||||||
|
rssl->wantRead = 0;
|
||||||
|
|
||||||
|
if (rssl->pendingWrite) {
|
||||||
|
int done;
|
||||||
|
|
||||||
|
/* This is probably just a write event */
|
||||||
|
rssl->pendingWrite = 0;
|
||||||
|
rv = redisBufferWrite(c, &done);
|
||||||
|
if (rv == REDIS_ERR) {
|
||||||
|
__redisAsyncDisconnect(ac);
|
||||||
|
return;
|
||||||
|
} else if (!done) {
|
||||||
|
_EL_ADD_WRITE(ac);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rv = redisBufferRead(c);
|
||||||
|
if (rv == REDIS_ERR) {
|
||||||
|
__redisAsyncDisconnect(ac);
|
||||||
|
} else {
|
||||||
|
_EL_ADD_READ(ac);
|
||||||
|
redisProcessCallbacks(ac);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisSSLAsyncWrite(redisAsyncContext *ac) {
|
||||||
|
int rv, done = 0;
|
||||||
|
redisSSL *rssl = ac->c.privctx;
|
||||||
|
redisContext *c = &ac->c;
|
||||||
|
|
||||||
|
rssl->pendingWrite = 0;
|
||||||
|
rv = redisBufferWrite(c, &done);
|
||||||
|
if (rv == REDIS_ERR) {
|
||||||
|
__redisAsyncDisconnect(ac);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!done) {
|
||||||
|
if (rssl->wantRead) {
|
||||||
|
/* Need to read-before-write */
|
||||||
|
rssl->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);
|
||||||
|
}
|
||||||
|
|
||||||
|
redisContextFuncs redisContextSSLFuncs = {
|
||||||
|
.close = redisNetClose,
|
||||||
|
.free_privctx = redisSSLFree,
|
||||||
|
.async_read = redisSSLAsyncRead,
|
||||||
|
.async_write = redisSSLAsyncWrite,
|
||||||
|
.read = redisSSLRead,
|
||||||
|
.write = redisSSLWrite
|
||||||
|
};
|
||||||
|
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
#!/bin/sh -ue
|
||||||
|
|
||||||
|
REDIS_SERVER=${REDIS_SERVER:-redis-server}
|
||||||
|
REDIS_PORT=${REDIS_PORT:-56379}
|
||||||
|
REDIS_SSL_PORT=${REDIS_SSL_PORT:-56443}
|
||||||
|
TEST_SSL=${TEST_SSL:-0}
|
||||||
|
SKIPS_AS_FAILS=${SKIPS_AS_FAILS:-0}
|
||||||
|
ENABLE_DEBUG_CMD=
|
||||||
|
SSL_TEST_ARGS=
|
||||||
|
SKIPS_ARG=${SKIPS_ARG:-}
|
||||||
|
REDIS_DOCKER=${REDIS_DOCKER:-}
|
||||||
|
|
||||||
|
# We need to enable the DEBUG command for redis-server >= 7.0.0
|
||||||
|
REDIS_MAJOR_VERSION="$(${REDIS_SERVER} --version|awk -F'[^0-9]+' '{ print $2 }')"
|
||||||
|
if [ "$REDIS_MAJOR_VERSION" -gt "6" ]; then
|
||||||
|
ENABLE_DEBUG_CMD="enable-debug-command local"
|
||||||
|
fi
|
||||||
|
|
||||||
|
tmpdir=$(mktemp -d)
|
||||||
|
PID_FILE=${tmpdir}/hiredis-test-redis.pid
|
||||||
|
SOCK_FILE=${tmpdir}/hiredis-test-redis.sock
|
||||||
|
|
||||||
|
if [ "$TEST_SSL" = "1" ]; then
|
||||||
|
SSL_CA_CERT=${tmpdir}/ca.crt
|
||||||
|
SSL_CA_KEY=${tmpdir}/ca.key
|
||||||
|
SSL_CERT=${tmpdir}/redis.crt
|
||||||
|
SSL_KEY=${tmpdir}/redis.key
|
||||||
|
|
||||||
|
openssl genrsa -out ${tmpdir}/ca.key 4096
|
||||||
|
openssl req \
|
||||||
|
-x509 -new -nodes -sha256 \
|
||||||
|
-key ${SSL_CA_KEY} \
|
||||||
|
-days 3650 \
|
||||||
|
-subj '/CN=Hiredis Test CA' \
|
||||||
|
-out ${SSL_CA_CERT}
|
||||||
|
openssl genrsa -out ${SSL_KEY} 2048
|
||||||
|
openssl req \
|
||||||
|
-new -sha256 \
|
||||||
|
-key ${SSL_KEY} \
|
||||||
|
-subj '/CN=Hiredis Test Cert' | \
|
||||||
|
openssl x509 \
|
||||||
|
-req -sha256 \
|
||||||
|
-CA ${SSL_CA_CERT} \
|
||||||
|
-CAkey ${SSL_CA_KEY} \
|
||||||
|
-CAserial ${tmpdir}/ca.txt \
|
||||||
|
-CAcreateserial \
|
||||||
|
-days 365 \
|
||||||
|
-out ${SSL_CERT}
|
||||||
|
|
||||||
|
SSL_TEST_ARGS="--ssl-host 127.0.0.1 --ssl-port ${REDIS_SSL_PORT} --ssl-ca-cert ${SSL_CA_CERT} --ssl-cert ${SSL_CERT} --ssl-key ${SSL_KEY}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
if [ -n "${REDIS_DOCKER}" ] ; then
|
||||||
|
docker kill redis-test-server
|
||||||
|
else
|
||||||
|
set +e
|
||||||
|
kill $(cat ${PID_FILE})
|
||||||
|
fi
|
||||||
|
rm -rf ${tmpdir}
|
||||||
|
}
|
||||||
|
trap cleanup INT TERM EXIT
|
||||||
|
|
||||||
|
# base config
|
||||||
|
cat > ${tmpdir}/redis.conf <<EOF
|
||||||
|
pidfile ${PID_FILE}
|
||||||
|
port ${REDIS_PORT}
|
||||||
|
unixsocket ${SOCK_FILE}
|
||||||
|
unixsocketperm 777
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# if not running in docker add these:
|
||||||
|
if [ ! -n "${REDIS_DOCKER}" ]; then
|
||||||
|
cat >> ${tmpdir}/redis.conf <<EOF
|
||||||
|
daemonize yes
|
||||||
|
${ENABLE_DEBUG_CMD}
|
||||||
|
bind 127.0.0.1
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
# if doing ssl, add these
|
||||||
|
if [ "$TEST_SSL" = "1" ]; then
|
||||||
|
cat >> ${tmpdir}/redis.conf <<EOF
|
||||||
|
tls-port ${REDIS_SSL_PORT}
|
||||||
|
tls-ca-cert-file ${SSL_CA_CERT}
|
||||||
|
tls-cert-file ${SSL_CERT}
|
||||||
|
tls-key-file ${SSL_KEY}
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ${tmpdir}
|
||||||
|
cat ${tmpdir}/redis.conf
|
||||||
|
if [ -n "${REDIS_DOCKER}" ] ; then
|
||||||
|
chmod a+wx ${tmpdir}
|
||||||
|
chmod a+r ${tmpdir}/*
|
||||||
|
docker run -d --rm --name redis-test-server \
|
||||||
|
-p ${REDIS_PORT}:${REDIS_PORT} \
|
||||||
|
-p ${REDIS_SSL_PORT}:${REDIS_SSL_PORT} \
|
||||||
|
-v ${tmpdir}:${tmpdir} \
|
||||||
|
${REDIS_DOCKER} \
|
||||||
|
${REDIS_SERVER} ${tmpdir}/redis.conf
|
||||||
|
else
|
||||||
|
${REDIS_SERVER} ${tmpdir}/redis.conf
|
||||||
|
fi
|
||||||
|
# Wait until we detect the unix socket
|
||||||
|
echo waiting for server
|
||||||
|
while [ ! -S "${SOCK_FILE}" ]; do sleep 1; done
|
||||||
|
|
||||||
|
# Treat skips as failures if directed
|
||||||
|
[ "$SKIPS_AS_FAILS" = 1 ] && SKIPS_ARG="${SKIPS_ARG} --skips-as-fails"
|
||||||
|
|
||||||
|
${TEST_PREFIX:-} ./hiredis-test -h 127.0.0.1 -p ${REDIS_PORT} -s ${SOCK_FILE} ${SSL_TEST_ARGS} ${SKIPS_ARG}
|
||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
VS_VERSION_INFO VERSIONINFO
|
||||||
|
FILEVERSION 1,3,0,0
|
||||||
|
PRODUCTVERSION 1,3,0,0
|
||||||
|
FILEFLAGSMASK 0x3fL
|
||||||
|
FILEFLAGS 0x0L
|
||||||
|
FILEOS VOS__WINDOWS32
|
||||||
|
FILETYPE VFT_DLL
|
||||||
|
FILESUBTYPE 0x0L
|
||||||
|
BEGIN
|
||||||
|
BLOCK "StringFileInfo"
|
||||||
|
BEGIN
|
||||||
|
BLOCK "040904b0"
|
||||||
|
BEGIN
|
||||||
|
VALUE "FileDescription", "Hiredis Redis Client Library"
|
||||||
|
VALUE "FileVersion", "1.3.0.0"
|
||||||
|
VALUE "InternalName", "hiredis"
|
||||||
|
VALUE "OriginalFilename", "hiredis.dll"
|
||||||
|
#ifdef _DEBUG
|
||||||
|
#ifdef _WIN64
|
||||||
|
VALUE "ProductName", "Hiredis Library Debug 64-bit"
|
||||||
|
#else
|
||||||
|
VALUE "ProductName", "Hiredis Library Debug 32-bit"
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#ifdef _WIN64
|
||||||
|
VALUE "ProductName", "Hiredis Library Release 64-bit"
|
||||||
|
#else
|
||||||
|
VALUE "ProductName", "Hiredis Library Release 32-bit"
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
VALUE "ProductVersion", "1.3.0.0"
|
||||||
|
END
|
||||||
|
END
|
||||||
|
BLOCK "VarFileInfo"
|
||||||
|
BEGIN
|
||||||
|
VALUE "Translation", 0x0409, 1200
|
||||||
|
END
|
||||||
|
END
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
#ifndef _WIN32_HELPER_INCLUDE
|
||||||
|
#define _WIN32_HELPER_INCLUDE
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
|
||||||
|
#include <winsock2.h> /* for struct timeval */
|
||||||
|
|
||||||
|
#ifndef inline
|
||||||
|
#define inline __inline
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef strcasecmp
|
||||||
|
#define strcasecmp stricmp
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef strncasecmp
|
||||||
|
#define strncasecmp strnicmp
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef va_copy
|
||||||
|
#define va_copy(d,s) ((d) = (s))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef snprintf
|
||||||
|
#define snprintf c99_snprintf
|
||||||
|
|
||||||
|
__inline int c99_vsnprintf(char* str, size_t size, const char* format, va_list ap)
|
||||||
|
{
|
||||||
|
int count = -1;
|
||||||
|
|
||||||
|
if (size != 0)
|
||||||
|
count = _vsnprintf_s(str, size, _TRUNCATE, format, ap);
|
||||||
|
if (count == -1)
|
||||||
|
count = _vscprintf(format, ap);
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
__inline int c99_snprintf(char* str, size_t size, const char* format, ...)
|
||||||
|
{
|
||||||
|
int count;
|
||||||
|
va_list ap;
|
||||||
|
|
||||||
|
va_start(ap, format);
|
||||||
|
count = c99_vsnprintf(str, size, format, ap);
|
||||||
|
va_end(ap);
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif /* _MSC_VER */
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#define strerror_r(errno,buf,len) strerror_s(buf,len,errno)
|
||||||
|
#endif /* _WIN32 */
|
||||||
|
|
||||||
|
#endif /* _WIN32_HELPER_INCLUDE */
|
||||||
Reference in New Issue
Block a user