1 /* -*- Mode: C; tab-width: 4 -*- */
2 /*
3 +----------------------------------------------------------------------+
4 | Copyright (c) 1997-2009 The PHP Group |
5 +----------------------------------------------------------------------+
6 | This source file is subject to version 3.01 of the PHP license, |
7 | that is bundled with this package in the file LICENSE, and is |
8 | available through the world-wide-web at the following url: |
9 | http://www.php.net/license/3_01.txt |
10 | If you did not receive a copy of the PHP license and are unable to |
11 | obtain it through the world-wide-web, please send a note to |
12 | license@php.net so we can mail you a copy immediately. |
13 +----------------------------------------------------------------------+
14 | Original Author: Michael Grunder <michael.grunder@gmail.com |
15 | Maintainer: Nicolas Favre-Felix <n.favre-felix@owlient.eu> |
16 +----------------------------------------------------------------------+
17 */
18
19 #ifdef HAVE_CONFIG_H
20 #include "config.h"
21 #endif
22
23 #include "redis_commands.h"
24
25 #include "php_network.h"
26
27 #ifndef PHP_WIN32
28 #include <netinet/tcp.h> /* TCP_KEEPALIVE */
29 #else
30 #include <winsock.h>
31 #endif
32
33 #include <zend_exceptions.h>
34
35 /* Georadius sort type */
36 typedef enum geoSortType {
37 SORT_NONE,
38 SORT_ASC,
39 SORT_DESC
40 } geoSortType;
41
42 /* Georadius store type */
43 typedef enum geoStoreType {
44 STORE_NONE,
45 STORE_COORD,
46 STORE_DIST
47 } geoStoreType;
48
49 /* Georadius options structure */
50 typedef struct geoOptions {
51 int withcoord;
52 int withdist;
53 int withhash;
54 long count;
55 geoSortType sort;
56 geoStoreType store;
57 zend_string *key;
58 } geoOptions;
59
60 /* Local passthrough macro for command construction. Given that these methods
61 * are generic (so they work whether the caller is Redis or RedisCluster) we
62 * will always have redis_sock, slot*, and */
63 #define REDIS_CMD_SPPRINTF(ret, kw, fmt, ...) \
64 redis_spprintf(redis_sock, slot, ret, kw, fmt, ##__VA_ARGS__)
65
66 /* Generic commands based on method signature and what kind of things we're
67 * processing. Lots of Redis commands take something like key, value, or
68 * key, value long. Each unique signature like this is written only once */
69
70 /* A command that takes no arguments */
redis_empty_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)71 int redis_empty_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
72 char *kw, char **cmd, int *cmd_len, short *slot,
73 void **ctx)
74 {
75 *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "");
76 return SUCCESS;
77 }
78
79 /* Helper to construct a raw command. Given that the cluster and non cluster
80 * versions are different (RedisCluster needs an additional argument to direct
81 * the command) we take the start of our array and count */
redis_build_raw_cmd(zval * z_args,int argc,char ** cmd,int * cmd_len)82 int redis_build_raw_cmd(zval *z_args, int argc, char **cmd, int *cmd_len)
83 {
84 smart_string cmdstr = {0};
85 int i;
86
87 /* Make sure our first argument is a string */
88 if (Z_TYPE(z_args[0]) != IS_STRING) {
89 php_error_docref(NULL, E_WARNING,
90 "When sending a 'raw' command, the first argument must be a string!");
91 return FAILURE;
92 }
93
94 /* Initialize our command string */
95 redis_cmd_init_sstr(&cmdstr, argc-1, Z_STRVAL(z_args[0]), Z_STRLEN(z_args[0]));
96
97 for (i = 1; i < argc; i++) {
98 switch (Z_TYPE(z_args[i])) {
99 case IS_STRING:
100 redis_cmd_append_sstr(&cmdstr, Z_STRVAL(z_args[i]),
101 Z_STRLEN(z_args[i]));
102 break;
103 case IS_LONG:
104 redis_cmd_append_sstr_long(&cmdstr,Z_LVAL(z_args[i]));
105 break;
106 case IS_DOUBLE:
107 redis_cmd_append_sstr_dbl(&cmdstr,Z_DVAL(z_args[i]));
108 break;
109 default:
110 php_error_docref(NULL, E_WARNING,
111 "Raw command arguments must be scalar values!");
112 efree(cmdstr.c);
113 return FAILURE;
114 }
115 }
116
117 /* Push command and length to caller */
118 *cmd = cmdstr.c;
119 *cmd_len = cmdstr.len;
120
121 return SUCCESS;
122 }
123
124 smart_string *
redis_build_script_cmd(smart_string * cmd,int argc,zval * z_args)125 redis_build_script_cmd(smart_string *cmd, int argc, zval *z_args)
126 {
127 int i;
128 zend_string *zstr;
129
130 if (Z_TYPE(z_args[0]) != IS_STRING) {
131 return NULL;
132 }
133 // Branch based on the directive
134 if (!strcasecmp(Z_STRVAL(z_args[0]), "flush") || !strcasecmp(Z_STRVAL(z_args[0]), "kill")) {
135 // Simple SCRIPT FLUSH, or SCRIPT_KILL command
136 REDIS_CMD_INIT_SSTR_STATIC(cmd, argc, "SCRIPT");
137 redis_cmd_append_sstr(cmd, Z_STRVAL(z_args[0]), Z_STRLEN(z_args[0]));
138 } else if (!strcasecmp(Z_STRVAL(z_args[0]), "load")) {
139 // Make sure we have a second argument, and it's not empty. If it is
140 // empty, we can just return an empty array (which is what Redis does)
141 if (argc < 2 || Z_TYPE(z_args[1]) != IS_STRING || Z_STRLEN(z_args[1]) < 1) {
142 return NULL;
143 }
144 // Format our SCRIPT LOAD command
145 REDIS_CMD_INIT_SSTR_STATIC(cmd, argc, "SCRIPT");
146 redis_cmd_append_sstr(cmd, "LOAD", sizeof("LOAD") - 1);
147 redis_cmd_append_sstr(cmd, Z_STRVAL(z_args[1]), Z_STRLEN(z_args[1]));
148 } else if (!strcasecmp(Z_STRVAL(z_args[0]), "exists")) {
149 // Make sure we have a second argument
150 if (argc < 2) {
151 return NULL;
152 }
153 /* Construct our SCRIPT EXISTS command */
154 REDIS_CMD_INIT_SSTR_STATIC(cmd, argc, "SCRIPT");
155 redis_cmd_append_sstr(cmd, "EXISTS", sizeof("EXISTS") - 1);
156
157 for (i = 1; i < argc; ++i) {
158 zstr = zval_get_string(&z_args[i]);
159 redis_cmd_append_sstr(cmd, ZSTR_VAL(zstr), ZSTR_LEN(zstr));
160 zend_string_release(zstr);
161 }
162 } else {
163 /* Unknown directive */
164 return NULL;
165 }
166 return cmd;
167 }
168
169 /* Command that takes one optional string */
redis_opt_str_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)170 int redis_opt_str_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw,
171 char **cmd, int *cmd_len, short *slot, void **ctx)
172 {
173 char *arg = NULL;
174 size_t arglen;
175
176 if (zend_parse_parameters(ZEND_NUM_ARGS(), "|s!", &arg, &arglen) == FAILURE) {
177 return FAILURE;
178 }
179
180 if (arg != NULL) {
181 *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "s", arg, arglen);
182 } else {
183 *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "");
184 }
185
186 return SUCCESS;
187 }
188
189 /* Generic command where we just take a string and do nothing to it*/
redis_str_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)190 int redis_str_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw,
191 char **cmd, int *cmd_len, short *slot, void **ctx)
192 {
193 char *arg;
194 size_t arg_len;
195
196 // Parse args
197 if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &arg, &arg_len)
198 ==FAILURE)
199 {
200 return FAILURE;
201 }
202
203 // Build the command without molesting the string
204 *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "s", arg, arg_len);
205
206 return SUCCESS;
207 }
208
209 /* Key, long, zval (serialized) */
redis_key_long_val_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)210 int redis_key_long_val_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
211 char *kw, char **cmd, int *cmd_len, short *slot,
212 void **ctx)
213 {
214 char *key = NULL;
215 size_t key_len;
216 zend_long expire;
217 zval *z_val;
218
219 if (zend_parse_parameters(ZEND_NUM_ARGS(), "slz", &key, &key_len,
220 &expire, &z_val) == FAILURE)
221 {
222 return FAILURE;
223 }
224
225 *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "klv", key, key_len, expire, z_val);
226
227 return SUCCESS;
228 }
229
230 /* Generic key, long, string (unserialized) */
redis_key_long_str_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)231 int redis_key_long_str_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
232 char *kw, char **cmd, int *cmd_len, short *slot,
233 void **ctx)
234 {
235 char *key, *val;
236 size_t key_len, val_len;
237 zend_long lval;
238
239 if (zend_parse_parameters(ZEND_NUM_ARGS(), "sls", &key, &key_len,
240 &lval, &val, &val_len) == FAILURE)
241 {
242 return FAILURE;
243 }
244
245 *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kds", key, key_len, (int)lval, val, val_len);
246
247 return SUCCESS;
248 }
249
250 /* Generic command construction when we just take a key and value */
redis_kv_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)251 int redis_kv_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
252 char *kw, char **cmd, int *cmd_len, short *slot,
253 void **ctx)
254 {
255 char *key;
256 size_t key_len;
257 zval *z_val;
258
259 if (zend_parse_parameters(ZEND_NUM_ARGS(), "sz", &key, &key_len,
260 &z_val) == FAILURE)
261 {
262 return FAILURE;
263 }
264
265 *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kv", key, key_len, z_val);
266
267 return SUCCESS;
268 }
269
270 /* Generic command that takes a key and an unserialized value */
redis_key_str_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)271 int redis_key_str_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
272 char *kw, char **cmd, int *cmd_len, short *slot,
273 void **ctx)
274 {
275 char *key, *val;
276 size_t key_len, val_len;
277
278 if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", &key, &key_len,
279 &val, &val_len) == FAILURE)
280 {
281 return FAILURE;
282 }
283
284 // Construct command
285 *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "ks", key, key_len, val, val_len);
286
287 return SUCCESS;
288 }
289
290 /* Key, string, string without serialization (ZCOUNT, ZREMRANGEBYSCORE) */
redis_key_str_str_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)291 int redis_key_str_str_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
292 char *kw, char **cmd, int *cmd_len, short *slot,
293 void **ctx)
294 {
295 char *k, *v1, *v2;
296 size_t klen, v1len, v2len;
297
298 if (zend_parse_parameters(ZEND_NUM_ARGS(), "sss", &k, &klen,
299 &v1, &v1len, &v2, &v2len) == FAILURE)
300 {
301 return FAILURE;
302 }
303
304 *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kss", k, klen, v1, v1len, v2, v2len);
305
306 // Success!
307 return SUCCESS;
308 }
309
310 /* Generic command that takes two keys */
redis_key_key_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)311 int redis_key_key_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
312 char *kw, char **cmd, int *cmd_len, short *slot,
313 void **ctx)
314 {
315 char *k1, *k2;
316 size_t k1len, k2len;
317 int k1free, k2free;
318
319 if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", &k1, &k1len,
320 &k2, &k2len) == FAILURE)
321 {
322 return FAILURE;
323 }
324
325 // Prefix both keys
326 k1free = redis_key_prefix(redis_sock, &k1, &k1len);
327 k2free = redis_key_prefix(redis_sock, &k2, &k2len);
328
329 // If a slot is requested, we can test that they hash the same
330 if (slot) {
331 // Slots where these keys resolve
332 short slot1 = cluster_hash_key(k1, k1len);
333 short slot2 = cluster_hash_key(k2, k2len);
334
335 // Check if Redis would give us a CROSSLOT error
336 if (slot1 != slot2) {
337 php_error_docref(0, E_WARNING, "Keys don't hash to the same slot");
338 if (k1free) efree(k1);
339 if (k2free) efree(k2);
340 return FAILURE;
341 }
342
343 // They're both the same
344 *slot = slot1;
345 }
346
347 /* Send keys as normal strings because we manually prefixed to check against
348 * cross slot error. */
349 *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "ss", k1, k1len, k2, k2len);
350
351 /* Clean keys up if we prefixed */
352 if (k1free) efree(k1);
353 if (k2free) efree(k2);
354
355 return SUCCESS;
356 }
357
358 /* Generic command construction where we take a key and a long */
redis_key_long_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)359 int redis_key_long_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
360 char *kw, char **cmd, int *cmd_len, short *slot,
361 void **ctx)
362 {
363 char *key;
364 size_t keylen;
365 zend_long lval;
366
367 if (zend_parse_parameters(ZEND_NUM_ARGS(), "sl", &key, &keylen, &lval)
368 ==FAILURE)
369 {
370 return FAILURE;
371 }
372
373 *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kl", key, keylen, lval);
374
375 // Success!
376 return SUCCESS;
377 }
378
379 /* long, long */
redis_long_long_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)380 int redis_long_long_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
381 char *kw, char **cmd, int *cmd_len, short *slot,
382 void **ctx)
383 {
384 zend_long v1, v2;
385
386 if (zend_parse_parameters(ZEND_NUM_ARGS(), "ll", &v1, &v2)
387 == FAILURE)
388 {
389 return FAILURE;
390 }
391
392 *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "ll", v1, v2);
393
394 return SUCCESS;
395 }
396
397 /* key, long, long */
redis_key_long_long_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)398 int redis_key_long_long_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
399 char *kw, char **cmd, int *cmd_len, short *slot,
400 void **ctx)
401 {
402 char *key;
403 size_t key_len;
404 zend_long val1, val2;
405
406 if (zend_parse_parameters(ZEND_NUM_ARGS(), "sll", &key, &key_len,
407 &val1, &val2) == FAILURE)
408 {
409 return FAILURE;
410 }
411
412 *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kll", key, key_len, val1, val2);
413
414 return SUCCESS;
415 }
416
417 /* Generic command where we take a single key */
redis_key_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)418 int redis_key_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
419 char *kw, char **cmd, int *cmd_len, short *slot,
420 void **ctx)
421 {
422 char *key;
423 size_t key_len;
424
425 if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &key, &key_len)
426 ==FAILURE)
427 {
428 return FAILURE;
429 }
430
431 *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "k", key, key_len);
432
433 return SUCCESS;
434 }
435
redis_flush_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)436 int redis_flush_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
437 char *kw, char **cmd, int *cmd_len, short *slot, void **ctx)
438 {
439 zend_bool async = 0;
440
441 if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &async) == FAILURE) {
442 return FAILURE;
443 }
444
445 if (async) {
446 *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "s", "ASYNC", sizeof("ASYNC") - 1);
447 } else {
448 *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "");
449 }
450
451 return SUCCESS;
452 }
453
454 /* Generic command where we take a key and a double */
redis_key_dbl_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)455 int redis_key_dbl_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
456 char *kw, char **cmd, int *cmd_len, short *slot,
457 void **ctx)
458 {
459 char *key;
460 size_t key_len;
461 double val;
462
463 if (zend_parse_parameters(ZEND_NUM_ARGS(), "sd", &key, &key_len,
464 &val) == FAILURE)
465 {
466 return FAILURE;
467 }
468
469 *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kf", key, key_len, val);
470
471 return SUCCESS;
472 }
473
474 /* Generic to construct SCAN and variant commands */
redis_fmt_scan_cmd(char ** cmd,REDIS_SCAN_TYPE type,char * key,int key_len,long it,char * pat,int pat_len,long count)475 int redis_fmt_scan_cmd(char **cmd, REDIS_SCAN_TYPE type, char *key, int key_len,
476 long it, char *pat, int pat_len, long count)
477 {
478 static char *kw[] = {"SCAN","SSCAN","HSCAN","ZSCAN"};
479 int argc;
480 smart_string cmdstr = {0};
481
482 // Figure out our argument count
483 argc = 1 + (type!=TYPE_SCAN) + (pat_len>0?2:0) + (count>0?2:0);
484
485 redis_cmd_init_sstr(&cmdstr, argc, kw[type], strlen(kw[type]));
486
487 // Append our key if it's not a regular SCAN command
488 if (type != TYPE_SCAN) {
489 redis_cmd_append_sstr(&cmdstr, key, key_len);
490 }
491
492 // Append cursor
493 redis_cmd_append_sstr_long(&cmdstr, it);
494
495 // Append count if we've got one
496 if (count) {
497 redis_cmd_append_sstr(&cmdstr,"COUNT",sizeof("COUNT")-1);
498 redis_cmd_append_sstr_long(&cmdstr, count);
499 }
500
501 // Append pattern if we've got one
502 if (pat_len) {
503 redis_cmd_append_sstr(&cmdstr,"MATCH",sizeof("MATCH")-1);
504 redis_cmd_append_sstr(&cmdstr,pat,pat_len);
505 }
506
507 // Push command to the caller, return length
508 *cmd = cmdstr.c;
509 return cmdstr.len;
510 }
511
512 /* ZRANGE/ZREVRANGE */
redis_zrange_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,int * withscores,short * slot,void ** ctx)513 int redis_zrange_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
514 char *kw, char **cmd, int *cmd_len, int *withscores,
515 short *slot, void **ctx)
516 {
517 char *key;
518 size_t key_len;
519 zend_long start, end;
520 zend_string *zkey;
521 zval *z_ws = NULL, *z_ele;
522
523 if (zend_parse_parameters(ZEND_NUM_ARGS(), "sll|z", &key, &key_len,
524 &start, &end, &z_ws) == FAILURE)
525 {
526 return FAILURE;
527 }
528
529 // Clear withscores arg
530 *withscores = 0;
531
532 /* Accept ['withscores' => true], or the legacy `true` value */
533 if (z_ws) {
534 if (Z_TYPE_P(z_ws) == IS_ARRAY) {
535 ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(z_ws), zkey, z_ele) {
536 ZVAL_DEREF(z_ele);
537 if (zend_string_equals_literal_ci(zkey, "withscores")) {
538 *withscores = zval_is_true(z_ele);
539 break;
540 }
541 } ZEND_HASH_FOREACH_END();
542 } else if (Z_TYPE_P(z_ws) == IS_TRUE) {
543 *withscores = Z_TYPE_P(z_ws) == IS_TRUE;
544 }
545 }
546
547 if (*withscores) {
548 *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kdds", key, key_len, start, end,
549 "WITHSCORES", sizeof("WITHSCORES") - 1);
550 } else {
551 *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kdd", key, key_len, start, end);
552 }
553
554 return SUCCESS;
555 }
556
redis_zrangebyscore_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,int * withscores,short * slot,void ** ctx)557 int redis_zrangebyscore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
558 char *kw, char **cmd, int *cmd_len, int *withscores,
559 short *slot, void **ctx)
560 {
561 char *key, *start, *end;
562 int has_limit = 0;
563 long offset, count;
564 size_t key_len, start_len, end_len;
565 zval *z_opt=NULL, *z_ele;
566 zend_string *zkey;
567 HashTable *ht_opt;
568
569 if (zend_parse_parameters(ZEND_NUM_ARGS(), "sss|a", &key, &key_len,
570 &start, &start_len, &end, &end_len, &z_opt)
571 ==FAILURE)
572 {
573 return FAILURE;
574 }
575
576 // Check for an options array
577 if (z_opt && Z_TYPE_P(z_opt) == IS_ARRAY) {
578 ht_opt = Z_ARRVAL_P(z_opt);
579 ZEND_HASH_FOREACH_STR_KEY_VAL(ht_opt, zkey, z_ele) {
580 /* All options require a string key type */
581 if (!zkey) continue;
582 ZVAL_DEREF(z_ele);
583 /* Check for withscores and limit */
584 if (zend_string_equals_literal_ci(zkey, "withscores")) {
585 *withscores = zval_is_true(z_ele);
586 } else if (zend_string_equals_literal_ci(zkey, "limit") && Z_TYPE_P(z_ele) == IS_ARRAY) {
587 HashTable *htlimit = Z_ARRVAL_P(z_ele);
588 zval *zoff, *zcnt;
589
590 /* We need two arguments (offset and count) */
591 if ((zoff = zend_hash_index_find(htlimit, 0)) != NULL &&
592 (zcnt = zend_hash_index_find(htlimit, 1)) != NULL
593 ) {
594 /* Set our limit if we can get valid longs from both args */
595 offset = zval_get_long(zoff);
596 count = zval_get_long(zcnt);
597 has_limit = 1;
598 }
599 }
600 } ZEND_HASH_FOREACH_END();
601 }
602
603 // Construct our command
604 if (*withscores) {
605 if (has_limit) {
606 *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "ksssdds", key, key_len,
607 start, start_len, end, end_len, "LIMIT", 5, offset, count,
608 "WITHSCORES", 10);
609 } else {
610 *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "ksss", key, key_len, start,
611 start_len, end, end_len, "WITHSCORES", 10);
612 }
613 } else {
614 if (has_limit) {
615 *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "ksssdd", key, key_len, start,
616 start_len, end, end_len, "LIMIT", 5, offset, count);
617 } else {
618 *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kss", key, key_len, start,
619 start_len, end, end_len);
620 }
621 }
622
623 return SUCCESS;
624 }
625
626 /* ZUNIONSTORE, ZINTERSTORE */
redis_zinter_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)627 int redis_zinter_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
628 char *kw, char **cmd, int *cmd_len, short *slot,
629 void **ctx)
630 {
631 char *key, *agg_op=NULL;
632 int key_free, argc = 2, keys_count;
633 size_t key_len, agg_op_len = 0;
634 zval *z_keys, *z_weights=NULL, *z_ele;
635 HashTable *ht_keys, *ht_weights=NULL;
636 smart_string cmdstr = {0};
637
638 // Parse args
639 if (zend_parse_parameters(ZEND_NUM_ARGS(), "sa|a!s", &key,
640 &key_len, &z_keys, &z_weights, &agg_op,
641 &agg_op_len) == FAILURE)
642 {
643 return FAILURE;
644 }
645
646 // Grab our keys
647 ht_keys = Z_ARRVAL_P(z_keys);
648
649 // Nothing to do if there aren't any
650 if ((keys_count = zend_hash_num_elements(ht_keys)) == 0) {
651 return FAILURE;
652 } else {
653 argc += keys_count;
654 }
655
656 // Handle WEIGHTS
657 if (z_weights != NULL) {
658 ht_weights = Z_ARRVAL_P(z_weights);
659 if (zend_hash_num_elements(ht_weights) != keys_count) {
660 php_error_docref(NULL, E_WARNING,
661 "WEIGHTS and keys array should be the same size!");
662 return FAILURE;
663 }
664
665 // "WEIGHTS" + key count
666 argc += keys_count + 1;
667 }
668
669 // AGGREGATE option
670 if (agg_op_len != 0) {
671 if (strncasecmp(agg_op, "SUM", sizeof("SUM")) &&
672 strncasecmp(agg_op, "MIN", sizeof("MIN")) &&
673 strncasecmp(agg_op, "MAX", sizeof("MAX")))
674 {
675 php_error_docref(NULL, E_WARNING,
676 "Invalid AGGREGATE option provided!");
677 return FAILURE;
678 }
679
680 // "AGGREGATE" + type
681 argc += 2;
682 }
683
684 // Prefix key
685 key_free = redis_key_prefix(redis_sock, &key, &key_len);
686
687 // Start building our command
688 redis_cmd_init_sstr(&cmdstr, argc, kw, strlen(kw));
689 redis_cmd_append_sstr(&cmdstr, key, key_len);
690 redis_cmd_append_sstr_int(&cmdstr, keys_count);
691
692 // Set our slot, free the key if we prefixed it
693 CMD_SET_SLOT(slot,key,key_len);
694 if (key_free) efree(key);
695
696 // Process input keys
697 ZEND_HASH_FOREACH_VAL(ht_keys, z_ele) {
698 zend_string *zstr = zval_get_string(z_ele);
699 char *key = ZSTR_VAL(zstr);
700 size_t key_len = ZSTR_LEN(zstr);
701
702 // Prefix key if necissary
703 int key_free = redis_key_prefix(redis_sock, &key, &key_len);
704
705 // If we're in Cluster mode, verify the slot is the same
706 if (slot && *slot != cluster_hash_key(key,key_len)) {
707 php_error_docref(NULL, E_WARNING,
708 "All keys don't hash to the same slot!");
709 efree(cmdstr.c);
710 zend_string_release(zstr);
711 if (key_free) efree(key);
712 return FAILURE;
713 }
714
715 // Append this input set
716 redis_cmd_append_sstr(&cmdstr, key, key_len);
717
718 // Cleanup
719 zend_string_release(zstr);
720 if (key_free) efree(key);
721 } ZEND_HASH_FOREACH_END();
722
723 // Weights
724 if (ht_weights != NULL) {
725 redis_cmd_append_sstr(&cmdstr, "WEIGHTS", sizeof("WEIGHTS")-1);
726
727 // Process our weights
728 ZEND_HASH_FOREACH_VAL(ht_weights, z_ele) {
729 // Ignore non numeric args unless they're inf/-inf
730 ZVAL_DEREF(z_ele);
731 switch (Z_TYPE_P(z_ele)) {
732 case IS_LONG:
733 redis_cmd_append_sstr_long(&cmdstr, Z_LVAL_P(z_ele));
734 break;
735 case IS_DOUBLE:
736 redis_cmd_append_sstr_dbl(&cmdstr, Z_DVAL_P(z_ele));
737 break;
738 case IS_STRING: {
739 double dval;
740 zend_long lval;
741 zend_uchar type = is_numeric_string(Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele), &lval, &dval, 0);
742 if (type == IS_LONG) {
743 redis_cmd_append_sstr_long(&cmdstr, lval);
744 break;
745 } else if (type == IS_DOUBLE) {
746 redis_cmd_append_sstr_dbl(&cmdstr, dval);
747 break;
748 } else if (strncasecmp(Z_STRVAL_P(z_ele), "-inf", sizeof("-inf") - 1) == 0 ||
749 strncasecmp(Z_STRVAL_P(z_ele), "+inf", sizeof("+inf") - 1) == 0 ||
750 strncasecmp(Z_STRVAL_P(z_ele), "inf", sizeof("inf") - 1) == 0
751 ) {
752 redis_cmd_append_sstr(&cmdstr, Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele));
753 break;
754 }
755 // fall through
756 }
757 default:
758 php_error_docref(NULL, E_WARNING,
759 "Weights must be numeric or '-inf','inf','+inf'");
760 efree(cmdstr.c);
761 return FAILURE;
762 }
763 } ZEND_HASH_FOREACH_END();
764 }
765
766 // AGGREGATE
767 if (agg_op_len != 0) {
768 redis_cmd_append_sstr(&cmdstr, "AGGREGATE", sizeof("AGGREGATE")-1);
769 redis_cmd_append_sstr(&cmdstr, agg_op, agg_op_len);
770 }
771
772 // Push out values
773 *cmd = cmdstr.c;
774 *cmd_len = cmdstr.len;
775
776 return SUCCESS;
777 }
778
779 /* SUBSCRIBE/PSUBSCRIBE */
redis_subscribe_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)780 int redis_subscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
781 char *kw, char **cmd, int *cmd_len, short *slot,
782 void **ctx)
783 {
784 zval *z_arr, *z_chan;
785 HashTable *ht_chan;
786 smart_string cmdstr = {0};
787 subscribeContext *sctx = ecalloc(1, sizeof(*sctx));
788 size_t key_len;
789 int key_free;
790 char *key;
791
792 if (zend_parse_parameters(ZEND_NUM_ARGS(), "af", &z_arr,
793 &(sctx->cb), &(sctx->cb_cache)) == FAILURE)
794 {
795 efree(sctx);
796 return FAILURE;
797 }
798
799 ht_chan = Z_ARRVAL_P(z_arr);
800 sctx->kw = kw;
801 sctx->argc = zend_hash_num_elements(ht_chan);
802
803 if (sctx->argc == 0) {
804 efree(sctx);
805 return FAILURE;
806 }
807
808 // Start command construction
809 redis_cmd_init_sstr(&cmdstr, sctx->argc, kw, strlen(kw));
810
811 // Iterate over channels
812 ZEND_HASH_FOREACH_VAL(ht_chan, z_chan) {
813 // We want to deal with strings here
814 zend_string *zstr = zval_get_string(z_chan);
815
816 // Grab channel name, prefix if required
817 key = ZSTR_VAL(zstr);
818 key_len = ZSTR_LEN(zstr);
819 key_free = redis_key_prefix(redis_sock, &key, &key_len);
820
821 // Add this channel
822 redis_cmd_append_sstr(&cmdstr, key, key_len);
823
824 zend_string_release(zstr);
825 // Free our key if it was prefixed
826 if (key_free) efree(key);
827 } ZEND_HASH_FOREACH_END();
828
829 // Push values out
830 *cmd_len = cmdstr.len;
831 *cmd = cmdstr.c;
832 *ctx = (void*)sctx;
833
834 // Pick a slot at random
835 CMD_RAND_SLOT(slot);
836
837 return SUCCESS;
838 }
839
840 /* UNSUBSCRIBE/PUNSUBSCRIBE */
redis_unsubscribe_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)841 int redis_unsubscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
842 char *kw, char **cmd, int *cmd_len, short *slot,
843 void **ctx)
844 {
845 zval *z_arr, *z_chan;
846 HashTable *ht_arr;
847 smart_string cmdstr = {0};
848 subscribeContext *sctx = ecalloc(1, sizeof(*sctx));
849
850 if (zend_parse_parameters(ZEND_NUM_ARGS(), "a", &z_arr) == FAILURE) {
851 efree(sctx);
852 return FAILURE;
853 }
854
855 ht_arr = Z_ARRVAL_P(z_arr);
856
857 sctx->argc = zend_hash_num_elements(ht_arr);
858 if (sctx->argc == 0) {
859 efree(sctx);
860 return FAILURE;
861 }
862
863 redis_cmd_init_sstr(&cmdstr, sctx->argc, kw, strlen(kw));
864
865 ZEND_HASH_FOREACH_VAL(ht_arr, z_chan) {
866 char *key = Z_STRVAL_P(z_chan);
867 size_t key_len = Z_STRLEN_P(z_chan);
868 int key_free;
869
870 key_free = redis_key_prefix(redis_sock, &key, &key_len);
871 redis_cmd_append_sstr(&cmdstr, key, key_len);
872 if (key_free) efree(key);
873 } ZEND_HASH_FOREACH_END();
874
875 // Push out vals
876 *cmd_len = cmdstr.len;
877 *cmd = cmdstr.c;
878 *ctx = (void*)sctx;
879
880 return SUCCESS;
881 }
882
883 /* ZRANGEBYLEX/ZREVRANGEBYLEX */
redis_zrangebylex_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)884 int redis_zrangebylex_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
885 char *kw, char **cmd, int *cmd_len, short *slot,
886 void **ctx)
887 {
888 char *key, *min, *max;
889 size_t key_len, min_len, max_len;
890 int argc = ZEND_NUM_ARGS();
891 zend_long offset, count;
892
893 /* We need either 3 or 5 arguments for this to be valid */
894 if (argc != 3 && argc != 5) {
895 php_error_docref(0, E_WARNING, "Must pass either 3 or 5 arguments");
896 return FAILURE;
897 }
898
899 if (zend_parse_parameters(argc, "sss|ll", &key, &key_len, &min, &min_len,
900 &max, &max_len, &offset, &count) == FAILURE)
901 {
902 return FAILURE;
903 }
904
905 /* min and max must start with '(' or '[', or be either '-' or '+' */
906 if (min_len < 1 || max_len < 1 ||
907 (min[0] != '(' && min[0] != '[' &&
908 (min[0] != '-' || min_len > 1) && (min[0] != '+' || min_len > 1)) ||
909 (max[0] != '(' && max[0] != '[' &&
910 (max[0] != '-' || max_len > 1) && (max[0] != '+' || max_len > 1)))
911 {
912 php_error_docref(0, E_WARNING,
913 "min and max arguments must start with '[' or '('");
914 return FAILURE;
915 }
916
917 /* Construct command */
918 if (argc == 3) {
919 *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kss", key, key_len, min, min_len,
920 max, max_len);
921 } else {
922 *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "ksssll", key, key_len, min, min_len,
923 max, max_len, "LIMIT", 5, offset, count);
924 }
925
926 return SUCCESS;
927 }
928
929 /* Validate ZLEX* min/max argument strings */
validate_zlex_arg(const char * arg,size_t len)930 static int validate_zlex_arg(const char *arg, size_t len) {
931 return (len > 1 && (*arg == '[' || *arg == '(')) ||
932 (len == 1 && (*arg == '+' || *arg == '-'));
933 }
934
935 /* ZLEXCOUNT/ZREMRANGEBYLEX */
redis_gen_zlex_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)936 int redis_gen_zlex_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
937 char *kw, char **cmd, int *cmd_len, short *slot,
938 void **ctx)
939 {
940 char *key, *min, *max;
941 size_t key_len, min_len, max_len;
942
943 /* Parse args */
944 if (zend_parse_parameters(ZEND_NUM_ARGS(), "sss", &key, &key_len,
945 &min, &min_len, &max, &max_len) == FAILURE)
946 {
947 return FAILURE;
948 }
949
950 /* Quick sanity check on min/max */
951 if (!validate_zlex_arg(min, min_len) || !validate_zlex_arg(max, max_len)) {
952 php_error_docref(NULL, E_WARNING,
953 "Min/Max args can be '-' or '+', or start with '[' or '('");
954 return FAILURE;
955 }
956
957 /* Construct command */
958 *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kss", key, key_len, min, min_len,
959 max, max_len);
960
961 return SUCCESS;
962 }
963
964 /* EVAL and EVALSHA */
redis_eval_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)965 int redis_eval_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw,
966 char **cmd, int *cmd_len, short *slot, void **ctx)
967 {
968 char *lua;
969 int argc = 0;
970 zval *z_arr = NULL, *z_ele;
971 HashTable *ht_arr;
972 zend_long num_keys = 0;
973 smart_string cmdstr = {0};
974 size_t lua_len;
975 zend_string *zstr;
976 short prevslot = -1;
977
978 /* Parse args */
979 if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|al", &lua, &lua_len,
980 &z_arr, &num_keys) == FAILURE)
981 {
982 return FAILURE;
983 }
984
985 /* Grab arg count */
986 if (z_arr != NULL) {
987 ht_arr = Z_ARRVAL_P(z_arr);
988 argc = zend_hash_num_elements(ht_arr);
989 }
990
991 /* EVAL[SHA] {script || sha1} {num keys} */
992 redis_cmd_init_sstr(&cmdstr, 2 + argc, kw, strlen(kw));
993 redis_cmd_append_sstr(&cmdstr, lua, lua_len);
994 redis_cmd_append_sstr_long(&cmdstr, num_keys);
995
996 // Iterate over our args if we have any
997 if (argc > 0) {
998 ZEND_HASH_FOREACH_VAL(ht_arr, z_ele) {
999 zstr = zval_get_string(z_ele);
1000
1001 /* If we're still on a key, prefix it check slot */
1002 if (num_keys-- > 0) {
1003 redis_cmd_append_sstr_key(&cmdstr, ZSTR_VAL(zstr), ZSTR_LEN(zstr), redis_sock, slot);
1004
1005 /* If we have been passed a slot, all keys must match */
1006 if (slot) {
1007 if (prevslot != -1 && prevslot != *slot) {
1008 zend_string_release(zstr);
1009 php_error_docref(0, E_WARNING, "All keys do not map to the same slot");
1010 return FAILURE;
1011 }
1012 prevslot = *slot;
1013 }
1014 } else {
1015 redis_cmd_append_sstr(&cmdstr, ZSTR_VAL(zstr), ZSTR_LEN(zstr));
1016 }
1017
1018 zend_string_release(zstr);
1019 } ZEND_HASH_FOREACH_END();
1020 } else {
1021 /* Any slot will do */
1022 CMD_RAND_SLOT(slot);
1023 }
1024
1025 *cmd = cmdstr.c;
1026 *cmd_len = cmdstr.len;
1027 return SUCCESS;
1028 }
1029
1030 /* Commands that take a key followed by a variable list of serializable
1031 * values (RPUSH, LPUSH, SADD, SREM, etc...) */
redis_key_varval_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)1032 int redis_key_varval_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1033 char *kw, char **cmd, int *cmd_len, short *slot,
1034 void **ctx)
1035 {
1036 zval *z_args;
1037 smart_string cmdstr = {0};
1038 size_t i;
1039 int argc = ZEND_NUM_ARGS();
1040
1041 // We at least need a key and one value
1042 if (argc < 2) {
1043 return FAILURE;
1044 }
1045
1046 // Make sure we at least have a key, and we can get other args
1047 z_args = emalloc(argc * sizeof(zval));
1048 if (zend_get_parameters_array(ht, argc, z_args) == FAILURE) {
1049 efree(z_args);
1050 return FAILURE;
1051 }
1052
1053 /* Initialize our command */
1054 redis_cmd_init_sstr(&cmdstr, argc, kw, strlen(kw));
1055
1056 /* Append key */
1057 zend_string *zstr = zval_get_string(&z_args[0]);
1058 redis_cmd_append_sstr_key(&cmdstr, ZSTR_VAL(zstr), ZSTR_LEN(zstr), redis_sock, slot);
1059 zend_string_release(zstr);
1060
1061 /* Add members */
1062 for (i = 1; i < argc; i++ ){
1063 redis_cmd_append_sstr_zval(&cmdstr, &z_args[i], redis_sock);
1064 }
1065
1066 // Push out values
1067 *cmd = cmdstr.c;
1068 *cmd_len = cmdstr.len;
1069
1070 // Cleanup arg array
1071 efree(z_args);
1072
1073 // Success!
1074 return SUCCESS;
1075 }
1076
1077 /* Commands that take a key and then an array of values */
1078 #define VAL_TYPE_VALUES 1
1079 #define VAL_TYPE_STRINGS 2
gen_key_arr_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,int valtype,char ** cmd,int * cmd_len,short * slot,void ** ctx)1080 static int gen_key_arr_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1081 char *kw, int valtype, char **cmd, int *cmd_len,
1082 short *slot, void **ctx)
1083 {
1084 zval *z_arr, *z_val;
1085 HashTable *ht_arr;
1086 smart_string cmdstr = {0};
1087 zend_string *zstr;
1088 int key_free, val_free, argc = 1;
1089 size_t val_len, key_len;
1090 char *key, *val;
1091
1092 if (zend_parse_parameters(ZEND_NUM_ARGS(), "sa", &key, &key_len,
1093 &z_arr) == FAILURE ||
1094 zend_hash_num_elements(Z_ARRVAL_P(z_arr)) == 0)
1095 {
1096 return FAILURE;
1097 }
1098
1099 /* Start constructing our command */
1100 ht_arr = Z_ARRVAL_P(z_arr);
1101 argc += zend_hash_num_elements(ht_arr);
1102 redis_cmd_init_sstr(&cmdstr, argc, kw, strlen(kw));
1103
1104 /* Prefix if required and append the key name */
1105 key_free = redis_key_prefix(redis_sock, &key, &key_len);
1106 redis_cmd_append_sstr(&cmdstr, key, key_len);
1107 CMD_SET_SLOT(slot, key, key_len);
1108 if (key_free) efree(key);
1109
1110 /* Iterate our hash table, serializing and appending values */
1111 assert(valtype == VAL_TYPE_VALUES || valtype == VAL_TYPE_STRINGS);
1112 ZEND_HASH_FOREACH_VAL(ht_arr, z_val) {
1113 if (valtype == VAL_TYPE_VALUES) {
1114 val_free = redis_pack(redis_sock, z_val, &val, &val_len);
1115 redis_cmd_append_sstr(&cmdstr, val, val_len);
1116 if (val_free) efree(val);
1117 } else {
1118 zstr = zval_get_string(z_val);
1119 redis_cmd_append_sstr(&cmdstr, ZSTR_VAL(zstr), ZSTR_LEN(zstr));
1120 zend_string_release(zstr);
1121 }
1122 } ZEND_HASH_FOREACH_END();
1123
1124 *cmd_len = cmdstr.len;
1125 *cmd = cmdstr.c;
1126
1127 return SUCCESS;
1128 }
1129
redis_key_val_arr_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)1130 int redis_key_val_arr_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1131 char *kw, char **cmd, int *cmd_len, short *slot,
1132 void **ctx)
1133 {
1134 return gen_key_arr_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, kw,
1135 VAL_TYPE_VALUES, cmd, cmd_len, slot, ctx);
1136 }
1137
redis_key_str_arr_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)1138 int redis_key_str_arr_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1139 char *kw, char **cmd, int *cmd_len, short *slot,
1140 void **ctx)
1141 {
1142 return gen_key_arr_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, kw,
1143 VAL_TYPE_STRINGS, cmd, cmd_len, slot, ctx);
1144 }
1145
1146 /* Generic function that takes a variable number of keys, with an optional
1147 * timeout value. This can handle various SUNION/SUNIONSTORE/BRPOP type
1148 * commands. */
gen_varkey_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,int kw_len,int min_argc,int has_timeout,char ** cmd,int * cmd_len,short * slot)1149 static int gen_varkey_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1150 char *kw, int kw_len, int min_argc, int has_timeout,
1151 char **cmd, int *cmd_len, short *slot)
1152 {
1153 zval *z_args, *z_ele;
1154 HashTable *ht_arr;
1155 char *key;
1156 int key_free, i, tail;
1157 size_t key_len;
1158 int single_array = 0, argc = ZEND_NUM_ARGS();
1159 smart_string cmdstr = {0};
1160 long timeout = 0;
1161 short kslot = -1;
1162 zend_string *zstr;
1163
1164 if (argc < min_argc) {
1165 zend_wrong_param_count();
1166 return FAILURE;
1167 }
1168
1169 // Allocate args
1170 z_args = emalloc(argc * sizeof(zval));
1171 if (zend_get_parameters_array(ht, argc, z_args) == FAILURE) {
1172 efree(z_args);
1173 return FAILURE;
1174 }
1175
1176 // Handle our "single array" case
1177 if (has_timeout == 0) {
1178 single_array = argc==1 && Z_TYPE(z_args[0]) == IS_ARRAY;
1179 } else {
1180 single_array = argc==2 && Z_TYPE(z_args[0]) == IS_ARRAY &&
1181 Z_TYPE(z_args[1]) == IS_LONG;
1182 timeout = Z_LVAL(z_args[1]);
1183 }
1184
1185 // If we're running a single array, rework args
1186 if (single_array) {
1187 ht_arr = Z_ARRVAL(z_args[0]);
1188 argc = zend_hash_num_elements(ht_arr);
1189 if (has_timeout) argc++;
1190 efree(z_args);
1191 z_args = NULL;
1192
1193 /* If the array is empty, we can simply abort */
1194 if (argc == 0) return FAILURE;
1195 }
1196
1197 // Begin construction of our command
1198 redis_cmd_init_sstr(&cmdstr, argc, kw, kw_len);
1199
1200 if (single_array) {
1201 ZEND_HASH_FOREACH_VAL(ht_arr, z_ele) {
1202 zstr = zval_get_string(z_ele);
1203 key = ZSTR_VAL(zstr);
1204 key_len = ZSTR_LEN(zstr);
1205 key_free = redis_key_prefix(redis_sock, &key, &key_len);
1206
1207 // Protect against CROSSLOT errors
1208 if (slot) {
1209 if (kslot == -1) {
1210 kslot = cluster_hash_key(key, key_len);
1211 } else if (cluster_hash_key(key,key_len)!=kslot) {
1212 zend_string_release(zstr);
1213 if (key_free) efree(key);
1214 php_error_docref(NULL, E_WARNING,
1215 "Not all keys hash to the same slot!");
1216 return FAILURE;
1217 }
1218 }
1219
1220 // Append this key, free it if we prefixed
1221 redis_cmd_append_sstr(&cmdstr, key, key_len);
1222 zend_string_release(zstr);
1223 if (key_free) efree(key);
1224 } ZEND_HASH_FOREACH_END();
1225 if (has_timeout) {
1226 redis_cmd_append_sstr_long(&cmdstr, timeout);
1227 }
1228 } else {
1229 if (has_timeout && Z_TYPE(z_args[argc-1])!=IS_LONG) {
1230 php_error_docref(NULL, E_ERROR,
1231 "Timeout value must be a LONG");
1232 efree(z_args);
1233 return FAILURE;
1234 }
1235
1236 tail = has_timeout ? argc-1 : argc;
1237 for(i = 0; i < tail; i++) {
1238 zstr = zval_get_string(&z_args[i]);
1239 key = ZSTR_VAL(zstr);
1240 key_len = ZSTR_LEN(zstr);
1241
1242 key_free = redis_key_prefix(redis_sock, &key, &key_len);
1243
1244 /* Protect against CROSSSLOT errors if we've got a slot */
1245 if (slot) {
1246 if ( kslot == -1) {
1247 kslot = cluster_hash_key(key, key_len);
1248 } else if (cluster_hash_key(key,key_len)!=kslot) {
1249 php_error_docref(NULL, E_WARNING,
1250 "Not all keys hash to the same slot");
1251 zend_string_release(zstr);
1252 if (key_free) efree(key);
1253 efree(z_args);
1254 return FAILURE;
1255 }
1256 }
1257
1258 // Append this key
1259 redis_cmd_append_sstr(&cmdstr, key, key_len);
1260 zend_string_release(zstr);
1261 if (key_free) efree(key);
1262 }
1263 if (has_timeout) {
1264 redis_cmd_append_sstr_long(&cmdstr, Z_LVAL(z_args[tail]));
1265 }
1266
1267 // Cleanup args
1268 efree(z_args);
1269 }
1270
1271 // Push out parameters
1272 if (slot) *slot = kslot;
1273 *cmd = cmdstr.c;
1274 *cmd_len = cmdstr.len;
1275
1276 return SUCCESS;
1277 }
1278
1279 /* Generic handling of every blocking pop command (BLPOP, BZPOP[MIN/MAX], etc */
redis_blocking_pop_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)1280 int redis_blocking_pop_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1281 char *kw, char **cmd, int *cmd_len, short *slot,
1282 void **ctx)
1283 {
1284 return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, kw,
1285 strlen(kw), 2, 2, cmd, cmd_len, slot);
1286 }
1287
1288 /*
1289 * Commands with specific signatures or that need unique functions because they
1290 * have specific processing (argument validation, etc) that make them unique
1291 */
1292
1293 /* Attempt to pull a long expiry from a zval. We're more restrictave than zval_get_long
1294 * because that function will return integers from things like open file descriptors
1295 * which should simply fail as a TTL */
redis_try_get_expiry(zval * zv,zend_long * lval)1296 static int redis_try_get_expiry(zval *zv, zend_long *lval) {
1297 double dval;
1298
1299 /* Success on an actual long or double */
1300 if (Z_TYPE_P(zv) == IS_LONG || Z_TYPE_P(zv) == IS_DOUBLE) {
1301 *lval = zval_get_long(zv);
1302 return SUCCESS;
1303 }
1304
1305 /* Automatically fail if we're not a string */
1306 if (Z_TYPE_P(zv) != IS_STRING)
1307 return FAILURE;
1308
1309 /* Attempt to get a long from the string */
1310 switch (is_numeric_string(Z_STRVAL_P(zv), Z_STRLEN_P(zv), lval, &dval, 0)) {
1311 case IS_DOUBLE:
1312 *lval = dval;
1313 /* fallthrough */
1314 case IS_LONG:
1315 return SUCCESS;
1316 default:
1317 return FAILURE;
1318 }
1319 }
1320
1321 /* SET */
redis_set_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)1322 int redis_set_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1323 char **cmd, int *cmd_len, short *slot, void **ctx)
1324 {
1325 smart_string cmdstr = {0};
1326 zval *z_value, *z_opts=NULL;
1327 char *key = NULL, *exp_type = NULL, *set_type = NULL;
1328 long exp_set = 0, keep_ttl = 0;
1329 zend_long expire = -1;
1330 size_t key_len;
1331
1332 // Make sure the function is being called correctly
1333 if (zend_parse_parameters(ZEND_NUM_ARGS(), "sz|z", &key, &key_len,
1334 &z_value, &z_opts) == FAILURE)
1335 {
1336 return FAILURE;
1337 }
1338
1339 // Check for an options array
1340 if (z_opts && Z_TYPE_P(z_opts) == IS_ARRAY) {
1341 HashTable *kt = Z_ARRVAL_P(z_opts);
1342 zend_string *zkey;
1343 zval *v;
1344
1345 /* Iterate our option array */
1346 ZEND_HASH_FOREACH_STR_KEY_VAL(kt, zkey, v) {
1347 ZVAL_DEREF(v);
1348 /* Detect PX or EX argument and validate timeout */
1349 if (zkey && ZSTR_IS_EX_PX_ARG(zkey)) {
1350 exp_set = 1;
1351
1352 /* Set expire type */
1353 exp_type = ZSTR_VAL(zkey);
1354
1355 /* Try to extract timeout */
1356 if (Z_TYPE_P(v) == IS_LONG) {
1357 expire = Z_LVAL_P(v);
1358 } else if (Z_TYPE_P(v) == IS_STRING) {
1359 expire = atol(Z_STRVAL_P(v));
1360 }
1361 } else if (Z_TYPE_P(v) == IS_STRING) {
1362 if (ZVAL_STRICMP_STATIC(v, "KEEPTTL")) {
1363 keep_ttl = 1;
1364 } else if (ZVAL_IS_NX_XX_ARG(v)) {
1365 set_type = Z_STRVAL_P(v);
1366 }
1367 }
1368 } ZEND_HASH_FOREACH_END();
1369 } else if (z_opts && Z_TYPE_P(z_opts) != IS_NULL) {
1370 if (redis_try_get_expiry(z_opts, &expire) == FAILURE) {
1371 php_error_docref(NULL, E_WARNING, "Expire must be a long, double, or a numeric string");
1372 return FAILURE;
1373 }
1374
1375 exp_set = 1;
1376 }
1377
1378 /* Protect the user from syntax errors but give them some info about what's wrong */
1379 if (exp_set && expire < 1) {
1380 php_error_docref(NULL, E_WARNING, "EXPIRE can't be < 1");
1381 return FAILURE;
1382 } else if (exp_type && keep_ttl) {
1383 php_error_docref(NULL, E_WARNING, "KEEPTTL can't be combined with EX or PX option");
1384 return FAILURE;
1385 }
1386
1387 /* Backward compatibility: If we are passed no options except an EXPIRE ttl, we
1388 * actually execute a SETEX command */
1389 if (expire > 0 && !exp_type && !set_type && !keep_ttl) {
1390 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "SETEX", "klv", key, key_len, expire, z_value);
1391 return SUCCESS;
1392 }
1393
1394 /* Calculate argc based on options set */
1395 int argc = 2 + (exp_type ? 2 : 0) + (set_type != NULL) + (keep_ttl != 0);
1396
1397 /* Initial SET <key> <value> */
1398 redis_cmd_init_sstr(&cmdstr, argc, "SET", 3);
1399 redis_cmd_append_sstr_key(&cmdstr, key, key_len, redis_sock, slot);
1400 redis_cmd_append_sstr_zval(&cmdstr, z_value, redis_sock);
1401
1402 if (exp_type) {
1403 redis_cmd_append_sstr(&cmdstr, exp_type, strlen(exp_type));
1404 redis_cmd_append_sstr_long(&cmdstr, (long)expire);
1405 }
1406
1407 if (set_type)
1408 redis_cmd_append_sstr(&cmdstr, set_type, strlen(set_type));
1409 if (keep_ttl)
1410 redis_cmd_append_sstr(&cmdstr, "KEEPTTL", 7);
1411
1412 /* Push command and length to the caller */
1413 *cmd = cmdstr.c;
1414 *cmd_len = cmdstr.len;
1415
1416 return SUCCESS;
1417 }
1418
1419 /* BRPOPLPUSH */
redis_brpoplpush_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)1420 int redis_brpoplpush_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1421 char **cmd, int *cmd_len, short *slot, void **ctx)
1422 {
1423 char *key1, *key2;
1424 size_t key1_len, key2_len;
1425 int key1_free, key2_free;
1426 short slot1, slot2;
1427 zend_long timeout;
1428
1429 if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssl", &key1, &key1_len,
1430 &key2, &key2_len, &timeout) == FAILURE)
1431 {
1432 return FAILURE;
1433 }
1434
1435 // Key prefixing
1436 key1_free = redis_key_prefix(redis_sock, &key1, &key1_len);
1437 key2_free = redis_key_prefix(redis_sock, &key2, &key2_len);
1438
1439 // In cluster mode, verify the slots match
1440 if (slot) {
1441 slot1 = cluster_hash_key(key1, key1_len);
1442 slot2 = cluster_hash_key(key2, key2_len);
1443 if (slot1 != slot2) {
1444 php_error_docref(NULL, E_WARNING,
1445 "Keys hash to different slots!");
1446 if (key1_free) efree(key1);
1447 if (key2_free) efree(key2);
1448 return FAILURE;
1449 }
1450
1451 // Both slots are the same
1452 *slot = slot1;
1453 }
1454
1455 // Consistency with Redis, if timeout < 0 use RPOPLPUSH
1456 if (timeout < 0) {
1457 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "RPOPLPUSH", "ss", key1, key1_len,
1458 key2, key2_len);
1459 } else {
1460 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "BRPOPLPUSH", "ssd", key1, key1_len,
1461 key2, key2_len, timeout);
1462 }
1463
1464 if (key1_free) efree(key1);
1465 if (key2_free) efree(key2);
1466 return SUCCESS;
1467 }
1468
1469 /* To maintain backward compatibility with earlier versions of phpredis, we
1470 * allow for an optional "increment by" argument for INCR and DECR even though
1471 * that's not how Redis proper works */
1472 #define TYPE_INCR 0
1473 #define TYPE_DECR 1
1474
1475 /* Handle INCR(BY) and DECR(BY) depending on optional increment value */
1476 static int
redis_atomic_increment(INTERNAL_FUNCTION_PARAMETERS,int type,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)1477 redis_atomic_increment(INTERNAL_FUNCTION_PARAMETERS, int type,
1478 RedisSock *redis_sock, char **cmd, int *cmd_len,
1479 short *slot, void **ctx)
1480 {
1481 char *key;
1482 size_t key_len;
1483 zend_long val = 1;
1484
1485 if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", &key, &key_len,
1486 &val) == FAILURE)
1487 {
1488 return FAILURE;
1489 }
1490
1491 /* If our value is 1 we use INCR/DECR. For other values, treat the call as
1492 * an INCRBY or DECRBY call */
1493 if (type == TYPE_INCR) {
1494 if (val == 1) {
1495 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "INCR", "k", key, key_len);
1496 } else {
1497 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "INCRBY", "kl", key, key_len, val);
1498 }
1499 } else {
1500 if (val == 1) {
1501 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "DECR", "k", key, key_len);
1502 } else {
1503 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "DECRBY", "kl", key, key_len, val);
1504 }
1505 }
1506
1507 /* Success */
1508 return SUCCESS;
1509 }
1510
1511 /* INCR */
redis_incr_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)1512 int redis_incr_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1513 char **cmd, int *cmd_len, short *slot, void **ctx)
1514 {
1515 return redis_atomic_increment(INTERNAL_FUNCTION_PARAM_PASSTHRU,
1516 TYPE_INCR, redis_sock, cmd, cmd_len, slot, ctx);
1517 }
1518
1519 /* DECR */
redis_decr_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)1520 int redis_decr_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1521 char **cmd, int *cmd_len, short *slot, void **ctx)
1522 {
1523 return redis_atomic_increment(INTERNAL_FUNCTION_PARAM_PASSTHRU,
1524 TYPE_DECR, redis_sock, cmd, cmd_len, slot, ctx);
1525 }
1526
1527 /* HINCRBY */
redis_hincrby_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)1528 int redis_hincrby_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1529 char **cmd, int *cmd_len, short *slot, void **ctx)
1530 {
1531 char *key, *mem;
1532 size_t key_len, mem_len;
1533 zend_long byval;
1534
1535 if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssl", &key, &key_len,
1536 &mem, &mem_len, &byval) == FAILURE)
1537 {
1538 return FAILURE;
1539 }
1540
1541 // Construct command
1542 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "HINCRBY", "ksl", key, key_len, mem, mem_len, byval);
1543
1544 // Success
1545 return SUCCESS;
1546 }
1547
1548 /* HINCRBYFLOAT */
redis_hincrbyfloat_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)1549 int redis_hincrbyfloat_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1550 char **cmd, int *cmd_len, short *slot, void **ctx)
1551 {
1552 char *key, *mem;
1553 size_t key_len, mem_len;
1554 double byval;
1555
1556 if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssd", &key, &key_len,
1557 &mem, &mem_len, &byval) == FAILURE)
1558 {
1559 return FAILURE;
1560 }
1561
1562 // Construct command
1563 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "HINCRBYFLOAT", "ksf", key, key_len, mem,
1564 mem_len, byval);
1565
1566 // Success
1567 return SUCCESS;
1568 }
1569
1570 /* HMGET */
redis_hmget_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)1571 int redis_hmget_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1572 char **cmd, int *cmd_len, short *slot, void **ctx)
1573 {
1574 char *key;
1575 zval *z_arr, *z_mems, *z_mem;
1576 int i, count, valid = 0, key_free;
1577 size_t key_len;
1578 HashTable *ht_arr;
1579 smart_string cmdstr = {0};
1580
1581 // Parse arguments
1582 if (zend_parse_parameters(ZEND_NUM_ARGS(), "sa", &key, &key_len,
1583 &z_arr) == FAILURE)
1584 {
1585 return FAILURE;
1586 }
1587
1588 // Our HashTable
1589 ht_arr = Z_ARRVAL_P(z_arr);
1590
1591 // We can abort if we have no elements
1592 if ((count = zend_hash_num_elements(ht_arr)) == 0) {
1593 return FAILURE;
1594 }
1595
1596 // Allocate memory for mems+1 so we can have a sentinel
1597 z_mems = ecalloc(count + 1, sizeof(zval));
1598
1599 // Iterate over our member array
1600 ZEND_HASH_FOREACH_VAL(ht_arr, z_mem) {
1601 ZVAL_DEREF(z_mem);
1602 // We can only handle string or long values here
1603 if ((Z_TYPE_P(z_mem) == IS_STRING && Z_STRLEN_P(z_mem) > 0)
1604 || Z_TYPE_P(z_mem) == IS_LONG
1605 ) {
1606 // Copy into our member array
1607 ZVAL_ZVAL(&z_mems[valid], z_mem, 1, 0);
1608
1609 // Increment the member count to actually send
1610 valid++;
1611 }
1612 } ZEND_HASH_FOREACH_END();
1613
1614 // If nothing was valid, fail
1615 if (valid == 0) {
1616 efree(z_mems);
1617 return FAILURE;
1618 }
1619
1620 // Sentinel so we can free this even if it's used and then we discard
1621 // the transaction manually or there is a transaction failure
1622 ZVAL_NULL(&z_mems[valid]);
1623
1624 // Start command construction
1625 redis_cmd_init_sstr(&cmdstr, valid+1, "HMGET", sizeof("HMGET")-1);
1626
1627 // Prefix our key
1628 key_free = redis_key_prefix(redis_sock, &key, &key_len);
1629
1630 redis_cmd_append_sstr(&cmdstr, key, key_len);
1631
1632 // Iterate over members, appending as arguments
1633 for(i = 0; i< valid; i++) {
1634 zend_string *zstr = zval_get_string(&z_mems[i]);
1635 redis_cmd_append_sstr(&cmdstr, ZSTR_VAL(zstr), ZSTR_LEN(zstr));
1636 zend_string_release(zstr);
1637 }
1638
1639 // Set our slot
1640 CMD_SET_SLOT(slot,key,key_len);
1641
1642 // Free our key if we prefixed it
1643 if (key_free) efree(key);
1644
1645 // Push out command, length, and key context
1646 *cmd = cmdstr.c;
1647 *cmd_len = cmdstr.len;
1648 *ctx = (void*)z_mems;
1649
1650 // Success!
1651 return SUCCESS;
1652 }
1653
1654 /* HMSET */
redis_hmset_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)1655 int redis_hmset_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1656 char **cmd, int *cmd_len, short *slot, void **ctx)
1657 {
1658 char *key;
1659 int key_free, count;
1660 size_t key_len;
1661 zend_ulong idx;
1662 zval *z_arr;
1663 HashTable *ht_vals;
1664 smart_string cmdstr = {0};
1665 zend_string *zkey;
1666 zval *z_val;
1667
1668 // Parse args
1669 if (zend_parse_parameters(ZEND_NUM_ARGS(), "sa", &key, &key_len,
1670 &z_arr) == FAILURE)
1671 {
1672 return FAILURE;
1673 }
1674
1675 // We can abort if we have no fields
1676 if ((count = zend_hash_num_elements(Z_ARRVAL_P(z_arr))) == 0) {
1677 return FAILURE;
1678 }
1679
1680 // Prefix our key
1681 key_free = redis_key_prefix(redis_sock, &key, &key_len);
1682
1683 // Grab our array as a HashTable
1684 ht_vals = Z_ARRVAL_P(z_arr);
1685
1686 // Initialize our HMSET command (key + 2x each array entry), add key
1687 redis_cmd_init_sstr(&cmdstr, 1+(count*2), "HMSET", sizeof("HMSET")-1);
1688 redis_cmd_append_sstr(&cmdstr, key, key_len);
1689
1690 // Start traversing our key => value array
1691 ZEND_HASH_FOREACH_KEY_VAL(ht_vals, idx, zkey, z_val) {
1692 char *mem, *val, kbuf[40];
1693 size_t val_len;
1694 int val_free;
1695 unsigned int mem_len;
1696
1697 // If the hash key is an integer, convert it to a string
1698 if (zkey) {
1699 mem_len = ZSTR_LEN(zkey);
1700 mem = ZSTR_VAL(zkey);
1701 } else {
1702 mem_len = snprintf(kbuf, sizeof(kbuf), ZEND_LONG_FMT, idx);
1703 mem = (char*)kbuf;
1704 }
1705
1706 // Serialize value (if directed)
1707 val_free = redis_pack(redis_sock, z_val, &val, &val_len);
1708
1709 // Append the key and value to our command
1710 redis_cmd_append_sstr(&cmdstr, mem, mem_len);
1711 redis_cmd_append_sstr(&cmdstr, val, val_len);
1712
1713 // Free our value if we serialized it
1714 if (val_free) efree(val);
1715 } ZEND_HASH_FOREACH_END();
1716
1717 // Set slot if directed
1718 CMD_SET_SLOT(slot,key,key_len);
1719
1720 // Free our key if we prefixed it
1721 if (key_free) efree(key);
1722
1723 // Push return pointers
1724 *cmd_len = cmdstr.len;
1725 *cmd = cmdstr.c;
1726
1727 // Success!
1728 return SUCCESS;
1729 }
1730
1731 /* HSTRLEN */
1732 int
redis_hstrlen_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)1733 redis_hstrlen_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1734 char **cmd, int *cmd_len, short *slot, void **ctx)
1735 {
1736 char *key, *field;
1737 size_t key_len, field_len;
1738
1739 if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", &key, &key_len,
1740 &field, &field_len) == FAILURE
1741 ) {
1742 return FAILURE;
1743 }
1744
1745 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "HSTRLEN", "ks", key, key_len, field, field_len);
1746
1747 return SUCCESS;
1748 }
1749
1750 /* BITPOS */
redis_bitpos_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)1751 int redis_bitpos_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1752 char **cmd, int *cmd_len, short *slot, void **ctx)
1753 {
1754 char *key;
1755 int argc;
1756 zend_long bit, start, end;
1757 size_t key_len;
1758
1759 argc = ZEND_NUM_ARGS();
1760 if (zend_parse_parameters(argc, "sl|ll", &key, &key_len, &bit,
1761 &start, &end) == FAILURE)
1762 {
1763 return FAILURE;
1764 }
1765
1766 // Prevalidate bit
1767 if (bit != 0 && bit != 1) {
1768 return FAILURE;
1769 }
1770
1771 // Construct command based on arg count
1772 if (argc == 2) {
1773 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "BITPOS", "kd", key, key_len, bit);
1774 } else if (argc == 3) {
1775 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "BITPOS", "kdd", key, key_len, bit,
1776 start);
1777 } else {
1778 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "BITPOS", "kddd", key, key_len, bit,
1779 start, end);
1780 }
1781
1782 return SUCCESS;
1783 }
1784
1785 /* BITOP */
redis_bitop_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)1786 int redis_bitop_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1787 char **cmd, int *cmd_len, short *slot, void **ctx)
1788 {
1789 zval *z_args;
1790 char *key;
1791 size_t key_len;
1792 int i, key_free, argc = ZEND_NUM_ARGS();
1793 smart_string cmdstr = {0};
1794 short kslot;
1795 zend_string *zstr;
1796
1797 // Allocate space for args, parse them as an array
1798 z_args = emalloc(argc * sizeof(zval));
1799 if (zend_get_parameters_array(ht, argc, z_args) == FAILURE ||
1800 argc < 3 || Z_TYPE(z_args[0]) != IS_STRING)
1801 {
1802 efree(z_args);
1803 return FAILURE;
1804 }
1805
1806 // If we were passed a slot pointer, init to a sentinel value
1807 if (slot) *slot = -1;
1808
1809 // Initialize command construction, add our operation argument
1810 redis_cmd_init_sstr(&cmdstr, argc, "BITOP", sizeof("BITOP")-1);
1811 redis_cmd_append_sstr(&cmdstr, Z_STRVAL(z_args[0]), Z_STRLEN(z_args[0]));
1812
1813 // Now iterate over our keys argument
1814 for (i = 1; i < argc; i++) {
1815 // Make sure we've got a string
1816 zstr = zval_get_string(&z_args[i]);
1817
1818 // Grab this key and length
1819 key = ZSTR_VAL(zstr);
1820 key_len = ZSTR_LEN(zstr);
1821
1822 // Prefix key, append
1823 key_free = redis_key_prefix(redis_sock, &key, &key_len);
1824 redis_cmd_append_sstr(&cmdstr, key, key_len);
1825
1826 // Verify slot if this is a Cluster request
1827 if (slot) {
1828 kslot = cluster_hash_key(key, key_len);
1829 if (*slot == -1 || kslot != *slot) {
1830 php_error_docref(NULL, E_WARNING,
1831 "Warning, not all keys hash to the same slot!");
1832 zend_string_release(zstr);
1833 if (key_free) efree(key);
1834 efree(z_args);
1835 return FAILURE;
1836 }
1837 *slot = kslot;
1838 }
1839
1840 zend_string_release(zstr);
1841 if (key_free) efree(key);
1842 }
1843
1844 // Free our argument array
1845 efree(z_args);
1846
1847 // Push out variables
1848 *cmd = cmdstr.c;
1849 *cmd_len = cmdstr.len;
1850
1851 return SUCCESS;
1852 }
1853
1854 /* BITCOUNT */
redis_bitcount_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)1855 int redis_bitcount_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1856 char **cmd, int *cmd_len, short *slot, void **ctx)
1857 {
1858 char *key;
1859 size_t key_len;
1860 zend_long start = 0, end = -1;
1861
1862 if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|ll", &key, &key_len,
1863 &start, &end) == FAILURE)
1864 {
1865 return FAILURE;
1866 }
1867
1868 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "BITCOUNT", "kdd", key, key_len,
1869 (int)start, (int)end);
1870
1871 return SUCCESS;
1872 }
1873
1874 /* PFADD and PFMERGE are the same except that in one case we serialize,
1875 * and in the other case we key prefix */
redis_gen_pf_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,int kw_len,int is_keys,char ** cmd,int * cmd_len,short * slot)1876 static int redis_gen_pf_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1877 char *kw, int kw_len, int is_keys, char **cmd,
1878 int *cmd_len, short *slot)
1879 {
1880 zval *z_arr, *z_ele;
1881 HashTable *ht_arr;
1882 smart_string cmdstr = {0};
1883 char *mem, *key;
1884 int key_free, mem_free, argc=1;
1885 size_t key_len, mem_len;
1886 zend_string *zstr;
1887
1888 // Parse arguments
1889 if (zend_parse_parameters(ZEND_NUM_ARGS(), "sa", &key, &key_len,
1890 &z_arr) == FAILURE)
1891 {
1892 return FAILURE;
1893 }
1894
1895 // Grab HashTable, count total argc
1896 ht_arr = Z_ARRVAL_P(z_arr);
1897 argc += zend_hash_num_elements(ht_arr);
1898
1899 // We need at least two arguments
1900 if (argc < 2) {
1901 return FAILURE;
1902 }
1903
1904 // Prefix key, set initial hash slot
1905 key_free = redis_key_prefix(redis_sock, &key, &key_len);
1906 if (slot) *slot = cluster_hash_key(key, key_len);
1907
1908 // Start command construction
1909 redis_cmd_init_sstr(&cmdstr, argc, kw, kw_len);
1910 redis_cmd_append_sstr(&cmdstr, key, key_len);
1911
1912 // Free key if we prefixed
1913 if (key_free) efree(key);
1914
1915 // Now iterate over the rest of our keys or values
1916 ZEND_HASH_FOREACH_VAL(ht_arr, z_ele) {
1917 // Prefix keys, serialize values
1918 if (is_keys) {
1919 zstr = zval_get_string(z_ele);
1920 mem = ZSTR_VAL(zstr);
1921 mem_len = ZSTR_LEN(zstr);
1922
1923 // Key prefix
1924 mem_free = redis_key_prefix(redis_sock, &mem, &mem_len);
1925
1926 // Verify slot
1927 if (slot && *slot != cluster_hash_key(mem, mem_len)) {
1928 php_error_docref(0, E_WARNING,
1929 "All keys must hash to the same slot!");
1930 zend_string_release(zstr);
1931 if (key_free) efree(key);
1932 return FAILURE;
1933 }
1934 } else {
1935 mem_free = redis_pack(redis_sock, z_ele, &mem, &mem_len);
1936
1937 zstr = NULL;
1938 if (!mem_free) {
1939 zstr = zval_get_string(z_ele);
1940 mem = ZSTR_VAL(zstr);
1941 mem_len = ZSTR_LEN(zstr);
1942 }
1943 }
1944
1945 // Append our key or member
1946 redis_cmd_append_sstr(&cmdstr, mem, mem_len);
1947
1948 // Clean up any allocated memory
1949 if (zstr) zend_string_release(zstr);
1950 if (mem_free) efree(mem);
1951 } ZEND_HASH_FOREACH_END();
1952
1953 // Push output arguments
1954 *cmd = cmdstr.c;
1955 *cmd_len = cmdstr.len;
1956
1957 return SUCCESS;
1958 }
1959
1960 /* PFADD */
redis_pfadd_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)1961 int redis_pfadd_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1962 char **cmd, int *cmd_len, short *slot, void **ctx)
1963 {
1964 return redis_gen_pf_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
1965 "PFADD", sizeof("PFADD")-1, 0, cmd, cmd_len, slot);
1966 }
1967
1968 /* PFMERGE */
redis_pfmerge_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)1969 int redis_pfmerge_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1970 char **cmd, int *cmd_len, short *slot, void **ctx)
1971 {
1972 return redis_gen_pf_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
1973 "PFMERGE", sizeof("PFMERGE")-1, 1, cmd, cmd_len, slot);
1974 }
1975
1976 /* PFCOUNT */
redis_pfcount_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)1977 int redis_pfcount_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1978 char **cmd, int *cmd_len, short *slot, void **ctx)
1979 {
1980 zval *z_keys, *z_key;
1981 HashTable *ht_keys;
1982 smart_string cmdstr = {0};
1983 int num_keys, key_free;
1984 size_t key_len;
1985 char *key;
1986 short kslot=-1;
1987 zend_string *zstr;
1988
1989 if (zend_parse_parameters(ZEND_NUM_ARGS(),"z",&z_keys) == FAILURE) {
1990 return FAILURE;
1991 }
1992
1993 /* If we were passed an array of keys, iterate through them prefixing if
1994 * required and capturing lengths and if we need to free them. Otherwise
1995 * attempt to treat the argument as a string and just pass one */
1996 if (Z_TYPE_P(z_keys) == IS_ARRAY) {
1997 /* Grab key hash table and the number of keys */
1998 ht_keys = Z_ARRVAL_P(z_keys);
1999 num_keys = zend_hash_num_elements(ht_keys);
2000
2001 /* There is no reason to send zero keys */
2002 if (num_keys == 0) {
2003 return FAILURE;
2004 }
2005
2006 /* Initialize the command with our number of arguments */
2007 redis_cmd_init_sstr(&cmdstr, num_keys, "PFCOUNT", sizeof("PFCOUNT")-1);
2008
2009 /* Append our key(s) */
2010 ZEND_HASH_FOREACH_VAL(ht_keys, z_key) {
2011 /* Turn our value into a string if it isn't one */
2012 zstr = zval_get_string(z_key);
2013 key = ZSTR_VAL(zstr);
2014 key_len = ZSTR_LEN(zstr);
2015
2016 /* Append this key to our command */
2017 key_free = redis_key_prefix(redis_sock, &key, &key_len);
2018 redis_cmd_append_sstr(&cmdstr, key, key_len);
2019
2020 /* Protect against CROSSLOT errors */
2021 if (slot) {
2022 if (kslot == -1) {
2023 kslot = cluster_hash_key(key, key_len);
2024 } else if (cluster_hash_key(key,key_len)!=kslot) {
2025 zend_string_release(zstr);
2026 if (key_free) efree(key);
2027 efree(cmdstr.c);
2028
2029 php_error_docref(NULL, E_WARNING,
2030 "Not all keys hash to the same slot!");
2031 return FAILURE;
2032 }
2033 }
2034
2035 /* Cleanup */
2036 zend_string_release(zstr);
2037 if (key_free) efree(key);
2038 } ZEND_HASH_FOREACH_END();
2039 } else {
2040 /* Construct our whole command */
2041 redis_cmd_init_sstr(&cmdstr, 1, "PFCOUNT", sizeof("PFCOUNT")-1);
2042
2043 /* Turn our key into a string if it's a different type */
2044 zstr = zval_get_string(z_keys);
2045 key = ZSTR_VAL(zstr);
2046 key_len = ZSTR_LEN(zstr);
2047 key_free = redis_key_prefix(redis_sock, &key, &key_len);
2048 redis_cmd_append_sstr(&cmdstr, key, key_len);
2049
2050 /* Hash our key */
2051 CMD_SET_SLOT(slot, key, key_len);
2052
2053 /* Cleanup */
2054 zend_string_release(zstr);
2055 if (key_free) efree(key);
2056 }
2057
2058 /* Push our command and length to the caller */
2059 *cmd = cmdstr.c;
2060 *cmd_len = cmdstr.len;
2061
2062 return SUCCESS;
2063 }
2064
redis_variadic_str_cmd(char * kw,zval * argv,int argc,int * cmd_len)2065 char *redis_variadic_str_cmd(char *kw, zval *argv, int argc, int *cmd_len) {
2066 smart_string cmdstr = {0};
2067 zend_string *zstr;
2068 int i;
2069
2070 redis_cmd_init_sstr(&cmdstr, argc, kw, strlen(kw));
2071
2072 for (i = 0; i < argc; i++) {
2073 zstr = zval_get_string(&argv[i]);
2074 redis_cmd_append_sstr_zstr(&cmdstr, zstr);
2075 zend_string_release(zstr);
2076 }
2077
2078 *cmd_len = cmdstr.len;
2079 return cmdstr.c;
2080 }
2081
redis_auth_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)2082 int redis_auth_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
2083 char **cmd, int *cmd_len, short *slot, void **ctx)
2084 {
2085 zend_string *user = NULL, *pass = NULL;
2086 zval *ztest;
2087
2088 if (zend_parse_parameters(ZEND_NUM_ARGS(), "z!", &ztest) == FAILURE ||
2089 redis_extract_auth_info(ztest, &user, &pass) == FAILURE)
2090 {
2091 return FAILURE;
2092 }
2093
2094 /* Construct either AUTH <user> <pass> or AUTH <pass> */
2095 if (user && pass) {
2096 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "AUTH", "SS", user, pass);
2097 } else {
2098 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "AUTH", "S", pass);
2099 }
2100
2101 redis_sock_set_auth(redis_sock, user, pass);
2102
2103 if (user) zend_string_release(user);
2104 if (pass) zend_string_release(pass);
2105
2106 return SUCCESS;
2107 }
2108
2109 /* SETBIT */
redis_setbit_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)2110 int redis_setbit_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
2111 char **cmd, int *cmd_len, short *slot, void **ctx)
2112 {
2113 char *key;
2114 size_t key_len;
2115 zend_long offset;
2116 zend_bool val;
2117
2118 if (zend_parse_parameters(ZEND_NUM_ARGS(), "slb", &key, &key_len,
2119 &offset, &val) == FAILURE)
2120 {
2121 return FAILURE;
2122 }
2123
2124 // Validate our offset
2125 if (offset < BITOP_MIN_OFFSET || offset > BITOP_MAX_OFFSET) {
2126 php_error_docref(0, E_WARNING,
2127 "Invalid OFFSET for bitop command (must be between 0-2^32-1)");
2128 return FAILURE;
2129 }
2130
2131 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "SETBIT", "kld", key, key_len, offset, (int)val);
2132
2133 return SUCCESS;
2134 }
2135
2136 /* LINSERT */
redis_linsert_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)2137 int redis_linsert_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
2138 char **cmd, int *cmd_len, short *slot, void **ctx)
2139 {
2140 char *key, *pos;
2141 size_t key_len, pos_len;
2142 zval *z_val, *z_pivot;
2143
2144 if (zend_parse_parameters(ZEND_NUM_ARGS(), "sszz", &key, &key_len,
2145 &pos, &pos_len, &z_pivot, &z_val) == FAILURE)
2146 {
2147 return FAILURE;
2148 }
2149
2150 // Validate position
2151 if (strncasecmp(pos, "after", 5) && strncasecmp(pos, "before", 6)) {
2152 php_error_docref(NULL, E_WARNING,
2153 "Position must be either 'BEFORE' or 'AFTER'");
2154 return FAILURE;
2155 }
2156
2157 /* Construct command */
2158 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "LINSERT", "ksvv", key, key_len, pos,
2159 pos_len, z_pivot, z_val);
2160
2161 // Success
2162 return SUCCESS;
2163 }
2164
2165 /* LREM */
redis_lrem_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)2166 int redis_lrem_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
2167 char **cmd, int *cmd_len, short *slot, void **ctx)
2168 {
2169 char *key;
2170 size_t key_len;
2171 zend_long count = 0;
2172 zval *z_val;
2173
2174 if (zend_parse_parameters(ZEND_NUM_ARGS(), "sz|l", &key, &key_len,
2175 &z_val, &count) == FAILURE)
2176 {
2177 return FAILURE;
2178 }
2179
2180 /* Construct command */
2181 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "LREM", "kdv", key, key_len, count, z_val);
2182
2183 // Success!
2184 return SUCCESS;
2185 }
2186
redis_smove_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)2187 int redis_smove_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
2188 char **cmd, int *cmd_len, short *slot, void **ctx)
2189 {
2190 char *src, *dst;
2191 size_t src_len, dst_len;
2192 int src_free, dst_free;
2193 zval *z_val;
2194
2195 if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssz", &src, &src_len,
2196 &dst, &dst_len, &z_val) == FAILURE)
2197 {
2198 return FAILURE;
2199 }
2200
2201 src_free = redis_key_prefix(redis_sock, &src, &src_len);
2202 dst_free = redis_key_prefix(redis_sock, &dst, &dst_len);
2203
2204 // Protect against a CROSSSLOT error
2205 if (slot) {
2206 short slot1 = cluster_hash_key(src, src_len);
2207 short slot2 = cluster_hash_key(dst, dst_len);
2208 if (slot1 != slot2) {
2209 php_error_docref(0, E_WARNING,
2210 "Source and destination keys don't hash to the same slot!");
2211 if (src_free) efree(src);
2212 if (dst_free) efree(dst);
2213 return FAILURE;
2214 }
2215 *slot = slot1;
2216 }
2217
2218 // Construct command
2219 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "SMOVE", "ssv", src, src_len, dst,
2220 dst_len, z_val);
2221
2222 // Cleanup
2223 if (src_free) efree(src);
2224 if (dst_free) efree(dst);
2225
2226 // Success!
2227 return SUCCESS;
2228 }
2229
2230 /* Generic command construction for HSET and HSETNX */
gen_hset_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot)2231 static int gen_hset_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
2232 char *kw, char **cmd, int *cmd_len, short *slot)
2233 {
2234 char *key, *mem;
2235 size_t key_len, mem_len;
2236 zval *z_val;
2237
2238 if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssz", &key, &key_len,
2239 &mem, &mem_len, &z_val) == FAILURE)
2240 {
2241 return FAILURE;
2242 }
2243
2244 /* Construct command */
2245 *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "ksv", key, key_len, mem, mem_len, z_val);
2246
2247 // Success
2248 return SUCCESS;
2249 }
2250
2251 /* HSET */
redis_hset_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)2252 int redis_hset_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
2253 char **cmd, int *cmd_len, short *slot, void **ctx)
2254 {
2255 return gen_hset_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, "HSET",
2256 cmd, cmd_len, slot);
2257 }
2258
2259 /* HSETNX */
redis_hsetnx_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)2260 int redis_hsetnx_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
2261 char **cmd, int *cmd_len, short *slot, void **ctx)
2262 {
2263 return gen_hset_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, "HSETNX",
2264 cmd, cmd_len, slot);
2265 }
2266
2267 /* SRANDMEMBER */
redis_srandmember_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx,short * have_count)2268 int redis_srandmember_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
2269 char **cmd, int *cmd_len, short *slot, void **ctx,
2270 short *have_count)
2271 {
2272 char *key;
2273 size_t key_len;
2274 zend_long count;
2275
2276 if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", &key, &key_len,
2277 &count) == FAILURE)
2278 {
2279 return FAILURE;
2280 }
2281
2282 // Set our have count flag
2283 *have_count = ZEND_NUM_ARGS() == 2;
2284
2285 // Two args means we have the optional COUNT
2286 if (*have_count) {
2287 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "SRANDMEMBER", "kl", key, key_len, count);
2288 } else {
2289 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "SRANDMEMBER", "k", key, key_len);
2290 }
2291
2292 return SUCCESS;
2293 }
2294
2295 /* ZINCRBY */
redis_zincrby_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)2296 int redis_zincrby_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
2297 char **cmd, int *cmd_len, short *slot, void **ctx)
2298 {
2299 char *key;
2300 size_t key_len;
2301 double incrby;
2302 zval *z_val;
2303
2304 if (zend_parse_parameters(ZEND_NUM_ARGS(), "sdz", &key, &key_len,
2305 &incrby, &z_val) == FAILURE)
2306 {
2307 return FAILURE;
2308 }
2309
2310 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "ZINCRBY", "kfv", key, key_len, incrby, z_val);
2311
2312 return SUCCESS;
2313 }
2314
2315 /* SORT */
redis_sort_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,int * using_store,char ** cmd,int * cmd_len,short * slot,void ** ctx)2316 int redis_sort_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
2317 int *using_store, char **cmd, int *cmd_len, short *slot,
2318 void **ctx)
2319 {
2320 zval *z_opts=NULL, *z_ele, z_argv;
2321 char *key;
2322 HashTable *ht_opts;
2323 smart_string cmdstr = {0};
2324 size_t key_len;
2325 int key_free;
2326
2327 if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|a", &key, &key_len,
2328 &z_opts) == FAILURE)
2329 {
2330 return FAILURE;
2331 }
2332
2333 // Default that we're not using store
2334 *using_store = 0;
2335
2336 // If we don't have an options array, the command is quite simple
2337 if (!z_opts || zend_hash_num_elements(Z_ARRVAL_P(z_opts)) == 0) {
2338 // Construct command
2339 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "SORT", "k", key, key_len);
2340
2341 /* Not storing */
2342 *using_store = 0;
2343
2344 return SUCCESS;
2345 }
2346
2347 // Create our hash table to hold our sort arguments
2348 array_init(&z_argv);
2349
2350 // SORT <key>
2351 key_free = redis_key_prefix(redis_sock, &key, &key_len);
2352 add_next_index_stringl(&z_argv, key, key_len);
2353 if (key_free) efree(key);
2354
2355 // Set slot
2356 CMD_SET_SLOT(slot,key,key_len);
2357
2358 // Grab the hash table
2359 ht_opts = Z_ARRVAL_P(z_opts);
2360
2361 // Handle BY pattern
2362 if (((z_ele = zend_hash_str_find(ht_opts, "by", sizeof("by") - 1)) != NULL ||
2363 (z_ele = zend_hash_str_find(ht_opts, "BY", sizeof("BY") - 1)) != NULL
2364 ) && Z_TYPE_P(z_ele) == IS_STRING
2365 ) {
2366 // "BY" option is disabled in cluster
2367 if (slot) {
2368 php_error_docref(NULL, E_WARNING,
2369 "SORT BY option is not allowed in Redis Cluster");
2370 zval_dtor(&z_argv);
2371 return FAILURE;
2372 }
2373
2374 // ... BY <pattern>
2375 add_next_index_stringl(&z_argv, "BY", sizeof("BY") - 1);
2376 add_next_index_stringl(&z_argv, Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele));
2377 }
2378
2379 // Handle ASC/DESC option
2380 if (((z_ele = zend_hash_str_find(ht_opts, "sort", sizeof("sort") - 1)) != NULL ||
2381 (z_ele = zend_hash_str_find(ht_opts, "SORT", sizeof("SORT") - 1)) != NULL
2382 ) && Z_TYPE_P(z_ele) == IS_STRING
2383 ) {
2384 // 'asc'|'desc'
2385 add_next_index_stringl(&z_argv, Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele));
2386 }
2387
2388 // STORE option
2389 if (((z_ele = zend_hash_str_find(ht_opts, "store", sizeof("store") - 1)) != NULL ||
2390 (z_ele = zend_hash_str_find(ht_opts, "STORE", sizeof("STORE") - 1)) != NULL
2391 ) && Z_TYPE_P(z_ele) == IS_STRING
2392 ) {
2393 // Slot verification
2394 int cross_slot = slot && *slot != cluster_hash_key(
2395 Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele));
2396
2397 if (cross_slot) {
2398 php_error_docref(0, E_WARNING,
2399 "Error, SORT key and STORE key have different slots!");
2400 zval_dtor(&z_argv);
2401 return FAILURE;
2402 }
2403
2404 // STORE <key>
2405 add_next_index_stringl(&z_argv, "STORE", sizeof("STORE") - 1);
2406 add_next_index_stringl(&z_argv, Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele));
2407
2408 // We are using STORE
2409 *using_store = 1;
2410 }
2411
2412 // GET option
2413 if (((z_ele = zend_hash_str_find(ht_opts, "get", sizeof("get") - 1)) != NULL ||
2414 (z_ele = zend_hash_str_find(ht_opts, "GET", sizeof("GET") - 1)) != NULL
2415 ) && (Z_TYPE_P(z_ele) == IS_STRING || Z_TYPE_P(z_ele) == IS_ARRAY)
2416 ) {
2417 // Disabled in cluster
2418 if (slot) {
2419 php_error_docref(NULL, E_WARNING,
2420 "GET option for SORT disabled in Redis Cluster");
2421 zval_dtor(&z_argv);
2422 return FAILURE;
2423 }
2424
2425 // If it's a string just add it
2426 if (Z_TYPE_P(z_ele) == IS_STRING) {
2427 add_next_index_stringl(&z_argv, "GET", sizeof("GET") - 1);
2428 add_next_index_stringl(&z_argv, Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele));
2429 } else {
2430 int added = 0;
2431 zval *z_key;
2432
2433 ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(z_ele), z_key) {
2434 // If we can't get the data, or it's not a string, skip
2435 if (z_key == NULL || Z_TYPE_P(z_key) != IS_STRING) {
2436 continue;
2437 }
2438 /* Add get per thing we're getting */
2439 add_next_index_stringl(&z_argv, "GET", sizeof("GET") - 1);
2440
2441 // Add this key to our argv array
2442 add_next_index_stringl(&z_argv, Z_STRVAL_P(z_key), Z_STRLEN_P(z_key));
2443 added++;
2444 } ZEND_HASH_FOREACH_END();
2445
2446 // Make sure we were able to add at least one
2447 if (added == 0) {
2448 php_error_docref(NULL, E_WARNING,
2449 "Array of GET values requested, but none are valid");
2450 zval_dtor(&z_argv);
2451 return FAILURE;
2452 }
2453 }
2454 }
2455
2456 // ALPHA
2457 if (((z_ele = zend_hash_str_find(ht_opts, "alpha", sizeof("alpha") - 1)) != NULL ||
2458 (z_ele = zend_hash_str_find(ht_opts, "ALPHA", sizeof("ALPHA") - 1)) != NULL) &&
2459 zval_is_true(z_ele)
2460 ) {
2461 add_next_index_stringl(&z_argv, "ALPHA", sizeof("ALPHA") - 1);
2462 }
2463
2464 // LIMIT <offset> <count>
2465 if (((z_ele = zend_hash_str_find(ht_opts, "limit", sizeof("limit") - 1)) != NULL ||
2466 (z_ele = zend_hash_str_find(ht_opts, "LIMIT", sizeof("LIMIT") - 1)) != NULL
2467 ) && Z_TYPE_P(z_ele) == IS_ARRAY
2468 ) {
2469 HashTable *ht_off = Z_ARRVAL_P(z_ele);
2470 zval *z_off, *z_cnt;
2471
2472 if ((z_off = zend_hash_index_find(ht_off, 0)) != NULL &&
2473 (z_cnt = zend_hash_index_find(ht_off, 1)) != NULL
2474 ) {
2475 if ((Z_TYPE_P(z_off) != IS_STRING && Z_TYPE_P(z_off) != IS_LONG) ||
2476 (Z_TYPE_P(z_cnt) != IS_STRING && Z_TYPE_P(z_cnt) != IS_LONG)
2477 ) {
2478 php_error_docref(NULL, E_WARNING,
2479 "LIMIT options on SORT command must be longs or strings");
2480 zval_dtor(&z_argv);
2481 return FAILURE;
2482 }
2483
2484 // Add LIMIT argument
2485 add_next_index_stringl(&z_argv, "LIMIT", sizeof("LIMIT") - 1);
2486
2487 long low, high;
2488 if (Z_TYPE_P(z_off) == IS_STRING) {
2489 low = atol(Z_STRVAL_P(z_off));
2490 } else {
2491 low = Z_LVAL_P(z_off);
2492 }
2493 if (Z_TYPE_P(z_cnt) == IS_STRING) {
2494 high = atol(Z_STRVAL_P(z_cnt));
2495 } else {
2496 high = Z_LVAL_P(z_cnt);
2497 }
2498
2499 // Add our two LIMIT arguments
2500 add_next_index_long(&z_argv, low);
2501 add_next_index_long(&z_argv, high);
2502 }
2503 }
2504
2505 // Start constructing our command
2506 HashTable *ht_argv = Z_ARRVAL_P(&z_argv);
2507 redis_cmd_init_sstr(&cmdstr, zend_hash_num_elements(ht_argv), "SORT",
2508 sizeof("SORT")-1);
2509
2510 // Iterate through our arguments
2511 ZEND_HASH_FOREACH_VAL(ht_argv, z_ele) {
2512 // Args are strings or longs
2513 if (Z_TYPE_P(z_ele) == IS_STRING) {
2514 redis_cmd_append_sstr(&cmdstr,Z_STRVAL_P(z_ele),
2515 Z_STRLEN_P(z_ele));
2516 } else {
2517 redis_cmd_append_sstr_long(&cmdstr, Z_LVAL_P(z_ele));
2518 }
2519 } ZEND_HASH_FOREACH_END();
2520
2521 /* Clean up our arguments array. Note we don't have to free any prefixed
2522 * key as that we didn't duplicate the pointer if we prefixed */
2523 zval_dtor(&z_argv);
2524
2525 // Push our length and command
2526 *cmd_len = cmdstr.len;
2527 *cmd = cmdstr.c;
2528
2529 // Success!
2530 return SUCCESS;
2531 }
2532
2533 /* HDEL */
redis_hdel_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)2534 int redis_hdel_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
2535 char **cmd, int *cmd_len, short *slot, void **ctx)
2536 {
2537 zval *z_args;
2538 smart_string cmdstr = {0};
2539 char *arg;
2540 int arg_free, i;
2541 size_t arg_len;
2542 int argc = ZEND_NUM_ARGS();
2543 zend_string *zstr;
2544
2545 // We need at least KEY and one member
2546 if (argc < 2) {
2547 return FAILURE;
2548 }
2549
2550 // Grab arguments as an array
2551 z_args = emalloc(argc * sizeof(zval));
2552 if (zend_get_parameters_array(ht, argc, z_args) == FAILURE) {
2553 efree(z_args);
2554 return FAILURE;
2555 }
2556
2557 // Get first argument (the key) as a string
2558 zstr = zval_get_string(&z_args[0]);
2559 arg = ZSTR_VAL(zstr);
2560 arg_len = ZSTR_LEN(zstr);
2561
2562 // Prefix
2563 arg_free = redis_key_prefix(redis_sock, &arg, &arg_len);
2564
2565 // Start command construction
2566 redis_cmd_init_sstr(&cmdstr, argc, "HDEL", sizeof("HDEL")-1);
2567 redis_cmd_append_sstr(&cmdstr, arg, arg_len);
2568
2569 // Set our slot, free key if we prefixed it
2570 CMD_SET_SLOT(slot,arg,arg_len);
2571 zend_string_release(zstr);
2572 if (arg_free) efree(arg);
2573
2574 // Iterate through the members we're removing
2575 for (i = 1; i < argc; i++) {
2576 zstr = zval_get_string(&z_args[i]);
2577 redis_cmd_append_sstr(&cmdstr, ZSTR_VAL(zstr), ZSTR_LEN(zstr));
2578 zend_string_release(zstr);
2579 }
2580
2581 // Push out values
2582 *cmd = cmdstr.c;
2583 *cmd_len = cmdstr.len;
2584
2585 // Cleanup
2586 efree(z_args);
2587
2588 // Success!
2589 return SUCCESS;
2590 }
2591
2592 /* ZADD */
redis_zadd_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)2593 int redis_zadd_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
2594 char **cmd, int *cmd_len, short *slot, void **ctx)
2595 {
2596 zval *z_args;
2597 char *key, *val, *exp_type = NULL;
2598 size_t key_len, val_len;
2599 int key_free, val_free;
2600 int num = ZEND_NUM_ARGS(), i = 1, argc;
2601 zend_bool ch = 0, incr = 0;
2602 smart_string cmdstr = {0};
2603 zend_string *zstr;
2604
2605 if (num < 3) return FAILURE;
2606 z_args = ecalloc(num, sizeof(zval));
2607 if (zend_get_parameters_array(ht, num, z_args) == FAILURE) {
2608 efree(z_args);
2609 return FAILURE;
2610 }
2611
2612 // Need key, [NX|XX] [CH] [INCR] score, value, [score, value...] */
2613 if (num % 2 == 0) {
2614 if (Z_TYPE(z_args[1]) != IS_ARRAY) {
2615 efree(z_args);
2616 return FAILURE;
2617 }
2618 zval *z_opt;
2619 ZEND_HASH_FOREACH_VAL(Z_ARRVAL(z_args[1]), z_opt) {
2620 if (Z_TYPE_P(z_opt) == IS_STRING) {
2621 if (ZVAL_IS_NX_XX_ARG(z_opt)) {
2622 exp_type = Z_STRVAL_P(z_opt);
2623 } else if (ZVAL_STRICMP_STATIC(z_opt, "CH")) {
2624 ch = 1;
2625 } else if (ZVAL_STRICMP_STATIC(z_opt, "INCR")) {
2626 if (num > 4) {
2627 // Only one score-element pair can be specified in this mode.
2628 efree(z_args);
2629 return FAILURE;
2630 }
2631 incr = 1;
2632 }
2633
2634 }
2635 } ZEND_HASH_FOREACH_END();
2636 argc = num - 1;
2637 if (exp_type) argc++;
2638 argc += ch + incr;
2639 i++;
2640 } else {
2641 argc = num;
2642 }
2643
2644 // Prefix our key
2645 zstr = zval_get_string(&z_args[0]);
2646 key = ZSTR_VAL(zstr);
2647 key_len = ZSTR_LEN(zstr);
2648 key_free = redis_key_prefix(redis_sock, &key, &key_len);
2649
2650 // Start command construction
2651 redis_cmd_init_sstr(&cmdstr, argc, "ZADD", sizeof("ZADD")-1);
2652 redis_cmd_append_sstr(&cmdstr, key, key_len);
2653
2654 // Set our slot, free key if we prefixed it
2655 CMD_SET_SLOT(slot,key,key_len);
2656 zend_string_release(zstr);
2657 if (key_free) efree(key);
2658
2659 if (exp_type) redis_cmd_append_sstr(&cmdstr, exp_type, 2);
2660 if (ch) redis_cmd_append_sstr(&cmdstr, "CH", 2);
2661 if (incr) redis_cmd_append_sstr(&cmdstr, "INCR", 4);
2662
2663 // Now the rest of our arguments
2664 while (i < num) {
2665 // Append score and member
2666 switch (Z_TYPE(z_args[i])) {
2667 case IS_LONG:
2668 case IS_DOUBLE:
2669 redis_cmd_append_sstr_dbl(&cmdstr, zval_get_double(&z_args[i]));
2670 break;
2671 case IS_STRING:
2672 /* The score values must be the string representation of a double
2673 * precision floating point number. +inf and -inf values are valid
2674 * values as well. */
2675 if (strncasecmp(Z_STRVAL(z_args[i]), "-inf", 4) == 0 ||
2676 strncasecmp(Z_STRVAL(z_args[i]), "+inf", 4) == 0 ||
2677 is_numeric_string(Z_STRVAL(z_args[i]), Z_STRLEN(z_args[i]), NULL, NULL, 0) != 0
2678 ) {
2679 redis_cmd_append_sstr(&cmdstr, Z_STRVAL(z_args[i]), Z_STRLEN(z_args[i]));
2680 break;
2681 }
2682 // fall through
2683 default:
2684 php_error_docref(NULL, E_WARNING, "Scores must be numeric or '-inf','+inf'");
2685 smart_string_free(&cmdstr);
2686 efree(z_args);
2687 return FAILURE;
2688 }
2689 // serialize value if requested
2690 val_free = redis_pack(redis_sock, &z_args[i+1], &val, &val_len);
2691 redis_cmd_append_sstr(&cmdstr, val, val_len);
2692
2693 // Free value if we serialized
2694 if (val_free) efree(val);
2695 i += 2;
2696 }
2697
2698 // Push output values
2699 *cmd = cmdstr.c;
2700 *cmd_len = cmdstr.len;
2701
2702 // Cleanup args
2703 efree(z_args);
2704
2705 return SUCCESS;
2706 }
2707
2708 /* OBJECT */
redis_object_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,REDIS_REPLY_TYPE * rtype,char ** cmd,int * cmd_len,short * slot,void ** ctx)2709 int redis_object_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
2710 REDIS_REPLY_TYPE *rtype, char **cmd, int *cmd_len,
2711 short *slot, void **ctx)
2712 {
2713 char *key, *subcmd;
2714 size_t key_len, subcmd_len;
2715
2716 if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", &subcmd,
2717 &subcmd_len, &key, &key_len) == FAILURE)
2718 {
2719 return FAILURE;
2720 }
2721
2722 // Format our command
2723 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "OBJECT", "sk", subcmd, subcmd_len, key, key_len);
2724
2725 // Push the reply type to our caller
2726 if (subcmd_len == 8 && (!strncasecmp(subcmd,"refcount",8) ||
2727 !strncasecmp(subcmd,"idletime",8)))
2728 {
2729 *rtype = TYPE_INT;
2730 } else if (subcmd_len == 8 && !strncasecmp(subcmd, "encoding", 8)) {
2731 *rtype = TYPE_BULK;
2732 } else {
2733 php_error_docref(NULL, E_WARNING,
2734 "Invalid subcommand sent to OBJECT");
2735 efree(*cmd);
2736 return FAILURE;
2737 }
2738
2739 // Success
2740 return SUCCESS;
2741 }
2742
2743 /* GEODIST */
redis_geodist_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)2744 int redis_geodist_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
2745 char **cmd, int *cmd_len, short *slot, void **ctx)
2746 {
2747 char *key, *source, *dest, *unit = NULL;
2748 size_t keylen, sourcelen, destlen, unitlen;
2749
2750 if (zend_parse_parameters(ZEND_NUM_ARGS(), "sss|s", &key, &keylen,
2751 &source, &sourcelen, &dest, &destlen, &unit,
2752 &unitlen) == FAILURE)
2753 {
2754 return FAILURE;
2755 }
2756
2757 /* Construct command */
2758 if (unit != NULL) {
2759 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "GEODIST", "ksss", key, keylen, source,
2760 sourcelen, dest, destlen, unit, unitlen);
2761 } else {
2762 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "GEODIST", "kss", key, keylen, source,
2763 sourcelen, dest, destlen);
2764 }
2765
2766 return SUCCESS;
2767 }
2768
get_georadius_store_type(zend_string * key)2769 geoStoreType get_georadius_store_type(zend_string *key) {
2770 if (ZSTR_LEN(key) == 5 && !strcasecmp(ZSTR_VAL(key), "store")) {
2771 return STORE_COORD;
2772 } else if (ZSTR_LEN(key) == 9 && !strcasecmp(ZSTR_VAL(key), "storedist")) {
2773 return STORE_DIST;
2774 }
2775
2776 return STORE_NONE;
2777 }
2778
2779 /* Helper function to extract optional arguments for GEORADIUS and GEORADIUSBYMEMBER */
get_georadius_opts(HashTable * ht,geoOptions * opts)2780 static int get_georadius_opts(HashTable *ht, geoOptions *opts) {
2781 char *optstr;
2782 zend_string *zkey;
2783 zval *optval;
2784
2785 /* Iterate over our argument array, collating which ones we have */
2786 ZEND_HASH_FOREACH_STR_KEY_VAL(ht, zkey, optval) {
2787 ZVAL_DEREF(optval);
2788
2789 /* If the key is numeric it's a non value option */
2790 if (zkey) {
2791 if (ZSTR_LEN(zkey) == 5 && !strcasecmp(ZSTR_VAL(zkey), "count")) {
2792 if (Z_TYPE_P(optval) != IS_LONG || Z_LVAL_P(optval) <= 0) {
2793 php_error_docref(NULL, E_WARNING,
2794 "COUNT must be an integer > 0!");
2795 if (opts->key) zend_string_release(opts->key);
2796 return FAILURE;
2797 }
2798
2799 /* Set our count */
2800 opts->count = Z_LVAL_P(optval);
2801 } else if (opts->store == STORE_NONE) {
2802 opts->store = get_georadius_store_type(zkey);
2803 if (opts->store != STORE_NONE) {
2804 opts->key = zval_get_string(optval);
2805 }
2806 }
2807 } else {
2808 /* Option needs to be a string */
2809 if (Z_TYPE_P(optval) != IS_STRING) continue;
2810
2811 optstr = Z_STRVAL_P(optval);
2812
2813 if (!strcasecmp(optstr, "withcoord")) {
2814 opts->withcoord = 1;
2815 } else if (!strcasecmp(optstr, "withdist")) {
2816 opts->withdist = 1;
2817 } else if (!strcasecmp(optstr, "withhash")) {
2818 opts->withhash = 1;
2819 } else if (!strcasecmp(optstr, "asc")) {
2820 opts->sort = SORT_ASC;
2821 } else if (!strcasecmp(optstr, "desc")) {
2822 opts->sort = SORT_DESC;
2823 }
2824 }
2825 } ZEND_HASH_FOREACH_END();
2826
2827 /* STORE and STOREDIST are not compatible with the WITH* options */
2828 if (opts->key != NULL && (opts->withcoord || opts->withdist || opts->withhash)) {
2829 php_error_docref(NULL, E_WARNING,
2830 "STORE[DIST] is not compatible with WITHCOORD, WITHDIST or WITHHASH");
2831
2832 if (opts->key) zend_string_release(opts->key);
2833 return FAILURE;
2834 }
2835
2836 /* Success */
2837 return SUCCESS;
2838 }
2839
2840 /* Helper to append options to a GEORADIUS or GEORADIUSBYMEMBER command */
append_georadius_opts(RedisSock * redis_sock,smart_string * str,short * slot,geoOptions * opt)2841 void append_georadius_opts(RedisSock *redis_sock, smart_string *str, short *slot,
2842 geoOptions *opt)
2843 {
2844 char *key;
2845 size_t keylen;
2846 int keyfree;
2847
2848 if (opt->withcoord)
2849 REDIS_CMD_APPEND_SSTR_STATIC(str, "WITHCOORD");
2850 if (opt->withdist)
2851 REDIS_CMD_APPEND_SSTR_STATIC(str, "WITHDIST");
2852 if (opt->withhash)
2853 REDIS_CMD_APPEND_SSTR_STATIC(str, "WITHHASH");
2854
2855 /* Append sort if it's not GEO_NONE */
2856 if (opt->sort == SORT_ASC) {
2857 REDIS_CMD_APPEND_SSTR_STATIC(str, "ASC");
2858 } else if (opt->sort == SORT_DESC) {
2859 REDIS_CMD_APPEND_SSTR_STATIC(str, "DESC");
2860 }
2861
2862 /* Append our count if we've got one */
2863 if (opt->count) {
2864 REDIS_CMD_APPEND_SSTR_STATIC(str, "COUNT");
2865 redis_cmd_append_sstr_long(str, opt->count);
2866 }
2867
2868 /* Append store options if we've got them */
2869 if (opt->store != STORE_NONE && opt->key != NULL) {
2870 /* Grab string bits and prefix if requested */
2871 key = ZSTR_VAL(opt->key);
2872 keylen = ZSTR_LEN(opt->key);
2873 keyfree = redis_key_prefix(redis_sock, &key, &keylen);
2874
2875 if (opt->store == STORE_COORD) {
2876 REDIS_CMD_APPEND_SSTR_STATIC(str, "STORE");
2877 } else {
2878 REDIS_CMD_APPEND_SSTR_STATIC(str, "STOREDIST");
2879 }
2880
2881 redis_cmd_append_sstr(str, key, keylen);
2882
2883 CMD_SET_SLOT(slot, key, keylen);
2884 if (keyfree) free(key);
2885 }
2886 }
2887
2888 /* GEORADIUS / GEORADIUS_RO */
redis_georadius_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)2889 int redis_georadius_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
2890 char *kw, char **cmd, int *cmd_len, short *slot,
2891 void **ctx)
2892 {
2893 char *key, *unit;
2894 short store_slot = 0;
2895 size_t keylen, unitlen;
2896 int argc = 5, keyfree;
2897 double lng, lat, radius;
2898 zval *opts = NULL;
2899 geoOptions gopts = {0};
2900 smart_string cmdstr = {0};
2901
2902 if (zend_parse_parameters(ZEND_NUM_ARGS(), "sddds|a", &key, &keylen,
2903 &lng, &lat, &radius, &unit, &unitlen, &opts)
2904 == FAILURE)
2905 {
2906 return FAILURE;
2907 }
2908
2909 /* Parse any GEORADIUS options we have */
2910 if (opts != NULL) {
2911 /* Attempt to parse our options array */
2912 if (get_georadius_opts(Z_ARRVAL_P(opts), &gopts) != SUCCESS)
2913 {
2914 return FAILURE;
2915 }
2916 }
2917
2918 /* Increment argc depending on options */
2919 argc += gopts.withcoord + gopts.withdist + gopts.withhash +
2920 (gopts.sort != SORT_NONE) + (gopts.count ? 2 : 0) +
2921 (gopts.store != STORE_NONE ? 2 : 0);
2922
2923 /* Begin construction of our command */
2924 redis_cmd_init_sstr(&cmdstr, argc, kw, strlen(kw));
2925
2926 /* Prefix and set slot */
2927 keyfree = redis_key_prefix(redis_sock, &key, &keylen);
2928 CMD_SET_SLOT(slot, key, keylen);
2929
2930 /* Append required arguments */
2931 redis_cmd_append_sstr(&cmdstr, key, keylen);
2932 redis_cmd_append_sstr_dbl(&cmdstr, lng);
2933 redis_cmd_append_sstr_dbl(&cmdstr, lat);
2934 redis_cmd_append_sstr_dbl(&cmdstr, radius);
2935 redis_cmd_append_sstr(&cmdstr, unit, unitlen);
2936
2937 /* Append optional arguments */
2938 append_georadius_opts(redis_sock, &cmdstr, slot ? &store_slot : NULL, &gopts);
2939
2940 /* Free key if it was prefixed */
2941 if (keyfree) efree(key);
2942 if (gopts.key) zend_string_release(gopts.key);
2943
2944 /* Protect the user from CROSSSLOT if we're in cluster */
2945 if (slot && gopts.store != STORE_NONE && *slot != store_slot) {
2946 php_error_docref(NULL, E_WARNING,
2947 "Key and STORE[DIST] key must hash to the same slot");
2948 efree(cmdstr.c);
2949 return FAILURE;
2950 }
2951
2952 /* Set slot, command and len, and return */
2953 *cmd = cmdstr.c;
2954 *cmd_len = cmdstr.len;
2955
2956 return SUCCESS;
2957 }
2958
2959 /* GEORADIUSBYMEMBER/GEORADIUSBYMEMBER_RO
2960 * key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] */
redis_georadiusbymember_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)2961 int redis_georadiusbymember_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
2962 char *kw, char **cmd, int *cmd_len, short *slot,
2963 void **ctx)
2964 {
2965 char *key, *mem, *unit;
2966 size_t keylen, memlen, unitlen;
2967 short store_slot = 0;
2968 int keyfree, argc = 4;
2969 double radius;
2970 geoOptions gopts = {0};
2971 zval *opts = NULL;
2972 smart_string cmdstr = {0};
2973
2974 if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssds|a", &key, &keylen,
2975 &mem, &memlen, &radius, &unit, &unitlen, &opts) == FAILURE)
2976 {
2977 return FAILURE;
2978 }
2979
2980 if (opts != NULL) {
2981 /* Attempt to parse our options array */
2982 if (get_georadius_opts(Z_ARRVAL_P(opts), &gopts) == FAILURE) {
2983 return FAILURE;
2984 }
2985 }
2986
2987 /* Increment argc based on options */
2988 argc += gopts.withcoord + gopts.withdist + gopts.withhash +
2989 (gopts.sort != SORT_NONE) + (gopts.count ? 2 : 0) +
2990 (gopts.store != STORE_NONE ? 2 : 0);
2991
2992 /* Begin command construction*/
2993 redis_cmd_init_sstr(&cmdstr, argc, kw, strlen(kw));
2994
2995 /* Prefix our key if we're prefixing and set the slot */
2996 keyfree = redis_key_prefix(redis_sock, &key, &keylen);
2997 CMD_SET_SLOT(slot, key, keylen);
2998
2999 /* Append required arguments */
3000 redis_cmd_append_sstr(&cmdstr, key, keylen);
3001 redis_cmd_append_sstr(&cmdstr, mem, memlen);
3002 redis_cmd_append_sstr_long(&cmdstr, radius);
3003 redis_cmd_append_sstr(&cmdstr, unit, unitlen);
3004
3005 /* Append options */
3006 append_georadius_opts(redis_sock, &cmdstr, slot ? &store_slot : NULL, &gopts);
3007
3008 /* Free key if we prefixed */
3009 if (keyfree) efree(key);
3010 if (gopts.key) zend_string_release(gopts.key);
3011
3012 /* Protect the user from CROSSSLOT if we're in cluster */
3013 if (slot && gopts.store != STORE_NONE && *slot != store_slot) {
3014 php_error_docref(NULL, E_WARNING,
3015 "Key and STORE[DIST] key must hash to the same slot");
3016 efree(cmdstr.c);
3017 return FAILURE;
3018 }
3019
3020 *cmd = cmdstr.c;
3021 *cmd_len = cmdstr.len;
3022
3023 return SUCCESS;
3024 }
3025
3026 /* MIGRATE */
redis_migrate_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3027 int redis_migrate_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3028 char **cmd, int *cmd_len, short *slot, void **ctx)
3029 {
3030 smart_string cmdstr = {0};
3031 char *host, *key;
3032 int argc, keyfree;
3033 zval *z_keys, *z_key;
3034 size_t hostlen, keylen;
3035 zend_long destdb, port, timeout;
3036 zend_bool copy = 0, replace = 0;
3037 zend_string *zstr;
3038
3039 if (zend_parse_parameters(ZEND_NUM_ARGS(), "slzll|bb", &host, &hostlen, &port,
3040 &z_keys, &destdb, &timeout, ©, &replace) == FAILURE)
3041 {
3042 return FAILURE;
3043 }
3044
3045 /* Protect against being passed an array with zero elements */
3046 if (Z_TYPE_P(z_keys) == IS_ARRAY && zend_hash_num_elements(Z_ARRVAL_P(z_keys)) == 0) {
3047 php_error_docref(NULL, E_WARNING, "Keys array cannot be empty");
3048 return FAILURE;
3049 }
3050
3051 /* host, port, key|"", dest-db, timeout, [copy, replace] [KEYS key1..keyN] */
3052 argc = 5 + copy + replace;
3053 if (Z_TYPE_P(z_keys) == IS_ARRAY) {
3054 /* +1 for the "KEYS" argument itself */
3055 argc += 1 + zend_hash_num_elements(Z_ARRVAL_P(z_keys));
3056 }
3057
3058 /* Initialize MIGRATE command with host and port */
3059 REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, argc, "MIGRATE");
3060 redis_cmd_append_sstr(&cmdstr, host, hostlen);
3061 redis_cmd_append_sstr_long(&cmdstr, port);
3062
3063 /* If passed a keys array the keys come later, otherwise pass the key to
3064 * migrate here */
3065 if (Z_TYPE_P(z_keys) == IS_ARRAY) {
3066 REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "");
3067 } else {
3068 /* Grab passed value as a string */
3069 zstr = zval_get_string(z_keys);
3070
3071 /* We may need to prefix our string */
3072 key = ZSTR_VAL(zstr);
3073 keylen = ZSTR_LEN(zstr);
3074 keyfree = redis_key_prefix(redis_sock, &key, &keylen);
3075
3076 /* Add key to migrate */
3077 redis_cmd_append_sstr(&cmdstr, key, keylen);
3078
3079 zend_string_release(zstr);
3080 if (keyfree) efree(key);
3081 }
3082
3083 redis_cmd_append_sstr_long(&cmdstr, destdb);
3084 redis_cmd_append_sstr_long(&cmdstr, timeout);
3085 REDIS_CMD_APPEND_SSTR_OPT_STATIC(&cmdstr, copy, "COPY");
3086 REDIS_CMD_APPEND_SSTR_OPT_STATIC(&cmdstr, replace, "REPLACE");
3087
3088 /* Append actual keys if we've got a keys array */
3089 if (Z_TYPE_P(z_keys) == IS_ARRAY) {
3090 REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "KEYS");
3091
3092 ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(z_keys), z_key) {
3093 zstr = zval_get_string(z_key);
3094
3095 key = ZSTR_VAL(zstr);
3096 keylen = ZSTR_LEN(zstr);
3097 keyfree = redis_key_prefix(redis_sock, &key, &keylen);
3098
3099 /* Append the key */
3100 redis_cmd_append_sstr(&cmdstr, key, keylen);
3101
3102 zend_string_release(zstr);
3103 if (keyfree) efree(key);
3104 } ZEND_HASH_FOREACH_END();
3105 }
3106
3107 *cmd = cmdstr.c;
3108 *cmd_len = cmdstr.len;
3109 return SUCCESS;
3110 }
3111
3112 /* EXISTS */
redis_exists_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3113 int redis_exists_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3114 char **cmd, int *cmd_len, short *slot, void **ctx)
3115 {
3116 return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
3117 "EXISTS", sizeof("EXISTS") - 1, 0, 0, cmd, cmd_len, slot);
3118 }
3119
3120 /* DEL */
redis_del_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3121 int redis_del_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3122 char **cmd, int *cmd_len, short *slot, void **ctx)
3123 {
3124 return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
3125 "DEL", sizeof("DEL")-1, 1, 0, cmd, cmd_len, slot);
3126 }
3127
3128 /* UNLINK */
redis_unlink_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3129 int redis_unlink_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3130 char **cmd, int *cmd_len, short *slot, void **ctx)
3131 {
3132 return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
3133 "UNLINK", sizeof("UNLINK")-1, 1, 0, cmd, cmd_len, slot);
3134 }
3135
3136 /* WATCH */
redis_watch_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3137 int redis_watch_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3138 char **cmd, int *cmd_len, short *slot, void **ctx)
3139 {
3140 return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
3141 "WATCH", sizeof("WATCH")-1, 1, 0, cmd, cmd_len, slot);
3142 }
3143
3144 /* SINTER */
redis_sinter_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3145 int redis_sinter_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3146 char **cmd, int *cmd_len, short *slot, void **ctx)
3147 {
3148 return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
3149 "SINTER", sizeof("SINTER")-1, 1, 0, cmd, cmd_len, slot);
3150 }
3151
3152 /* SINTERSTORE */
redis_sinterstore_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3153 int redis_sinterstore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3154 char **cmd, int *cmd_len, short *slot, void **ctx)
3155 {
3156 return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
3157 "SINTERSTORE", sizeof("SINTERSTORE")-1, 1, 0, cmd, cmd_len, slot);
3158 }
3159
3160 /* SUNION */
redis_sunion_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3161 int redis_sunion_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3162 char **cmd, int *cmd_len, short *slot, void **ctx)
3163 {
3164 return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
3165 "SUNION", sizeof("SUNION")-1, 1, 0, cmd, cmd_len, slot);
3166 }
3167
3168 /* SUNIONSTORE */
redis_sunionstore_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3169 int redis_sunionstore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3170 char **cmd, int *cmd_len, short *slot, void **ctx)
3171 {
3172 return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
3173 "SUNIONSTORE", sizeof("SUNIONSTORE")-1, 2, 0, cmd, cmd_len, slot);
3174 }
3175
3176 /* SDIFF */
redis_sdiff_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3177 int redis_sdiff_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3178 char **cmd, int *cmd_len, short *slot, void **ctx)
3179 {
3180 return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, "SDIFF",
3181 sizeof("SDIFF")-1, 1, 0, cmd, cmd_len, slot);
3182 }
3183
3184 /* SDIFFSTORE */
redis_sdiffstore_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3185 int redis_sdiffstore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3186 char **cmd, int *cmd_len, short *slot, void **ctx)
3187 {
3188 return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
3189 "SDIFFSTORE", sizeof("SDIFFSTORE")-1, 1, 0, cmd, cmd_len, slot);
3190 }
3191
3192 /* COMMAND */
redis_command_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3193 int redis_command_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3194 char **cmd, int *cmd_len, short *slot, void **ctx)
3195 {
3196 char *kw=NULL;
3197 zval *z_arg;
3198 size_t kw_len;
3199
3200 /* Parse our args */
3201 if (zend_parse_parameters(ZEND_NUM_ARGS(), "|sz", &kw, &kw_len,
3202 &z_arg) == FAILURE)
3203 {
3204 return FAILURE;
3205 }
3206
3207 /* Construct our command */
3208 if (!kw) {
3209 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "COMMAND", "");
3210 } else if (!z_arg) {
3211 /* Sanity check */
3212 if (strncasecmp(kw, "count", sizeof("count") - 1)) {
3213 return FAILURE;
3214 }
3215 /* COMMAND COUNT */
3216 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "COMMAND", "s", "COUNT", sizeof("COUNT") - 1);
3217 } else if (Z_TYPE_P(z_arg) == IS_STRING) {
3218 /* Sanity check */
3219 if (strncasecmp(kw, "info", sizeof("info") - 1)) {
3220 return FAILURE;
3221 }
3222
3223 /* COMMAND INFO <cmd> */
3224 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "COMMAND", "ss", "INFO", sizeof("INFO") - 1,
3225 Z_STRVAL_P(z_arg), Z_STRLEN_P(z_arg));
3226 } else {
3227 int arr_len;
3228
3229 /* Sanity check on args */
3230 if (strncasecmp(kw, "getkeys", sizeof("getkeys")-1) ||
3231 Z_TYPE_P(z_arg)!=IS_ARRAY ||
3232 (arr_len=zend_hash_num_elements(Z_ARRVAL_P(z_arg)))<1)
3233 {
3234 return FAILURE;
3235 }
3236
3237 zval *z_ele;
3238 HashTable *ht_arr = Z_ARRVAL_P(z_arg);
3239 smart_string cmdstr = {0};
3240
3241 redis_cmd_init_sstr(&cmdstr, 1 + arr_len, "COMMAND", sizeof("COMMAND")-1);
3242 redis_cmd_append_sstr(&cmdstr, "GETKEYS", sizeof("GETKEYS")-1);
3243
3244 ZEND_HASH_FOREACH_VAL(ht_arr, z_ele) {
3245 zend_string *zstr = zval_get_string(z_ele);
3246 redis_cmd_append_sstr(&cmdstr, ZSTR_VAL(zstr), ZSTR_LEN(zstr));
3247 zend_string_release(zstr);
3248 } ZEND_HASH_FOREACH_END();
3249
3250 *cmd = cmdstr.c;
3251 *cmd_len = cmdstr.len;
3252 }
3253
3254 /* Any slot will do */
3255 CMD_RAND_SLOT(slot);
3256
3257 return SUCCESS;
3258 }
3259
3260 /* XADD */
redis_xadd_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3261 int redis_xadd_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3262 char **cmd, int *cmd_len, short *slot, void **ctx)
3263 {
3264 smart_string cmdstr = {0};
3265 zend_string *arrkey;
3266 zval *z_fields, *value;
3267 zend_long maxlen = 0;
3268 zend_bool approx = 0;
3269 zend_ulong idx;
3270 HashTable *ht_fields;
3271 int fcount, argc;
3272 char *key, *id;
3273 size_t keylen, idlen;
3274
3275 if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssa|lb", &key, &keylen,
3276 &id, &idlen, &z_fields, &maxlen, &approx) == FAILURE)
3277 {
3278 return FAILURE;
3279 }
3280
3281 /* At least one field and string are required */
3282 ht_fields = Z_ARRVAL_P(z_fields);
3283 if ((fcount = zend_hash_num_elements(ht_fields)) == 0) {
3284 return FAILURE;
3285 }
3286
3287 if (maxlen < 0 || (maxlen == 0 && approx != 0)) {
3288 php_error_docref(NULL, E_WARNING,
3289 "Warning: Invalid MAXLEN argument or approximate flag");
3290 }
3291
3292
3293 /* Calculate argc for XADD. It's a bit complex because we've got
3294 * an optional MAXLEN argument which can either take the form MAXLEN N
3295 * or MAXLEN ~ N */
3296 argc = 2 + (fcount*2) + (maxlen > 0 ? (approx ? 3 : 2) : 0);
3297
3298 /* XADD key ID field string [field string ...] */
3299 REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, argc, "XADD");
3300 redis_cmd_append_sstr_key(&cmdstr, key, keylen, redis_sock, slot);
3301
3302 /* Now append our MAXLEN bits if we've got them */
3303 if (maxlen > 0) {
3304 REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "MAXLEN");
3305 REDIS_CMD_APPEND_SSTR_OPT_STATIC(&cmdstr, approx, "~");
3306 redis_cmd_append_sstr_long(&cmdstr, maxlen);
3307 }
3308
3309 /* Now append ID and field(s) */
3310 redis_cmd_append_sstr(&cmdstr, id, idlen);
3311 ZEND_HASH_FOREACH_KEY_VAL(ht_fields, idx, arrkey, value) {
3312 redis_cmd_append_sstr_arrkey(&cmdstr, arrkey, idx);
3313 redis_cmd_append_sstr_zval(&cmdstr, value, redis_sock);
3314 } ZEND_HASH_FOREACH_END();
3315
3316 *cmd = cmdstr.c;
3317 *cmd_len = cmdstr.len;
3318 return SUCCESS;
3319 }
3320
3321 // XPENDING key group [start end count] [consumer]
redis_xpending_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3322 int redis_xpending_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3323 char **cmd, int *cmd_len, short *slot, void **ctx)
3324 {
3325 smart_string cmdstr = {0};
3326 char *key, *group, *start = NULL, *end = NULL, *consumer = NULL;
3327 size_t keylen, grouplen, startlen, endlen, consumerlen;
3328 int argc;
3329 zend_long count = -1;
3330
3331 // XPENDING mystream group55 - + 10 consumer-123
3332 if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss|ssls", &key,
3333 &keylen, &group, &grouplen, &start, &startlen,
3334 &end, &endlen, &count, &consumer, &consumerlen)
3335 == FAILURE)
3336 {
3337 return FAILURE;
3338 }
3339
3340 /* If we've been passed a start argument, we also need end and count */
3341 if (start != NULL && (end == NULL || count < 0)) {
3342 return FAILURE;
3343 }
3344
3345 /* Calculate argc. It's either 2, 5, or 6 */
3346 argc = 2 + (start != NULL ? 3 + (consumer != NULL) : 0);
3347
3348 /* Construct command and add required arguments */
3349 REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, argc, "XPENDING");
3350 redis_cmd_append_sstr_key(&cmdstr, key, keylen, redis_sock, slot);
3351 redis_cmd_append_sstr(&cmdstr, group, grouplen);
3352
3353 /* Add optional argumentst */
3354 if (start) {
3355 redis_cmd_append_sstr(&cmdstr, start, startlen);
3356 redis_cmd_append_sstr(&cmdstr, end, endlen);
3357 redis_cmd_append_sstr_long(&cmdstr, (long)count);
3358
3359 /* Finally add consumer if we have it */
3360 if (consumer) redis_cmd_append_sstr(&cmdstr, consumer, consumerlen);
3361 }
3362
3363 *cmd = cmdstr.c;
3364 *cmd_len = cmdstr.len;
3365 return SUCCESS;
3366 }
3367
3368 /* X[REV]RANGE key start end [COUNT count] */
redis_xrange_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)3369 int redis_xrange_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3370 char *kw, char **cmd, int *cmd_len, short *slot,
3371 void **ctx)
3372 {
3373 smart_string cmdstr = {0};
3374 char *key, *start, *end;
3375 size_t keylen, startlen, endlen;
3376 zend_long count = -1;
3377
3378 if (zend_parse_parameters(ZEND_NUM_ARGS(), "sss|l", &key, &keylen,
3379 &start, &startlen, &end, &endlen, &count)
3380 == FAILURE)
3381 {
3382 return FAILURE;
3383 }
3384
3385 redis_cmd_init_sstr(&cmdstr, 3 + (2 * (count > -1)), kw, strlen(kw));
3386 redis_cmd_append_sstr_key(&cmdstr, key, keylen, redis_sock, slot);
3387 redis_cmd_append_sstr(&cmdstr, start, startlen);
3388 redis_cmd_append_sstr(&cmdstr, end, endlen);
3389
3390 if (count > -1) {
3391 redis_cmd_append_sstr(&cmdstr, "COUNT", sizeof("COUNT")-1);
3392 redis_cmd_append_sstr_long(&cmdstr, count);
3393 }
3394
3395 *cmd = cmdstr.c;
3396 *cmd_len = cmdstr.len;
3397 return SUCCESS;
3398 }
3399
3400 /* Helper function to take an associative array and append the Redis
3401 * STREAMS stream [stream...] id [id ...] arguments to a command string. */
3402 static int
append_stream_args(smart_string * cmdstr,HashTable * ht,RedisSock * redis_sock,short * slot)3403 append_stream_args(smart_string *cmdstr, HashTable *ht, RedisSock *redis_sock,
3404 short *slot)
3405 {
3406 char *kptr, kbuf[40];
3407 int klen, i, pos = 0;
3408 zend_string *key, *idstr;
3409 short oldslot;
3410 zval **id;
3411 zend_ulong idx;
3412
3413 /* Append STREAM qualifier */
3414 REDIS_CMD_APPEND_SSTR_STATIC(cmdstr, "STREAMS");
3415
3416 /* Sentinel slot */
3417 if (slot) oldslot = -1;
3418
3419 /* Allocate memory to keep IDs */
3420 id = emalloc(sizeof(*id) * zend_hash_num_elements(ht));
3421
3422 /* Iterate over our stream => id array appending streams and retaining each
3423 * value for final arguments */
3424 ZEND_HASH_FOREACH_KEY_VAL(ht, idx, key, id[pos++]) {
3425 if (key) {
3426 klen = ZSTR_LEN(key);
3427 kptr = ZSTR_VAL(key);
3428 } else {
3429 klen = snprintf(kbuf, sizeof(kbuf), "%ld", (long)idx);
3430 kptr = (char*)kbuf;
3431 }
3432
3433 /* Append stream key */
3434 redis_cmd_append_sstr_key(cmdstr, kptr, klen, redis_sock, slot);
3435
3436 /* Protect the user against CROSSSLOT to avoid confusion */
3437 if (slot) {
3438 if (oldslot != -1 && *slot != oldslot) {
3439 php_error_docref(NULL, E_WARNING,
3440 "Warning, not all keys hash to the same slot!");
3441 efree(id);
3442 return FAILURE;
3443 }
3444 oldslot = *slot;
3445 }
3446 } ZEND_HASH_FOREACH_END();
3447
3448 /* Add our IDs */
3449 for (i = 0; i < pos; i++) {
3450 idstr = zval_get_string(id[i]);
3451 redis_cmd_append_sstr(cmdstr, ZSTR_VAL(idstr), ZSTR_LEN(idstr));
3452 zend_string_release(idstr);
3453 }
3454
3455 /* Clean up ID container array */
3456 efree(id);
3457
3458 return 0;
3459 }
3460
3461 /* XREAD [COUNT count] [BLOCK ms] STREAMS key [key ...] id [id ...] */
redis_xread_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3462 int redis_xread_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3463 char **cmd, int *cmd_len, short *slot, void **ctx)
3464 {
3465 smart_string cmdstr = {0};
3466 zend_long count = -1, block = -1;
3467 zval *z_streams;
3468 int argc, scount;
3469 HashTable *kt;
3470
3471 if (zend_parse_parameters(ZEND_NUM_ARGS(), "a|ll", &z_streams,
3472 &count, &block) == FAILURE)
3473 {
3474 return FAILURE;
3475 }
3476
3477 /* At least one stream and ID is required */
3478 kt = Z_ARRVAL_P(z_streams);
3479 if ((scount = zend_hash_num_elements(kt)) < 1) {
3480 return FAILURE;
3481 }
3482
3483 /* Calculate argc and start constructing command */
3484 argc = 1 + (2 * scount) + (2 * (count > -1)) + (2 * (block > -1));
3485 REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, argc, "XREAD");
3486
3487 /* Append COUNT if we have it */
3488 if (count > -1) {
3489 REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "COUNT");
3490 redis_cmd_append_sstr_long(&cmdstr, count);
3491 }
3492
3493 /* Append BLOCK if we have it */
3494 if (block > -1) {
3495 REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "BLOCK");
3496 redis_cmd_append_sstr_long(&cmdstr, block);
3497 }
3498
3499 /* Append final STREAM key [key ...] id [id ...] arguments */
3500 if (append_stream_args(&cmdstr, kt, redis_sock, slot) < 0) {
3501 efree(cmdstr.c);
3502 return FAILURE;
3503 }
3504
3505 *cmd = cmdstr.c;
3506 *cmd_len = cmdstr.len;
3507 return SUCCESS;
3508 }
3509
3510 /* XREADGROUP GROUP group consumer [COUNT count] [BLOCK ms]
3511 * STREAMS key [key ...] id [id ...] */
redis_xreadgroup_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3512 int redis_xreadgroup_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3513 char **cmd, int *cmd_len, short *slot, void **ctx)
3514 {
3515 smart_string cmdstr = {0};
3516 zval *z_streams;
3517 HashTable *kt;
3518 char *group, *consumer;
3519 size_t grouplen, consumerlen;
3520 int scount, argc;
3521 zend_long count, block;
3522 zend_bool no_count = 1, no_block = 1;
3523
3524 if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssa|l!l!", &group,
3525 &grouplen, &consumer, &consumerlen, &z_streams,
3526 &count, &no_count, &block, &no_block) == FAILURE)
3527 {
3528 return FAILURE;
3529 }
3530
3531 /* Negative COUNT or BLOCK is illegal so abort immediately */
3532 if ((!no_count && count < 0) || (!no_block && block < 0)) {
3533 php_error_docref(NULL, E_WARNING, "Negative values for COUNT or BLOCK are illegal.");
3534 return FAILURE;
3535 }
3536
3537 /* Redis requires at least one stream */
3538 kt = Z_ARRVAL_P(z_streams);
3539 if ((scount = zend_hash_num_elements(kt)) < 1) {
3540 return FAILURE;
3541 }
3542
3543 /* Calculate argc and start constructing commands */
3544 argc = 4 + (2 * scount) + (2 * !no_count) + (2 * !no_block);
3545 REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, argc, "XREADGROUP");
3546
3547 /* Group and consumer */
3548 REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "GROUP");
3549 redis_cmd_append_sstr(&cmdstr, group, grouplen);
3550 redis_cmd_append_sstr(&cmdstr, consumer, consumerlen);
3551
3552 /* Append COUNT if we have it */
3553 if (!no_count) {
3554 REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "COUNT");
3555 redis_cmd_append_sstr_long(&cmdstr, count);
3556 }
3557
3558 /* Append BLOCK argument if we have it */
3559 if (!no_block) {
3560 REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "BLOCK");
3561 redis_cmd_append_sstr_long(&cmdstr, block);
3562 }
3563
3564 /* Finally append stream and id args */
3565 if (append_stream_args(&cmdstr, kt, redis_sock, slot) < 0) {
3566 efree(cmdstr.c);
3567 return FAILURE;
3568 }
3569
3570 *cmd = cmdstr.c;
3571 *cmd_len = cmdstr.len;
3572 return SUCCESS;
3573 }
3574
3575 /* XACK key group id [id ...] */
redis_xack_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3576 int redis_xack_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3577 char **cmd, int *cmd_len, short *slot, void **ctx)
3578 {
3579 smart_string cmdstr = {0};
3580 char *key, *group;
3581 size_t keylen, grouplen;
3582 zend_string *idstr;
3583 zval *z_ids, *z_id;
3584 HashTable *ht_ids;
3585 int idcount;
3586
3587 if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssa", &key, &keylen,
3588 &group, &grouplen, &z_ids) == FAILURE)
3589 {
3590 return FAILURE;
3591 }
3592
3593 ht_ids = Z_ARRVAL_P(z_ids);
3594 if ((idcount = zend_hash_num_elements(ht_ids)) < 1) {
3595 return FAILURE;
3596 }
3597
3598 /* Create command and add initial arguments */
3599 REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, 2 + idcount, "XACK");
3600 redis_cmd_append_sstr_key(&cmdstr, key, keylen, redis_sock, slot);
3601 redis_cmd_append_sstr(&cmdstr, group, grouplen);
3602
3603 /* Append IDs */
3604 ZEND_HASH_FOREACH_VAL(ht_ids, z_id) {
3605 idstr = zval_get_string(z_id);
3606 redis_cmd_append_sstr(&cmdstr, ZSTR_VAL(idstr), ZSTR_LEN(idstr));
3607 zend_string_release(idstr);
3608 } ZEND_HASH_FOREACH_END();
3609
3610 *cmd = cmdstr.c;
3611 *cmd_len = cmdstr.len;
3612 return SUCCESS;
3613 }
3614
3615 /* XCLAIM options container */
3616 typedef struct xclaimOptions {
3617 struct {
3618 char *type;
3619 int64_t time;
3620 } idle;
3621 zend_long retrycount;
3622 int force;
3623 int justid;
3624 } xclaimOptions;
3625
3626 /* Attempt to extract an int64_t from the provided zval */
zval_get_i64(zval * zv,int64_t * retval)3627 static int zval_get_i64(zval *zv, int64_t *retval) {
3628 if (Z_TYPE_P(zv) == IS_LONG) {
3629 *retval = (int64_t)Z_LVAL_P(zv);
3630 return SUCCESS;
3631 } else if (Z_TYPE_P(zv) == IS_DOUBLE) {
3632 *retval = (int64_t)Z_DVAL_P(zv);
3633 return SUCCESS;
3634 } else if (Z_TYPE_P(zv) == IS_STRING) {
3635 zend_long lval;
3636 double dval;
3637
3638 switch (is_numeric_string(Z_STRVAL_P(zv), Z_STRLEN_P(zv), &lval, &dval, 1)) {
3639 case IS_LONG:
3640 *retval = (int64_t)lval;
3641 return SUCCESS;
3642 case IS_DOUBLE:
3643 *retval = (int64_t)dval;
3644 return SUCCESS;
3645 }
3646 }
3647
3648 /* If we make it here we have failed */
3649 return FAILURE;
3650 }
3651
3652 /* Helper function to get an integer XCLAIM argument. This can overflow a
3653 * 32-bit PHP long so we have to extract it as an int64_t. If the value is
3654 * not a valid number or negative, we'll inform the user of the problem and
3655 * that the argument is being ignored. */
get_xclaim_i64_arg(const char * key,zval * zv)3656 static int64_t get_xclaim_i64_arg(const char *key, zval *zv) {
3657 int64_t retval = -1;
3658
3659 /* Extract an i64, and if we can't let the user know there is an issue. */
3660 if (zval_get_i64(zv, &retval) == FAILURE || retval < 0) {
3661 php_error_docref(NULL, E_WARNING,
3662 "Invalid XCLAIM option '%s' will be ignored", key);
3663 }
3664
3665 return retval;
3666 }
3667
3668 /* Helper to extract XCLAIM options */
get_xclaim_options(zval * z_arr,xclaimOptions * opt)3669 static void get_xclaim_options(zval *z_arr, xclaimOptions *opt) {
3670 HashTable *ht;
3671 zend_string *zkey;
3672 char *kval;
3673 size_t klen;
3674 zval *zv;
3675
3676 /* Initialize options array to sane defaults */
3677 memset(opt, 0, sizeof(*opt));
3678 opt->retrycount = -1;
3679 opt->idle.time = -1;
3680
3681 /* Early return if we don't have any options */
3682 if (z_arr == NULL)
3683 return;
3684
3685 /* Iterate over our options array */
3686 ht = Z_ARRVAL_P(z_arr);
3687 ZEND_HASH_FOREACH_STR_KEY_VAL(ht, zkey, zv) {
3688 if (zkey) {
3689 kval = ZSTR_VAL(zkey);
3690 klen = ZSTR_LEN(zkey);
3691
3692 if (klen == 4) {
3693 if (!strncasecmp(kval, "TIME", 4)) {
3694 opt->idle.type = "TIME";
3695 opt->idle.time = get_xclaim_i64_arg("TIME", zv);
3696 } else if (!strncasecmp(kval, "IDLE", 4)) {
3697 opt->idle.type = "IDLE";
3698 opt->idle.time = get_xclaim_i64_arg("IDLE", zv);
3699 }
3700 } else if (klen == 10 && !strncasecmp(kval, "RETRYCOUNT", 10)) {
3701 opt->retrycount = zval_get_long(zv);
3702 }
3703 } else {
3704 if (Z_TYPE_P(zv) == IS_STRING) {
3705 kval = Z_STRVAL_P(zv);
3706 klen = Z_STRLEN_P(zv);
3707 if (klen == 5 && !strncasecmp(kval, "FORCE", 5)) {
3708 opt->force = 1;
3709 } else if (klen == 6 && !strncasecmp(kval, "JUSTID", 6)) {
3710 opt->justid = 1;
3711 }
3712 }
3713 }
3714 } ZEND_HASH_FOREACH_END();
3715 }
3716
3717 /* Count argc for any options we may have */
xclaim_options_argc(xclaimOptions * opt)3718 static int xclaim_options_argc(xclaimOptions *opt) {
3719 int argc = 0;
3720
3721 if (opt->idle.type != NULL && opt->idle.time != -1)
3722 argc += 2;
3723 if (opt->retrycount != -1)
3724 argc += 2;
3725 if (opt->force)
3726 argc++;
3727 if (opt->justid)
3728 argc++;
3729
3730 return argc;
3731 }
3732
3733 /* Append XCLAIM options */
append_xclaim_options(smart_string * cmd,xclaimOptions * opt)3734 static void append_xclaim_options(smart_string *cmd, xclaimOptions *opt) {
3735 /* IDLE/TIME long */
3736 if (opt->idle.type != NULL && opt->idle.time != -1) {
3737 redis_cmd_append_sstr(cmd, opt->idle.type, strlen(opt->idle.type));
3738 redis_cmd_append_sstr_i64(cmd, opt->idle.time);
3739 }
3740
3741 /* RETRYCOUNT */
3742 if (opt->retrycount != -1) {
3743 REDIS_CMD_APPEND_SSTR_STATIC(cmd, "RETRYCOUNT");
3744 redis_cmd_append_sstr_long(cmd, opt->retrycount);
3745 }
3746
3747 /* FORCE and JUSTID */
3748 if (opt->force)
3749 REDIS_CMD_APPEND_SSTR_STATIC(cmd, "FORCE");
3750 if (opt->justid)
3751 REDIS_CMD_APPEND_SSTR_STATIC(cmd, "JUSTID");
3752 }
3753
3754 /* XCLAIM <key> <group> <consumer> <min-idle-time> <ID-1> <ID-2>
3755 [IDLE <milliseconds>] [TIME <mstime>] [RETRYCOUNT <count>]
3756 [FORCE] [JUSTID] */
redis_xclaim_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3757 int redis_xclaim_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3758 char **cmd, int *cmd_len, short *slot, void **ctx)
3759 {
3760 smart_string cmdstr = {0};
3761 char *key, *group, *consumer;
3762 size_t keylen, grouplen, consumerlen;
3763 zend_long min_idle;
3764 int argc, id_count;
3765 zval *z_ids, *z_id, *z_opts = NULL;
3766 zend_string *zstr;
3767 HashTable *ht_ids;
3768 xclaimOptions opts;
3769
3770 if (zend_parse_parameters(ZEND_NUM_ARGS(), "sssla|a", &key, &keylen,
3771 &group, &grouplen, &consumer, &consumerlen, &min_idle,
3772 &z_ids, &z_opts) == FAILURE)
3773 {
3774 return FAILURE;
3775 }
3776
3777 /* At least one id is required */
3778 ht_ids = Z_ARRVAL_P(z_ids);
3779 if ((id_count = zend_hash_num_elements(ht_ids)) < 1) {
3780 return FAILURE;
3781 }
3782
3783 /* Extract options array if we've got them */
3784 get_xclaim_options(z_opts, &opts);
3785
3786 /* Now we have enough information to calculate argc */
3787 argc = 4 + id_count + xclaim_options_argc(&opts);
3788
3789 /* Start constructing our command */
3790 REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, argc, "XCLAIM");
3791 redis_cmd_append_sstr_key(&cmdstr, key, keylen, redis_sock, slot);
3792 redis_cmd_append_sstr(&cmdstr, group, grouplen);
3793 redis_cmd_append_sstr(&cmdstr, consumer, consumerlen);
3794 redis_cmd_append_sstr_long(&cmdstr, min_idle);
3795
3796 /* Add IDs */
3797 ZEND_HASH_FOREACH_VAL(ht_ids, z_id) {
3798 zstr = zval_get_string(z_id);
3799 redis_cmd_append_sstr(&cmdstr, ZSTR_VAL(zstr), ZSTR_LEN(zstr));
3800 zend_string_release(zstr);
3801 } ZEND_HASH_FOREACH_END();
3802
3803 /* Finally add our options */
3804 append_xclaim_options(&cmdstr, &opts);
3805
3806 /* Success */
3807 *cmd = cmdstr.c;
3808 *cmd_len = cmdstr.len;
3809 return SUCCESS;
3810 }
3811
3812 /* XGROUP HELP
3813 * XGROUP CREATE key groupname id [MKSTREAM]
3814 * XGROUP SETID key group id
3815 * XGROUP DESTROY key groupname
3816 * XGROUP DELCONSUMER key groupname consumername */
redis_xgroup_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3817 int redis_xgroup_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3818 char **cmd, int *cmd_len, short *slot, void **ctx)
3819 {
3820 char *op, *key = NULL, *arg1 = NULL, *arg2 = NULL;
3821 size_t oplen, keylen, arg1len, arg2len;
3822 zend_bool mkstream = 0;
3823 int argc = ZEND_NUM_ARGS();
3824
3825 if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|sssb", &op, &oplen,
3826 &key, &keylen, &arg1, &arg1len, &arg2, &arg2len,
3827 &mkstream) == FAILURE)
3828 {
3829 return FAILURE;
3830 }
3831
3832 if (argc == 1 && oplen == 4 && !strncasecmp(op, "HELP", 4)) {
3833 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "XGROUP", "s", "HELP", 4);
3834 return SUCCESS;
3835 } else if (argc >= 4 && (oplen == 6 && !strncasecmp(op, "CREATE", 6))) {
3836 if (mkstream) {
3837 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "XGROUP", "sksss", op, oplen, key, keylen,
3838 arg1, arg1len, arg2, arg2len, "MKSTREAM",
3839 sizeof("MKSTREAM") - 1);
3840 } else {
3841 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "XGROUP", "skss", op, oplen, key, keylen,
3842 arg1, arg1len, arg2, arg2len);
3843 }
3844 return SUCCESS;
3845 } else if (argc == 4 && ((oplen == 5 && !strncasecmp(op, "SETID", 5)) ||
3846 (oplen == 11 && !strncasecmp(op, "DELCONSUMER", 11))))
3847 {
3848 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "XGROUP", "skss", op, oplen, key, keylen,
3849 arg1, arg1len, arg2, arg2len);
3850 return SUCCESS;
3851 } else if (argc == 3 && ((oplen == 7 && !strncasecmp(op, "DESTROY", 7)))) {
3852 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "XGROUP", "sks", op, oplen, key,
3853 keylen, arg1, arg1len);
3854 return SUCCESS;
3855 }
3856
3857 /* Didn't detect any valid XGROUP command pattern */
3858 return FAILURE;
3859 }
3860
3861 /* XINFO CONSUMERS key group
3862 * XINFO GROUPS key
3863 * XINFO STREAM key [FULL [COUNT N]]
3864 * XINFO HELP */
redis_xinfo_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3865 int redis_xinfo_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3866 char **cmd, int *cmd_len, short *slot, void **ctx)
3867 {
3868 char *op, *key, *arg = NULL;
3869 size_t oplen, keylen, arglen;
3870 zend_long count = -1;
3871 int argc = ZEND_NUM_ARGS();
3872 char fmt[] = "skssl";
3873
3874 if (argc > 4 || zend_parse_parameters(ZEND_NUM_ARGS(), "s|ssl",
3875 &op, &oplen, &key, &keylen, &arg,
3876 &arglen, &count) == FAILURE)
3877 {
3878 return FAILURE;
3879 }
3880
3881 /* Handle everything except XINFO STREAM */
3882 if (strncasecmp(op, "STREAM", 6) != 0) {
3883 fmt[argc] = '\0';
3884 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "XINFO", fmt, op, oplen, key, keylen,
3885 arg, arglen);
3886 return SUCCESS;
3887 }
3888
3889 /* 'FULL' is the only legal option to XINFO STREAM */
3890 if (argc > 2 && strncasecmp(arg, "FULL", 4) != 0) {
3891 php_error_docref(NULL, E_WARNING, "'%s' is not a valid option for XINFO STREAM", arg);
3892 return FAILURE;
3893 }
3894
3895 /* If we have a COUNT bump the argument count to account for the 'COUNT' literal */
3896 if (argc == 4) argc++;
3897
3898 fmt[argc] = '\0';
3899
3900 /* Build our XINFO STREAM variant */
3901 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "XINFO", fmt, "STREAM", 6, key, keylen,
3902 "FULL", 4, "COUNT", 5, count);
3903
3904 return SUCCESS;
3905 }
3906
3907 /* XTRIM MAXLEN [~] count */
redis_xtrim_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3908 int redis_xtrim_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3909 char **cmd, int *cmd_len, short *slot, void **ctx)
3910 {
3911 char *key;
3912 size_t keylen;
3913 zend_long maxlen;
3914 zend_bool approx = 0;
3915
3916 if (zend_parse_parameters(ZEND_NUM_ARGS(), "sl|b", &key, &keylen,
3917 &maxlen, &approx) == FAILURE)
3918 {
3919 return FAILURE;
3920 }
3921
3922 if (approx) {
3923 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "XTRIM", "kssl", key, keylen,
3924 "MAXLEN", 6, "~", 1, maxlen);
3925 } else {
3926 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "XTRIM", "ksl", key, keylen,
3927 "MAXLEN", 6, maxlen);
3928 }
3929
3930 return SUCCESS;
3931 }
3932
3933 int
redis_sentinel_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)3934 redis_sentinel_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3935 char *kw, char **cmd, int *cmd_len, short *slot, void **ctx)
3936 {
3937 if (zend_parse_parameters_none() == FAILURE) {
3938
3939 return FAILURE;
3940 }
3941 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "SENTINEL", "s", kw, strlen(kw));
3942 return SUCCESS;
3943 }
3944
3945 int
redis_sentinel_str_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)3946 redis_sentinel_str_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3947 char *kw, char **cmd, int *cmd_len, short *slot, void **ctx)
3948 {
3949 zend_string *name;
3950
3951 if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &name) == FAILURE) {
3952 return FAILURE;
3953 }
3954 *cmd_len = REDIS_CMD_SPPRINTF(cmd, "SENTINEL", "sS", kw, strlen(kw), name);
3955 return SUCCESS;
3956 }
3957
3958 /*
3959 * Redis commands that don't deal with the server at all. The RedisSock*
3960 * pointer is the only thing retrieved differently, so we just take that
3961 * in addition to the standard INTERNAL_FUNCTION_PARAMETERS for arg parsing,
3962 * return value handling, and thread safety. */
3963
redis_getoption_handler(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,redisCluster * c)3964 void redis_getoption_handler(INTERNAL_FUNCTION_PARAMETERS,
3965 RedisSock *redis_sock, redisCluster *c)
3966 {
3967 zend_long option;
3968
3969 if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &option)
3970 == FAILURE)
3971 {
3972 RETURN_FALSE;
3973 }
3974
3975 // Return the requested option
3976 switch(option) {
3977 case REDIS_OPT_SERIALIZER:
3978 RETURN_LONG(redis_sock->serializer);
3979 case REDIS_OPT_COMPRESSION:
3980 RETURN_LONG(redis_sock->compression);
3981 case REDIS_OPT_COMPRESSION_LEVEL:
3982 RETURN_LONG(redis_sock->compression_level);
3983 case REDIS_OPT_PREFIX:
3984 if (redis_sock->prefix) {
3985 RETURN_STRINGL(ZSTR_VAL(redis_sock->prefix), ZSTR_LEN(redis_sock->prefix));
3986 }
3987 RETURN_NULL();
3988 case REDIS_OPT_READ_TIMEOUT:
3989 RETURN_DOUBLE(redis_sock->read_timeout);
3990 case REDIS_OPT_TCP_KEEPALIVE:
3991 RETURN_LONG(redis_sock->tcp_keepalive);
3992 case REDIS_OPT_SCAN:
3993 RETURN_LONG(redis_sock->scan);
3994 case REDIS_OPT_REPLY_LITERAL:
3995 RETURN_LONG(redis_sock->reply_literal);
3996 case REDIS_OPT_NULL_MBULK_AS_NULL:
3997 RETURN_LONG(redis_sock->null_mbulk_as_null);
3998 case REDIS_OPT_FAILOVER:
3999 RETURN_LONG(c->failover);
4000 case REDIS_OPT_MAX_RETRIES:
4001 RETURN_LONG(redis_sock->max_retries);
4002 case REDIS_OPT_BACKOFF_ALGORITHM:
4003 RETURN_LONG(redis_sock->backoff.algorithm);
4004 case REDIS_OPT_BACKOFF_BASE:
4005 RETURN_LONG(redis_sock->backoff.base / 1000);
4006 case REDIS_OPT_BACKOFF_CAP:
4007 RETURN_LONG(redis_sock->backoff.cap / 1000);
4008 default:
4009 RETURN_FALSE;
4010 }
4011 }
4012
redis_setoption_handler(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,redisCluster * c)4013 void redis_setoption_handler(INTERNAL_FUNCTION_PARAMETERS,
4014 RedisSock *redis_sock, redisCluster *c)
4015 {
4016 zend_long val_long, option;
4017 zval *val;
4018 zend_string *val_str;
4019 struct timeval read_tv;
4020 int tcp_keepalive = 0;
4021 php_netstream_data_t *sock;
4022
4023 if (zend_parse_parameters(ZEND_NUM_ARGS(), "lz", &option,
4024 &val) == FAILURE)
4025 {
4026 RETURN_FALSE;
4027 }
4028
4029 switch(option) {
4030 case REDIS_OPT_SERIALIZER:
4031 val_long = zval_get_long(val);
4032 if (val_long == REDIS_SERIALIZER_NONE
4033 || val_long == REDIS_SERIALIZER_PHP
4034 || val_long == REDIS_SERIALIZER_JSON
4035 #ifdef HAVE_REDIS_IGBINARY
4036 || val_long == REDIS_SERIALIZER_IGBINARY
4037 #endif
4038 #ifdef HAVE_REDIS_MSGPACK
4039 || val_long == REDIS_SERIALIZER_MSGPACK
4040 #endif
4041 ) {
4042 redis_sock->serializer = val_long;
4043 RETURN_TRUE;
4044 }
4045 break;
4046 case REDIS_OPT_REPLY_LITERAL:
4047 val_long = zval_get_long(val);
4048 redis_sock->reply_literal = val_long != 0;
4049 RETURN_TRUE;
4050 case REDIS_OPT_NULL_MBULK_AS_NULL:
4051 val_long = zval_get_long(val);
4052 redis_sock->null_mbulk_as_null = val_long != 0;
4053 RETURN_TRUE;
4054 case REDIS_OPT_COMPRESSION:
4055 val_long = zval_get_long(val);
4056 if (val_long == REDIS_COMPRESSION_NONE
4057 #ifdef HAVE_REDIS_LZF
4058 || val_long == REDIS_COMPRESSION_LZF
4059 #endif
4060 #ifdef HAVE_REDIS_ZSTD
4061 || val_long == REDIS_COMPRESSION_ZSTD
4062 #endif
4063 #ifdef HAVE_REDIS_LZ4
4064 || val_long == REDIS_COMPRESSION_LZ4
4065 #endif
4066 ) {
4067 redis_sock->compression = val_long;
4068 RETURN_TRUE;
4069 }
4070 break;
4071 case REDIS_OPT_COMPRESSION_LEVEL:
4072 val_long = zval_get_long(val);
4073 redis_sock->compression_level = val_long;
4074 RETURN_TRUE;
4075 case REDIS_OPT_PREFIX:
4076 if (redis_sock->prefix) {
4077 zend_string_release(redis_sock->prefix);
4078 redis_sock->prefix = NULL;
4079 }
4080 val_str = zval_get_string(val);
4081 if (ZSTR_LEN(val_str) > 0) {
4082 redis_sock->prefix = val_str;
4083 } else {
4084 zend_string_release(val_str);
4085 }
4086 RETURN_TRUE;
4087 case REDIS_OPT_READ_TIMEOUT:
4088 redis_sock->read_timeout = zval_get_double(val);
4089 if (redis_sock->stream) {
4090 read_tv.tv_sec = (time_t)redis_sock->read_timeout;
4091 read_tv.tv_usec = (int)((redis_sock->read_timeout -
4092 read_tv.tv_sec) * 1000000);
4093 php_stream_set_option(redis_sock->stream,
4094 PHP_STREAM_OPTION_READ_TIMEOUT, 0,
4095 &read_tv);
4096 }
4097 RETURN_TRUE;
4098 case REDIS_OPT_TCP_KEEPALIVE:
4099
4100 /* Don't set TCP_KEEPALIVE if we're using a unix socket. */
4101 if (ZSTR_VAL(redis_sock->host)[0] == '/' && redis_sock->port < 1) {
4102 RETURN_FALSE;
4103 }
4104 tcp_keepalive = zval_get_long(val) > 0 ? 1 : 0;
4105 if (redis_sock->tcp_keepalive == tcp_keepalive) {
4106 RETURN_TRUE;
4107 }
4108 if (redis_sock->stream) {
4109 /* set TCP_KEEPALIVE */
4110 sock = (php_netstream_data_t*)redis_sock->stream->abstract;
4111 if (setsockopt(sock->socket, SOL_SOCKET, SO_KEEPALIVE, (char*)&tcp_keepalive,
4112 sizeof(tcp_keepalive)) == -1) {
4113 RETURN_FALSE;
4114 }
4115 redis_sock->tcp_keepalive = tcp_keepalive;
4116 }
4117 RETURN_TRUE;
4118 case REDIS_OPT_SCAN:
4119 val_long = zval_get_long(val);
4120 if (val_long == REDIS_SCAN_NORETRY) {
4121 redis_sock->scan &= ~REDIS_SCAN_RETRY;
4122 } else if (val_long == REDIS_SCAN_NOPREFIX) {
4123 redis_sock->scan &= ~REDIS_SCAN_PREFIX;
4124 } else if (val_long == REDIS_SCAN_RETRY || val_long == REDIS_SCAN_PREFIX) {
4125 redis_sock->scan |= val_long;
4126 } else {
4127 break;
4128 }
4129 RETURN_TRUE;
4130 case REDIS_OPT_FAILOVER:
4131 if (c == NULL) RETURN_FALSE;
4132 val_long = zval_get_long(val);
4133 if (val_long == REDIS_FAILOVER_NONE ||
4134 val_long == REDIS_FAILOVER_ERROR ||
4135 val_long == REDIS_FAILOVER_DISTRIBUTE ||
4136 val_long == REDIS_FAILOVER_DISTRIBUTE_SLAVES)
4137 {
4138 c->failover = val_long;
4139 RETURN_TRUE;
4140 }
4141 break;
4142 case REDIS_OPT_MAX_RETRIES:
4143 val_long = zval_get_long(val);
4144 if(val_long >= 0) {
4145 redis_sock->max_retries = val_long;
4146 RETURN_TRUE;
4147 }
4148 break;
4149 case REDIS_OPT_BACKOFF_ALGORITHM:
4150 val_long = zval_get_long(val);
4151 if(val_long >= 0 &&
4152 val_long < REDIS_BACKOFF_ALGORITHMS) {
4153 redis_sock->backoff.algorithm = val_long;
4154 RETURN_TRUE;
4155 }
4156 break;
4157 case REDIS_OPT_BACKOFF_BASE:
4158 val_long = zval_get_long(val);
4159 if(val_long >= 0) {
4160 redis_sock->backoff.base = val_long * 1000;
4161 RETURN_TRUE;
4162 }
4163 break;
4164 case REDIS_OPT_BACKOFF_CAP:
4165 val_long = zval_get_long(val);
4166 if(val_long >= 0) {
4167 redis_sock->backoff.cap = val_long * 1000;
4168 RETURN_TRUE;
4169 }
4170 break;
4171 EMPTY_SWITCH_DEFAULT_CASE()
4172 }
4173 RETURN_FALSE;
4174 }
4175
redis_prefix_handler(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock)4176 void redis_prefix_handler(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock) {
4177 char *key;
4178 size_t key_len;
4179
4180 if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &key, &key_len)
4181 ==FAILURE)
4182 {
4183 RETURN_FALSE;
4184 }
4185
4186 if (redis_sock->prefix) {
4187 int keyfree = redis_key_prefix(redis_sock, &key, &key_len);
4188 RETVAL_STRINGL(key, key_len);
4189 if (keyfree) efree(key);
4190 } else {
4191 RETURN_STRINGL(key, key_len);
4192 }
4193 }
4194
redis_serialize_handler(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock)4195 void redis_serialize_handler(INTERNAL_FUNCTION_PARAMETERS,
4196 RedisSock *redis_sock)
4197 {
4198 zval *z_val;
4199 char *val;
4200 size_t val_len;
4201
4202 if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &z_val) == FAILURE) {
4203 RETURN_FALSE;
4204 }
4205
4206 int val_free = redis_serialize(redis_sock, z_val, &val, &val_len);
4207
4208 RETVAL_STRINGL(val, val_len);
4209 if (val_free) efree(val);
4210 }
4211
redis_unserialize_handler(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,zend_class_entry * ex)4212 void redis_unserialize_handler(INTERNAL_FUNCTION_PARAMETERS,
4213 RedisSock *redis_sock, zend_class_entry *ex)
4214 {
4215 char *value;
4216 size_t value_len;
4217
4218 // Parse our arguments
4219 if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &value, &value_len)
4220 == FAILURE)
4221 {
4222 RETURN_FALSE;
4223 }
4224
4225 // We only need to attempt unserialization if we have a serializer running
4226 if (redis_sock->serializer == REDIS_SERIALIZER_NONE) {
4227 // Just return the value that was passed to us
4228 RETURN_STRINGL(value, value_len);
4229 }
4230
4231 zval z_ret;
4232 if (!redis_unserialize(redis_sock, value, value_len, &z_ret)) {
4233 // Badly formed input, throw an exception
4234 zend_throw_exception(ex, "Invalid serialized data, or unserialization error", 0);
4235 RETURN_FALSE;
4236 }
4237 RETURN_ZVAL(&z_ret, 0, 0);
4238 }
4239
redis_compress_handler(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock)4240 void redis_compress_handler(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock) {
4241 zend_string *zstr;
4242 size_t len;
4243 char *buf;
4244 int cmp_free;
4245
4246 if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &zstr) == FAILURE) {
4247 RETURN_FALSE;
4248 }
4249
4250 cmp_free = redis_compress(redis_sock, &buf, &len, ZSTR_VAL(zstr), ZSTR_LEN(zstr));
4251 RETVAL_STRINGL(buf, len);
4252 if (cmp_free) efree(buf);
4253 }
4254
redis_uncompress_handler(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,zend_class_entry * ex)4255 void redis_uncompress_handler(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
4256 zend_class_entry *ex)
4257 {
4258 zend_string *zstr;
4259 size_t len;
4260 char *buf;
4261
4262 if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &zstr) == FAILURE) {
4263 RETURN_FALSE;
4264 } else if (ZSTR_LEN(zstr) == 0 || redis_sock->compression == REDIS_COMPRESSION_NONE) {
4265 RETURN_STR_COPY(zstr);
4266 }
4267
4268 if (!redis_uncompress(redis_sock, &buf, &len, ZSTR_VAL(zstr), ZSTR_LEN(zstr))) {
4269 zend_throw_exception(ex, "Invalid compressed data or uncompression error", 0);
4270 RETURN_FALSE;
4271 }
4272
4273 RETVAL_STRINGL(buf, len);
4274 efree(buf);
4275 }
4276
redis_pack_handler(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock)4277 void redis_pack_handler(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock) {
4278 int valfree;
4279 size_t len;
4280 char *val;
4281 zval *zv;
4282
4283 if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &zv) == FAILURE) {
4284 RETURN_FALSE;
4285 }
4286
4287 valfree = redis_pack(redis_sock, zv, &val, &len);
4288 RETVAL_STRINGL(val, len);
4289 if (valfree) efree(val);
4290 }
4291
redis_unpack_handler(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock)4292 void redis_unpack_handler(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock) {
4293 zend_string *str;
4294
4295 if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &str) == FAILURE) {
4296 RETURN_FALSE;
4297 }
4298
4299 if (redis_unpack(redis_sock, ZSTR_VAL(str), ZSTR_LEN(str), return_value) == 0) {
4300 RETURN_STR_COPY(str);
4301 }
4302 }
4303 /* vim: set tabstop=4 softtabstop=4 expandtab shiftwidth=4: */
4304