PageRenderTime 50ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/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

Large files files are truncated, but you can click here to view the full file

  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);

Large files files are truncated, but you can click here to view the full file