PageRenderTime 33ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 1ms

/library.c

http://github.com/nicolasff/phpredis
C | 2827 lines | 2131 code | 394 blank | 302 comment | 535 complexity | 6b7532b62dd83be256e65f998384c4d1 MD5 | raw file
Possible License(s): BSD-3-Clause, MPL-2.0-no-copyleft-exception
  1. #ifdef HAVE_CONFIG_H
  2. #include "config.h"
  3. #endif
  4. #include "common.h"
  5. #include "php_network.h"
  6. #include <sys/types.h>
  7. #ifdef HAVE_REDIS_IGBINARY
  8. #include "igbinary/igbinary.h"
  9. #endif
  10. #ifdef HAVE_REDIS_MSGPACK
  11. #include "msgpack/php_msgpack.h"
  12. #endif
  13. #ifdef HAVE_REDIS_LZF
  14. #include <lzf.h>
  15. #ifndef LZF_MARGIN
  16. #define LZF_MARGIN 128
  17. #endif
  18. #endif
  19. #ifdef HAVE_REDIS_ZSTD
  20. #include <zstd.h>
  21. #endif
  22. #include <zend_exceptions.h>
  23. #include "php_redis.h"
  24. #include "library.h"
  25. #include "redis_commands.h"
  26. #ifdef HAVE_REDIS_JSON
  27. #include <ext/json/php_json.h>
  28. #endif
  29. #include <ext/standard/php_rand.h>
  30. #define UNSERIALIZE_NONE 0
  31. #define UNSERIALIZE_KEYS 1
  32. #define UNSERIALIZE_VALS 2
  33. #define UNSERIALIZE_ALL 3
  34. #define SCORE_DECODE_NONE 0
  35. #define SCORE_DECODE_INT 1
  36. #define SCORE_DECODE_DOUBLE 2
  37. #ifndef PHP_WIN32
  38. #include <netinet/tcp.h> /* TCP_NODELAY */
  39. #include <sys/socket.h> /* SO_KEEPALIVE */
  40. #else
  41. #include <winsock.h>
  42. #endif
  43. extern zend_class_entry *redis_ce;
  44. extern zend_class_entry *redis_exception_ce;
  45. extern int le_redis_pconnect;
  46. static ConnectionPool *
  47. redis_sock_get_connection_pool(RedisSock *redis_sock)
  48. {
  49. ConnectionPool *p;
  50. zend_string *persistent_id = strpprintf(0, "phpredis_%s:%d", ZSTR_VAL(redis_sock->host), redis_sock->port);
  51. zend_resource *le;
  52. if ((le = zend_hash_find_ptr(&EG(persistent_list), persistent_id)) == NULL) {
  53. p = pecalloc(1, sizeof(*p), 1);
  54. zend_llist_init(&p->list, sizeof(php_stream *), NULL, 1);
  55. #if (PHP_VERSION_ID < 70300)
  56. zend_resource res;
  57. res.type = le_redis_pconnect;
  58. res.ptr = p;
  59. le = &res;
  60. zend_hash_str_update_mem(&EG(persistent_list), ZSTR_VAL(persistent_id), ZSTR_LEN(persistent_id), le, sizeof(*le));
  61. #else
  62. le = zend_register_persistent_resource(ZSTR_VAL(persistent_id), ZSTR_LEN(persistent_id), p, le_redis_pconnect);
  63. #endif
  64. }
  65. zend_string_release(persistent_id);
  66. return le->ptr;
  67. }
  68. /* Helper to reselect the proper DB number when we reconnect */
  69. static int reselect_db(RedisSock *redis_sock) {
  70. char *cmd, *response;
  71. int cmd_len, response_len;
  72. cmd_len = redis_spprintf(redis_sock, NULL, &cmd, "SELECT", "d",
  73. redis_sock->dbNumber);
  74. if (redis_sock_write(redis_sock, cmd, cmd_len) < 0) {
  75. efree(cmd);
  76. return -1;
  77. }
  78. efree(cmd);
  79. if ((response = redis_sock_read(redis_sock, &response_len)) == NULL) {
  80. return -1;
  81. }
  82. if (strncmp(response, "+OK", 3)) {
  83. efree(response);
  84. return -1;
  85. }
  86. efree(response);
  87. return 0;
  88. }
  89. /* Helper to resend AUTH <password> in the case of a reconnect */
  90. PHP_REDIS_API int
  91. redis_sock_auth(RedisSock *redis_sock)
  92. {
  93. char *cmd, *response;
  94. int cmd_len, response_len;
  95. cmd_len = redis_spprintf(redis_sock, NULL, &cmd, "AUTH", "S", redis_sock->auth);
  96. if (redis_sock_write(redis_sock, cmd, cmd_len) < 0) {
  97. efree(cmd);
  98. return -1;
  99. }
  100. efree(cmd);
  101. response = redis_sock_read(redis_sock, &response_len);
  102. if (response == NULL) {
  103. return -1;
  104. }
  105. if (strncmp(response, "+OK", 3)) {
  106. efree(response);
  107. return -1;
  108. }
  109. efree(response);
  110. return 0;
  111. }
  112. /* Helper function and macro to test a RedisSock error prefix. */
  113. #define REDIS_SOCK_ERRCMP_STATIC(rs, s) redis_sock_errcmp(rs, s, sizeof(s)-1)
  114. static int redis_sock_errcmp(RedisSock *redis_sock, const char *err, size_t errlen) {
  115. return ZSTR_LEN(redis_sock->err) >= errlen &&
  116. memcmp(ZSTR_VAL(redis_sock->err), err, errlen) == 0;
  117. }
  118. /* Helper function that will throw an exception for a small number of ERR codes
  119. * returned by Redis. Typically we just return FALSE to the caller in the event
  120. * of an ERROR reply, but for the following error types:
  121. * 1) MASTERDOWN
  122. * 2) AUTH
  123. * 3) LOADING
  124. */
  125. static void
  126. redis_error_throw(RedisSock *redis_sock)
  127. {
  128. /* Short circuit if we have no redis_sock or any error */
  129. if (redis_sock == NULL || redis_sock->err == NULL)
  130. return;
  131. /* We may want to flip this logic and check for MASTERDOWN, AUTH,
  132. * and LOADING but that may have side effects (esp for things like
  133. * Disque) */
  134. if (!REDIS_SOCK_ERRCMP_STATIC(redis_sock, "ERR") &&
  135. !REDIS_SOCK_ERRCMP_STATIC(redis_sock, "NOSCRIPT") &&
  136. !REDIS_SOCK_ERRCMP_STATIC(redis_sock, "NOQUORUM") &&
  137. !REDIS_SOCK_ERRCMP_STATIC(redis_sock, "NOGOODSLAVE") &&
  138. !REDIS_SOCK_ERRCMP_STATIC(redis_sock, "WRONGTYPE") &&
  139. !REDIS_SOCK_ERRCMP_STATIC(redis_sock, "BUSYGROUP") &&
  140. !REDIS_SOCK_ERRCMP_STATIC(redis_sock, "NOGROUP"))
  141. {
  142. REDIS_THROW_EXCEPTION( ZSTR_VAL(redis_sock->err), 0);
  143. }
  144. }
  145. PHP_REDIS_API int
  146. redis_check_eof(RedisSock *redis_sock, int no_throw)
  147. {
  148. int count;
  149. char *errmsg;
  150. if (!redis_sock || !redis_sock->stream || redis_sock->status == REDIS_SOCK_STATUS_FAILED) {
  151. if (!no_throw) {
  152. REDIS_THROW_EXCEPTION( "Connection closed", 0);
  153. }
  154. return -1;
  155. }
  156. /* NOITCE: set errno = 0 here
  157. *
  158. * There is a bug in php socket stream to check liveness of a connection:
  159. * if (0 >= recv(sock->socket, &buf, sizeof(buf), MSG_PEEK) && php_socket_errno() != EWOULDBLOCK) {
  160. * alive = 0;
  161. * }
  162. * If last errno is EWOULDBLOCK and recv returns 0 because of connection closed, alive would not be
  163. * set to 0. However, the connection is close indeed. The php_stream_eof is not reliable. This will
  164. * cause a "read error on connection" exception when use a closed persistent connection.
  165. *
  166. * We work around this by set errno = 0 first.
  167. *
  168. * Bug fix of php: https://github.com/php/php-src/pull/1456
  169. * */
  170. errno = 0;
  171. if (php_stream_eof(redis_sock->stream) == 0) {
  172. /* Success */
  173. return 0;
  174. } else if (redis_sock->mode == MULTI || redis_sock->watching) {
  175. errmsg = "Connection lost and socket is in MULTI/watching mode";
  176. } else {
  177. errmsg = "Connection lost";
  178. /* TODO: configurable max retry count */
  179. for (count = 0; count < 10; ++count) {
  180. /* close existing stream before reconnecting */
  181. if (redis_sock->stream) {
  182. redis_sock_disconnect(redis_sock, 1);
  183. }
  184. // Wait for a while before trying to reconnect
  185. if (redis_sock->retry_interval) {
  186. // Random factor to avoid having several (or many) concurrent connections trying to reconnect at the same time
  187. long retry_interval = (count ? redis_sock->retry_interval : (php_rand() % redis_sock->retry_interval));
  188. usleep(retry_interval);
  189. }
  190. /* reconnect */
  191. if (redis_sock_connect(redis_sock) == 0) {
  192. /* check for EOF again. */
  193. errno = 0;
  194. if (php_stream_eof(redis_sock->stream) == 0) {
  195. /* If we're using a password, attempt a reauthorization */
  196. if (redis_sock->auth && redis_sock_auth(redis_sock) != 0) {
  197. errmsg = "AUTH failed while reconnecting";
  198. break;
  199. }
  200. redis_sock->status = REDIS_SOCK_STATUS_READY;
  201. /* If we're using a non-zero db, reselect it */
  202. if (redis_sock->dbNumber && reselect_db(redis_sock) != 0) {
  203. errmsg = "SELECT failed while reconnecting";
  204. break;
  205. }
  206. /* Success */
  207. return 0;
  208. }
  209. }
  210. }
  211. }
  212. /* close stream and mark socket as failed */
  213. redis_sock_disconnect(redis_sock, 1);
  214. redis_sock->status = REDIS_SOCK_STATUS_FAILED;
  215. if (!no_throw) {
  216. REDIS_THROW_EXCEPTION( errmsg, 0);
  217. }
  218. return -1;
  219. }
  220. PHP_REDIS_API int
  221. redis_sock_read_scan_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
  222. REDIS_SCAN_TYPE type, zend_long *iter)
  223. {
  224. REDIS_REPLY_TYPE reply_type;
  225. long reply_info;
  226. char *p_iter;
  227. /* Our response should have two multibulk replies */
  228. if(redis_read_reply_type(redis_sock, &reply_type, &reply_info)<0
  229. || reply_type != TYPE_MULTIBULK || reply_info != 2)
  230. {
  231. return -1;
  232. }
  233. /* The BULK response iterator */
  234. if(redis_read_reply_type(redis_sock, &reply_type, &reply_info)<0
  235. || reply_type != TYPE_BULK)
  236. {
  237. return -1;
  238. }
  239. /* Attempt to read the iterator */
  240. if(!(p_iter = redis_sock_read_bulk_reply(redis_sock, reply_info))) {
  241. return -1;
  242. }
  243. /* Push the iterator out to the caller */
  244. *iter = atol(p_iter);
  245. efree(p_iter);
  246. /* Read our actual keys/members/etc differently depending on what kind of
  247. scan command this is. They all come back in slightly different ways */
  248. switch(type) {
  249. case TYPE_SCAN:
  250. return redis_mbulk_reply_raw(INTERNAL_FUNCTION_PARAM_PASSTHRU,
  251. redis_sock, NULL, NULL);
  252. case TYPE_SSCAN:
  253. return redis_sock_read_multibulk_reply(
  254. INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL);
  255. case TYPE_ZSCAN:
  256. return redis_mbulk_reply_zipped_keys_dbl(INTERNAL_FUNCTION_PARAM_PASSTHRU,
  257. redis_sock, NULL, NULL);
  258. case TYPE_HSCAN:
  259. return redis_mbulk_reply_zipped_vals(INTERNAL_FUNCTION_PARAM_PASSTHRU,
  260. redis_sock, NULL, NULL);
  261. default:
  262. return -1;
  263. }
  264. }
  265. PHP_REDIS_API int redis_subscribe_response(INTERNAL_FUNCTION_PARAMETERS,
  266. RedisSock *redis_sock, zval *z_tab,
  267. void *ctx)
  268. {
  269. subscribeContext *sctx = (subscribeContext*)ctx;
  270. zval *z_tmp, z_resp;
  271. // Consume response(s) from subscribe, which will vary on argc
  272. while(sctx->argc--) {
  273. if (!redis_sock_read_multibulk_reply_zval(
  274. INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, &z_resp)
  275. ) {
  276. efree(sctx);
  277. return -1;
  278. }
  279. // We'll need to find the command response
  280. if ((z_tmp = zend_hash_index_find(Z_ARRVAL(z_resp), 0)) == NULL) {
  281. zval_dtor(&z_resp);
  282. efree(sctx);
  283. return -1;
  284. }
  285. // Make sure the command response matches the command we called
  286. if(strcasecmp(Z_STRVAL_P(z_tmp), sctx->kw) !=0) {
  287. zval_dtor(&z_resp);
  288. efree(sctx);
  289. return -1;
  290. }
  291. zval_dtor(&z_resp);
  292. }
  293. zval z_ret, z_args[4];
  294. sctx->cb.retval = &z_ret;
  295. sctx->cb.params = z_args;
  296. sctx->cb.no_separation = 0;
  297. /* Multibulk response, {[pattern], type, channel, payload } */
  298. while(1) {
  299. zval *z_type, *z_chan, *z_pat = NULL, *z_data;
  300. HashTable *ht_tab;
  301. int tab_idx=1, is_pmsg;
  302. if (!redis_sock_read_multibulk_reply_zval(
  303. INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, &z_resp)) break;
  304. ht_tab = Z_ARRVAL(z_resp);
  305. if ((z_type = zend_hash_index_find(ht_tab, 0)) == NULL ||
  306. Z_TYPE_P(z_type) != IS_STRING
  307. ) {
  308. break;
  309. }
  310. // Check for message or pmessage
  311. if(!strncmp(Z_STRVAL_P(z_type), "message", 7) ||
  312. !strncmp(Z_STRVAL_P(z_type), "pmessage", 8))
  313. {
  314. is_pmsg = *Z_STRVAL_P(z_type)=='p';
  315. } else {
  316. break;
  317. }
  318. // Extract pattern if it's a pmessage
  319. if(is_pmsg) {
  320. if ((z_pat = zend_hash_index_find(ht_tab, tab_idx++)) == NULL) {
  321. break;
  322. }
  323. }
  324. // Extract channel and data
  325. if ((z_chan = zend_hash_index_find(ht_tab, tab_idx++)) == NULL ||
  326. (z_data = zend_hash_index_find(ht_tab, tab_idx++)) == NULL
  327. ) {
  328. break;
  329. }
  330. // Different args for SUBSCRIBE and PSUBSCRIBE
  331. z_args[0] = *getThis();
  332. if(is_pmsg) {
  333. z_args[1] = *z_pat;
  334. z_args[2] = *z_chan;
  335. z_args[3] = *z_data;
  336. } else {
  337. z_args[1] = *z_chan;
  338. z_args[2] = *z_data;
  339. }
  340. // Set arg count
  341. sctx->cb.param_count = tab_idx;
  342. // Execute callback
  343. if(zend_call_function(&(sctx->cb), &(sctx->cb_cache))
  344. ==FAILURE)
  345. {
  346. break;
  347. }
  348. // If we have a return value free it
  349. zval_ptr_dtor(&z_ret);
  350. zval_dtor(&z_resp);
  351. }
  352. // This is an error state, clean up
  353. zval_dtor(&z_resp);
  354. efree(sctx);
  355. return -1;
  356. }
  357. PHP_REDIS_API int redis_unsubscribe_response(INTERNAL_FUNCTION_PARAMETERS,
  358. RedisSock *redis_sock, zval *z_tab,
  359. void *ctx)
  360. {
  361. subscribeContext *sctx = (subscribeContext*)ctx;
  362. zval *z_chan, zv, *z_ret = &zv, z_resp;
  363. int i;
  364. array_init(z_ret);
  365. for (i = 0; i < sctx->argc; i++) {
  366. if (!redis_sock_read_multibulk_reply_zval(
  367. INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, &z_resp) ||
  368. (z_chan = zend_hash_index_find(Z_ARRVAL(z_resp), 1)) == NULL
  369. ) {
  370. zval_dtor(z_ret);
  371. return -1;
  372. }
  373. add_assoc_bool(z_ret, Z_STRVAL_P(z_chan), 1);
  374. zval_dtor(&z_resp);
  375. }
  376. efree(sctx);
  377. RETVAL_ZVAL(z_ret, 0, 1);
  378. // Success
  379. return 0;
  380. }
  381. PHP_REDIS_API zval *
  382. redis_sock_read_multibulk_reply_zval(INTERNAL_FUNCTION_PARAMETERS,
  383. RedisSock *redis_sock, zval *z_tab)
  384. {
  385. char inbuf[4096];
  386. int numElems;
  387. size_t len;
  388. ZVAL_NULL(z_tab);
  389. if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len) < 0) {
  390. return NULL;
  391. }
  392. if(inbuf[0] != '*') {
  393. return NULL;
  394. }
  395. numElems = atoi(inbuf+1);
  396. array_init(z_tab);
  397. redis_mbulk_reply_loop(redis_sock, z_tab, numElems, UNSERIALIZE_ALL);
  398. return z_tab;
  399. }
  400. /**
  401. * redis_sock_read_bulk_reply
  402. */
  403. PHP_REDIS_API char *
  404. redis_sock_read_bulk_reply(RedisSock *redis_sock, int bytes)
  405. {
  406. int offset = 0, nbytes;
  407. char *reply;
  408. size_t got;
  409. if (-1 == bytes || -1 == redis_check_eof(redis_sock, 0)) {
  410. return NULL;
  411. }
  412. nbytes = bytes + 2;
  413. /* Allocate memory for string */
  414. reply = emalloc(nbytes);
  415. /* Consume bulk string */
  416. while (offset < nbytes) {
  417. got = php_stream_read(redis_sock->stream, reply + offset, nbytes - offset);
  418. if (got == 0 && php_stream_eof(redis_sock->stream)) break;
  419. offset += got;
  420. }
  421. /* Protect against reading too few bytes */
  422. if (offset < nbytes) {
  423. /* Error or EOF */
  424. REDIS_THROW_EXCEPTION("socket error on read socket", 0);
  425. efree(reply);
  426. return NULL;
  427. }
  428. /* Null terminate reply string */
  429. reply[bytes] = '\0';
  430. return reply;
  431. }
  432. /**
  433. * redis_sock_read
  434. */
  435. PHP_REDIS_API char *
  436. redis_sock_read(RedisSock *redis_sock, int *buf_len)
  437. {
  438. char inbuf[4096];
  439. size_t len;
  440. *buf_len = 0;
  441. if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len) < 0) {
  442. return NULL;
  443. }
  444. switch(inbuf[0]) {
  445. case '-':
  446. redis_sock_set_err(redis_sock, inbuf+1, len);
  447. /* Filter our ERROR through the few that should actually throw */
  448. redis_error_throw(redis_sock);
  449. return NULL;
  450. case '$':
  451. *buf_len = atoi(inbuf + 1);
  452. return redis_sock_read_bulk_reply(redis_sock, *buf_len);
  453. case '*':
  454. /* For null multi-bulk replies (like timeouts from brpoplpush): */
  455. if(memcmp(inbuf + 1, "-1", 2) == 0) {
  456. return NULL;
  457. }
  458. /* fall through */
  459. case '+':
  460. case ':':
  461. /* Single Line Reply */
  462. /* +OK or :123 */
  463. if (len > 1) {
  464. *buf_len = len;
  465. return estrndup(inbuf, *buf_len);
  466. }
  467. default:
  468. zend_throw_exception_ex(redis_exception_ce, 0,
  469. "protocol error, got '%c' as reply type byte\n",
  470. inbuf[0]
  471. );
  472. }
  473. return NULL;
  474. }
  475. /* A simple union to store the various arg types we might handle in our
  476. * redis_spprintf command formatting function */
  477. union resparg {
  478. char *str;
  479. zend_string *zstr;
  480. zval *zv;
  481. int ival;
  482. long lval;
  483. double dval;
  484. };
  485. /* A printf like method to construct a Redis RESP command. It has been extended
  486. * to take a few different format specifiers that are convenient to phpredis.
  487. *
  488. * s - C string followed by length as a
  489. * S - Pointer to a zend_string
  490. * k - Same as 's' but the value will be prefixed if phpredis is set up do do
  491. * that and the working slot will be set if it has been passed.
  492. * v - A z_val which will be serialized if phpredis is configured to serialize.
  493. * f - A double value
  494. * F - Alias to 'f'
  495. * i - An integer
  496. * d - Alias to 'i'
  497. * l - A long
  498. * L - Alias to 'l'
  499. */
  500. PHP_REDIS_API int
  501. redis_spprintf(RedisSock *redis_sock, short *slot, char **ret, char *kw, char *fmt, ...) {
  502. smart_string cmd = {0};
  503. va_list ap;
  504. union resparg arg;
  505. char *dup;
  506. int argfree;
  507. size_t arglen;
  508. va_start(ap, fmt);
  509. /* Header */
  510. redis_cmd_init_sstr(&cmd, strlen(fmt), kw, strlen(kw));
  511. while (*fmt) {
  512. switch (*fmt) {
  513. case 's':
  514. arg.str = va_arg(ap, char*);
  515. arglen = va_arg(ap, size_t);
  516. redis_cmd_append_sstr(&cmd, arg.str, arglen);
  517. break;
  518. case 'S':
  519. arg.zstr = va_arg(ap, zend_string*);
  520. redis_cmd_append_sstr(&cmd, ZSTR_VAL(arg.zstr), ZSTR_LEN(arg.zstr));
  521. break;
  522. case 'k':
  523. arg.str = va_arg(ap, char*);
  524. arglen = va_arg(ap, size_t);
  525. argfree = redis_key_prefix(redis_sock, &arg.str, &arglen);
  526. redis_cmd_append_sstr(&cmd, arg.str, arglen);
  527. if (slot) *slot = cluster_hash_key(arg.str, arglen);
  528. if (argfree) efree(arg.str);
  529. break;
  530. case 'v':
  531. arg.zv = va_arg(ap, zval*);
  532. argfree = redis_pack(redis_sock, arg.zv, &dup, &arglen);
  533. redis_cmd_append_sstr(&cmd, dup, arglen);
  534. if (argfree) efree(dup);
  535. break;
  536. case 'f':
  537. case 'F':
  538. arg.dval = va_arg(ap, double);
  539. redis_cmd_append_sstr_dbl(&cmd, arg.dval);
  540. break;
  541. case 'i':
  542. case 'd':
  543. arg.ival = va_arg(ap, int);
  544. redis_cmd_append_sstr_int(&cmd, arg.ival);
  545. break;
  546. case 'l':
  547. case 'L':
  548. arg.lval = va_arg(ap, long);
  549. redis_cmd_append_sstr_long(&cmd, arg.lval);
  550. break;
  551. }
  552. fmt++;
  553. }
  554. /* varargs cleanup */
  555. va_end(ap);
  556. /* Null terminate */
  557. smart_string_0(&cmd);
  558. /* Push command string, return length */
  559. *ret = cmd.c;
  560. return cmd.len;
  561. }
  562. /*
  563. * Given a smart string, number of arguments, a keyword, and the length of the keyword
  564. * initialize our smart string with the proper Redis header for the command to follow
  565. */
  566. int redis_cmd_init_sstr(smart_string *str, int num_args, char *keyword, int keyword_len) {
  567. smart_string_appendc(str, '*');
  568. smart_string_append_long(str, num_args + 1);
  569. smart_string_appendl(str, _NL, sizeof(_NL) -1);
  570. smart_string_appendc(str, '$');
  571. smart_string_append_long(str, keyword_len);
  572. smart_string_appendl(str, _NL, sizeof(_NL) - 1);
  573. smart_string_appendl(str, keyword, keyword_len);
  574. smart_string_appendl(str, _NL, sizeof(_NL) - 1);
  575. return str->len;
  576. }
  577. /*
  578. * Append a command sequence to a smart_string
  579. */
  580. int redis_cmd_append_sstr(smart_string *str, char *append, int append_len) {
  581. smart_string_appendc(str, '$');
  582. smart_string_append_long(str, append_len);
  583. smart_string_appendl(str, _NL, sizeof(_NL) - 1);
  584. smart_string_appendl(str, append, append_len);
  585. smart_string_appendl(str, _NL, sizeof(_NL) - 1);
  586. /* Return our new length */
  587. return str->len;
  588. }
  589. /*
  590. * Append an integer to a smart string command
  591. */
  592. int redis_cmd_append_sstr_int(smart_string *str, int append) {
  593. char int_buf[32];
  594. int int_len = snprintf(int_buf, sizeof(int_buf), "%d", append);
  595. return redis_cmd_append_sstr(str, int_buf, int_len);
  596. }
  597. /*
  598. * Append a long to a smart string command
  599. */
  600. int redis_cmd_append_sstr_long(smart_string *str, long append) {
  601. char long_buf[32];
  602. int long_len = snprintf(long_buf, sizeof(long_buf), "%ld", append);
  603. return redis_cmd_append_sstr(str, long_buf, long_len);
  604. }
  605. /*
  606. * Append a 64-bit integer to our command
  607. */
  608. int redis_cmd_append_sstr_i64(smart_string *str, int64_t append) {
  609. char nbuf[64];
  610. int len = snprintf(nbuf, sizeof(nbuf), "%" PRId64, append);
  611. return redis_cmd_append_sstr(str, nbuf, len);
  612. }
  613. /*
  614. * Append a double to a smart string command
  615. */
  616. int
  617. redis_cmd_append_sstr_dbl(smart_string *str, double value)
  618. {
  619. char tmp[64];
  620. int len, retval;
  621. /* Convert to string */
  622. len = snprintf(tmp, sizeof(tmp), "%.16g", value);
  623. // Append the string
  624. retval = redis_cmd_append_sstr(str, tmp, len);
  625. /* Return new length */
  626. return retval;
  627. }
  628. /* Append a zval to a redis command. The value will be serialized if we are
  629. * configured to do that */
  630. int redis_cmd_append_sstr_zval(smart_string *str, zval *z, RedisSock *redis_sock) {
  631. char *val;
  632. size_t vallen;
  633. int valfree, retval;
  634. valfree = redis_pack(redis_sock, z, &val, &vallen);
  635. retval = redis_cmd_append_sstr(str, val, vallen);
  636. if (valfree) efree(val);
  637. return retval;
  638. }
  639. /* Append a string key to a redis command. This function takes care of prefixing the key
  640. * for the caller and setting the slot argument if it is passed non null */
  641. int redis_cmd_append_sstr_key(smart_string *str, char *key, size_t len, RedisSock *redis_sock, short *slot) {
  642. int valfree, retval;
  643. valfree = redis_key_prefix(redis_sock, &key, &len);
  644. if (slot) *slot = cluster_hash_key(key, len);
  645. retval = redis_cmd_append_sstr(str, key, len);
  646. if (valfree) efree(key);
  647. return retval;
  648. }
  649. /* Append an array key to a redis smart string command. This function
  650. * handles the boilerplate conditionals around string or integer keys */
  651. int redis_cmd_append_sstr_arrkey(smart_string *cmd, zend_string *kstr, zend_ulong idx)
  652. {
  653. char *arg, kbuf[128];
  654. int len;
  655. if (kstr) {
  656. len = ZSTR_LEN(kstr);
  657. arg = ZSTR_VAL(kstr);
  658. } else {
  659. len = snprintf(kbuf, sizeof(kbuf), "%ld", (long)idx);
  660. arg = (char*)kbuf;
  661. }
  662. return redis_cmd_append_sstr(cmd, arg, len);
  663. }
  664. PHP_REDIS_API void redis_bulk_double_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) {
  665. char *response;
  666. int response_len;
  667. double ret;
  668. if ((response = redis_sock_read(redis_sock, &response_len)) == NULL) {
  669. if (IS_ATOMIC(redis_sock)) {
  670. RETURN_FALSE;
  671. }
  672. add_next_index_bool(z_tab, 0);
  673. return;
  674. }
  675. ret = atof(response);
  676. efree(response);
  677. if (IS_ATOMIC(redis_sock)) {
  678. RETURN_DOUBLE(ret);
  679. } else {
  680. add_next_index_double(z_tab, ret);
  681. }
  682. }
  683. PHP_REDIS_API void redis_type_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) {
  684. char *response;
  685. int response_len;
  686. long l;
  687. if ((response = redis_sock_read(redis_sock, &response_len)) == NULL) {
  688. if (IS_ATOMIC(redis_sock)) {
  689. RETURN_FALSE;
  690. }
  691. add_next_index_bool(z_tab, 0);
  692. return;
  693. }
  694. if (strncmp(response, "+string", 7) == 0) {
  695. l = REDIS_STRING;
  696. } else if (strncmp(response, "+set", 4) == 0){
  697. l = REDIS_SET;
  698. } else if (strncmp(response, "+list", 5) == 0){
  699. l = REDIS_LIST;
  700. } else if (strncmp(response, "+zset", 5) == 0){
  701. l = REDIS_ZSET;
  702. } else if (strncmp(response, "+hash", 5) == 0){
  703. l = REDIS_HASH;
  704. } else if (strncmp(response, "+stream", 7) == 0) {
  705. l = REDIS_STREAM;
  706. } else {
  707. l = REDIS_NOT_FOUND;
  708. }
  709. efree(response);
  710. if (IS_ATOMIC(redis_sock)) {
  711. RETURN_LONG(l);
  712. } else {
  713. add_next_index_long(z_tab, l);
  714. }
  715. }
  716. PHP_REDIS_API void redis_info_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) {
  717. char *response;
  718. int response_len;
  719. zval z_ret;
  720. /* Read bulk response */
  721. if ((response = redis_sock_read(redis_sock, &response_len)) == NULL) {
  722. RETURN_FALSE;
  723. }
  724. /* Parse it into a zval array */
  725. ZVAL_UNDEF(&z_ret);
  726. redis_parse_info_response(response, &z_ret);
  727. /* Free source response */
  728. efree(response);
  729. if (IS_ATOMIC(redis_sock)) {
  730. RETVAL_ZVAL(&z_ret, 0, 1);
  731. } else {
  732. add_next_index_zval(z_tab, &z_ret);
  733. }
  734. }
  735. PHP_REDIS_API void
  736. redis_parse_info_response(char *response, zval *z_ret)
  737. {
  738. char *cur, *pos;
  739. array_init(z_ret);
  740. cur = response;
  741. while(1) {
  742. /* skip comments and empty lines */
  743. if (*cur == '#' || *cur == '\r') {
  744. if ((cur = strstr(cur, _NL)) == NULL) {
  745. break;
  746. }
  747. cur += 2;
  748. continue;
  749. }
  750. /* key */
  751. if ((pos = strchr(cur, ':')) == NULL) {
  752. break;
  753. }
  754. char *key = cur;
  755. int key_len = pos - cur;
  756. key[key_len] = '\0';
  757. /* value */
  758. cur = pos + 1;
  759. if ((pos = strstr(cur, _NL)) == NULL) {
  760. break;
  761. }
  762. char *value = cur;
  763. int value_len = pos - cur;
  764. value[value_len] = '\0';
  765. double dval;
  766. zend_long lval;
  767. zend_uchar type = is_numeric_string(value, value_len, &lval, &dval, 0);
  768. if (type == IS_LONG) {
  769. add_assoc_long_ex(z_ret, key, key_len, lval);
  770. } else if (type == IS_DOUBLE) {
  771. add_assoc_double_ex(z_ret, key, key_len, dval);
  772. } else {
  773. add_assoc_stringl_ex(z_ret, key, key_len, value, value_len);
  774. }
  775. cur = pos + 2; /* \r, \n */
  776. }
  777. }
  778. /*
  779. * Specialized handling of the CLIENT LIST output so it comes out in a simple way for PHP userland code
  780. * to handle.
  781. */
  782. PHP_REDIS_API void redis_client_list_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab) {
  783. char *resp;
  784. int resp_len;
  785. zval z_ret;
  786. /* Make sure we can read the bulk response from Redis */
  787. if ((resp = redis_sock_read(redis_sock, &resp_len)) == NULL) {
  788. RETURN_FALSE;
  789. }
  790. /* Parse it out */
  791. redis_parse_client_list_response(resp, &z_ret);
  792. /* Free our response */
  793. efree(resp);
  794. /* Return or append depending if we're atomic */
  795. if (IS_ATOMIC(redis_sock)) {
  796. RETVAL_ZVAL(&z_ret, 0, 1);
  797. } else {
  798. add_next_index_zval(z_tab, &z_ret);
  799. }
  800. }
  801. PHP_REDIS_API void
  802. redis_parse_client_list_response(char *response, zval *z_ret)
  803. {
  804. char *p, *lpos, *kpos = NULL, *vpos = NULL, *p2, *key, *value;
  805. int klen = 0, done = 0, is_numeric;
  806. zval z_sub_result;
  807. /* Allocate for response and our user */
  808. array_init(z_ret);
  809. array_init(&z_sub_result);
  810. // Pointers for parsing
  811. p = response;
  812. lpos = response;
  813. /* While we've got more to parse */
  814. while(!done) {
  815. /* What character are we on */
  816. switch(*p) {
  817. /* We're done */
  818. case '\0':
  819. done = 1;
  820. break;
  821. /* \n, ' ' mean we can pull a k/v pair */
  822. case '\n':
  823. case ' ':
  824. /* Grab our value */
  825. vpos = lpos;
  826. /* There is some communication error or Redis bug if we don't
  827. have a key and value, but check anyway. */
  828. if(kpos && vpos) {
  829. /* Allocate, copy in our key */
  830. key = estrndup(kpos, klen);
  831. /* Allocate, copy in our value */
  832. value = estrndup(lpos, p - lpos);
  833. /* Treat numbers as numbers, strings as strings */
  834. is_numeric = 1;
  835. for(p2 = value; *p2; ++p2) {
  836. if(*p2 < '0' || *p2 > '9') {
  837. is_numeric = 0;
  838. break;
  839. }
  840. }
  841. /* Add as a long or string, depending */
  842. if(is_numeric == 1) {
  843. add_assoc_long(&z_sub_result, key, atol(value));
  844. } else {
  845. add_assoc_string(&z_sub_result, key, value);
  846. }
  847. efree(value);
  848. // If we hit a '\n', then we can add this user to our list
  849. if(*p == '\n') {
  850. /* Add our user */
  851. add_next_index_zval(z_ret, &z_sub_result);
  852. /* If we have another user, make another one */
  853. if(*(p+1) != '\0') {
  854. array_init(&z_sub_result);
  855. }
  856. }
  857. // Free our key
  858. efree(key);
  859. } else {
  860. // Something is wrong
  861. zval_dtor(z_ret);
  862. ZVAL_BOOL(z_ret, 0);
  863. return;
  864. }
  865. /* Move forward */
  866. lpos = p + 1;
  867. break;
  868. /* We can pull the key and null terminate at our sep */
  869. case '=':
  870. /* Key, key length */
  871. kpos = lpos;
  872. klen = p - lpos;
  873. /* Move forward */
  874. lpos = p + 1;
  875. break;
  876. }
  877. /* Increment */
  878. p++;
  879. }
  880. }
  881. PHP_REDIS_API void
  882. redis_boolean_response_impl(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
  883. zval *z_tab, void *ctx,
  884. SuccessCallback success_callback)
  885. {
  886. char *response;
  887. int response_len;
  888. zend_bool ret = 0;
  889. if ((response = redis_sock_read(redis_sock, &response_len)) != NULL) {
  890. ret = (*response == '+');
  891. efree(response);
  892. }
  893. if (ret && success_callback != NULL) {
  894. success_callback(redis_sock);
  895. }
  896. if (IS_ATOMIC(redis_sock)) {
  897. RETURN_BOOL(ret);
  898. } else {
  899. add_next_index_bool(z_tab, ret);
  900. }
  901. }
  902. PHP_REDIS_API void redis_boolean_response(INTERNAL_FUNCTION_PARAMETERS,
  903. RedisSock *redis_sock, zval *z_tab,
  904. void *ctx)
  905. {
  906. redis_boolean_response_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
  907. z_tab, ctx, NULL);
  908. }
  909. PHP_REDIS_API void redis_long_response(INTERNAL_FUNCTION_PARAMETERS,
  910. RedisSock *redis_sock, zval * z_tab,
  911. void *ctx)
  912. {
  913. char *response;
  914. int response_len;
  915. if ((response = redis_sock_read(redis_sock, &response_len))
  916. == NULL)
  917. {
  918. if (IS_ATOMIC(redis_sock)) {
  919. RETURN_FALSE;
  920. }
  921. add_next_index_bool(z_tab, 0);
  922. return;
  923. }
  924. if(response[0] == ':') {
  925. int64_t ret = phpredis_atoi64(response + 1);
  926. if (IS_ATOMIC(redis_sock)) {
  927. if(ret > LONG_MAX) { /* overflow */
  928. RETVAL_STRINGL(response + 1, response_len - 1);
  929. } else {
  930. RETVAL_LONG((long)ret);
  931. }
  932. } else {
  933. if(ret > LONG_MAX) { /* overflow */
  934. add_next_index_stringl(z_tab, response + 1, response_len - 1);
  935. } else {
  936. add_next_index_long(z_tab, (long)ret);
  937. }
  938. }
  939. } else {
  940. if (IS_ATOMIC(redis_sock)) {
  941. RETVAL_FALSE;
  942. } else {
  943. add_next_index_null(z_tab);
  944. }
  945. }
  946. efree(response);
  947. }
  948. /* Helper method to convert [key, value, key, value] into [key => value,
  949. * key => value] when returning data to the caller. Depending on our decode
  950. * flag we'll convert the value data types */
  951. static void array_zip_values_and_scores(RedisSock *redis_sock, zval *z_tab,
  952. int decode)
  953. {
  954. zval z_ret, z_sub;
  955. HashTable *keytable;
  956. array_init(&z_ret);
  957. keytable = Z_ARRVAL_P(z_tab);
  958. for(zend_hash_internal_pointer_reset(keytable);
  959. zend_hash_has_more_elements(keytable) == SUCCESS;
  960. zend_hash_move_forward(keytable)) {
  961. zval *z_key_p, *z_value_p;
  962. if ((z_key_p = zend_hash_get_current_data(keytable)) == NULL) {
  963. continue; /* this should never happen, according to the PHP people. */
  964. }
  965. /* get current value, a key */
  966. zend_string *hkey = zval_get_string(z_key_p);
  967. /* move forward */
  968. zend_hash_move_forward(keytable);
  969. /* fetch again */
  970. if ((z_value_p = zend_hash_get_current_data(keytable)) == NULL) {
  971. zend_string_release(hkey);
  972. continue; /* this should never happen, according to the PHP people. */
  973. }
  974. /* get current value, a hash value now. */
  975. char *hval = Z_STRVAL_P(z_value_p);
  976. /* Decode the score depending on flag */
  977. if (decode == SCORE_DECODE_INT && Z_STRLEN_P(z_value_p) > 0) {
  978. add_assoc_long_ex(&z_ret, ZSTR_VAL(hkey), ZSTR_LEN(hkey), atoi(hval+1));
  979. } else if (decode == SCORE_DECODE_DOUBLE) {
  980. add_assoc_double_ex(&z_ret, ZSTR_VAL(hkey), ZSTR_LEN(hkey), atof(hval));
  981. } else {
  982. ZVAL_ZVAL(&z_sub, z_value_p, 1, 0);
  983. add_assoc_zval_ex(&z_ret, ZSTR_VAL(hkey), ZSTR_LEN(hkey), &z_sub);
  984. }
  985. zend_string_release(hkey);
  986. }
  987. /* replace */
  988. zval_dtor(z_tab);
  989. ZVAL_ZVAL(z_tab, &z_ret, 0, 0);
  990. }
  991. static int
  992. read_mbulk_header(RedisSock *redis_sock, int *nelem)
  993. {
  994. char line[4096];
  995. size_t len;
  996. /* Throws exception on failure */
  997. if (redis_sock_gets(redis_sock, line, sizeof(line)-1, &len) < 0)
  998. return -1;
  999. if (line[0] != '*') {
  1000. if (IS_ATOMIC(redis_sock)) {
  1001. if (line[0] == '-') {
  1002. redis_sock_set_err(redis_sock, line+1, len-1);
  1003. }
  1004. }
  1005. return -1;
  1006. }
  1007. *nelem = atoi(line+1);
  1008. return 0;
  1009. }
  1010. static int
  1011. redis_mbulk_reply_zipped(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
  1012. zval *z_tab, int unserialize, int decode)
  1013. {
  1014. char inbuf[4096];
  1015. int numElems;
  1016. size_t len;
  1017. if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len) < 0) {
  1018. return -1;
  1019. }
  1020. if(inbuf[0] != '*') {
  1021. if (IS_ATOMIC(redis_sock)) {
  1022. RETVAL_FALSE;
  1023. } else {
  1024. add_next_index_bool(z_tab, 0);
  1025. }
  1026. if (*inbuf == TYPE_ERR) {
  1027. redis_sock_set_err(redis_sock, inbuf + 1, len - 1);
  1028. }
  1029. return -1;
  1030. }
  1031. numElems = atoi(inbuf+1);
  1032. zval z_multi_result;
  1033. array_init(&z_multi_result); /* pre-allocate array for multi's results. */
  1034. /* Grab our key, value, key, value array */
  1035. redis_mbulk_reply_loop(redis_sock, &z_multi_result, numElems, unserialize);
  1036. /* Zip keys and values */
  1037. array_zip_values_and_scores(redis_sock, &z_multi_result, decode);
  1038. if (IS_ATOMIC(redis_sock)) {
  1039. RETVAL_ZVAL(&z_multi_result, 0, 1);
  1040. } else {
  1041. add_next_index_zval(z_tab, &z_multi_result);
  1042. }
  1043. return 0;
  1044. }
  1045. /* Consume message ID */
  1046. PHP_REDIS_API int
  1047. redis_sock_read_single_line(RedisSock *redis_sock, char *buffer, size_t buflen,
  1048. size_t *linelen, int set_err)
  1049. {
  1050. REDIS_REPLY_TYPE type;
  1051. long info;
  1052. if (redis_read_reply_type(redis_sock, &type, &info) < 0 ||
  1053. (type != TYPE_LINE && type != TYPE_ERR))
  1054. {
  1055. return -1;
  1056. }
  1057. if (redis_sock_gets(redis_sock, buffer, buflen, linelen) < 0) {
  1058. return -1;
  1059. }
  1060. if (set_err && type == TYPE_ERR) {
  1061. if (IS_ATOMIC(redis_sock)) {
  1062. redis_sock_set_err(redis_sock, buffer, *linelen);
  1063. }
  1064. }
  1065. return type == TYPE_LINE ? 0 : -1;
  1066. }
  1067. /* Helper function to consume Redis stream message data. This is useful for
  1068. * multiple stream callers (e.g. XREAD[GROUP], and X[REV]RANGE handlers). */
  1069. PHP_REDIS_API int
  1070. redis_read_stream_messages(RedisSock *redis_sock, int count, zval *z_ret
  1071. )
  1072. {
  1073. zval z_message;
  1074. int i, mhdr, fields;
  1075. char *id = NULL;
  1076. int idlen;
  1077. /* Iterate over each message */
  1078. for (i = 0; i < count; i++) {
  1079. /* Consume inner multi-bulk header, message ID itself and finally
  1080. * the multi-bulk header for field and values */
  1081. if ((read_mbulk_header(redis_sock, &mhdr) < 0 || mhdr != 2) ||
  1082. ((id = redis_sock_read(redis_sock, &idlen)) == NULL) ||
  1083. (read_mbulk_header(redis_sock, &fields) < 0 || fields % 2 != 0))
  1084. {
  1085. if (id) efree(id);
  1086. return -1;
  1087. }
  1088. array_init(&z_message);
  1089. redis_mbulk_reply_loop(redis_sock, &z_message, fields, UNSERIALIZE_VALS);
  1090. array_zip_values_and_scores(redis_sock, &z_message, SCORE_DECODE_NONE);
  1091. add_assoc_zval_ex(z_ret, id, idlen, &z_message);
  1092. efree(id);
  1093. }
  1094. return 0;
  1095. }
  1096. PHP_REDIS_API int
  1097. redis_xrange_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
  1098. zval *z_tab, void *ctx)
  1099. {
  1100. zval z_messages;
  1101. int messages;
  1102. array_init(&z_messages);
  1103. if (read_mbulk_header(redis_sock, &messages) < 0 ||
  1104. redis_read_stream_messages(redis_sock, messages, &z_messages) < 0)
  1105. {
  1106. zval_dtor(&z_messages);
  1107. if (IS_ATOMIC(redis_sock)) {
  1108. RETVAL_FALSE;
  1109. } else {
  1110. add_next_index_bool(z_tab, 0);
  1111. }
  1112. return -1;
  1113. }
  1114. if (IS_ATOMIC(redis_sock)) {
  1115. RETVAL_ZVAL(&z_messages, 0, 1);
  1116. } else {
  1117. add_next_index_zval(z_tab, &z_messages);
  1118. }
  1119. return 0;
  1120. }
  1121. PHP_REDIS_API int
  1122. redis_read_stream_messages_multi(RedisSock *redis_sock, int count, zval *z_streams
  1123. )
  1124. {
  1125. zval z_messages;
  1126. int i, shdr, messages;
  1127. char *id = NULL;
  1128. int idlen;
  1129. for (i = 0; i < count; i++) {
  1130. if ((read_mbulk_header(redis_sock, &shdr) < 0 || shdr != 2) ||
  1131. (id = redis_sock_read(redis_sock, &idlen)) == NULL ||
  1132. read_mbulk_header(redis_sock, &messages) < 0)
  1133. {
  1134. if (id) efree(id);
  1135. return -1;
  1136. }
  1137. array_init(&z_messages);
  1138. if (redis_read_stream_messages(redis_sock, messages, &z_messages) < 0)
  1139. goto failure;
  1140. add_assoc_zval_ex(z_streams, id, idlen, &z_messages);
  1141. efree(id);
  1142. }
  1143. return 0;
  1144. failure:
  1145. efree(id);
  1146. zval_dtor(&z_messages);
  1147. return -1;
  1148. }
  1149. PHP_REDIS_API int
  1150. redis_xread_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
  1151. zval *z_tab, void *ctx)
  1152. {
  1153. zval z_rv;
  1154. int streams;
  1155. if (read_mbulk_header(redis_sock, &streams) < 0)
  1156. goto failure;
  1157. array_init(&z_rv);
  1158. if (redis_read_stream_messages_multi(redis_sock, streams, &z_rv) < 0)
  1159. goto cleanup;
  1160. if (IS_ATOMIC(redis_sock)) {
  1161. RETVAL_ZVAL(&z_rv, 0, 1);
  1162. } else {
  1163. add_next_index_zval(z_tab, &z_rv);
  1164. }
  1165. return 0;
  1166. cleanup:
  1167. zval_dtor(&z_rv);
  1168. failure:
  1169. if (IS_ATOMIC(redis_sock)) {
  1170. RETVAL_FALSE;
  1171. } else {
  1172. add_next_index_bool(z_tab, 0);
  1173. }
  1174. return -1;
  1175. }
  1176. /* This helper function does that actual XCLAIM response handling, which can be used by both
  1177. * Redis and RedisCluster. Note that XCLAIM is somewhat unique in that its reply type depends
  1178. * on whether or not it was called with the JUSTID option */
  1179. PHP_REDIS_API int
  1180. redis_read_xclaim_response(RedisSock *redis_sock, int count, zval *rv) {
  1181. zval z_msg;
  1182. REDIS_REPLY_TYPE type;
  1183. char *id = NULL;
  1184. int i, fields, idlen;
  1185. long li;
  1186. for (i = 0; i < count; i++) {
  1187. /* Consume inner reply type */
  1188. if (redis_read_reply_type(redis_sock, &type, &li) < 0 ||
  1189. (type != TYPE_BULK && type != TYPE_MULTIBULK) ||
  1190. (type == TYPE_BULK && li <= 0)) return -1;
  1191. /* TYPE_BULK is the JUSTID variant, otherwise it's standard xclaim response */
  1192. if (type == TYPE_BULK) {
  1193. if ((id = redis_sock_read_bulk_reply(redis_sock, (size_t)li)) == NULL)
  1194. return -1;
  1195. add_next_index_stringl(rv, id, li);
  1196. efree(id);
  1197. } else {
  1198. if ((li != 2 || (id = redis_sock_read(redis_sock, &idlen)) == NULL) ||
  1199. (read_mbulk_header(redis_sock, &fields) < 0 || fields % 2 != 0))
  1200. {
  1201. if (id) efree(id);
  1202. return -1;
  1203. }
  1204. array_init(&z_msg);
  1205. redis_mbulk_reply_loop(redis_sock, &z_msg, fields, UNSERIALIZE_VALS);
  1206. array_zip_values_and_scores(redis_sock, &z_msg, SCORE_DECODE_NONE);
  1207. add_assoc_zval_ex(rv, id, idlen, &z_msg);
  1208. efree(id);
  1209. }
  1210. }
  1211. return 0;
  1212. }
  1213. PHP_REDIS_API int
  1214. redis_xclaim_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
  1215. zval *z_tab, void *ctx)
  1216. {
  1217. zval z_ret;
  1218. int messages;
  1219. /* All XCLAIM responses start multibulk */
  1220. if (read_mbulk_header(redis_sock, &messages) < 0)
  1221. goto failure;
  1222. array_init(&z_ret);
  1223. if (redis_read_xclaim_response(redis_sock, messages, &z_ret) < 0) {
  1224. zval_dtor(&z_ret);
  1225. goto failure;
  1226. }
  1227. if (IS_ATOMIC(redis_sock)) {
  1228. RETVAL_ZVAL(&z_ret, 0, 1);
  1229. } else {
  1230. add_next_index_zval(z_tab, &z_ret);
  1231. }
  1232. return 0;
  1233. failure:
  1234. if (IS_ATOMIC(redis_sock)) {
  1235. RETVAL_FALSE;
  1236. } else {
  1237. add_next_index_bool(z_tab, 0);
  1238. }
  1239. return -1;
  1240. }
  1241. PHP_REDIS_API int
  1242. redis_read_xinfo_response(RedisSock *redis_sock, zval *z_ret, int elements)
  1243. {
  1244. zval zv;
  1245. int i, len = 0;
  1246. char *key = NULL, *data;
  1247. REDIS_REPLY_TYPE type;
  1248. long li;
  1249. for (i = 0; i < elements; ++i) {
  1250. if (redis_read_reply_type(redis_sock, &type, &li) < 0) {
  1251. goto failure;
  1252. }
  1253. switch (type) {
  1254. case TYPE_BULK:
  1255. if ((data = redis_sock_read_bulk_reply(redis_sock, li)) == NULL) {
  1256. goto failure;
  1257. } else if (key) {
  1258. add_assoc_stringl_ex(z_ret, key, len, data, li);
  1259. efree(data);
  1260. efree(key);
  1261. key = NULL;
  1262. } else {
  1263. key = data;
  1264. len = li;
  1265. }
  1266. break;
  1267. case TYPE_INT:
  1268. if (key) {
  1269. add_assoc_long_ex(z_ret, key, len, li);
  1270. efree(key);
  1271. key = NULL;
  1272. } else {
  1273. len = spprintf(&key, 0, "%ld", li);
  1274. }
  1275. break;
  1276. case TYPE_MULTIBULK:
  1277. array_init(&zv);
  1278. if (redis_read_xinfo_response(redis_sock, &zv, li) != SUCCESS) {
  1279. zval_dtor(&zv);
  1280. goto failure;
  1281. }
  1282. if (key) {
  1283. add_assoc_zval_ex(z_ret, key, len, &zv);
  1284. efree(key);
  1285. key = NULL;
  1286. } else {
  1287. add_next_index_zval(z_ret, &zv);
  1288. }
  1289. break;
  1290. default:
  1291. goto failure;
  1292. }
  1293. }
  1294. return SUCCESS;
  1295. failure:
  1296. if (key) efree(key);
  1297. return FAILURE;
  1298. }
  1299. PHP_REDIS_API int
  1300. redis_xinfo_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx)
  1301. {
  1302. zval z_ret;
  1303. int elements;
  1304. if (read_mbulk_header(redis_sock, &elements) == SUCCESS) {
  1305. array_init(&z_ret);
  1306. if (redis_read_xinfo_response(redis_sock, &z_ret, elements) == SUCCESS) {
  1307. if (IS_ATOMIC(redis_sock)) {
  1308. RETVAL_ZVAL(&z_ret, 0, 1);
  1309. } else {
  1310. add_next_index_zval(z_tab, &z_ret);
  1311. }
  1312. return SUCCESS;
  1313. }
  1314. zval_dtor(&z_ret);
  1315. }
  1316. if (IS_ATOMIC(redis_sock)) {
  1317. RETVAL_FALSE;
  1318. } else {
  1319. add_next_index_bool(z_tab, 0);
  1320. }
  1321. return FAILURE;
  1322. }
  1323. /* Zipped key => value reply but we don't touch anything (e.g. CONFIG GET) */
  1324. PHP_REDIS_API int redis_mbulk_reply_zipped_raw(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx)
  1325. {
  1326. return redis_mbulk_reply_zipped(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
  1327. z_tab, UNSERIALIZE_NONE, SCORE_DECODE_NONE);
  1328. }
  1329. /* Zipped key => value reply unserializing keys and decoding the score as an integer (PUBSUB) */
  1330. PHP_REDIS_API int redis_mbulk_reply_zipped_keys_int(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
  1331. zval *z_tab, void *ctx)
  1332. {
  1333. return redis_mbulk_reply_zipped(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
  1334. z_tab, UNSERIALIZE_KEYS, SCORE_DECODE_INT);
  1335. }
  1336. /* Zipped key => value reply unserializing keys and decoding the score as a double (ZSET commands) */
  1337. PHP_REDIS_API int redis_mbulk_reply_zipped_keys_dbl(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
  1338. zval *z_tab, void *ctx)
  1339. {
  1340. return redis_mbulk_reply_zipped(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
  1341. z_tab, UNSERIALIZE_KEYS, SCORE_DECODE_DOUBLE);
  1342. }
  1343. /* Zipped key => value reply where only the values are unserialized (e.g. HMGET) */
  1344. PHP_REDIS_API int redis_mbulk_reply_zipped_vals(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
  1345. zval *z_tab, void *ctx)
  1346. {
  1347. return redis_mbulk_reply_zipped(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
  1348. z_tab, UNSERIALIZE_VALS, SCORE_DECODE_NONE);
  1349. }
  1350. PHP_REDIS_API void
  1351. redis_1_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx)
  1352. {
  1353. char *response;
  1354. int response_len;
  1355. zend_bool ret = 0;
  1356. if ((response = redis_sock_read(redis_sock, &response_len)) != NULL) {
  1357. ret = (response[1] == '1');
  1358. efree(response);
  1359. }
  1360. if (IS_ATOMIC(redis_sock)) {
  1361. RETURN_BOOL(ret);
  1362. } else {
  1363. add_next_index_bool(z_tab, ret);
  1364. }
  1365. }
  1366. PHP_REDIS_API void redis_string_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) {
  1367. char *response;
  1368. int response_len;
  1369. if ((response = redis_sock_read(redis_sock, &response_len))
  1370. == NULL)
  1371. {
  1372. if (IS_ATOMIC(redis_sock)) {
  1373. RETURN_FALSE;
  1374. }
  1375. add_next_index_bool(z_tab, 0);
  1376. return;
  1377. }
  1378. if (IS_ATOMIC(redis_sock)) {
  1379. if (!redis_unpack(redis_sock, response, response_len, return_value)) {
  1380. RETVAL_STRINGL(response, response_len);
  1381. }
  1382. } else {
  1383. zval z_unpacked;
  1384. if (redis_unpack(redis_sock, response, response_len, &z_unpacked)) {
  1385. add_next_index_zval(z_tab, &z_unpacked);
  1386. } else {
  1387. add_next_index_stringl(z_tab, response, response_len);
  1388. }
  1389. }
  1390. efree(response);
  1391. }
  1392. PHP_REDIS_API
  1393. void redis_single_line_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
  1394. zval *z_tab, void *ctx)
  1395. {
  1396. char buffer[4096];
  1397. size_t len;
  1398. if (redis_sock_read_single_line(redis_sock, buffer, sizeof(buffer), &len, 1) < 0) {
  1399. if (IS_ATOMIC(redis_sock)) {
  1400. RETURN_FALSE;
  1401. } else {
  1402. add_next_index_bool(z_tab, 0);
  1403. }
  1404. return;
  1405. }
  1406. //str = estrndup(buffer, len);
  1407. if (IS_ATOMIC(redis_sock)) {
  1408. RETVAL_STRINGL(buffer, len);
  1409. } else {
  1410. add_next_index_stringl(z_tab, buffer, len);
  1411. }
  1412. }
  1413. /* like string response, but never unserialized. */
  1414. PHP_REDIS_API void
  1415. redis_ping_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
  1416. zval *z_tab, void *ctx)
  1417. {
  1418. char *response;
  1419. int response_len;
  1420. if ((response = redis_sock_read(redis_sock, &response_len))
  1421. == NULL)
  1422. {
  1423. if (IS_ATOMIC(redis_sock)) {
  1424. RETURN_FALSE;
  1425. }
  1426. add_next_index_bool(z_tab, 0);
  1427. return;
  1428. }
  1429. if (IS_ATOMIC(redis_sock)) {
  1430. RETVAL_STRINGL(response, response_len);
  1431. } else {
  1432. add_next_index_stringl(z_tab, response, response_len);
  1433. }
  1434. efree(response);
  1435. }
  1436. /* Response for DEBUG object which is a formatted single line reply */
  1437. PHP_REDIS_API void redis_debug_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
  1438. zval *z_tab, void *ctx)
  1439. {
  1440. char *resp, *p, *p2, *p3, *p4;
  1441. int is_numeric, resp_len;
  1442. /* Add or return false if we can't read from the socket */
  1443. if((resp = redis_sock_read(redis_sock, &resp_len))==NULL) {
  1444. if (IS_ATOMIC(redis_sock)) {
  1445. RETURN_FALSE;
  1446. }
  1447. add_next_index_bool(z_tab, 0);
  1448. return;
  1449. }
  1450. zval z_result;
  1451. array_init(&z_result);
  1452. /* Skip the '+' */
  1453. p = resp + 1;
  1454. /* <info>:<value> <info2:value2> ... */
  1455. while((p2 = strchr(p, ':'))!=NULL) {
  1456. /* Null terminate at the ':' */
  1457. *p2++ = '\0';
  1458. /* Null terminate at the space if we have one */
  1459. if((p3 = strchr(p2, ' '))!=NULL) {
  1460. *p3++ = '\0';
  1461. } else {
  1462. p3 = resp + resp_len;
  1463. }
  1464. is_numeric = 1;
  1465. for(p4=p2; *p4; ++p4) {
  1466. if(*p4 < '0' || *p4 > '9') {
  1467. is_numeric = 0;
  1468. break;
  1469. }
  1470. }
  1471. /* Add our value */
  1472. if(is_numeric) {
  1473. add_assoc_long(&z_result, p, atol(p2));
  1474. } else {
  1475. add_assoc_string(&z_result, p, p2);
  1476. }
  1477. p = p3;
  1478. }
  1479. efree(resp);
  1480. if (IS_ATOMIC(redis_sock)) {
  1481. RETVAL_ZVAL(&z_result, 0, 1);
  1482. } else {
  1483. add_next_index_zval(z_tab, &z_result);
  1484. }
  1485. }
  1486. /**
  1487. * redis_sock_create
  1488. */
  1489. PHP_REDIS_API RedisSock*
  1490. redis_sock_create(char *host, int host_len, int port,
  1491. double timeout, double read_timeout,
  1492. int persistent, char *persistent_id,
  1493. long retry_interval)
  1494. {
  1495. RedisSock *redis_sock;
  1496. redis_sock = ecalloc(1, sizeof(RedisSock));
  1497. redis_sock->host = zend_string_init(host, host_len, 0);
  1498. redis_sock->stream = NULL;
  1499. redis_sock->status = REDIS_SOCK_STATUS_DISCONNECTED;
  1500. redis_sock->watching = 0;
  1501. redis_sock->dbNumber = 0;
  1502. redis_sock->retry_interval = retry_interval * 1000;
  1503. redis_sock->persistent = persistent;
  1504. redis_sock->persistent_id = NULL;
  1505. if (persistent && persistent_id != NULL) {
  1506. redis_sock->persistent_id = zend_string_init(persistent_id, strlen(persistent_id), 0);
  1507. }
  1508. redis_sock->port = port;
  1509. redis_sock->timeout = timeout;
  1510. redis_sock->read_timeout = read_timeout;
  1511. redis_sock->serializer = REDIS_SERIALIZER_NONE;
  1512. redis_sock->compression = REDIS_COMPRESSION_NONE;
  1513. redis_sock->compression_level = 0; /* default */
  1514. redis_sock->mode = ATOMIC;
  1515. redis_sock->head = NULL;
  1516. redis_sock->current = NULL;
  1517. redis_sock->pipeline_cmd = NULL;
  1518. redis_sock->err = NULL;
  1519. redis_sock->scan = REDIS_SCAN_NORETRY;
  1520. redis_sock->readonly = 0;
  1521. redis_sock->tcp_keepalive = 0;
  1522. redis_sock->reply_literal = 0;
  1523. return redis_sock;
  1524. }
  1525. static int
  1526. redis_sock_check_liveness(RedisSock *redis_sock)
  1527. {
  1528. char inbuf[4096], uniqid[64];
  1529. int uniqid_len;
  1530. smart_string cmd = {0};
  1531. struct timeval tv;
  1532. size_t len;
  1533. if (redis_sock->auth) {
  1534. redis_cmd_init_sstr(&cmd, 1, "AUTH", sizeof("AUTH") - 1);
  1535. redis_cmd_append_sstr(&cmd, ZSTR_VAL(redis_sock->auth), ZSTR_LEN(redis_sock->auth));
  1536. }
  1537. gettimeofday(&tv, NULL);
  1538. uniqid_len = snprintf(uniqid, sizeof(uniqid), "phpredis_pool:%08lx%05lx:%08lx", (long)tv.tv_sec, (long)tv.tv_usec, (long)php_rand());
  1539. redis_cmd_init_sstr(&cmd, 1, "ECHO", sizeof("ECHO") - 1);
  1540. redis_cmd_append_sstr(&cmd, uniqid, uniqid_len);
  1541. smart_string_0(&cmd);
  1542. if (redis_sock_write(redis_sock, cmd.c, cmd.len) < 0) {
  1543. smart_string_free(&cmd);
  1544. return FAILURE;
  1545. }
  1546. smart_string_free(&cmd);
  1547. if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len) < 0) {
  1548. return FAILURE;
  1549. } else if (redis_sock->auth) {
  1550. if (strncmp(inbuf, "+OK", 3) != 0) {
  1551. return FAILURE;
  1552. } else if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len) < 0) {
  1553. return FAILURE;
  1554. }
  1555. }
  1556. if (*inbuf != TYPE_BULK ||
  1557. atoi(inbuf + 1) != uniqid_len ||
  1558. redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len) < 0 ||
  1559. strncmp(inbuf, uniqid, uniqid_len) != 0
  1560. ) {
  1561. return FAILURE;
  1562. }
  1563. return SUCCESS;
  1564. }
  1565. /**
  1566. * redis_sock_connect
  1567. */
  1568. PHP_REDIS_API int redis_sock_connect(RedisSock *redis_sock)
  1569. {
  1570. struct timeval tv, read_tv, *tv_ptr = NULL;
  1571. zend_string *persistent_id = NULL, *estr = NULL;
  1572. char host[1024], *pos, *address, *schema = NULL;
  1573. const char *fmtstr = "%s://%s:%d";
  1574. int host_len, usocket = 0, err = 0, tcp_flag = 1;
  1575. ConnectionPool *p = NULL;
  1576. if (redis_sock->stream != NULL) {
  1577. redis_sock_disconnect(redis_sock, 0);
  1578. }
  1579. address = ZSTR_VAL(redis_sock->host);
  1580. if ((pos = strstr(address, "://")) != NULL) {
  1581. schema = estrndup(address, pos - address);
  1582. address = pos + sizeof("://") - 1;
  1583. }
  1584. if (address[0] == '/' && redis_sock->port < 1) {
  1585. host_len = snprintf(host, sizeof(host), "unix://%s", address);
  1586. usocket = 1;
  1587. } else {
  1588. if(redis_sock->port == 0)
  1589. redis_sock->port = 6379;
  1590. #ifdef HAVE_IPV6
  1591. /* If we've got IPv6 and find a colon in our address, convert to proper
  1592. * IPv6 [host]:port format */
  1593. if (strchr(address, ':') != NULL) {
  1594. fmtstr = "%s://[%s]:%d";
  1595. }
  1596. #endif
  1597. host_len = snprintf(host, sizeof(host), fmtstr, schema ? schema : "tcp", address, redis_sock->port);
  1598. if (schema) efree(schema);
  1599. }
  1600. if (redis_sock->persistent) {
  1601. if (INI_INT("redis.pconnect.pooling_enabled")) {
  1602. p = redis_sock_get_connection_pool(redis_sock);
  1603. if (zend_llist_count(&p->list) > 0) {
  1604. redis_sock->stream = *(php_stream **)zend_llist_get_last(&p->list);
  1605. zend_llist_remove_tail(&p->list);
  1606. if (redis_sock_check_liveness(redis_sock) == SUCCESS) {
  1607. redis_sock->status = REDIS_SOCK_STATUS_READY;
  1608. return SUCCESS;
  1609. } else if (redis_sock->stream) {
  1610. php_stream_pclose(redis_sock->stream);
  1611. redis_sock->stream = NULL;
  1612. }
  1613. p->nb_active--;
  1614. }
  1615. int limit = INI_INT("redis.pconnect.connection_limit");
  1616. if (limit > 0 && p->nb_active >= limit) {
  1617. redis_sock_set_err(redis_sock, "Connection limit reached", sizeof("Connection limit reached") - 1);
  1618. return FAILURE;
  1619. }
  1620. gettimeofday(&tv, NULL);
  1621. persistent_id = strpprintf(0, "phpredis_%ld%ld", tv.tv_sec, tv.tv_usec);
  1622. } else {
  1623. if (redis_sock->persistent_id) {
  1624. persistent_id = strpprintf(0, "phpredis:%s:%s", host, ZSTR_VAL(redis_sock->persistent_id));
  1625. } else {
  1626. persistent_id = strpprintf(0, "phpredis:%s:%f", host, redis_sock->timeout);
  1627. }
  1628. }
  1629. }
  1630. tv.tv_sec = (time_t)redis_sock->timeout;
  1631. tv.tv_usec = (int)((redis_sock->timeout - tv.tv_sec) * 1000000);
  1632. if (tv.tv_sec != 0 || tv.tv_usec != 0) {
  1633. tv_ptr = &tv;
  1634. }
  1635. redis_sock->stream = php_stream_xport_create(host, host_len,
  1636. 0, STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT,
  1637. persistent_id ? ZSTR_VAL(persistent_id) : NULL,
  1638. tv_ptr, NULL, &estr, &err);
  1639. if (persistent_id) {
  1640. zend_string_release(persistent_id);
  1641. }
  1642. if (!redis_sock->stream) {
  1643. if (estr) {
  1644. redis_sock_set_err(redis_sock, ZSTR_VAL(estr), ZSTR_LEN(estr));
  1645. zend_string_release(estr);
  1646. }
  1647. return FAILURE;
  1648. }
  1649. if (p) p->nb_active++;
  1650. /* Attempt to set TCP_NODELAY/TCP_KEEPALIVE if we're not using a unix socket. */
  1651. if (!usocket) {
  1652. php_netstream_data_t *sock = (php_netstream_data_t*)redis_sock->stream->abstract;
  1653. err = setsockopt(sock->socket, IPPROTO_TCP, TCP_NODELAY, (char*) &tcp_flag, sizeof(tcp_flag));
  1654. PHPREDIS_NOTUSED(err);
  1655. err = setsockopt(sock->socket, SOL_SOCKET, SO_KEEPALIVE, (char*) &redis_sock->tcp_keepalive, sizeof(redis_sock->tcp_keepalive));
  1656. PHPREDIS_NOTUSED(err);
  1657. }
  1658. php_stream_auto_cleanup(redis_sock->stream);
  1659. read_tv.tv_sec = (time_t)redis_sock->read_timeout;
  1660. read_tv.tv_usec = (int)((redis_sock->read_timeout - read_tv.tv_sec) * 1000000);
  1661. if (read_tv.tv_sec != 0 || read_tv.tv_usec != 0) {
  1662. php_stream_set_option(redis_sock->stream,PHP_STREAM_OPTION_READ_TIMEOUT,
  1663. 0, &read_tv);
  1664. }
  1665. php_stream_set_option(redis_sock->stream,
  1666. PHP_STREAM_OPTION_WRITE_BUFFER, PHP_STREAM_BUFFER_NONE, NULL);
  1667. redis_sock->status = REDIS_SOCK_STATUS_CONNECTED;
  1668. return SUCCESS;
  1669. }
  1670. /**
  1671. * redis_sock_server_open
  1672. */
  1673. PHP_REDIS_API int
  1674. redis_sock_server_open(RedisSock *redis_sock)
  1675. {
  1676. if (redis_sock) {
  1677. switch (redis_sock->status) {
  1678. case REDIS_SOCK_STATUS_DISCONNECTED:
  1679. if (redis_sock_connect(redis_sock) != SUCCESS) {
  1680. break;
  1681. } else if (redis_sock->status == REDIS_SOCK_STATUS_READY) {
  1682. return SUCCESS;
  1683. }
  1684. // fall through
  1685. case REDIS_SOCK_STATUS_CONNECTED:
  1686. if (redis_sock->auth && redis_sock_auth(redis_sock) != SUCCESS) {
  1687. break;
  1688. }
  1689. redis_sock->status = REDIS_SOCK_STATUS_READY;
  1690. // fall through
  1691. case REDIS_SOCK_STATUS_READY:
  1692. return SUCCESS;
  1693. default:
  1694. return FAILURE;
  1695. }
  1696. }
  1697. return FAILURE;
  1698. }
  1699. /**
  1700. * redis_sock_disconnect
  1701. */
  1702. PHP_REDIS_API int
  1703. redis_sock_disconnect(RedisSock *redis_sock, int force)
  1704. {
  1705. if (redis_sock == NULL) {
  1706. return FAILURE;
  1707. } else if (redis_sock->stream) {
  1708. if (redis_sock->persistent) {
  1709. ConnectionPool *p = NULL;
  1710. if (INI_INT("redis.pconnect.pooling_enabled")) {
  1711. p = redis_sock_get_connection_pool(redis_sock);
  1712. }
  1713. if (force) {
  1714. php_stream_pclose(redis_sock->stream);
  1715. if (p) p->nb_active--;
  1716. } else if (p) {
  1717. zend_llist_prepend_element(&p->list, &redis_sock->stream);
  1718. }
  1719. } else {
  1720. php_stream_close(redis_sock->stream);
  1721. }
  1722. redis_sock->stream = NULL;
  1723. }
  1724. redis_sock->mode = ATOMIC;
  1725. redis_sock->status = REDIS_SOCK_STATUS_DISCONNECTED;
  1726. redis_sock->watching = 0;
  1727. return SUCCESS;
  1728. }
  1729. /**
  1730. * redis_sock_set_err
  1731. */
  1732. PHP_REDIS_API void
  1733. redis_sock_set_err(RedisSock *redis_sock, const char *msg, int msg_len)
  1734. {
  1735. // Free our last error
  1736. if (redis_sock->err != NULL) {
  1737. zend_string_release(redis_sock->err);
  1738. redis_sock->err = NULL;
  1739. }
  1740. if (msg != NULL && msg_len > 0) {
  1741. // Copy in our new error message
  1742. redis_sock->err = zend_string_init(msg, msg_len, 0);
  1743. }
  1744. }
  1745. /**
  1746. * redis_sock_read_multibulk_reply
  1747. */
  1748. PHP_REDIS_API int redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAMETERS,
  1749. RedisSock *redis_sock, zval *z_tab,
  1750. void *ctx)
  1751. {
  1752. char inbuf[4096];
  1753. int numElems;
  1754. size_t len;
  1755. if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len) < 0) {
  1756. return -1;
  1757. }
  1758. if(inbuf[0] != '*') {
  1759. if (IS_ATOMIC(redis_sock)) {
  1760. if (inbuf[0] == '-') {
  1761. redis_sock_set_err(redis_sock, inbuf+1, len);
  1762. }
  1763. RETVAL_FALSE;
  1764. } else {
  1765. add_next_index_bool(z_tab, 0);
  1766. }
  1767. return -1;
  1768. }
  1769. numElems = atoi(inbuf+1);
  1770. zval z_multi_result;
  1771. array_init(&z_multi_result); /* pre-allocate array for multi's results. */
  1772. redis_mbulk_reply_loop(redis_sock, &z_multi_result, numElems, UNSERIALIZE_ALL);
  1773. if (IS_ATOMIC(redis_sock)) {
  1774. RETVAL_ZVAL(&z_multi_result, 0, 1);
  1775. } else {
  1776. add_next_index_zval(z_tab, &z_multi_result);
  1777. }
  1778. /*zval_copy_ctor(return_value); */
  1779. return 0;
  1780. }
  1781. /* Like multibulk reply, but don't touch the values, they won't be unserialized
  1782. * (this is used by HKEYS). */
  1783. PHP_REDIS_API int
  1784. redis_mbulk_reply_raw(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx)
  1785. {
  1786. char inbuf[4096];
  1787. int numElems;
  1788. size_t len;
  1789. if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len) < 0) {
  1790. return -1;
  1791. }
  1792. if(inbuf[0] != '*') {
  1793. if (IS_ATOMIC(redis_sock)) {
  1794. if (inbuf[0] == '-') {
  1795. redis_sock_set_err(redis_sock, inbuf+1, len);
  1796. }
  1797. RETVAL_FALSE;
  1798. } else {
  1799. add_next_index_bool(z_tab, 0);
  1800. }
  1801. return -1;
  1802. }
  1803. numElems = atoi(inbuf+1);
  1804. zval z_multi_result;
  1805. array_init(&z_multi_result); /* pre-allocate array for multi's results. */
  1806. redis_mbulk_reply_loop(redis_sock, &z_multi_result, numElems, UNSERIALIZE_NONE);
  1807. if (IS_ATOMIC(redis_sock)) {
  1808. RETVAL_ZVAL(&z_multi_result, 0, 1);
  1809. } else {
  1810. add_next_index_zval(z_tab, &z_multi_result);
  1811. }
  1812. /*zval_copy_ctor(return_value); */
  1813. return 0;
  1814. }
  1815. PHP_REDIS_API void
  1816. redis_mbulk_reply_loop(RedisSock *redis_sock, zval *z_tab, int count,
  1817. int unserialize)
  1818. {
  1819. zval z_unpacked;
  1820. char *line;
  1821. int i, len;
  1822. for (i = 0; i < count; ++i) {
  1823. if ((line = redis_sock_read(redis_sock, &len)) == NULL) {
  1824. add_next_index_bool(z_tab, 0);
  1825. continue;
  1826. }
  1827. /* We will attempt unserialization, if we're unserializing everything,
  1828. * or if we're unserializing keys and we're on a key, or we're
  1829. * unserializing values and we're on a value! */
  1830. int unwrap = (
  1831. (unserialize == UNSERIALIZE_ALL) ||
  1832. (unserialize == UNSERIALIZE_KEYS && i % 2 == 0) ||
  1833. (unserialize == UNSERIALIZE_VALS && i % 2 != 0)
  1834. );
  1835. if (unwrap && redis_unpack(redis_sock, line, len, &z_unpacked)) {
  1836. add_next_index_zval(z_tab, &z_unpacked);
  1837. } else {
  1838. add_next_index_stringl(z_tab, line, len);
  1839. }
  1840. efree(line);
  1841. }
  1842. }
  1843. /* Specialized multibulk processing for HMGET where we need to pair requested
  1844. * keys with their returned values */
  1845. PHP_REDIS_API int redis_mbulk_reply_assoc(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx)
  1846. {
  1847. char inbuf[4096], *response;
  1848. int response_len;
  1849. int i, numElems;
  1850. size_t len;
  1851. zval *z_keys = ctx;
  1852. if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len) < 0) {
  1853. goto failure;
  1854. }
  1855. if (*inbuf != TYPE_MULTIBULK) {
  1856. if (IS_ATOMIC(redis_sock)) {
  1857. RETVAL_FALSE;
  1858. } else {
  1859. add_next_index_bool(z_tab, 0);
  1860. }
  1861. if (*inbuf == TYPE_ERR) {
  1862. redis_sock_set_err(redis_sock, inbuf + 1, len - 1);
  1863. }
  1864. goto failure;
  1865. }
  1866. numElems = atoi(inbuf+1);
  1867. zval z_multi_result;
  1868. array_init(&z_multi_result); /* pre-allocate array for multi's results. */
  1869. for(i = 0; i < numElems; ++i) {
  1870. zend_string *zstr = zval_get_string(&z_keys[i]);
  1871. response = redis_sock_read(redis_sock, &response_len);
  1872. if(response != NULL) {
  1873. zval z_unpacked;
  1874. if (redis_unpack(redis_sock, response, response_len, &z_unpacked)) {
  1875. add_assoc_zval_ex(&z_multi_result, ZSTR_VAL(zstr), ZSTR_LEN(zstr), &z_unpacked);
  1876. } else {
  1877. add_assoc_stringl_ex(&z_multi_result, ZSTR_VAL(zstr), ZSTR_LEN(zstr), response, response_len);
  1878. }
  1879. efree(response);
  1880. } else {
  1881. add_assoc_bool_ex(&z_multi_result, ZSTR_VAL(zstr), ZSTR_LEN(zstr), 0);
  1882. }
  1883. zend_string_release(zstr);
  1884. zval_dtor(&z_keys[i]);
  1885. }
  1886. efree(z_keys);
  1887. if (IS_ATOMIC(redis_sock)) {
  1888. RETVAL_ZVAL(&z_multi_result, 0, 1);
  1889. } else {
  1890. add_next_index_zval(z_tab, &z_multi_result);
  1891. }
  1892. return SUCCESS;
  1893. failure:
  1894. if (z_keys != NULL) {
  1895. for (i = 0; Z_TYPE(z_keys[i]) != IS_NULL; ++i) {
  1896. zval_dtor(&z_keys[i]);
  1897. }
  1898. efree(z_keys);
  1899. }
  1900. return FAILURE;
  1901. }
  1902. /**
  1903. * redis_sock_write
  1904. */
  1905. PHP_REDIS_API int
  1906. redis_sock_write(RedisSock *redis_sock, char *cmd, size_t sz)
  1907. {
  1908. if (redis_check_eof(redis_sock, 0) == 0 &&
  1909. php_stream_write(redis_sock->stream, cmd, sz) == sz
  1910. ) {
  1911. return sz;
  1912. }
  1913. return -1;
  1914. }
  1915. /**
  1916. * redis_free_socket
  1917. */
  1918. PHP_REDIS_API void redis_free_socket(RedisSock *redis_sock)
  1919. {
  1920. if (redis_sock->prefix) {
  1921. zend_string_release(redis_sock->prefix);
  1922. }
  1923. if (redis_sock->pipeline_cmd) {
  1924. zend_string_release(redis_sock->pipeline_cmd);
  1925. }
  1926. if (redis_sock->err) {
  1927. zend_string_release(redis_sock->err);
  1928. }
  1929. if (redis_sock->auth) {
  1930. zend_string_release(redis_sock->auth);
  1931. }
  1932. if (redis_sock->persistent_id) {
  1933. zend_string_release(redis_sock->persistent_id);
  1934. }
  1935. if (redis_sock->host) {
  1936. zend_string_release(redis_sock->host);
  1937. }
  1938. efree(redis_sock);
  1939. }
  1940. PHP_REDIS_API int
  1941. redis_pack(RedisSock *redis_sock, zval *z, char **val, size_t *val_len)
  1942. {
  1943. char *buf;
  1944. int valfree;
  1945. size_t len;
  1946. valfree = redis_serialize(redis_sock, z, &buf, &len);
  1947. switch (redis_sock->compression) {
  1948. case REDIS_COMPRESSION_LZF:
  1949. #ifdef HAVE_REDIS_LZF
  1950. {
  1951. char *data;
  1952. uint32_t res;
  1953. double size;
  1954. /* preserve compatibility with PECL lzf_compress margin (greater of 4% and LZF_MARGIN) */
  1955. size = len + MIN(UINT_MAX - len, MAX(LZF_MARGIN, len / 25));
  1956. data = emalloc(size);
  1957. if ((res = lzf_compress(buf, len, data, size)) > 0) {
  1958. if (valfree) efree(buf);
  1959. *val = data;
  1960. *val_len = res;
  1961. return 1;
  1962. }
  1963. efree(data);
  1964. }
  1965. #endif
  1966. break;
  1967. case REDIS_COMPRESSION_ZSTD:
  1968. #ifdef HAVE_REDIS_ZSTD
  1969. {
  1970. char *data;
  1971. size_t size;
  1972. int level;
  1973. if (redis_sock->compression_level < 1) {
  1974. #ifdef ZSTD_CLEVEL_DEFAULT
  1975. level = ZSTD_CLEVEL_DEFAULT;
  1976. #else
  1977. level = 3;
  1978. #endif
  1979. } else if (redis_sock->compression_level > ZSTD_maxCLevel()) {
  1980. level = ZSTD_maxCLevel();
  1981. } else {
  1982. level = redis_sock->compression_level;
  1983. }
  1984. size = ZSTD_compressBound(len);
  1985. data = emalloc(size);
  1986. size = ZSTD_compress(data, size, buf, len, level);
  1987. if (!ZSTD_isError(size)) {
  1988. if (valfree) efree(buf);
  1989. data = erealloc(data, size);
  1990. *val = data;
  1991. *val_len = size;
  1992. return 1;
  1993. }
  1994. efree(data);
  1995. }
  1996. #endif
  1997. break;
  1998. }
  1999. *val = buf;
  2000. *val_len = len;
  2001. return valfree;
  2002. }
  2003. PHP_REDIS_API int
  2004. redis_unpack(RedisSock *redis_sock, const char *val, int val_len, zval *z_ret)
  2005. {
  2006. switch (redis_sock->compression) {
  2007. case REDIS_COMPRESSION_LZF:
  2008. #ifdef HAVE_REDIS_LZF
  2009. {
  2010. char *data;
  2011. int i;
  2012. uint32_t res;
  2013. errno = E2BIG;
  2014. /* start from two-times bigger buffer and
  2015. * increase it exponentially if needed */
  2016. for (i = 2; errno == E2BIG; i *= 2) {
  2017. data = emalloc(i * val_len);
  2018. if ((res = lzf_decompress(val, val_len, data, i * val_len)) == 0) {
  2019. /* errno != E2BIG will brake for loop */
  2020. efree(data);
  2021. continue;
  2022. } else if (redis_unserialize(redis_sock, data, res, z_ret) == 0) {
  2023. ZVAL_STRINGL(z_ret, data, res);
  2024. }
  2025. efree(data);
  2026. return 1;
  2027. }
  2028. }
  2029. #endif
  2030. break;
  2031. case REDIS_COMPRESSION_ZSTD:
  2032. #ifdef HAVE_REDIS_ZSTD
  2033. {
  2034. char *data;
  2035. size_t len;
  2036. len = ZSTD_getFrameContentSize(val, val_len);
  2037. if (len >= 0) {
  2038. data = emalloc(len);
  2039. len = ZSTD_decompress(data, len, val, val_len);
  2040. if (ZSTD_isError(len)) {
  2041. efree(data);
  2042. break;
  2043. } else if (redis_unserialize(redis_sock, data, len, z_ret) == 0) {
  2044. ZVAL_STRINGL(z_ret, data, len);
  2045. }
  2046. efree(data);
  2047. return 1;
  2048. }
  2049. }
  2050. #endif
  2051. break;
  2052. }
  2053. return redis_unserialize(redis_sock, val, val_len, z_ret);
  2054. }
  2055. PHP_REDIS_API int
  2056. redis_serialize(RedisSock *redis_sock, zval *z, char **val, size_t *val_len
  2057. )
  2058. {
  2059. php_serialize_data_t ht;
  2060. smart_str sstr = {0};
  2061. #ifdef HAVE_REDIS_IGBINARY
  2062. size_t sz;
  2063. uint8_t *val8;
  2064. #endif
  2065. *val = NULL;
  2066. *val_len = 0;
  2067. switch(redis_sock->serializer) {
  2068. case REDIS_SERIALIZER_NONE:
  2069. switch(Z_TYPE_P(z)) {
  2070. case IS_STRING:
  2071. *val = Z_STRVAL_P(z);
  2072. *val_len = Z_STRLEN_P(z);
  2073. break;
  2074. case IS_OBJECT:
  2075. *val = "Object";
  2076. *val_len = 6;
  2077. break;
  2078. case IS_ARRAY:
  2079. *val = "Array";
  2080. *val_len = 5;
  2081. break;
  2082. default: { /* copy */
  2083. zend_string *zstr = zval_get_string(z);
  2084. *val = estrndup(ZSTR_VAL(zstr), ZSTR_LEN(zstr));
  2085. *val_len = ZSTR_LEN(zstr);
  2086. zend_string_release(zstr);
  2087. return 1;
  2088. }
  2089. }
  2090. break;
  2091. case REDIS_SERIALIZER_PHP:
  2092. PHP_VAR_SERIALIZE_INIT(ht);
  2093. php_var_serialize(&sstr, z, &ht);
  2094. *val = estrndup(ZSTR_VAL(sstr.s), ZSTR_LEN(sstr.s));
  2095. *val_len = ZSTR_LEN(sstr.s);
  2096. smart_str_free(&sstr);
  2097. PHP_VAR_SERIALIZE_DESTROY(ht);
  2098. return 1;
  2099. case REDIS_SERIALIZER_MSGPACK:
  2100. #ifdef HAVE_REDIS_MSGPACK
  2101. php_msgpack_serialize(&sstr, z);
  2102. *val = estrndup(ZSTR_VAL(sstr.s), ZSTR_LEN(sstr.s));
  2103. *val_len = ZSTR_LEN(sstr.s);
  2104. smart_str_free(&sstr);
  2105. return 1;
  2106. #endif
  2107. break;
  2108. case REDIS_SERIALIZER_IGBINARY:
  2109. #ifdef HAVE_REDIS_IGBINARY
  2110. if(igbinary_serialize(&val8, (size_t *)&sz, z) == 0) {
  2111. *val = (char*)val8;
  2112. *val_len = sz;
  2113. return 1;
  2114. }
  2115. #endif
  2116. break;
  2117. case REDIS_SERIALIZER_JSON:
  2118. #ifdef HAVE_REDIS_JSON
  2119. php_json_encode(&sstr, z, PHP_JSON_OBJECT_AS_ARRAY);
  2120. *val = estrndup(ZSTR_VAL(sstr.s), ZSTR_LEN(sstr.s));
  2121. *val_len = ZSTR_LEN(sstr.s);
  2122. smart_str_free(&sstr);
  2123. return 1;
  2124. #endif
  2125. break;
  2126. EMPTY_SWITCH_DEFAULT_CASE()
  2127. }
  2128. return 0;
  2129. }
  2130. PHP_REDIS_API int
  2131. redis_unserialize(RedisSock* redis_sock, const char *val, int val_len,
  2132. zval *z_ret)
  2133. {
  2134. php_unserialize_data_t var_hash;
  2135. int ret = 0;
  2136. switch(redis_sock->serializer) {
  2137. case REDIS_SERIALIZER_NONE:
  2138. /* Nothing to do */
  2139. break;
  2140. case REDIS_SERIALIZER_PHP:
  2141. PHP_VAR_UNSERIALIZE_INIT(var_hash);
  2142. ret = php_var_unserialize(z_ret, (const unsigned char **)&val,
  2143. (const unsigned char *)val + val_len,
  2144. &var_hash);
  2145. PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
  2146. break;
  2147. case REDIS_SERIALIZER_MSGPACK:
  2148. #ifdef HAVE_REDIS_MSGPACK
  2149. ret = !php_msgpack_unserialize(z_ret, (char *)val, (size_t)val_len);
  2150. #endif
  2151. break;
  2152. case REDIS_SERIALIZER_IGBINARY:
  2153. #ifdef HAVE_REDIS_IGBINARY
  2154. /*
  2155. * Check if the given string starts with an igbinary header.
  2156. *
  2157. * A modern igbinary string consists of the following format:
  2158. *
  2159. * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
  2160. * | header (4) | type (1) | ... (n) | NUL (1) |
  2161. * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
  2162. *
  2163. * With header being either 0x00000001 or 0x00000002
  2164. * (encoded as big endian).
  2165. *
  2166. * Not all versions contain the trailing NULL byte though, so
  2167. * do not check for that.
  2168. */
  2169. if (val_len < 5
  2170. || (memcmp(val, "\x00\x00\x00\x01", 4) != 0
  2171. && memcmp(val, "\x00\x00\x00\x02", 4) != 0))
  2172. {
  2173. /* This is most definitely not an igbinary string, so do
  2174. not try to unserialize this as one. */
  2175. break;
  2176. }
  2177. ret = !igbinary_unserialize((const uint8_t *)val, (size_t)val_len, z_ret);
  2178. #endif
  2179. break;
  2180. case REDIS_SERIALIZER_JSON:
  2181. #ifdef HAVE_REDIS_JSON
  2182. #if (PHP_MAJOR_VERSION == 7 && PHP_MINOR_VERSION < 1)
  2183. JSON_G(error_code) = PHP_JSON_ERROR_NONE;
  2184. php_json_decode(z_ret, (char*)val, val_len, 1, PHP_JSON_PARSER_DEFAULT_DEPTH);
  2185. ret = JSON_G(error_code) == PHP_JSON_ERROR_NONE;
  2186. #else
  2187. ret = !php_json_decode(z_ret, (char *)val, val_len, 1, PHP_JSON_PARSER_DEFAULT_DEPTH);
  2188. #endif
  2189. #endif
  2190. break;
  2191. EMPTY_SWITCH_DEFAULT_CASE()
  2192. }
  2193. return ret;
  2194. }
  2195. PHP_REDIS_API int
  2196. redis_key_prefix(RedisSock *redis_sock, char **key, size_t *key_len) {
  2197. int ret_len;
  2198. char *ret;
  2199. if (redis_sock->prefix == NULL) {
  2200. return 0;
  2201. }
  2202. ret_len = ZSTR_LEN(redis_sock->prefix) + *key_len;
  2203. ret = ecalloc(1 + ret_len, 1);
  2204. memcpy(ret, ZSTR_VAL(redis_sock->prefix), ZSTR_LEN(redis_sock->prefix));
  2205. memcpy(ret + ZSTR_LEN(redis_sock->prefix), *key, *key_len);
  2206. *key = ret;
  2207. *key_len = ret_len;
  2208. return 1;
  2209. }
  2210. /*
  2211. * Processing for variant reply types (think EVAL)
  2212. */
  2213. PHP_REDIS_API int
  2214. redis_sock_gets(RedisSock *redis_sock, char *buf, int buf_size,
  2215. size_t *line_size)
  2216. {
  2217. // Handle EOF
  2218. if(-1 == redis_check_eof(redis_sock, 0)) {
  2219. return -1;
  2220. }
  2221. if(php_stream_get_line(redis_sock->stream, buf, buf_size, line_size)
  2222. == NULL)
  2223. {
  2224. char *errmsg = NULL;
  2225. if (redis_sock->port < 0) {
  2226. spprintf(&errmsg, 0, "read error on connection to %s", ZSTR_VAL(redis_sock->host));
  2227. } else {
  2228. spprintf(&errmsg, 0, "read error on connection to %s:%d", ZSTR_VAL(redis_sock->host), redis_sock->port);
  2229. }
  2230. // Close our socket
  2231. redis_sock_disconnect(redis_sock, 1);
  2232. // Throw a read error exception
  2233. REDIS_THROW_EXCEPTION(errmsg, 0);
  2234. efree(errmsg);
  2235. return -1;
  2236. }
  2237. /* We don't need \r\n */
  2238. *line_size-=2;
  2239. buf[*line_size]='\0';
  2240. /* Success! */
  2241. return 0;
  2242. }
  2243. PHP_REDIS_API int
  2244. redis_read_reply_type(RedisSock *redis_sock, REDIS_REPLY_TYPE *reply_type,
  2245. long *reply_info)
  2246. {
  2247. // Make sure we haven't lost the connection, even trying to reconnect
  2248. if(-1 == redis_check_eof(redis_sock, 0)) {
  2249. // Failure
  2250. *reply_type = EOF;
  2251. return -1;
  2252. }
  2253. // Attempt to read the reply-type byte
  2254. if((*reply_type = php_stream_getc(redis_sock->stream)) == EOF) {
  2255. REDIS_THROW_EXCEPTION( "socket error on read socket", 0);
  2256. return -1;
  2257. }
  2258. // If this is a BULK, MULTI BULK, or simply an INTEGER response, we can
  2259. // extract the value or size info here
  2260. if(*reply_type == TYPE_INT || *reply_type == TYPE_BULK ||
  2261. *reply_type == TYPE_MULTIBULK)
  2262. {
  2263. // Buffer to hold size information
  2264. char inbuf[255];
  2265. /* Read up to our newline */
  2266. if (php_stream_gets(redis_sock->stream, inbuf, sizeof(inbuf)) == NULL) {
  2267. return -1;
  2268. }
  2269. /* Set our size response */
  2270. *reply_info = atol(inbuf);
  2271. }
  2272. /* Success! */
  2273. return 0;
  2274. }
  2275. /*
  2276. * Read a single line response, having already consumed the reply-type byte
  2277. */
  2278. static int
  2279. redis_read_variant_line(RedisSock *redis_sock, REDIS_REPLY_TYPE reply_type,
  2280. int as_string, zval *z_ret)
  2281. {
  2282. // Buffer to read our single line reply
  2283. char inbuf[4096];
  2284. size_t len;
  2285. /* Attempt to read our single line reply */
  2286. if(redis_sock_gets(redis_sock, inbuf, sizeof(inbuf), &len) < 0) {
  2287. return -1;
  2288. }
  2289. /* Throw exception on SYNC error otherwise just set error string */
  2290. if(reply_type == TYPE_ERR) {
  2291. redis_sock_set_err(redis_sock, inbuf, len);
  2292. redis_error_throw(redis_sock);
  2293. ZVAL_FALSE(z_ret);
  2294. } else if (as_string) {
  2295. ZVAL_STRINGL(z_ret, inbuf, len);
  2296. } else {
  2297. ZVAL_TRUE(z_ret);
  2298. }
  2299. return 0;
  2300. }
  2301. PHP_REDIS_API int
  2302. redis_read_variant_bulk(RedisSock *redis_sock, int size, zval *z_ret
  2303. )
  2304. {
  2305. // Attempt to read the bulk reply
  2306. char *bulk_resp = redis_sock_read_bulk_reply(redis_sock, size);
  2307. /* Set our reply to FALSE on failure, and the string on success */
  2308. if(bulk_resp == NULL) {
  2309. ZVAL_FALSE(z_ret);
  2310. return -1;
  2311. }
  2312. ZVAL_STRINGL(z_ret, bulk_resp, size);
  2313. efree(bulk_resp);
  2314. return 0;
  2315. }
  2316. PHP_REDIS_API int
  2317. redis_read_multibulk_recursive(RedisSock *redis_sock, int elements, int status_strings,
  2318. zval *z_ret)
  2319. {
  2320. long reply_info;
  2321. REDIS_REPLY_TYPE reply_type;
  2322. zval z_subelem;
  2323. // Iterate while we have elements
  2324. while(elements > 0) {
  2325. // Attempt to read our reply type
  2326. if(redis_read_reply_type(redis_sock, &reply_type, &reply_info
  2327. ) < 0)
  2328. {
  2329. zend_throw_exception_ex(redis_exception_ce, 0,
  2330. "protocol error, couldn't parse MULTI-BULK response\n");
  2331. return FAILURE;
  2332. }
  2333. // Switch on our reply-type byte
  2334. switch(reply_type) {
  2335. case TYPE_ERR:
  2336. case TYPE_LINE:
  2337. redis_read_variant_line(redis_sock, reply_type, status_strings,
  2338. &z_subelem);
  2339. add_next_index_zval(z_ret, &z_subelem);
  2340. break;
  2341. case TYPE_INT:
  2342. // Add our long value
  2343. add_next_index_long(z_ret, reply_info);
  2344. break;
  2345. case TYPE_BULK:
  2346. // Init a zval for our bulk response, read and add it
  2347. redis_read_variant_bulk(redis_sock, reply_info, &z_subelem);
  2348. add_next_index_zval(z_ret, &z_subelem);
  2349. break;
  2350. case TYPE_MULTIBULK:
  2351. // Construct an array for our sub element, and add it, and recurse
  2352. array_init(&z_subelem);
  2353. add_next_index_zval(z_ret, &z_subelem);
  2354. redis_read_multibulk_recursive(redis_sock, reply_info, status_strings,
  2355. &z_subelem);
  2356. break;
  2357. default:
  2358. // Stop the compiler from whinging
  2359. break;
  2360. }
  2361. /* Decrement our element counter */
  2362. elements--;
  2363. }
  2364. return 0;
  2365. }
  2366. static int
  2367. variant_reply_generic(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
  2368. int status_strings, zval *z_tab, void *ctx)
  2369. {
  2370. // Reply type, and reply size vars
  2371. REDIS_REPLY_TYPE reply_type;
  2372. long reply_info;
  2373. zval z_ret;
  2374. // Attempt to read our header
  2375. if(redis_read_reply_type(redis_sock,&reply_type,&reply_info) < 0)
  2376. {
  2377. return -1;
  2378. }
  2379. /* Switch based on our top level reply type */
  2380. switch(reply_type) {
  2381. case TYPE_ERR:
  2382. case TYPE_LINE:
  2383. redis_read_variant_line(redis_sock, reply_type, status_strings, &z_ret);
  2384. break;
  2385. case TYPE_INT:
  2386. ZVAL_LONG(&z_ret, reply_info);
  2387. break;
  2388. case TYPE_BULK:
  2389. redis_read_variant_bulk(redis_sock, reply_info, &z_ret);
  2390. break;
  2391. case TYPE_MULTIBULK:
  2392. /* Initialize an array for our multi-bulk response */
  2393. array_init(&z_ret);
  2394. // If we've got more than zero elements, parse our multi bulk
  2395. // response recursively
  2396. if (reply_info > -1) {
  2397. redis_read_multibulk_recursive(redis_sock, reply_info, status_strings, &z_ret);
  2398. }
  2399. break;
  2400. default:
  2401. zend_throw_exception_ex(redis_exception_ce, 0,
  2402. "protocol error, got '%c' as reply-type byte\n", reply_type);
  2403. return FAILURE;
  2404. }
  2405. if (IS_ATOMIC(redis_sock)) {
  2406. /* Set our return value */
  2407. RETVAL_ZVAL(&z_ret, 0, 1);
  2408. } else {
  2409. add_next_index_zval(z_tab, &z_ret);
  2410. }
  2411. /* Success */
  2412. return 0;
  2413. }
  2414. PHP_REDIS_API int
  2415. redis_read_raw_variant_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
  2416. zval *z_tab, void *ctx)
  2417. {
  2418. return variant_reply_generic(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
  2419. redis_sock->reply_literal, z_tab, ctx);
  2420. }
  2421. PHP_REDIS_API int
  2422. redis_read_variant_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
  2423. zval *z_tab, void *ctx)
  2424. {
  2425. return variant_reply_generic(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, 0, z_tab, ctx);
  2426. }
  2427. PHP_REDIS_API int
  2428. redis_read_variant_reply_strings(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
  2429. zval *z_tab, void *ctx)
  2430. {
  2431. return variant_reply_generic(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, 1, z_tab, ctx);
  2432. }
  2433. /* vim: set tabstop=4 softtabstop=4 expandtab shiftwidth=4: */