Files
phpredis/library.c
michael-grunder 4a6c01bc1a Send a newline with the QUIT command
Addresses #381
2013-09-07 13:51:33 -07:00

1846 lines
48 KiB
C

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "common.h"
#include "php_network.h"
#include <sys/types.h>
#ifndef _MSC_VER
#include <netinet/tcp.h> /* TCP_NODELAY */
#include <sys/socket.h>
#endif
#include <ext/standard/php_smart_str.h>
#include <ext/standard/php_var.h>
#ifdef HAVE_REDIS_IGBINARY
#include "igbinary/igbinary.h"
#endif
#include <zend_exceptions.h>
#include "php_redis.h"
#include "library.h"
#include <ext/standard/php_math.h>
#define UNSERIALIZE_ONLY_VALUES 0
#define UNSERIALIZE_ALL 1
extern zend_class_entry *redis_ce;
extern zend_class_entry *redis_exception_ce;
extern zend_class_entry *spl_ce_RuntimeException;
PHPAPI void redis_stream_close(RedisSock *redis_sock TSRMLS_DC) {
if (!redis_sock->persistent) {
php_stream_close(redis_sock->stream);
} else {
php_stream_pclose(redis_sock->stream);
}
}
PHPAPI int redis_check_eof(RedisSock *redis_sock TSRMLS_DC)
{
int eof;
int count = 0;
if (!redis_sock->stream) {
return -1;
}
eof = php_stream_eof(redis_sock->stream);
for (; eof; count++) {
if((MULTI == redis_sock->mode) || redis_sock->watching || count == 10) { /* too many failures */
if(redis_sock->stream) { /* close stream if still here */
redis_stream_close(redis_sock TSRMLS_CC);
redis_sock->stream = NULL;
redis_sock->mode = ATOMIC;
redis_sock->status = REDIS_SOCK_STATUS_FAILED;
redis_sock->watching = 0;
}
zend_throw_exception(redis_exception_ce, "Connection lost", 0 TSRMLS_CC);
return -1;
}
if(redis_sock->stream) { /* close existing stream before reconnecting */
redis_stream_close(redis_sock TSRMLS_CC);
redis_sock->stream = NULL;
redis_sock->mode = ATOMIC;
redis_sock->watching = 0;
}
// Wait for a while before trying to reconnect
if (redis_sock->retry_interval) {
// Random factor to avoid having several (or many) concurrent connections trying to reconnect at the same time
long retry_interval = (count ? redis_sock->retry_interval : (random() % redis_sock->retry_interval));
usleep(retry_interval);
}
redis_sock_connect(redis_sock TSRMLS_CC); /* reconnect */
if(redis_sock->stream) { /* check for EOF again. */
eof = php_stream_eof(redis_sock->stream);
}
}
// Reselect the DB.
if (count && redis_sock->dbNumber) {
char *cmd, *response;
int cmd_len, response_len;
cmd_len = redis_cmd_format_static(&cmd, "SELECT", "d", redis_sock->dbNumber);
if (redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) {
efree(cmd);
return -1;
}
efree(cmd);
if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) {
return -1;
}
if (strncmp(response, "+OK", 3)) {
efree(response);
return -1;
}
efree(response);
}
return 0;
}
PHPAPI zval *redis_sock_read_multibulk_reply_zval(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock) {
char inbuf[1024];
int numElems;
zval *z_tab;
if(-1 == redis_check_eof(redis_sock TSRMLS_CC)) {
return NULL;
}
if(php_stream_gets(redis_sock->stream, inbuf, 1024) == NULL) {
redis_stream_close(redis_sock TSRMLS_CC);
redis_sock->stream = NULL;
redis_sock->status = REDIS_SOCK_STATUS_FAILED;
redis_sock->mode = ATOMIC;
redis_sock->watching = 0;
zend_throw_exception(redis_exception_ce, "read error on connection", 0 TSRMLS_CC);
return NULL;
}
if(inbuf[0] != '*') {
return NULL;
}
numElems = atoi(inbuf+1);
MAKE_STD_ZVAL(z_tab);
array_init(z_tab);
redis_sock_read_multibulk_reply_loop(INTERNAL_FUNCTION_PARAM_PASSTHRU,
redis_sock, z_tab, numElems, 1, UNSERIALIZE_ALL);
return z_tab;
}
/**
* redis_sock_read_bulk_reply
*/
PHPAPI char *redis_sock_read_bulk_reply(RedisSock *redis_sock, int bytes TSRMLS_DC)
{
int offset = 0;
size_t got;
char * reply;
if(-1 == redis_check_eof(redis_sock TSRMLS_CC)) {
return NULL;
}
if (bytes == -1) {
return NULL;
} else {
char c;
int i;
reply = emalloc(bytes+1);
while(offset < bytes) {
got = php_stream_read(redis_sock->stream, reply + offset, bytes-offset);
if (got <= 0) {
/* Error or EOF */
zend_throw_exception(redis_exception_ce, "socket error on read socket", 0 TSRMLS_CC);
break;
}
offset += got;
}
for(i = 0; i < 2; i++) {
php_stream_read(redis_sock->stream, &c, 1);
}
}
reply[bytes] = 0;
return reply;
}
/**
* redis_sock_read
*/
PHPAPI char *redis_sock_read(RedisSock *redis_sock, int *buf_len TSRMLS_DC)
{
char inbuf[1024];
char *resp = NULL;
size_t err_len;
if(-1 == redis_check_eof(redis_sock TSRMLS_CC)) {
return NULL;
}
if(php_stream_gets(redis_sock->stream, inbuf, 1024) == NULL) {
redis_stream_close(redis_sock TSRMLS_CC);
redis_sock->stream = NULL;
redis_sock->status = REDIS_SOCK_STATUS_FAILED;
redis_sock->mode = ATOMIC;
redis_sock->watching = 0;
zend_throw_exception(redis_exception_ce, "read error on connection", 0 TSRMLS_CC);
return NULL;
}
switch(inbuf[0]) {
case '-':
err_len = strlen(inbuf+1) - 2;
redis_sock_set_err(redis_sock, inbuf+1, err_len);
/* stale data */
if(memcmp(inbuf + 1, "-ERR SYNC ", 10) == 0) {
zend_throw_exception(redis_exception_ce, "SYNC with master in progress", 0 TSRMLS_CC);
}
return NULL;
case '$':
*buf_len = atoi(inbuf + 1);
resp = redis_sock_read_bulk_reply(redis_sock, *buf_len TSRMLS_CC);
return resp;
case '*':
/* For null multi-bulk replies (like timeouts from brpoplpush): */
if(memcmp(inbuf + 1, "-1", 2) == 0) {
return NULL;
}
/* fall through */
case '+':
case ':':
// Single Line Reply
/* :123\r\n */
*buf_len = strlen(inbuf) - 2;
if(*buf_len >= 2) {
resp = emalloc(1+*buf_len);
memcpy(resp, inbuf, *buf_len);
resp[*buf_len] = 0;
return resp;
}
default:
zend_throw_exception_ex(
redis_exception_ce,
0 TSRMLS_CC,
"protocol error, got '%c' as reply type byte\n",
inbuf[0]
);
}
return NULL;
}
void add_constant_long(zend_class_entry *ce, char *name, int value) {
zval *constval;
constval = pemalloc(sizeof(zval), 1);
INIT_PZVAL(constval);
ZVAL_LONG(constval, value);
zend_hash_add(&ce->constants_table, name, 1 + strlen(name),
(void*)&constval, sizeof(zval*), NULL);
}
int
integer_length(int i) {
int sz = 0;
int ci = abs(i);
while (ci > 0) {
ci /= 10;
sz++;
}
if (i == 0) { /* log 0 doesn't make sense. */
sz = 1;
} else if (i < 0) { /* allow for neg sign as well. */
sz++;
}
return sz;
}
int
redis_cmd_format_header(char **ret, char *keyword, int arg_count) {
// Our return buffer
smart_str buf = {0};
// Keyword length
int l = strlen(keyword);
smart_str_appendc(&buf, '*');
smart_str_append_long(&buf, arg_count + 1);
smart_str_appendl(&buf, _NL, sizeof(_NL) -1);
smart_str_appendc(&buf, '$');
smart_str_append_long(&buf, l);
smart_str_appendl(&buf, _NL, sizeof(_NL) -1);
smart_str_appendl(&buf, keyword, l);
smart_str_appendl(&buf, _NL, sizeof(_NL) - 1);
// Set our return pointer
*ret = buf.c;
// Return the length
return buf.len;
}
int
redis_cmd_format_static(char **ret, char *keyword, char *format, ...) {
char *p = format;
va_list ap;
smart_str buf = {0};
int l = strlen(keyword);
char *dbl_str;
int dbl_len;
va_start(ap, format);
/* add header */
smart_str_appendc(&buf, '*');
smart_str_append_long(&buf, strlen(format) + 1);
smart_str_appendl(&buf, _NL, sizeof(_NL) - 1);
smart_str_appendc(&buf, '$');
smart_str_append_long(&buf, l);
smart_str_appendl(&buf, _NL, sizeof(_NL) - 1);
smart_str_appendl(&buf, keyword, l);
smart_str_appendl(&buf, _NL, sizeof(_NL) - 1);
while (*p) {
smart_str_appendc(&buf, '$');
switch(*p) {
case 's': {
char *val = va_arg(ap, char*);
int val_len = va_arg(ap, int);
smart_str_append_long(&buf, val_len);
smart_str_appendl(&buf, _NL, sizeof(_NL) - 1);
smart_str_appendl(&buf, val, val_len);
}
break;
case 'f':
case 'F': {
double d = va_arg(ap, double);
REDIS_DOUBLE_TO_STRING(dbl_str, dbl_len, d)
smart_str_append_long(&buf, dbl_len);
smart_str_appendl(&buf, _NL, sizeof(_NL) - 1);
smart_str_appendl(&buf, dbl_str, dbl_len);
efree(dbl_str);
}
break;
case 'i':
case 'd': {
int i = va_arg(ap, int);
char tmp[32];
int tmp_len = snprintf(tmp, sizeof(tmp), "%d", i);
smart_str_append_long(&buf, tmp_len);
smart_str_appendl(&buf, _NL, sizeof(_NL) - 1);
smart_str_appendl(&buf, tmp, tmp_len);
}
break;
case 'l':
case 'L': {
long l = va_arg(ap, long);
char tmp[32];
int tmp_len = snprintf(tmp, sizeof(tmp), "%ld", l);
smart_str_append_long(&buf, tmp_len);
smart_str_appendl(&buf, _NL, sizeof(_NL) -1);
smart_str_appendl(&buf, tmp, tmp_len);
}
break;
}
p++;
smart_str_appendl(&buf, _NL, sizeof(_NL) - 1);
}
smart_str_0(&buf);
*ret = buf.c;
return buf.len;
}
/**
* This command behave somehow like printf, except that strings need 2 arguments:
* Their data and their size (strlen).
* Supported formats are: %d, %i, %s, %l
*/
int
redis_cmd_format(char **ret, char *format, ...) {
smart_str buf = {0};
va_list ap;
char *p = format;
char *dbl_str;
int dbl_len;
va_start(ap, format);
while (*p) {
if (*p == '%') {
switch (*(++p)) {
case 's': {
char *tmp = va_arg(ap, char*);
int tmp_len = va_arg(ap, int);
smart_str_appendl(&buf, tmp, tmp_len);
}
break;
case 'F':
case 'f': {
double d = va_arg(ap, double);
REDIS_DOUBLE_TO_STRING(dbl_str, dbl_len, d)
smart_str_append_long(&buf, dbl_len);
smart_str_appendl(&buf, _NL, sizeof(_NL) - 1);
smart_str_appendl(&buf, dbl_str, dbl_len);
efree(dbl_str);
}
break;
case 'd':
case 'i': {
int i = va_arg(ap, int);
char tmp[32];
int tmp_len = snprintf(tmp, sizeof(tmp), "%d", i);
smart_str_appendl(&buf, tmp, tmp_len);
}
break;
}
} else {
smart_str_appendc(&buf, *p);
}
p++;
}
smart_str_0(&buf);
*ret = buf.c;
return buf.len;
}
/*
* Append a command sequence to a Redis command
*/
int redis_cmd_append_str(char **cmd, int cmd_len, char *append, int append_len) {
// Smart string buffer
smart_str buf = {0};
// Append the current command to our smart_str
smart_str_appendl(&buf, *cmd, cmd_len);
// Append our new command sequence
smart_str_appendc(&buf, '$');
smart_str_append_long(&buf, append_len);
smart_str_appendl(&buf, _NL, sizeof(_NL) -1);
smart_str_appendl(&buf, append, append_len);
smart_str_appendl(&buf, _NL, sizeof(_NL) -1);
// Free our old command
efree(*cmd);
// Set our return pointer
*cmd = buf.c;
// Return new command length
return buf.len;
}
/*
* Given a smart string, number of arguments, a keyword, and the length of the keyword
* initialize our smart string with the proper Redis header for the command to follow
*/
int redis_cmd_init_sstr(smart_str *str, int num_args, char *keyword, int keyword_len) {
smart_str_appendc(str, '*');
smart_str_append_long(str, num_args + 1);
smart_str_appendl(str, _NL, sizeof(_NL) -1);
smart_str_appendc(str, '$');
smart_str_append_long(str, keyword_len);
smart_str_appendl(str, _NL, sizeof(_NL) - 1);
smart_str_appendl(str, keyword, keyword_len);
smart_str_appendl(str, _NL, sizeof(_NL) - 1);
return str->len;
}
/*
* Append a command sequence to a smart_str
*/
int redis_cmd_append_sstr(smart_str *str, char *append, int append_len) {
smart_str_appendc(str, '$');
smart_str_append_long(str, append_len);
smart_str_appendl(str, _NL, sizeof(_NL) - 1);
smart_str_appendl(str, append, append_len);
smart_str_appendl(str, _NL, sizeof(_NL) - 1);
// Return our new length
return str->len;
}
/*
* Append an integer to a smart string command
*/
int redis_cmd_append_sstr_int(smart_str *str, int append) {
char int_buf[32];
int int_len = snprintf(int_buf, sizeof(int_buf), "%d", append);
return redis_cmd_append_sstr(str, int_buf, int_len);
}
/*
* Append a long to a smart string command
*/
int redis_cmd_append_sstr_long(smart_str *str, long append) {
char long_buf[32];
int long_len = snprintf(long_buf, sizeof(long_buf), "%ld", append);
return redis_cmd_append_sstr(str, long_buf, long_len);
}
/*
* Append a double to a smart string command
*/
int redis_cmd_append_sstr_dbl(smart_str *str, double value) {
char *dbl_str;
int dbl_len;
/// Convert to double
REDIS_DOUBLE_TO_STRING(dbl_str, dbl_len, value);
// Append the string
int retval = redis_cmd_append_sstr(str, dbl_str, dbl_len);
// Free our double string
efree(dbl_str);
// Return new length
return retval;
}
/*
* Append an integer command to a Redis command
*/
int redis_cmd_append_int(char **cmd, int cmd_len, int append) {
char int_buf[32];
// Conver to an int, capture length
int int_len = snprintf(int_buf, sizeof(int_buf), "%d", append);
// Return the new length
return redis_cmd_append_str(cmd, cmd_len, int_buf, int_len);
}
PHPAPI void redis_bulk_double_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) {
char *response;
int response_len;
double ret;
if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) {
IF_MULTI_OR_PIPELINE() {
add_next_index_bool(z_tab, 0);
} else {
RETURN_FALSE;
}
return;
}
ret = atof(response);
efree(response);
IF_MULTI_OR_PIPELINE() {
add_next_index_double(z_tab, ret);
} else {
RETURN_DOUBLE(ret);
}
}
PHPAPI void redis_type_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) {
char *response;
int response_len;
long l;
if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) {
IF_MULTI_OR_PIPELINE() {
add_next_index_bool(z_tab, 0);
} else {
RETURN_FALSE;
}
}
if (strncmp(response, "+string", 7) == 0) {
l = REDIS_STRING;
} else if (strncmp(response, "+set", 4) == 0){
l = REDIS_SET;
} else if (strncmp(response, "+list", 5) == 0){
l = REDIS_LIST;
} else if (strncmp(response, "+zset", 5) == 0){
l = REDIS_ZSET;
} else if (strncmp(response, "+hash", 5) == 0){
l = REDIS_HASH;
} else {
l = REDIS_NOT_FOUND;
}
efree(response);
IF_MULTI_OR_PIPELINE() {
add_next_index_long(z_tab, l);
} else {
RETURN_LONG(l);
}
}
PHPAPI void redis_info_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) {
char *response;
int response_len;
char *pos, *cur;
char *key, *value, *p;
int is_numeric;
zval *z_multi_result;
if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) {
RETURN_FALSE;
}
MAKE_STD_ZVAL(z_multi_result);
array_init(z_multi_result); /* pre-allocate array for multi's results. */
/* response :: [response_line]
* response_line :: key ':' value CRLF
*/
cur = response;
while(1) {
/* skip comments and empty lines */
if(*cur == '#' || *cur == '\r') {
if(!(cur = strchr(cur, '\n')))
break;
cur++;
continue;
}
/* key */
pos = strchr(cur, ':');
if(pos == NULL) {
break;
}
key = emalloc(pos - cur + 1);
memcpy(key, cur, pos-cur);
key[pos-cur] = 0;
/* value */
cur = pos + 1;
pos = strchr(cur, '\r');
if(pos == NULL) {
break;
}
value = emalloc(pos - cur + 1);
memcpy(value, cur, pos-cur);
value[pos-cur] = 0;
pos += 2; /* \r, \n */
cur = pos;
is_numeric = 1;
for(p = value; *p; ++p) {
if(*p < '0' || *p > '9') {
is_numeric = 0;
break;
}
}
if(is_numeric == 1) {
add_assoc_long(z_multi_result, key, atol(value));
efree(value);
} else {
add_assoc_string(z_multi_result, key, value, 0);
}
efree(key);
}
efree(response);
IF_MULTI_OR_PIPELINE() {
add_next_index_zval(z_tab, z_multi_result);
} else {
RETVAL_ZVAL(z_multi_result, 0, 1);
}
}
/*
* Specialized handling of the CLIENT LIST output so it comes out in a simple way for PHP userland code
* to handle.
*/
PHPAPI void redis_client_list_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab) {
char *resp;
int resp_len;
zval *z_result, *z_sub_result;
// Make sure we can read a response from Redis
if((resp = redis_sock_read(redis_sock, &resp_len TSRMLS_CC)) == NULL) {
RETURN_FALSE;
}
// Allocate memory for our response
MAKE_STD_ZVAL(z_result);
array_init(z_result);
// Allocate memory for one user (there should be at least one, namely us!)
ALLOC_INIT_ZVAL(z_sub_result);
array_init(z_sub_result);
// Pointers for parsing
char *p = resp, *lpos = resp, *kpos = NULL, *vpos = NULL, *p2, *key, *value;
// Key length, done flag
int klen, done = 0, is_numeric;
// While we've got more to parse
while(!done) {
// What character are we on
switch(*p) {
/* We're done */
case '\0':
done = 1;
break;
/* \n, ' ' mean we can pull a k/v pair */
case '\n':
case ' ':
// Grab our value
vpos = lpos;
// There is some communication error or Redis bug if we don't
// have a key and value, but check anyway.
if(kpos && vpos) {
// Allocate, copy in our key
key = emalloc(klen + 1);
strncpy(key, kpos, klen);
key[klen] = 0;
// Allocate, copy in our value
value = emalloc(p-lpos+1);
strncpy(value,lpos,p-lpos+1);
value[p-lpos]=0;
// Treat numbers as numbers, strings as strings
is_numeric = 1;
for(p2 = value; *p; ++p) {
if(*p < '0' || *p > '9') {
is_numeric = 0;
break;
}
}
// Add as a long or string, depending
if(is_numeric == 1) {
add_assoc_long(z_sub_result, key, atol(value));
efree(value);
} else {
add_assoc_string(z_sub_result, key, value, 0);
}
// If we hit a '\n', then we can add this user to our list
if(*p == '\n') {
// Add our user
add_next_index_zval(z_result, z_sub_result);
// If we have another user, make another one
if(*(p+1) != '\0') {
ALLOC_INIT_ZVAL(z_sub_result);
array_init(z_sub_result);
}
}
// Free our key
efree(key);
} else {
// Something is wrong
efree(resp);
RETURN_FALSE;
}
// Move forward
lpos = p + 1;
break;
/* We can pull the key and null terminate at our sep */
case '=':
// Key, key length
kpos = lpos;
klen = p - lpos;
// Move forward
lpos = p + 1;
break;
}
// Increment
p++;
}
// Free our respoonse
efree(resp);
IF_MULTI_OR_PIPELINE() {
add_next_index_zval(z_tab, z_result);
} else {
RETVAL_ZVAL(z_result, 0, 1);
}
}
PHPAPI void redis_boolean_response_impl(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx, SuccessCallback success_callback) {
char *response;
int response_len;
char ret;
if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) {
IF_MULTI_OR_PIPELINE() {
add_next_index_bool(z_tab, 0);
return;
}
RETURN_FALSE;
}
ret = response[0];
efree(response);
IF_MULTI_OR_PIPELINE() {
if (ret == '+') {
if (success_callback != NULL) {
success_callback(redis_sock);
}
add_next_index_bool(z_tab, 1);
} else {
add_next_index_bool(z_tab, 0);
}
} else {
if (ret == '+') {
if (success_callback != NULL) {
success_callback(redis_sock);
}
RETURN_TRUE;
} else {
RETURN_FALSE;
}
}
}
PHPAPI void redis_boolean_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) {
redis_boolean_response_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, ctx, NULL);
}
PHPAPI void redis_long_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval * z_tab, void *ctx) {
char *response;
int response_len;
if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) {
IF_MULTI_OR_PIPELINE() {
add_next_index_bool(z_tab, 0);
return;
} else {
RETURN_FALSE;
}
}
if(response[0] == ':') {
long long ret = atoll(response + 1);
IF_MULTI_OR_PIPELINE() {
if(ret > LONG_MAX) { /* overflow */
add_next_index_stringl(z_tab, response+1, response_len-1, 1);
} else {
efree(response);
add_next_index_long(z_tab, (long)ret);
}
} else {
if(ret > LONG_MAX) { /* overflow */
RETURN_STRINGL(response+1, response_len-1, 1);
} else {
efree(response);
RETURN_LONG((long)ret);
}
}
} else {
efree(response);
IF_MULTI_OR_PIPELINE() {
add_next_index_null(z_tab);
} else {
RETURN_FALSE;
}
}
}
PHPAPI int redis_sock_read_multibulk_reply_zipped_with_flag(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, int flag) {
/*
int ret = redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab TSRMLS_CC);
array_zip_values_and_scores(return_value, 0);
*/
char inbuf[1024];
int numElems;
zval *z_multi_result;
if(-1 == redis_check_eof(redis_sock TSRMLS_CC)) {
return -1;
}
if(php_stream_gets(redis_sock->stream, inbuf, 1024) == NULL) {
redis_stream_close(redis_sock TSRMLS_CC);
redis_sock->stream = NULL;
redis_sock->status = REDIS_SOCK_STATUS_FAILED;
redis_sock->mode = ATOMIC;
redis_sock->watching = 0;
zend_throw_exception(redis_exception_ce, "read error on connection", 0 TSRMLS_CC);
return -1;
}
if(inbuf[0] != '*') {
IF_MULTI_OR_PIPELINE() {
add_next_index_bool(z_tab, 0);
} else {
RETVAL_FALSE;
}
return -1;
}
numElems = atoi(inbuf+1);
MAKE_STD_ZVAL(z_multi_result);
array_init(z_multi_result); /* pre-allocate array for multi's results. */
redis_sock_read_multibulk_reply_loop(INTERNAL_FUNCTION_PARAM_PASSTHRU,
redis_sock, z_multi_result, numElems, 1, flag ? UNSERIALIZE_ALL : UNSERIALIZE_ONLY_VALUES);
array_zip_values_and_scores(redis_sock, z_multi_result, 0 TSRMLS_CC);
IF_MULTI_OR_PIPELINE() {
add_next_index_zval(z_tab, z_multi_result);
} else {
*return_value = *z_multi_result;
zval_copy_ctor(return_value);
zval_dtor(z_multi_result);
efree(z_multi_result);
}
return 0;
}
PHPAPI int redis_sock_read_multibulk_reply_zipped(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) {
return redis_sock_read_multibulk_reply_zipped_with_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, 1);
}
PHPAPI int redis_sock_read_multibulk_reply_zipped_strings(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) {
return redis_sock_read_multibulk_reply_zipped_with_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, 0);
}
PHPAPI void redis_1_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) {
char *response;
int response_len;
char ret;
if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) {
IF_MULTI_OR_PIPELINE() {
add_next_index_bool(z_tab, 0);
return;
} else {
RETURN_FALSE;
}
}
ret = response[1];
efree(response);
IF_MULTI_OR_PIPELINE() {
if(ret == '1') {
add_next_index_bool(z_tab, 1);
} else {
add_next_index_bool(z_tab, 0);
}
} else {
if (ret == '1') {
RETURN_TRUE;
} else {
RETURN_FALSE;
}
}
}
PHPAPI void redis_string_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) {
char *response;
int response_len;
if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) {
IF_MULTI_OR_PIPELINE() {
add_next_index_bool(z_tab, 0);
return;
}
RETURN_FALSE;
}
IF_MULTI_OR_PIPELINE() {
zval *z = NULL;
if(redis_unserialize(redis_sock, response, response_len, &z TSRMLS_CC) == 1) {
efree(response);
add_next_index_zval(z_tab, z);
} else {
add_next_index_stringl(z_tab, response, response_len, 0);
}
} else {
if(redis_unserialize(redis_sock, response, response_len, &return_value TSRMLS_CC) == 0) {
RETURN_STRINGL(response, response_len, 0);
} else {
efree(response);
}
}
}
/* like string response, but never unserialized. */
PHPAPI void redis_ping_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) {
char *response;
int response_len;
if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) {
IF_MULTI_OR_PIPELINE() {
add_next_index_bool(z_tab, 0);
return;
}
RETURN_FALSE;
}
IF_MULTI_OR_PIPELINE() {
add_next_index_stringl(z_tab, response, response_len, 0);
} else {
RETURN_STRINGL(response, response_len, 0);
}
}
/**
* redis_sock_create
*/
PHPAPI RedisSock* redis_sock_create(char *host, int host_len, unsigned short port,
double timeout, int persistent, char *persistent_id,
long retry_interval,
zend_bool lazy_connect)
{
RedisSock *redis_sock;
redis_sock = ecalloc(1, sizeof(RedisSock));
redis_sock->host = estrndup(host, host_len);
redis_sock->stream = NULL;
redis_sock->status = REDIS_SOCK_STATUS_DISCONNECTED;
redis_sock->watching = 0;
redis_sock->dbNumber = 0;
redis_sock->retry_interval = retry_interval * 1000;
redis_sock->persistent = persistent;
redis_sock->lazy_connect = lazy_connect;
if(persistent_id) {
size_t persistent_id_len = strlen(persistent_id);
redis_sock->persistent_id = ecalloc(persistent_id_len + 1, 1);
memcpy(redis_sock->persistent_id, persistent_id, persistent_id_len);
} else {
redis_sock->persistent_id = NULL;
}
memcpy(redis_sock->host, host, host_len);
redis_sock->host[host_len] = '\0';
redis_sock->port = port;
redis_sock->timeout = timeout;
redis_sock->read_timeout = timeout;
redis_sock->serializer = REDIS_SERIALIZER_NONE;
redis_sock->mode = ATOMIC;
redis_sock->head = NULL;
redis_sock->current = NULL;
redis_sock->pipeline_head = NULL;
redis_sock->pipeline_current = NULL;
redis_sock->err = NULL;
redis_sock->err_len = 0;
return redis_sock;
}
/**
* redis_sock_connect
*/
PHPAPI int redis_sock_connect(RedisSock *redis_sock TSRMLS_DC)
{
struct timeval tv, read_tv, *tv_ptr = NULL;
char *host = NULL, *persistent_id = NULL, *errstr = NULL;
int host_len, err = 0;
php_netstream_data_t *sock;
int tcp_flag = 1;
if (redis_sock->stream != NULL) {
redis_sock_disconnect(redis_sock TSRMLS_CC);
}
tv.tv_sec = (time_t)redis_sock->timeout;
tv.tv_usec = (int)((redis_sock->timeout - tv.tv_sec) * 1000000);
if(tv.tv_sec != 0 || tv.tv_usec != 0) {
tv_ptr = &tv;
}
read_tv.tv_sec = (time_t)redis_sock->read_timeout;
read_tv.tv_usec = (int)((redis_sock->read_timeout - read_tv.tv_sec) * 1000000);
if(redis_sock->host[0] == '/' && redis_sock->port < 1) {
host_len = spprintf(&host, 0, "unix://%s", redis_sock->host);
} else {
if(redis_sock->port == 0)
redis_sock->port = 6379;
host_len = spprintf(&host, 0, "%s:%d", redis_sock->host, redis_sock->port);
}
if (redis_sock->persistent) {
if (redis_sock->persistent_id) {
spprintf(&persistent_id, 0, "phpredis:%s:%s", host, redis_sock->persistent_id);
} else {
spprintf(&persistent_id, 0, "phpredis:%s:%f", host, redis_sock->timeout);
}
}
redis_sock->stream = php_stream_xport_create(host, host_len, ENFORCE_SAFE_MODE,
STREAM_XPORT_CLIENT
| STREAM_XPORT_CONNECT,
persistent_id, tv_ptr, NULL, &errstr, &err
);
if (persistent_id) {
efree(persistent_id);
}
efree(host);
if (!redis_sock->stream) {
efree(errstr);
return -1;
}
/* set TCP_NODELAY */
sock = (php_netstream_data_t*)redis_sock->stream->abstract;
setsockopt(sock->socket, IPPROTO_TCP, TCP_NODELAY, (char *) &tcp_flag, sizeof(int));
php_stream_auto_cleanup(redis_sock->stream);
if(tv.tv_sec != 0 || tv.tv_usec != 0) {
php_stream_set_option(redis_sock->stream, PHP_STREAM_OPTION_READ_TIMEOUT,
0, &read_tv);
}
php_stream_set_option(redis_sock->stream,
PHP_STREAM_OPTION_WRITE_BUFFER,
PHP_STREAM_BUFFER_NONE, NULL);
redis_sock->status = REDIS_SOCK_STATUS_CONNECTED;
return 0;
}
/**
* redis_sock_server_open
*/
PHPAPI int redis_sock_server_open(RedisSock *redis_sock, int force_connect TSRMLS_DC)
{
int res = -1;
switch (redis_sock->status) {
case REDIS_SOCK_STATUS_DISCONNECTED:
return redis_sock_connect(redis_sock TSRMLS_CC);
case REDIS_SOCK_STATUS_CONNECTED:
res = 0;
break;
case REDIS_SOCK_STATUS_UNKNOWN:
if (force_connect > 0 && redis_sock_connect(redis_sock TSRMLS_CC) < 0) {
res = -1;
} else {
res = 0;
redis_sock->status = REDIS_SOCK_STATUS_CONNECTED;
}
break;
}
return res;
}
/**
* redis_sock_disconnect
*/
PHPAPI int redis_sock_disconnect(RedisSock *redis_sock TSRMLS_DC)
{
if (redis_sock == NULL) {
return 1;
}
redis_sock->dbNumber = 0;
if (redis_sock->stream != NULL) {
if (!redis_sock->persistent) {
redis_sock_write(redis_sock, "QUIT" _NL, sizeof("QUIT" _NL) - 1 TSRMLS_CC);
}
redis_sock->status = REDIS_SOCK_STATUS_DISCONNECTED;
redis_sock->watching = 0;
if(redis_sock->stream && !redis_sock->persistent) { /* still valid after the write? */
php_stream_close(redis_sock->stream);
}
redis_sock->stream = NULL;
return 1;
}
return 0;
}
PHPAPI void redis_send_discard(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock)
{
char *cmd;
int response_len, cmd_len;
char * response;
cmd_len = redis_cmd_format_static(&cmd, "DISCARD", "");
if (redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) {
efree(cmd);
RETURN_FALSE;
}
efree(cmd);
if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) {
RETURN_FALSE;
}
if(response_len == 3 && strncmp(response, "+OK", 3) == 0) {
RETURN_TRUE;
}
RETURN_FALSE;
}
/**
* redis_sock_set_err
*/
PHPAPI int redis_sock_set_err(RedisSock *redis_sock, const char *msg, int msg_len) {
// Allocate/Reallocate our last error member
if(msg != NULL && msg_len > 0) {
if(redis_sock->err == NULL) {
redis_sock->err = emalloc(msg_len + 1);
} else if(msg_len > redis_sock->err_len) {
redis_sock->err = erealloc(redis_sock->err, msg_len +1);
}
// Copy in our new error message, set new length, and null terminate
memcpy(redis_sock->err, msg, msg_len);
redis_sock->err[msg_len] = '\0';
redis_sock->err_len = msg_len;
} else {
// Free our last error
if(redis_sock->err != NULL) {
efree(redis_sock->err);
}
// Set to null, with zero length
redis_sock->err = NULL;
redis_sock->err_len = 0;
}
// Success
return 0;
}
/**
* redis_sock_read_multibulk_reply
*/
PHPAPI int redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx)
{
char inbuf[1024];
int numElems;
zval *z_multi_result;
if(-1 == redis_check_eof(redis_sock TSRMLS_CC)) {
return -1;
}
if(php_stream_gets(redis_sock->stream, inbuf, 1024) == NULL) {
redis_stream_close(redis_sock TSRMLS_CC);
redis_sock->stream = NULL;
redis_sock->status = REDIS_SOCK_STATUS_FAILED;
redis_sock->mode = ATOMIC;
redis_sock->watching = 0;
zend_throw_exception(redis_exception_ce, "read error on connection", 0 TSRMLS_CC);
return -1;
}
if(inbuf[0] != '*') {
IF_MULTI_OR_PIPELINE() {
add_next_index_bool(z_tab, 0);
} else {
RETVAL_FALSE;
}
return -1;
}
numElems = atoi(inbuf+1);
MAKE_STD_ZVAL(z_multi_result);
array_init(z_multi_result); /* pre-allocate array for multi's results. */
redis_sock_read_multibulk_reply_loop(INTERNAL_FUNCTION_PARAM_PASSTHRU,
redis_sock, z_multi_result, numElems, 1, UNSERIALIZE_ALL);
IF_MULTI_OR_PIPELINE() {
add_next_index_zval(z_tab, z_multi_result);
} else {
*return_value = *z_multi_result;
efree(z_multi_result);
}
//zval_copy_ctor(return_value);
return 0;
}
/**
* Like multibulk reply, but don't touch the values, they won't be compressed. (this is used by HKEYS).
*/
PHPAPI int redis_sock_read_multibulk_reply_raw(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx)
{
char inbuf[1024];
int numElems;
zval *z_multi_result;
if(-1 == redis_check_eof(redis_sock TSRMLS_CC)) {
return -1;
}
if(php_stream_gets(redis_sock->stream, inbuf, 1024) == NULL) {
redis_stream_close(redis_sock TSRMLS_CC);
redis_sock->stream = NULL;
redis_sock->status = REDIS_SOCK_STATUS_FAILED;
redis_sock->mode = ATOMIC;
redis_sock->watching = 0;
zend_throw_exception(redis_exception_ce, "read error on connection", 0 TSRMLS_CC);
return -1;
}
if(inbuf[0] != '*') {
IF_MULTI_OR_PIPELINE() {
add_next_index_bool(z_tab, 0);
} else {
RETVAL_FALSE;
}
return -1;
}
numElems = atoi(inbuf+1);
MAKE_STD_ZVAL(z_multi_result);
array_init(z_multi_result); /* pre-allocate array for multi's results. */
redis_sock_read_multibulk_reply_loop(INTERNAL_FUNCTION_PARAM_PASSTHRU,
redis_sock, z_multi_result, numElems, 0, UNSERIALIZE_ALL);
IF_MULTI_OR_PIPELINE() {
add_next_index_zval(z_tab, z_multi_result);
} else {
*return_value = *z_multi_result;
efree(z_multi_result);
}
//zval_copy_ctor(return_value);
return 0;
}
PHPAPI int
redis_sock_read_multibulk_reply_loop(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
zval *z_tab, int numElems, int unwrap_key, int unserialize_even_only)
{
char *response;
int response_len;
while(numElems > 0) {
response = redis_sock_read(redis_sock, &response_len TSRMLS_CC);
if(response != NULL) {
zval *z = NULL;
int can_unserialize = unwrap_key;
if(unserialize_even_only == UNSERIALIZE_ONLY_VALUES && numElems % 2 == 0)
can_unserialize = 0;
if(can_unserialize && redis_unserialize(redis_sock, response, response_len, &z TSRMLS_CC) == 1) {
efree(response);
add_next_index_zval(z_tab, z);
} else {
add_next_index_stringl(z_tab, response, response_len, 0);
}
} else {
add_next_index_bool(z_tab, 0);
}
numElems --;
}
return 0;
}
/**
* redis_sock_read_multibulk_reply_assoc
*/
PHPAPI int redis_sock_read_multibulk_reply_assoc(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx)
{
char inbuf[1024], *response;
int response_len;
int i, numElems;
zval *z_multi_result;
zval **z_keys = ctx;
if(-1 == redis_check_eof(redis_sock TSRMLS_CC)) {
return -1;
}
if(php_stream_gets(redis_sock->stream, inbuf, 1024) == NULL) {
redis_stream_close(redis_sock TSRMLS_CC);
redis_sock->stream = NULL;
redis_sock->status = REDIS_SOCK_STATUS_FAILED;
redis_sock->mode = ATOMIC;
redis_sock->watching = 0;
zend_throw_exception(redis_exception_ce, "read error on connection", 0 TSRMLS_CC);
return -1;
}
if(inbuf[0] != '*') {
IF_MULTI_OR_PIPELINE() {
add_next_index_bool(z_tab, 0);
} else {
RETVAL_FALSE;
}
return -1;
}
numElems = atoi(inbuf+1);
MAKE_STD_ZVAL(z_multi_result);
array_init(z_multi_result); /* pre-allocate array for multi's results. */
for(i = 0; i < numElems; ++i) {
response = redis_sock_read(redis_sock, &response_len TSRMLS_CC);
if(response != NULL) {
zval *z = NULL;
if(redis_unserialize(redis_sock, response, response_len, &z TSRMLS_CC) == 1) {
efree(response);
add_assoc_zval_ex(z_multi_result, Z_STRVAL_P(z_keys[i]), 1+Z_STRLEN_P(z_keys[i]), z);
} else {
add_assoc_stringl_ex(z_multi_result, Z_STRVAL_P(z_keys[i]), 1+Z_STRLEN_P(z_keys[i]), response, response_len, 0);
}
} else {
add_assoc_bool_ex(z_multi_result, Z_STRVAL_P(z_keys[i]), 1+Z_STRLEN_P(z_keys[i]), 0);
}
zval_dtor(z_keys[i]);
efree(z_keys[i]);
}
efree(z_keys);
IF_MULTI_OR_PIPELINE() {
add_next_index_zval(z_tab, z_multi_result);
} else {
*return_value = *z_multi_result;
zval_copy_ctor(return_value);
INIT_PZVAL(return_value);
zval_dtor(z_multi_result);
efree(z_multi_result);
}
return 0;
}
/**
* redis_sock_write
*/
PHPAPI int redis_sock_write(RedisSock *redis_sock, char *cmd, size_t sz TSRMLS_DC)
{
if(redis_sock && redis_sock->status == REDIS_SOCK_STATUS_DISCONNECTED) {
zend_throw_exception(redis_exception_ce, "Connection closed", 0 TSRMLS_CC);
return -1;
}
if(-1 == redis_check_eof(redis_sock TSRMLS_CC)) {
return -1;
}
return php_stream_write(redis_sock->stream, cmd, sz);
}
/**
* redis_free_socket
*/
PHPAPI void redis_free_socket(RedisSock *redis_sock)
{
if(redis_sock->prefix) {
efree(redis_sock->prefix);
}
if(redis_sock->err) {
efree(redis_sock->err);
}
if(redis_sock->auth) {
efree(redis_sock->auth);
}
if(redis_sock->persistent_id) {
efree(redis_sock->persistent_id);
}
efree(redis_sock->host);
efree(redis_sock);
}
PHPAPI int
redis_serialize(RedisSock *redis_sock, zval *z, char **val, int *val_len TSRMLS_DC) {
#if ZEND_MODULE_API_NO >= 20100000
php_serialize_data_t ht;
#else
HashTable ht;
#endif
smart_str sstr = {0};
zval *z_copy;
size_t sz;
uint8_t *val8;
switch(redis_sock->serializer) {
case REDIS_SERIALIZER_NONE:
switch(Z_TYPE_P(z)) {
case IS_STRING:
*val = Z_STRVAL_P(z);
*val_len = Z_STRLEN_P(z);
return 0;
case IS_OBJECT:
MAKE_STD_ZVAL(z_copy);
ZVAL_STRINGL(z_copy, "Object", 6, 1);
break;
case IS_ARRAY:
MAKE_STD_ZVAL(z_copy);
ZVAL_STRINGL(z_copy, "Array", 5, 1);
break;
default: /* copy */
MAKE_STD_ZVAL(z_copy);
*z_copy = *z;
zval_copy_ctor(z_copy);
break;
}
/* return string */
convert_to_string(z_copy);
*val = Z_STRVAL_P(z_copy);
*val_len = Z_STRLEN_P(z_copy);
efree(z_copy);
return 1;
case REDIS_SERIALIZER_PHP:
#if ZEND_MODULE_API_NO >= 20100000
PHP_VAR_SERIALIZE_INIT(ht);
#else
zend_hash_init(&ht, 10, NULL, NULL, 0);
#endif
php_var_serialize(&sstr, &z, &ht TSRMLS_CC);
*val = sstr.c;
*val_len = (int)sstr.len;
#if ZEND_MODULE_API_NO >= 20100000
PHP_VAR_SERIALIZE_DESTROY(ht);
#else
zend_hash_destroy(&ht);
#endif
return 1;
case REDIS_SERIALIZER_IGBINARY:
#ifdef HAVE_REDIS_IGBINARY
if(igbinary_serialize(&val8, (size_t *)&sz, z TSRMLS_CC) == 0) { /* ok */
*val = (char*)val8;
*val_len = (int)sz;
return 1;
}
#endif
return 0;
}
return 0;
}
PHPAPI int
redis_unserialize(RedisSock *redis_sock, const char *val, int val_len, zval **return_value TSRMLS_DC) {
php_unserialize_data_t var_hash;
int ret, rv_free = 0;
switch(redis_sock->serializer) {
case REDIS_SERIALIZER_NONE:
return 0;
case REDIS_SERIALIZER_PHP:
if(!*return_value) {
MAKE_STD_ZVAL(*return_value);
rv_free = 1;
}
#if ZEND_MODULE_API_NO >= 20100000
PHP_VAR_UNSERIALIZE_INIT(var_hash);
#else
memset(&var_hash, 0, sizeof(var_hash));
#endif
if(!php_var_unserialize(return_value, (const unsigned char**)&val,
(const unsigned char*)val + val_len, &var_hash TSRMLS_CC)) {
if(rv_free==1) efree(*return_value);
ret = 0;
} else {
ret = 1;
}
#if ZEND_MODULE_API_NO >= 20100000
PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
#else
var_destroy(&var_hash);
#endif
return ret;
case REDIS_SERIALIZER_IGBINARY:
#ifdef HAVE_REDIS_IGBINARY
if(!*return_value) {
MAKE_STD_ZVAL(*return_value);
}
if(igbinary_unserialize((const uint8_t *)val, (size_t)val_len, return_value TSRMLS_CC) == 0) {
return 1;
}
efree(*return_value);
#endif
return 0;
break;
}
return 0;
}
PHPAPI int
redis_key_prefix(RedisSock *redis_sock, char **key, int *key_len TSRMLS_DC) {
int ret_len;
char *ret;
if(redis_sock->prefix == NULL || redis_sock->prefix_len == 0) {
return 0;
}
ret_len = redis_sock->prefix_len + *key_len;
ret = ecalloc(1 + ret_len, 1);
memcpy(ret, redis_sock->prefix, redis_sock->prefix_len);
memcpy(ret + redis_sock->prefix_len, *key, *key_len);
*key = ret;
*key_len = ret_len;
return 1;
}
/*
* Processing for variant reply types (think EVAL)
*/
PHPAPI int
redis_sock_gets(RedisSock *redis_sock, char *buf, int buf_size, size_t *line_size TSRMLS_DC) {
// Handle EOF
if(-1 == redis_check_eof(redis_sock TSRMLS_CC)) {
return -1;
}
if(php_stream_get_line(redis_sock->stream, buf, buf_size, line_size) == NULL) {
// Close, put our socket state into error
redis_stream_close(redis_sock TSRMLS_CC);
redis_sock->stream = NULL;
redis_sock->status = REDIS_SOCK_STATUS_FAILED;
redis_sock->mode = ATOMIC;
redis_sock->watching = 0;
// Throw a read error exception
zend_throw_exception(redis_exception_ce, "read error on connection", 0 TSRMLS_CC);
}
// We don't need \r\n
*line_size-=2;
buf[*line_size]='\0';
// Success!
return 0;
}
PHPAPI int
redis_read_reply_type(RedisSock *redis_sock, REDIS_REPLY_TYPE *reply_type, int *reply_info TSRMLS_DC) {
// Make sure we haven't lost the connection, even trying to reconnect
if(-1 == redis_check_eof(redis_sock TSRMLS_CC)) {
// Failure
return -1;
}
// Attempt to read the reply-type byte
if((*reply_type = php_stream_getc(redis_sock->stream)) == EOF) {
zend_throw_exception(redis_exception_ce, "socket error on read socket", 0 TSRMLS_CC);
}
// If this is a BULK, MULTI BULK, or simply an INTEGER response, we can extract the value or size info here
if(*reply_type == TYPE_INT || *reply_type == TYPE_BULK || *reply_type == TYPE_MULTIBULK) {
// Buffer to hold size information
char inbuf[255];
// Read up to our newline
if(php_stream_gets(redis_sock->stream, inbuf, sizeof(inbuf)) < 0) {
return -1;
}
// Set our size response
*reply_info = atoi(inbuf);
}
// Success!
return 0;
}
/*
* Read a single line response, having already consumed the reply-type byte
*/
PHPAPI int
redis_read_variant_line(RedisSock *redis_sock, REDIS_REPLY_TYPE reply_type, zval **z_ret TSRMLS_DC) {
// Buffer to read our single line reply
char inbuf[1024];
size_t line_size;
// Attempt to read our single line reply
if(redis_sock_gets(redis_sock, inbuf, sizeof(inbuf), &line_size TSRMLS_CC) < 0) {
return -1;
}
// If this is an error response, check if it is a SYNC error, and throw in that case
if(reply_type == TYPE_ERR) {
if(memcmp(inbuf, "ERR SYNC", 9) == 0) {
zend_throw_exception(redis_exception_ce, "SYNC with master in progress", 0 TSRMLS_CC);
}
// Set our last error
redis_sock_set_err(redis_sock, inbuf, line_size);
// Set our response to FALSE
ZVAL_FALSE(*z_ret);
} else {
// Set our response to TRUE
ZVAL_TRUE(*z_ret);
}
return 0;
}
PHPAPI int
redis_read_variant_bulk(RedisSock *redis_sock, int size, zval **z_ret TSRMLS_DC) {
// Attempt to read the bulk reply
char *bulk_resp = redis_sock_read_bulk_reply(redis_sock, size TSRMLS_CC);
// Set our reply to FALSE on failure, and the string on success
if(bulk_resp == NULL) {
ZVAL_FALSE(*z_ret);
return -1;
} else {
ZVAL_STRINGL(*z_ret, bulk_resp, size, 0);
return 0;
}
}
PHPAPI int
redis_read_multibulk_recursive(RedisSock *redis_sock, int elements, zval **z_ret TSRMLS_DC) {
int reply_info;
REDIS_REPLY_TYPE reply_type;
zval *z_subelem;
// Iterate while we have elements
while(elements > 0) {
// Attempt to read our reply type
if(redis_read_reply_type(redis_sock, &reply_type, &reply_info TSRMLS_CC) < 0) {
zend_throw_exception_ex(redis_exception_ce, 0 TSRMLS_CC, "protocol error, couldn't parse MULTI-BULK response\n", reply_type);
return -1;
}
// Switch on our reply-type byte
switch(reply_type) {
case TYPE_ERR:
case TYPE_LINE:
ALLOC_INIT_ZVAL(z_subelem);
redis_read_variant_line(redis_sock, reply_type, &z_subelem TSRMLS_CC);
add_next_index_zval(*z_ret, z_subelem);
break;
case TYPE_INT:
// Add our long value
add_next_index_long(*z_ret, reply_info);
break;
case TYPE_BULK:
// Init a zval for our bulk response, read and add it
ALLOC_INIT_ZVAL(z_subelem);
redis_read_variant_bulk(redis_sock, reply_info, &z_subelem TSRMLS_CC);
add_next_index_zval(*z_ret, z_subelem);
break;
case TYPE_MULTIBULK:
// Construct an array for our sub element, and add it, and recurse
ALLOC_INIT_ZVAL(z_subelem);
array_init(z_subelem);
add_next_index_zval(*z_ret, z_subelem);
redis_read_multibulk_recursive(redis_sock, reply_info, &z_subelem TSRMLS_CC);
break;
}
// Decrement our element counter
elements--;
}
return 0;
}
PHPAPI int
redis_read_variant_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab) {
// Reply type, and reply size vars
REDIS_REPLY_TYPE reply_type;
int reply_info;
//char *bulk_resp;
zval *z_ret;
// Attempt to read our header
if(redis_read_reply_type(redis_sock, &reply_type, &reply_info TSRMLS_CC) < 0) {
return -1;
}
// Our return ZVAL
MAKE_STD_ZVAL(z_ret);
// Switch based on our top level reply type
switch(reply_type) {
case TYPE_ERR:
case TYPE_LINE:
redis_read_variant_line(redis_sock, reply_type, &z_ret TSRMLS_CC);
break;
case TYPE_INT:
ZVAL_LONG(z_ret, reply_info);
break;
case TYPE_BULK:
redis_read_variant_bulk(redis_sock, reply_info, &z_ret TSRMLS_CC);
break;
case TYPE_MULTIBULK:
// Initialize an array for our multi-bulk response
array_init(z_ret);
// If we've got more than zero elements, parse our multi bulk respoinse recursively
if(reply_info > -1) {
redis_read_multibulk_recursive(redis_sock, reply_info, &z_ret TSRMLS_CC);
}
break;
default:
// Protocol error
zend_throw_exception_ex(redis_exception_ce, 0 TSRMLS_CC, "protocol error, got '%c' as reply-type byte\n", reply_type);
break;
}
IF_MULTI_OR_PIPELINE() {
add_next_index_zval(z_tab, z_ret);
} else {
// Set our return value
*return_value = *z_ret;
zval_copy_ctor(return_value);
zval_dtor(z_ret);
efree(z_ret);
}
// Success
return 0;
}
/* vim: set tabstop=4 softtabstop=4 noexpandtab shiftwidth=4: */