]> git.saurik.com Git - redis.git/blame_incremental - redis-cli.c
Fixed a redis-cli bug, was using free instead of zfree call
[redis.git] / redis-cli.c
... / ...
CommitLineData
1/* Redis CLI (command line interface)
2 *
3 * Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com>
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 *
9 * * Redistributions of source code must retain the above copyright notice,
10 * this list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * * Neither the name of Redis nor the names of its contributors may be used
15 * to endorse or promote products derived from this software without
16 * specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
22 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 * POSSIBILITY OF SUCH DAMAGE.
29 */
30
31#include "fmacros.h"
32
33#include <stdio.h>
34#include <string.h>
35#include <stdlib.h>
36#include <unistd.h>
37#include <ctype.h>
38
39#include "anet.h"
40#include "sds.h"
41#include "adlist.h"
42#include "zmalloc.h"
43#include "linenoise.h"
44
45#define REDIS_CMD_INLINE 1
46#define REDIS_CMD_BULK 2
47#define REDIS_CMD_MULTIBULK 4
48
49#define REDIS_NOTUSED(V) ((void) V)
50
51static struct config {
52 char *hostip;
53 int hostport;
54 long repeat;
55 int dbnum;
56 int interactive;
57 int monitor_mode;
58 int pubsub_mode;
59 char *auth;
60} config;
61
62struct redisCommand {
63 char *name;
64 int arity;
65};
66
67static struct redisCommand cmdTable[] = {
68 {"auth",2},
69 {"get",2},
70 {"set",3},
71 {"setnx",3},
72 {"setex",4},
73 {"append",3},
74 {"substr",4},
75 {"del",-2},
76 {"exists",2},
77 {"incr",2},
78 {"decr",2},
79 {"rpush",3},
80 {"lpush",3},
81 {"rpop",2},
82 {"lpop",2},
83 {"brpop",-3},
84 {"blpop",-3},
85 {"llen",2},
86 {"lindex",3},
87 {"lset",4},
88 {"lrange",4},
89 {"ltrim",4},
90 {"lrem",4},
91 {"rpoplpush",3},
92 {"sadd",3},
93 {"srem",3},
94 {"smove",4},
95 {"sismember",3},
96 {"scard",2},
97 {"spop",2},
98 {"srandmember",2},
99 {"sinter",-2},
100 {"sinterstore",-3},
101 {"sunion",-2},
102 {"sunionstore",-3},
103 {"sdiff",-2},
104 {"sdiffstore",-3},
105 {"smembers",2},
106 {"zadd",4},
107 {"zincrby",4},
108 {"zrem",3},
109 {"zremrangebyscore",4},
110 {"zmerge",-3},
111 {"zmergeweighed",-4},
112 {"zrange",-4},
113 {"zrank",3},
114 {"zrevrank",3},
115 {"zrangebyscore",-4},
116 {"zcount",4},
117 {"zrevrange",-4},
118 {"zcard",2},
119 {"zscore",3},
120 {"incrby",3},
121 {"decrby",3},
122 {"getset",3},
123 {"randomkey",1},
124 {"select",2},
125 {"move",3},
126 {"rename",3},
127 {"renamenx",3},
128 {"keys",2},
129 {"dbsize",1},
130 {"ping",1},
131 {"echo",2},
132 {"save",1},
133 {"bgsave",1},
134 {"rewriteaof",1},
135 {"bgrewriteaof",1},
136 {"shutdown",1},
137 {"lastsave",1},
138 {"type",2},
139 {"flushdb",1},
140 {"flushall",1},
141 {"sort",-2},
142 {"info",1},
143 {"mget",-2},
144 {"expire",3},
145 {"expireat",3},
146 {"ttl",2},
147 {"slaveof",3},
148 {"debug",-2},
149 {"mset",-3},
150 {"msetnx",-3},
151 {"monitor",1},
152 {"multi",1},
153 {"exec",1},
154 {"discard",1},
155 {"hset",4},
156 {"hget",3},
157 {"hmset",-4},
158 {"hmget",-3},
159 {"hincrby",4},
160 {"hdel",3},
161 {"hlen",2},
162 {"hkeys",2},
163 {"hvals",2},
164 {"hgetall",2},
165 {"hexists",3},
166 {"config",-2},
167 {"subscribe",-2},
168 {"unsubscribe",-1},
169 {"psubscribe",-2},
170 {"punsubscribe",-1},
171 {"publish",3},
172 {NULL,0}
173};
174
175static int cliReadReply(int fd);
176static void usage();
177
178static struct redisCommand *lookupCommand(char *name) {
179 int j = 0;
180 while(cmdTable[j].name != NULL) {
181 if (!strcasecmp(name,cmdTable[j].name)) return &cmdTable[j];
182 j++;
183 }
184 return NULL;
185}
186
187static int cliConnect(void) {
188 char err[ANET_ERR_LEN];
189 static int fd = ANET_ERR;
190
191 if (fd == ANET_ERR) {
192 fd = anetTcpConnect(err,config.hostip,config.hostport);
193 if (fd == ANET_ERR) {
194 fprintf(stderr, "Could not connect to Redis at %s:%d: %s", config.hostip, config.hostport, err);
195 return -1;
196 }
197 anetTcpNoDelay(NULL,fd);
198 }
199 return fd;
200}
201
202static sds cliReadLine(int fd) {
203 sds line = sdsempty();
204
205 while(1) {
206 char c;
207 ssize_t ret;
208
209 ret = read(fd,&c,1);
210 if (ret == -1) {
211 sdsfree(line);
212 return NULL;
213 } else if ((ret == 0) || (c == '\n')) {
214 break;
215 } else {
216 line = sdscatlen(line,&c,1);
217 }
218 }
219 return sdstrim(line,"\r\n");
220}
221
222static int cliReadSingleLineReply(int fd, int quiet) {
223 sds reply = cliReadLine(fd);
224
225 if (reply == NULL) return 1;
226 if (!quiet)
227 printf("%s\n", reply);
228 sdsfree(reply);
229 return 0;
230}
231
232static void printStringRepr(char *s, int len) {
233 printf("\"");
234 while(len--) {
235 switch(*s) {
236 case '\\':
237 case '"':
238 printf("\\%c",*s);
239 break;
240 case '\n': printf("\\n"); break;
241 case '\r': printf("\\r"); break;
242 case '\t': printf("\\t"); break;
243 case '\a': printf("\\a"); break;
244 case '\b': printf("\\b"); break;
245 default:
246 if (isprint(*s))
247 printf("%c",*s);
248 else
249 printf("\\x%02x",(unsigned char)*s);
250 break;
251 }
252 s++;
253 }
254 printf("\"\n");
255}
256
257static int cliReadBulkReply(int fd) {
258 sds replylen = cliReadLine(fd);
259 char *reply, crlf[2];
260 int bulklen;
261
262 if (replylen == NULL) return 1;
263 bulklen = atoi(replylen);
264 if (bulklen == -1) {
265 sdsfree(replylen);
266 printf("(nil)\n");
267 return 0;
268 }
269 reply = zmalloc(bulklen);
270 anetRead(fd,reply,bulklen);
271 anetRead(fd,crlf,2);
272 if (!isatty(fileno(stdout))) {
273 if (bulklen && fwrite(reply,bulklen,1,stdout) == 0) {
274 zfree(reply);
275 return 1;
276 }
277 } else {
278 /* If you are producing output for the standard output we want
279 * a more interesting output with quoted characters and so forth */
280 printStringRepr(reply,bulklen);
281 }
282 zfree(reply);
283 return 0;
284}
285
286static int cliReadMultiBulkReply(int fd) {
287 sds replylen = cliReadLine(fd);
288 int elements, c = 1;
289
290 if (replylen == NULL) return 1;
291 elements = atoi(replylen);
292 if (elements == -1) {
293 sdsfree(replylen);
294 printf("(nil)\n");
295 return 0;
296 }
297 if (elements == 0) {
298 printf("(empty list or set)\n");
299 }
300 while(elements--) {
301 printf("%d. ", c);
302 if (cliReadReply(fd)) return 1;
303 c++;
304 }
305 return 0;
306}
307
308static int cliReadReply(int fd) {
309 char type;
310
311 if (anetRead(fd,&type,1) <= 0) exit(1);
312 switch(type) {
313 case '-':
314 printf("(error) ");
315 cliReadSingleLineReply(fd,0);
316 return 1;
317 case '+':
318 return cliReadSingleLineReply(fd,0);
319 case ':':
320 printf("(integer) ");
321 return cliReadSingleLineReply(fd,0);
322 case '$':
323 return cliReadBulkReply(fd);
324 case '*':
325 return cliReadMultiBulkReply(fd);
326 default:
327 printf("protocol error, got '%c' as reply type byte\n", type);
328 return 1;
329 }
330}
331
332static int selectDb(int fd) {
333 int retval;
334 sds cmd;
335 char type;
336
337 if (config.dbnum == 0)
338 return 0;
339
340 cmd = sdsempty();
341 cmd = sdscatprintf(cmd,"SELECT %d\r\n",config.dbnum);
342 anetWrite(fd,cmd,sdslen(cmd));
343 anetRead(fd,&type,1);
344 if (type <= 0 || type != '+') return 1;
345 retval = cliReadSingleLineReply(fd,1);
346 if (retval) {
347 return retval;
348 }
349 return 0;
350}
351
352static int cliSendCommand(int argc, char **argv, int repeat) {
353 struct redisCommand *rc = lookupCommand(argv[0]);
354 int fd, j, retval = 0;
355 sds cmd;
356
357 if (!rc) {
358 fprintf(stderr,"Unknown command '%s'\n",argv[0]);
359 return 1;
360 }
361
362 if ((rc->arity > 0 && argc != rc->arity) ||
363 (rc->arity < 0 && argc < -rc->arity)) {
364 fprintf(stderr,"Wrong number of arguments for '%s'\n",rc->name);
365 return 1;
366 }
367 if (!strcasecmp(rc->name,"monitor")) config.monitor_mode = 1;
368 if (!strcasecmp(rc->name,"subscribe") ||
369 !strcasecmp(rc->name,"psubscribe")) config.pubsub_mode = 1;
370 if ((fd = cliConnect()) == -1) return 1;
371
372 /* Select db number */
373 retval = selectDb(fd);
374 if (retval) {
375 fprintf(stderr,"Error setting DB num\n");
376 return 1;
377 }
378
379 while(repeat--) {
380 /* Build the command to send */
381 cmd = sdscatprintf(sdsempty(),"*%d\r\n",argc);
382 for (j = 0; j < argc; j++) {
383 cmd = sdscatprintf(cmd,"$%lu\r\n",
384 (unsigned long)sdslen(argv[j]));
385 cmd = sdscatlen(cmd,argv[j],sdslen(argv[j]));
386 cmd = sdscatlen(cmd,"\r\n",2);
387 }
388 anetWrite(fd,cmd,sdslen(cmd));
389 sdsfree(cmd);
390
391 while (config.monitor_mode) {
392 cliReadSingleLineReply(fd,0);
393 }
394
395 if (config.pubsub_mode) {
396 printf("Reading messages... (press Ctrl-c to quit)\n");
397 while (1) {
398 cliReadReply(fd);
399 printf("\n");
400 }
401 }
402
403 retval = cliReadReply(fd);
404 if (retval) {
405 return retval;
406 }
407 }
408 return 0;
409}
410
411static int parseOptions(int argc, char **argv) {
412 int i;
413
414 for (i = 1; i < argc; i++) {
415 int lastarg = i==argc-1;
416
417 if (!strcmp(argv[i],"-h") && !lastarg) {
418 char *ip = zmalloc(32);
419 if (anetResolve(NULL,argv[i+1],ip) == ANET_ERR) {
420 printf("Can't resolve %s\n", argv[i]);
421 exit(1);
422 }
423 config.hostip = ip;
424 i++;
425 } else if (!strcmp(argv[i],"-h") && lastarg) {
426 usage();
427 } else if (!strcmp(argv[i],"-p") && !lastarg) {
428 config.hostport = atoi(argv[i+1]);
429 i++;
430 } else if (!strcmp(argv[i],"-r") && !lastarg) {
431 config.repeat = strtoll(argv[i+1],NULL,10);
432 i++;
433 } else if (!strcmp(argv[i],"-n") && !lastarg) {
434 config.dbnum = atoi(argv[i+1]);
435 i++;
436 } else if (!strcmp(argv[i],"-a") && !lastarg) {
437 config.auth = argv[i+1];
438 i++;
439 } else if (!strcmp(argv[i],"-i")) {
440 config.interactive = 1;
441 } else {
442 break;
443 }
444 }
445 return i;
446}
447
448static sds readArgFromStdin(void) {
449 char buf[1024];
450 sds arg = sdsempty();
451
452 while(1) {
453 int nread = read(fileno(stdin),buf,1024);
454
455 if (nread == 0) break;
456 else if (nread == -1) {
457 perror("Reading from standard input");
458 exit(1);
459 }
460 arg = sdscatlen(arg,buf,nread);
461 }
462 return arg;
463}
464
465static void usage() {
466 fprintf(stderr, "usage: redis-cli [-h host] [-p port] [-a authpw] [-r repeat_times] [-n db_num] [-i] cmd arg1 arg2 arg3 ... argN\n");
467 fprintf(stderr, "usage: echo \"argN\" | redis-cli [-h host] [-a authpw] [-p port] [-r repeat_times] [-n db_num] cmd arg1 arg2 ... arg(N-1)\n");
468 fprintf(stderr, "\nIf a pipe from standard input is detected this data is used as last argument.\n\n");
469 fprintf(stderr, "example: cat /etc/passwd | redis-cli set my_passwd\n");
470 fprintf(stderr, "example: redis-cli get my_passwd\n");
471 fprintf(stderr, "example: redis-cli -r 100 lpush mylist x\n");
472 fprintf(stderr, "\nRun in interactive mode: redis-cli -i or just don't pass any command\n");
473 exit(1);
474}
475
476/* Turn the plain C strings into Sds strings */
477static char **convertToSds(int count, char** args) {
478 int j;
479 char **sds = zmalloc(sizeof(char*)*count+1);
480
481 for(j = 0; j < count; j++)
482 sds[j] = sdsnew(args[j]);
483
484 return sds;
485}
486
487static char **splitArguments(char *line, int *argc) {
488 char *p = line;
489 char *current = NULL;
490 char **vector = NULL;
491
492 *argc = 0;
493 while(1) {
494 /* skip blanks */
495 while(*p && isspace(*p)) p++;
496 if (*p) {
497 /* get a token */
498 int inq=0; /* set to 1 if we are in "quotes" */
499 int done = 0;
500
501 if (current == NULL) current = sdsempty();
502 while(!done) {
503 if (inq) {
504 if (*p == '\\' && *(p+1)) {
505 char c;
506
507 p++;
508 switch(*p) {
509 case 'n': c = '\n'; break;
510 case 'r': c = '\r'; break;
511 case 't': c = '\t'; break;
512 case 'b': c = '\b'; break;
513 case 'a': c = '\a'; break;
514 default: c = *p; break;
515 }
516 current = sdscatlen(current,&c,1);
517 } else if (*p == '"') {
518 done = 1;
519 } else {
520 current = sdscatlen(current,p,1);
521 }
522 } else {
523 switch(*p) {
524 case ' ':
525 case '\n':
526 case '\r':
527 case '\t':
528 case '\0':
529 done=1;
530 break;
531 case '"':
532 inq=1;
533 break;
534 default:
535 current = sdscatlen(current,p,1);
536 break;
537 }
538 }
539 if (*p) p++;
540 }
541 /* add the token to the vector */
542 vector = zrealloc(vector,((*argc)+1)*sizeof(char*));
543 vector[*argc] = current;
544 (*argc)++;
545 current = NULL;
546 } else {
547 return vector;
548 }
549 }
550}
551
552#define LINE_BUFLEN 4096
553static void repl() {
554 int argc, j;
555 char *line, **argv;
556
557 while((line = linenoise("redis> ")) != NULL) {
558 if (line[0] != '\0') {
559 argv = splitArguments(line,&argc);
560 linenoiseHistoryAdd(line);
561 if (argc > 0) {
562 if (strcasecmp(argv[0],"quit") == 0 ||
563 strcasecmp(argv[0],"exit") == 0)
564 exit(0);
565 else
566 cliSendCommand(argc, argv, 1);
567 }
568 /* Free the argument vector */
569 for (j = 0; j < argc; j++)
570 sdsfree(argv[j]);
571 zfree(argv);
572 }
573 /* linenoise() returns malloc-ed lines like readline() */
574 free(line);
575 }
576 exit(0);
577}
578
579int main(int argc, char **argv) {
580 int firstarg;
581 char **argvcopy;
582 struct redisCommand *rc;
583
584 config.hostip = "127.0.0.1";
585 config.hostport = 6379;
586 config.repeat = 1;
587 config.dbnum = 0;
588 config.interactive = 0;
589 config.monitor_mode = 0;
590 config.pubsub_mode = 0;
591 config.auth = NULL;
592
593 firstarg = parseOptions(argc,argv);
594 argc -= firstarg;
595 argv += firstarg;
596
597 if (config.auth != NULL) {
598 char *authargv[2];
599
600 authargv[0] = "AUTH";
601 authargv[1] = config.auth;
602 cliSendCommand(2, convertToSds(2, authargv), 1);
603 }
604
605 if (argc == 0 || config.interactive == 1) repl();
606
607 argvcopy = convertToSds(argc, argv);
608
609 /* Read the last argument from stdandard input if needed */
610 if ((rc = lookupCommand(argv[0])) != NULL) {
611 if (rc->arity > 0 && argc == rc->arity-1) {
612 sds lastarg = readArgFromStdin();
613 argvcopy[argc] = lastarg;
614 argc++;
615 }
616 }
617
618 return cliSendCommand(argc, argvcopy, config.repeat);
619}