xref: /redis-3.2.3/src/config.c (revision d04afd62)
1 /* Configuration file parsing and CONFIG GET/SET commands implementation.
2  *
3  * Copyright (c) 2009-2012, 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 
32 #include "redis.h"
33 
34 /*-----------------------------------------------------------------------------
35  * Config file parsing
36  *----------------------------------------------------------------------------*/
37 
38 int yesnotoi(char *s) {
39     if (!strcasecmp(s,"yes")) return 1;
40     else if (!strcasecmp(s,"no")) return 0;
41     else return -1;
42 }
43 
44 void appendServerSaveParams(time_t seconds, int changes) {
45     server.saveparams = zrealloc(server.saveparams,sizeof(struct saveparam)*(server.saveparamslen+1));
46     server.saveparams[server.saveparamslen].seconds = seconds;
47     server.saveparams[server.saveparamslen].changes = changes;
48     server.saveparamslen++;
49 }
50 
51 void resetServerSaveParams() {
52     zfree(server.saveparams);
53     server.saveparams = NULL;
54     server.saveparamslen = 0;
55 }
56 
57 void loadServerConfigFromString(char *config) {
58     char *err = NULL;
59     int linenum = 0, totlines, i;
60     sds *lines;
61 
62     lines = sdssplitlen(config,strlen(config),"\n",1,&totlines);
63 
64     for (i = 0; i < totlines; i++) {
65         sds *argv;
66         int argc;
67 
68         linenum = i+1;
69         lines[i] = sdstrim(lines[i]," \t\r\n");
70 
71         /* Skip comments and blank lines */
72         if (lines[i][0] == '#' || lines[i][0] == '\0') continue;
73 
74         /* Split into arguments */
75         argv = sdssplitargs(lines[i],&argc);
76         if (argv == NULL) {
77             err = "Unbalanced quotes in configuration line";
78             goto loaderr;
79         }
80 
81         /* Skip this line if the resulting command vector is empty. */
82         if (argc == 0) {
83             sdsfreesplitres(argv,argc);
84             return;
85         }
86         sdstolower(argv[0]);
87 
88         /* Execute config directives */
89         if (!strcasecmp(argv[0],"timeout") && argc == 2) {
90             server.maxidletime = atoi(argv[1]);
91             if (server.maxidletime < 0) {
92                 err = "Invalid timeout value"; goto loaderr;
93             }
94         } else if (!strcasecmp(argv[0],"tcp-keepalive") && argc == 2) {
95             server.tcpkeepalive = atoi(argv[1]);
96             if (server.tcpkeepalive < 0) {
97                 err = "Invalid tcp-keepalive value"; goto loaderr;
98             }
99         } else if (!strcasecmp(argv[0],"port") && argc == 2) {
100             server.port = atoi(argv[1]);
101             if (server.port < 0 || server.port > 65535) {
102                 err = "Invalid port"; goto loaderr;
103             }
104         } else if (!strcasecmp(argv[0],"bind") && argc == 2) {
105             server.bindaddr = zstrdup(argv[1]);
106         } else if (!strcasecmp(argv[0],"unixsocket") && argc == 2) {
107             server.unixsocket = zstrdup(argv[1]);
108         } else if (!strcasecmp(argv[0],"unixsocketperm") && argc == 2) {
109             errno = 0;
110             server.unixsocketperm = (mode_t)strtol(argv[1], NULL, 8);
111             if (errno || server.unixsocketperm > 0777) {
112                 err = "Invalid socket file permissions"; goto loaderr;
113             }
114         } else if (!strcasecmp(argv[0],"save")) {
115             if (argc == 3) {
116                 int seconds = atoi(argv[1]);
117                 int changes = atoi(argv[2]);
118                 if (seconds < 1 || changes < 0) {
119                     err = "Invalid save parameters"; goto loaderr;
120                 }
121                 appendServerSaveParams(seconds,changes);
122             } else if (argc == 2 && !strcasecmp(argv[1],"")) {
123                 resetServerSaveParams();
124             }
125         } else if (!strcasecmp(argv[0],"dir") && argc == 2) {
126             if (chdir(argv[1]) == -1) {
127                 redisLog(REDIS_WARNING,"Can't chdir to '%s': %s",
128                     argv[1], strerror(errno));
129                 exit(1);
130             }
131         } else if (!strcasecmp(argv[0],"loglevel") && argc == 2) {
132             if (!strcasecmp(argv[1],"debug")) server.verbosity = REDIS_DEBUG;
133             else if (!strcasecmp(argv[1],"verbose")) server.verbosity = REDIS_VERBOSE;
134             else if (!strcasecmp(argv[1],"notice")) server.verbosity = REDIS_NOTICE;
135             else if (!strcasecmp(argv[1],"warning")) server.verbosity = REDIS_WARNING;
136             else {
137                 err = "Invalid log level. Must be one of debug, notice, warning";
138                 goto loaderr;
139             }
140         } else if (!strcasecmp(argv[0],"logfile") && argc == 2) {
141             FILE *logfp;
142 
143             server.logfile = zstrdup(argv[1]);
144             if (!strcasecmp(server.logfile,"stdout")) {
145                 zfree(server.logfile);
146                 server.logfile = NULL;
147             }
148             if (server.logfile) {
149                 /* Test if we are able to open the file. The server will not
150                  * be able to abort just for this problem later... */
151                 logfp = fopen(server.logfile,"a");
152                 if (logfp == NULL) {
153                     err = sdscatprintf(sdsempty(),
154                         "Can't open the log file: %s", strerror(errno));
155                     goto loaderr;
156                 }
157                 fclose(logfp);
158             }
159         } else if (!strcasecmp(argv[0],"syslog-enabled") && argc == 2) {
160             if ((server.syslog_enabled = yesnotoi(argv[1])) == -1) {
161                 err = "argument must be 'yes' or 'no'"; goto loaderr;
162             }
163         } else if (!strcasecmp(argv[0],"syslog-ident") && argc == 2) {
164             if (server.syslog_ident) zfree(server.syslog_ident);
165             server.syslog_ident = zstrdup(argv[1]);
166         } else if (!strcasecmp(argv[0],"syslog-facility") && argc == 2) {
167             struct {
168                 const char     *name;
169                 const int       value;
170             } validSyslogFacilities[] = {
171                 {"user",    LOG_USER},
172                 {"local0",  LOG_LOCAL0},
173                 {"local1",  LOG_LOCAL1},
174                 {"local2",  LOG_LOCAL2},
175                 {"local3",  LOG_LOCAL3},
176                 {"local4",  LOG_LOCAL4},
177                 {"local5",  LOG_LOCAL5},
178                 {"local6",  LOG_LOCAL6},
179                 {"local7",  LOG_LOCAL7},
180                 {NULL, 0}
181             };
182             int i;
183 
184             for (i = 0; validSyslogFacilities[i].name; i++) {
185                 if (!strcasecmp(validSyslogFacilities[i].name, argv[1])) {
186                     server.syslog_facility = validSyslogFacilities[i].value;
187                     break;
188                 }
189             }
190 
191             if (!validSyslogFacilities[i].name) {
192                 err = "Invalid log facility. Must be one of USER or between LOCAL0-LOCAL7";
193                 goto loaderr;
194             }
195         } else if (!strcasecmp(argv[0],"databases") && argc == 2) {
196             server.dbnum = atoi(argv[1]);
197             if (server.dbnum < 1) {
198                 err = "Invalid number of databases"; goto loaderr;
199             }
200         } else if (!strcasecmp(argv[0],"include") && argc == 2) {
201             loadServerConfig(argv[1],NULL);
202         } else if (!strcasecmp(argv[0],"maxclients") && argc == 2) {
203             server.maxclients = atoi(argv[1]);
204             if (server.maxclients < 1) {
205                 err = "Invalid max clients limit"; goto loaderr;
206             }
207         } else if (!strcasecmp(argv[0],"maxmemory") && argc == 2) {
208             server.maxmemory = memtoll(argv[1],NULL);
209         } else if (!strcasecmp(argv[0],"maxmemory-policy") && argc == 2) {
210             if (!strcasecmp(argv[1],"volatile-lru")) {
211                 server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_LRU;
212             } else if (!strcasecmp(argv[1],"volatile-random")) {
213                 server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_RANDOM;
214             } else if (!strcasecmp(argv[1],"volatile-ttl")) {
215                 server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_TTL;
216             } else if (!strcasecmp(argv[1],"allkeys-lru")) {
217                 server.maxmemory_policy = REDIS_MAXMEMORY_ALLKEYS_LRU;
218             } else if (!strcasecmp(argv[1],"allkeys-random")) {
219                 server.maxmemory_policy = REDIS_MAXMEMORY_ALLKEYS_RANDOM;
220             } else if (!strcasecmp(argv[1],"noeviction")) {
221                 server.maxmemory_policy = REDIS_MAXMEMORY_NO_EVICTION;
222             } else {
223                 err = "Invalid maxmemory policy";
224                 goto loaderr;
225             }
226         } else if (!strcasecmp(argv[0],"maxmemory-samples") && argc == 2) {
227             server.maxmemory_samples = atoi(argv[1]);
228             if (server.maxmemory_samples <= 0) {
229                 err = "maxmemory-samples must be 1 or greater";
230                 goto loaderr;
231             }
232         } else if (!strcasecmp(argv[0],"slaveof") && argc == 3) {
233             server.masterhost = sdsnew(argv[1]);
234             server.masterport = atoi(argv[2]);
235             server.repl_state = REDIS_REPL_CONNECT;
236         } else if (!strcasecmp(argv[0],"repl-ping-slave-period") && argc == 2) {
237             server.repl_ping_slave_period = atoi(argv[1]);
238             if (server.repl_ping_slave_period <= 0) {
239                 err = "repl-ping-slave-period must be 1 or greater";
240                 goto loaderr;
241             }
242         } else if (!strcasecmp(argv[0],"repl-timeout") && argc == 2) {
243             server.repl_timeout = atoi(argv[1]);
244             if (server.repl_timeout <= 0) {
245                 err = "repl-timeout must be 1 or greater";
246                 goto loaderr;
247             }
248         } else if (!strcasecmp(argv[0],"repl-disable-tcp-nodelay") && argc==2) {
249             if ((server.repl_disable_tcp_nodelay = yesnotoi(argv[1])) == -1) {
250                 err = "argument must be 'yes' or 'no'"; goto loaderr;
251             }
252         } else if (!strcasecmp(argv[0],"repl-backlog-size") && argc == 2) {
253             long long size = strtoll(argv[1],NULL,10);
254             if (size <= 0) {
255                 err = "repl-backlog-size must be 1 or greater.";
256                 goto loaderr;
257             }
258             resizeReplicationBacklog(size);
259         } else if (!strcasecmp(argv[0],"repl-backlog-ttl") && argc == 2) {
260             server.repl_backlog_time_limit = atoi(argv[1]);
261             if (server.repl_backlog_time_limit < 0) {
262                 err = "repl-backlog-ttl can't be negative ";
263                 goto loaderr;
264             }
265         } else if (!strcasecmp(argv[0],"masterauth") && argc == 2) {
266         	server.masterauth = zstrdup(argv[1]);
267         } else if (!strcasecmp(argv[0],"slave-serve-stale-data") && argc == 2) {
268             if ((server.repl_serve_stale_data = yesnotoi(argv[1])) == -1) {
269                 err = "argument must be 'yes' or 'no'"; goto loaderr;
270             }
271         } else if (!strcasecmp(argv[0],"slave-read-only") && argc == 2) {
272             if ((server.repl_slave_ro = yesnotoi(argv[1])) == -1) {
273                 err = "argument must be 'yes' or 'no'"; goto loaderr;
274             }
275         } else if (!strcasecmp(argv[0],"rdbcompression") && argc == 2) {
276             if ((server.rdb_compression = yesnotoi(argv[1])) == -1) {
277                 err = "argument must be 'yes' or 'no'"; goto loaderr;
278             }
279         } else if (!strcasecmp(argv[0],"rdbchecksum") && argc == 2) {
280             if ((server.rdb_checksum = yesnotoi(argv[1])) == -1) {
281                 err = "argument must be 'yes' or 'no'"; goto loaderr;
282             }
283         } else if (!strcasecmp(argv[0],"activerehashing") && argc == 2) {
284             if ((server.activerehashing = yesnotoi(argv[1])) == -1) {
285                 err = "argument must be 'yes' or 'no'"; goto loaderr;
286             }
287         } else if (!strcasecmp(argv[0],"daemonize") && argc == 2) {
288             if ((server.daemonize = yesnotoi(argv[1])) == -1) {
289                 err = "argument must be 'yes' or 'no'"; goto loaderr;
290             }
291         } else if (!strcasecmp(argv[0],"hz") && argc == 2) {
292             server.hz = atoi(argv[1]);
293             if (server.hz < REDIS_MIN_HZ) server.hz = REDIS_MIN_HZ;
294             if (server.hz > REDIS_MAX_HZ) server.hz = REDIS_MAX_HZ;
295         } else if (!strcasecmp(argv[0],"appendonly") && argc == 2) {
296             int yes;
297 
298             if ((yes = yesnotoi(argv[1])) == -1) {
299                 err = "argument must be 'yes' or 'no'"; goto loaderr;
300             }
301             server.aof_state = yes ? REDIS_AOF_ON : REDIS_AOF_OFF;
302         } else if (!strcasecmp(argv[0],"appendfilename") && argc == 2) {
303             zfree(server.aof_filename);
304             server.aof_filename = zstrdup(argv[1]);
305         } else if (!strcasecmp(argv[0],"no-appendfsync-on-rewrite")
306                    && argc == 2) {
307             if ((server.aof_no_fsync_on_rewrite= yesnotoi(argv[1])) == -1) {
308                 err = "argument must be 'yes' or 'no'"; goto loaderr;
309             }
310         } else if (!strcasecmp(argv[0],"appendfsync") && argc == 2) {
311             if (!strcasecmp(argv[1],"no")) {
312                 server.aof_fsync = AOF_FSYNC_NO;
313             } else if (!strcasecmp(argv[1],"always")) {
314                 server.aof_fsync = AOF_FSYNC_ALWAYS;
315             } else if (!strcasecmp(argv[1],"everysec")) {
316                 server.aof_fsync = AOF_FSYNC_EVERYSEC;
317             } else {
318                 err = "argument must be 'no', 'always' or 'everysec'";
319                 goto loaderr;
320             }
321         } else if (!strcasecmp(argv[0],"auto-aof-rewrite-percentage") &&
322                    argc == 2)
323         {
324             server.aof_rewrite_perc = atoi(argv[1]);
325             if (server.aof_rewrite_perc < 0) {
326                 err = "Invalid negative percentage for AOF auto rewrite";
327                 goto loaderr;
328             }
329         } else if (!strcasecmp(argv[0],"auto-aof-rewrite-min-size") &&
330                    argc == 2)
331         {
332             server.aof_rewrite_min_size = memtoll(argv[1],NULL);
333         } else if (!strcasecmp(argv[0],"requirepass") && argc == 2) {
334             if (strlen(argv[1]) > REDIS_AUTHPASS_MAX_LEN) {
335                 err = "Password is longer than REDIS_AUTHPASS_MAX_LEN";
336                 goto loaderr;
337             }
338             server.requirepass = zstrdup(argv[1]);
339         } else if (!strcasecmp(argv[0],"pidfile") && argc == 2) {
340             zfree(server.pidfile);
341             server.pidfile = zstrdup(argv[1]);
342         } else if (!strcasecmp(argv[0],"dbfilename") && argc == 2) {
343             zfree(server.rdb_filename);
344             server.rdb_filename = zstrdup(argv[1]);
345         } else if (!strcasecmp(argv[0],"hash-max-ziplist-entries") && argc == 2) {
346             server.hash_max_ziplist_entries = memtoll(argv[1], NULL);
347         } else if (!strcasecmp(argv[0],"hash-max-ziplist-value") && argc == 2) {
348             server.hash_max_ziplist_value = memtoll(argv[1], NULL);
349         } else if (!strcasecmp(argv[0],"list-max-ziplist-entries") && argc == 2){
350             server.list_max_ziplist_entries = memtoll(argv[1], NULL);
351         } else if (!strcasecmp(argv[0],"list-max-ziplist-value") && argc == 2) {
352             server.list_max_ziplist_value = memtoll(argv[1], NULL);
353         } else if (!strcasecmp(argv[0],"set-max-intset-entries") && argc == 2) {
354             server.set_max_intset_entries = memtoll(argv[1], NULL);
355         } else if (!strcasecmp(argv[0],"zset-max-ziplist-entries") && argc == 2) {
356             server.zset_max_ziplist_entries = memtoll(argv[1], NULL);
357         } else if (!strcasecmp(argv[0],"zset-max-ziplist-value") && argc == 2) {
358             server.zset_max_ziplist_value = memtoll(argv[1], NULL);
359         } else if (!strcasecmp(argv[0],"rename-command") && argc == 3) {
360             struct redisCommand *cmd = lookupCommand(argv[1]);
361             int retval;
362 
363             if (!cmd) {
364                 err = "No such command in rename-command";
365                 goto loaderr;
366             }
367 
368             /* If the target command name is the empty string we just
369              * remove it from the command table. */
370             retval = dictDelete(server.commands, argv[1]);
371             redisAssert(retval == DICT_OK);
372 
373             /* Otherwise we re-add the command under a different name. */
374             if (sdslen(argv[2]) != 0) {
375                 sds copy = sdsdup(argv[2]);
376 
377                 retval = dictAdd(server.commands, copy, cmd);
378                 if (retval != DICT_OK) {
379                     sdsfree(copy);
380                     err = "Target command name already exists"; goto loaderr;
381                 }
382             }
383         } else if (!strcasecmp(argv[0],"cluster-enabled") && argc == 2) {
384             if ((server.cluster_enabled = yesnotoi(argv[1])) == -1) {
385                 err = "argument must be 'yes' or 'no'"; goto loaderr;
386             }
387         } else if (!strcasecmp(argv[0],"cluster-config-file") && argc == 2) {
388             zfree(server.cluster_configfile);
389             server.cluster_configfile = zstrdup(argv[1]);
390         } else if (!strcasecmp(argv[0],"cluster-node-timeout") && argc == 2) {
391             server.cluster_node_timeout = atoi(argv[1]);
392             if (server.cluster_node_timeout <= 0) {
393                 err = "cluster node timeout must be 1 or greater"; goto loaderr;
394             }
395         } else if (!strcasecmp(argv[0],"lua-time-limit") && argc == 2) {
396             server.lua_time_limit = strtoll(argv[1],NULL,10);
397         } else if (!strcasecmp(argv[0],"slowlog-log-slower-than") &&
398                    argc == 2)
399         {
400             server.slowlog_log_slower_than = strtoll(argv[1],NULL,10);
401         } else if (!strcasecmp(argv[0],"slowlog-max-len") && argc == 2) {
402             server.slowlog_max_len = strtoll(argv[1],NULL,10);
403         } else if (!strcasecmp(argv[0],"client-output-buffer-limit") &&
404                    argc == 5)
405         {
406             int class = getClientLimitClassByName(argv[1]);
407             unsigned long long hard, soft;
408             int soft_seconds;
409 
410             if (class == -1) {
411                 err = "Unrecognized client limit class";
412                 goto loaderr;
413             }
414             hard = memtoll(argv[2],NULL);
415             soft = memtoll(argv[3],NULL);
416             soft_seconds = atoi(argv[4]);
417             if (soft_seconds < 0) {
418                 err = "Negative number of seconds in soft limit is invalid";
419                 goto loaderr;
420             }
421             server.client_obuf_limits[class].hard_limit_bytes = hard;
422             server.client_obuf_limits[class].soft_limit_bytes = soft;
423             server.client_obuf_limits[class].soft_limit_seconds = soft_seconds;
424         } else if (!strcasecmp(argv[0],"stop-writes-on-bgsave-error") &&
425                    argc == 2) {
426             if ((server.stop_writes_on_bgsave_err = yesnotoi(argv[1])) == -1) {
427                 err = "argument must be 'yes' or 'no'"; goto loaderr;
428             }
429         } else if (!strcasecmp(argv[0],"slave-priority") && argc == 2) {
430             server.slave_priority = atoi(argv[1]);
431         } else if (!strcasecmp(argv[0],"notify-keyspace-events") && argc == 2) {
432             int flags = keyspaceEventsStringToFlags(argv[1]);
433 
434             if (flags == -1) {
435                 err = "Invalid event class character. Use 'g$lshzxeA'.";
436                 goto loaderr;
437             }
438             server.notify_keyspace_events = flags;
439         } else if (!strcasecmp(argv[0],"sentinel")) {
440             /* argc == 1 is handled by main() as we need to enter the sentinel
441              * mode ASAP. */
442             if (argc != 1) {
443                 if (!server.sentinel_mode) {
444                     err = "sentinel directive while not in sentinel mode";
445                     goto loaderr;
446                 }
447                 err = sentinelHandleConfiguration(argv+1,argc-1);
448                 if (err) goto loaderr;
449             }
450         } else {
451             err = "Bad directive or wrong number of arguments"; goto loaderr;
452         }
453         sdsfreesplitres(argv,argc);
454     }
455     sdsfreesplitres(lines,totlines);
456 
457     /* Sanity checks. */
458     if (server.cluster_enabled && server.masterhost) {
459         err = "slaveof directive not allowed in cluster mode";
460         goto loaderr;
461     }
462     return;
463 
464 loaderr:
465     fprintf(stderr, "\n*** FATAL CONFIG FILE ERROR ***\n");
466     fprintf(stderr, "Reading the configuration file, at line %d\n", linenum);
467     fprintf(stderr, ">>> '%s'\n", lines[i]);
468     fprintf(stderr, "%s\n", err);
469     exit(1);
470 }
471 
472 /* Load the server configuration from the specified filename.
473  * The function appends the additional configuration directives stored
474  * in the 'options' string to the config file before loading.
475  *
476  * Both filename and options can be NULL, in such a case are considered
477  * empty. This way loadServerConfig can be used to just load a file or
478  * just load a string. */
479 void loadServerConfig(char *filename, char *options) {
480     sds config = sdsempty();
481     char buf[REDIS_CONFIGLINE_MAX+1];
482 
483     /* Load the file content */
484     if (filename) {
485         FILE *fp;
486 
487         if (filename[0] == '-' && filename[1] == '\0') {
488             fp = stdin;
489         } else {
490             if ((fp = fopen(filename,"r")) == NULL) {
491                 redisLog(REDIS_WARNING,
492                     "Fatal error, can't open config file '%s'", filename);
493                 exit(1);
494             }
495         }
496         while(fgets(buf,REDIS_CONFIGLINE_MAX+1,fp) != NULL)
497             config = sdscat(config,buf);
498         if (fp != stdin) fclose(fp);
499     }
500     /* Append the additional options */
501     if (options) {
502         config = sdscat(config,"\n");
503         config = sdscat(config,options);
504     }
505     loadServerConfigFromString(config);
506     sdsfree(config);
507 }
508 
509 /*-----------------------------------------------------------------------------
510  * CONFIG command for remote configuration
511  *----------------------------------------------------------------------------*/
512 
513 void configSetCommand(redisClient *c) {
514     robj *o;
515     long long ll;
516     redisAssertWithInfo(c,c->argv[2],c->argv[2]->encoding == REDIS_ENCODING_RAW);
517     redisAssertWithInfo(c,c->argv[2],c->argv[3]->encoding == REDIS_ENCODING_RAW);
518     o = c->argv[3];
519 
520     if (!strcasecmp(c->argv[2]->ptr,"dbfilename")) {
521         zfree(server.rdb_filename);
522         server.rdb_filename = zstrdup(o->ptr);
523     } else if (!strcasecmp(c->argv[2]->ptr,"requirepass")) {
524         if (sdslen(o->ptr) > REDIS_AUTHPASS_MAX_LEN) goto badfmt;
525         zfree(server.requirepass);
526         server.requirepass = ((char*)o->ptr)[0] ? zstrdup(o->ptr) : NULL;
527     } else if (!strcasecmp(c->argv[2]->ptr,"masterauth")) {
528         zfree(server.masterauth);
529         server.masterauth = zstrdup(o->ptr);
530     } else if (!strcasecmp(c->argv[2]->ptr,"maxmemory")) {
531         if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
532             ll < 0) goto badfmt;
533         server.maxmemory = ll;
534         if (server.maxmemory) {
535             if (server.maxmemory < zmalloc_used_memory()) {
536                 redisLog(REDIS_WARNING,"WARNING: the new maxmemory value set via CONFIG SET is smaller than the current memory usage. This will result in keys eviction and/or inability to accept new write commands depending on the maxmemory-policy.");
537             }
538             freeMemoryIfNeeded();
539         }
540     } else if (!strcasecmp(c->argv[2]->ptr,"hz")) {
541         if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
542             ll < 0) goto badfmt;
543         server.hz = (int) ll;
544         if (server.hz < REDIS_MIN_HZ) server.hz = REDIS_MIN_HZ;
545         if (server.hz > REDIS_MAX_HZ) server.hz = REDIS_MAX_HZ;
546     } else if (!strcasecmp(c->argv[2]->ptr,"maxmemory-policy")) {
547         if (!strcasecmp(o->ptr,"volatile-lru")) {
548             server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_LRU;
549         } else if (!strcasecmp(o->ptr,"volatile-random")) {
550             server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_RANDOM;
551         } else if (!strcasecmp(o->ptr,"volatile-ttl")) {
552             server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_TTL;
553         } else if (!strcasecmp(o->ptr,"allkeys-lru")) {
554             server.maxmemory_policy = REDIS_MAXMEMORY_ALLKEYS_LRU;
555         } else if (!strcasecmp(o->ptr,"allkeys-random")) {
556             server.maxmemory_policy = REDIS_MAXMEMORY_ALLKEYS_RANDOM;
557         } else if (!strcasecmp(o->ptr,"noeviction")) {
558             server.maxmemory_policy = REDIS_MAXMEMORY_NO_EVICTION;
559         } else {
560             goto badfmt;
561         }
562     } else if (!strcasecmp(c->argv[2]->ptr,"maxmemory-samples")) {
563         if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
564             ll <= 0) goto badfmt;
565         server.maxmemory_samples = ll;
566     } else if (!strcasecmp(c->argv[2]->ptr,"timeout")) {
567         if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
568             ll < 0 || ll > LONG_MAX) goto badfmt;
569         server.maxidletime = ll;
570     } else if (!strcasecmp(c->argv[2]->ptr,"tcp-keepalive")) {
571         if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
572             ll < 0 || ll > INT_MAX) goto badfmt;
573         server.tcpkeepalive = ll;
574     } else if (!strcasecmp(c->argv[2]->ptr,"appendfsync")) {
575         if (!strcasecmp(o->ptr,"no")) {
576             server.aof_fsync = AOF_FSYNC_NO;
577         } else if (!strcasecmp(o->ptr,"everysec")) {
578             server.aof_fsync = AOF_FSYNC_EVERYSEC;
579         } else if (!strcasecmp(o->ptr,"always")) {
580             server.aof_fsync = AOF_FSYNC_ALWAYS;
581         } else {
582             goto badfmt;
583         }
584     } else if (!strcasecmp(c->argv[2]->ptr,"no-appendfsync-on-rewrite")) {
585         int yn = yesnotoi(o->ptr);
586 
587         if (yn == -1) goto badfmt;
588         server.aof_no_fsync_on_rewrite = yn;
589     } else if (!strcasecmp(c->argv[2]->ptr,"appendonly")) {
590         int enable = yesnotoi(o->ptr);
591 
592         if (enable == -1) goto badfmt;
593         if (enable == 0 && server.aof_state != REDIS_AOF_OFF) {
594             stopAppendOnly();
595         } else if (enable && server.aof_state == REDIS_AOF_OFF) {
596             if (startAppendOnly() == REDIS_ERR) {
597                 addReplyError(c,
598                     "Unable to turn on AOF. Check server logs.");
599                 return;
600             }
601         }
602     } else if (!strcasecmp(c->argv[2]->ptr,"auto-aof-rewrite-percentage")) {
603         if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
604         server.aof_rewrite_perc = ll;
605     } else if (!strcasecmp(c->argv[2]->ptr,"auto-aof-rewrite-min-size")) {
606         if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
607         server.aof_rewrite_min_size = ll;
608     } else if (!strcasecmp(c->argv[2]->ptr,"save")) {
609         int vlen, j;
610         sds *v = sdssplitlen(o->ptr,sdslen(o->ptr)," ",1,&vlen);
611 
612         /* Perform sanity check before setting the new config:
613          * - Even number of args
614          * - Seconds >= 1, changes >= 0 */
615         if (vlen & 1) {
616             sdsfreesplitres(v,vlen);
617             goto badfmt;
618         }
619         for (j = 0; j < vlen; j++) {
620             char *eptr;
621             long val;
622 
623             val = strtoll(v[j], &eptr, 10);
624             if (eptr[0] != '\0' ||
625                 ((j & 1) == 0 && val < 1) ||
626                 ((j & 1) == 1 && val < 0)) {
627                 sdsfreesplitres(v,vlen);
628                 goto badfmt;
629             }
630         }
631         /* Finally set the new config */
632         resetServerSaveParams();
633         for (j = 0; j < vlen; j += 2) {
634             time_t seconds;
635             int changes;
636 
637             seconds = strtoll(v[j],NULL,10);
638             changes = strtoll(v[j+1],NULL,10);
639             appendServerSaveParams(seconds, changes);
640         }
641         sdsfreesplitres(v,vlen);
642     } else if (!strcasecmp(c->argv[2]->ptr,"slave-serve-stale-data")) {
643         int yn = yesnotoi(o->ptr);
644 
645         if (yn == -1) goto badfmt;
646         server.repl_serve_stale_data = yn;
647     } else if (!strcasecmp(c->argv[2]->ptr,"slave-read-only")) {
648         int yn = yesnotoi(o->ptr);
649 
650         if (yn == -1) goto badfmt;
651         server.repl_slave_ro = yn;
652     } else if (!strcasecmp(c->argv[2]->ptr,"dir")) {
653         if (chdir((char*)o->ptr) == -1) {
654             addReplyErrorFormat(c,"Changing directory: %s", strerror(errno));
655             return;
656         }
657     } else if (!strcasecmp(c->argv[2]->ptr,"hash-max-ziplist-entries")) {
658         if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
659         server.hash_max_ziplist_entries = ll;
660     } else if (!strcasecmp(c->argv[2]->ptr,"hash-max-ziplist-value")) {
661         if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
662         server.hash_max_ziplist_value = ll;
663     } else if (!strcasecmp(c->argv[2]->ptr,"list-max-ziplist-entries")) {
664         if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
665         server.list_max_ziplist_entries = ll;
666     } else if (!strcasecmp(c->argv[2]->ptr,"list-max-ziplist-value")) {
667         if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
668         server.list_max_ziplist_value = ll;
669     } else if (!strcasecmp(c->argv[2]->ptr,"set-max-intset-entries")) {
670         if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
671         server.set_max_intset_entries = ll;
672     } else if (!strcasecmp(c->argv[2]->ptr,"zset-max-ziplist-entries")) {
673         if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
674         server.zset_max_ziplist_entries = ll;
675     } else if (!strcasecmp(c->argv[2]->ptr,"zset-max-ziplist-value")) {
676         if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
677         server.zset_max_ziplist_value = ll;
678     } else if (!strcasecmp(c->argv[2]->ptr,"lua-time-limit")) {
679         if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
680         server.lua_time_limit = ll;
681     } else if (!strcasecmp(c->argv[2]->ptr,"slowlog-log-slower-than")) {
682         if (getLongLongFromObject(o,&ll) == REDIS_ERR) goto badfmt;
683         server.slowlog_log_slower_than = ll;
684     } else if (!strcasecmp(c->argv[2]->ptr,"slowlog-max-len")) {
685         if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
686         server.slowlog_max_len = (unsigned)ll;
687     } else if (!strcasecmp(c->argv[2]->ptr,"loglevel")) {
688         if (!strcasecmp(o->ptr,"warning")) {
689             server.verbosity = REDIS_WARNING;
690         } else if (!strcasecmp(o->ptr,"notice")) {
691             server.verbosity = REDIS_NOTICE;
692         } else if (!strcasecmp(o->ptr,"verbose")) {
693             server.verbosity = REDIS_VERBOSE;
694         } else if (!strcasecmp(o->ptr,"debug")) {
695             server.verbosity = REDIS_DEBUG;
696         } else {
697             goto badfmt;
698         }
699     } else if (!strcasecmp(c->argv[2]->ptr,"client-output-buffer-limit")) {
700         int vlen, j;
701         sds *v = sdssplitlen(o->ptr,sdslen(o->ptr)," ",1,&vlen);
702 
703         /* We need a multiple of 4: <class> <hard> <soft> <soft_seconds> */
704         if (vlen % 4) {
705             sdsfreesplitres(v,vlen);
706             goto badfmt;
707         }
708 
709         /* Sanity check of single arguments, so that we either refuse the
710          * whole configuration string or accept it all, even if a single
711          * error in a single client class is present. */
712         for (j = 0; j < vlen; j++) {
713             char *eptr;
714             long val;
715 
716             if ((j % 4) == 0) {
717                 if (getClientLimitClassByName(v[j]) == -1) {
718                     sdsfreesplitres(v,vlen);
719                     goto badfmt;
720                 }
721             } else {
722                 val = strtoll(v[j], &eptr, 10);
723                 if (eptr[0] != '\0' || val < 0) {
724                     sdsfreesplitres(v,vlen);
725                     goto badfmt;
726                 }
727             }
728         }
729         /* Finally set the new config */
730         for (j = 0; j < vlen; j += 4) {
731             int class;
732             unsigned long long hard, soft;
733             int soft_seconds;
734 
735             class = getClientLimitClassByName(v[j]);
736             hard = strtoll(v[j+1],NULL,10);
737             soft = strtoll(v[j+2],NULL,10);
738             soft_seconds = strtoll(v[j+3],NULL,10);
739 
740             server.client_obuf_limits[class].hard_limit_bytes = hard;
741             server.client_obuf_limits[class].soft_limit_bytes = soft;
742             server.client_obuf_limits[class].soft_limit_seconds = soft_seconds;
743         }
744         sdsfreesplitres(v,vlen);
745     } else if (!strcasecmp(c->argv[2]->ptr,"stop-writes-on-bgsave-error")) {
746         int yn = yesnotoi(o->ptr);
747 
748         if (yn == -1) goto badfmt;
749         server.stop_writes_on_bgsave_err = yn;
750     } else if (!strcasecmp(c->argv[2]->ptr,"repl-ping-slave-period")) {
751         if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll <= 0) goto badfmt;
752         server.repl_ping_slave_period = ll;
753     } else if (!strcasecmp(c->argv[2]->ptr,"repl-timeout")) {
754         if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll <= 0) goto badfmt;
755         server.repl_timeout = ll;
756     } else if (!strcasecmp(c->argv[2]->ptr,"repl-backlog-size")) {
757         if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll <= 0) goto badfmt;
758         resizeReplicationBacklog(ll);
759     } else if (!strcasecmp(c->argv[2]->ptr,"repl-backlog-ttl")) {
760         if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
761         server.repl_backlog_time_limit = ll;
762     } else if (!strcasecmp(c->argv[2]->ptr,"watchdog-period")) {
763         if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
764         if (ll)
765             enableWatchdog(ll);
766         else
767             disableWatchdog();
768     } else if (!strcasecmp(c->argv[2]->ptr,"rdbcompression")) {
769         int yn = yesnotoi(o->ptr);
770 
771         if (yn == -1) goto badfmt;
772         server.rdb_compression = yn;
773     } else if (!strcasecmp(c->argv[2]->ptr,"notify-keyspace-events")) {
774         int flags = keyspaceEventsStringToFlags(o->ptr);
775 
776         if (flags == -1) goto badfmt;
777         server.notify_keyspace_events = flags;
778     } else if (!strcasecmp(c->argv[2]->ptr,"repl-disable-tcp-nodelay")) {
779         int yn = yesnotoi(o->ptr);
780 
781         if (yn == -1) goto badfmt;
782         server.repl_disable_tcp_nodelay = yn;
783     } else if (!strcasecmp(c->argv[2]->ptr,"slave-priority")) {
784         if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
785             ll <= 0) goto badfmt;
786         server.slave_priority = ll;
787     } else if (!strcasecmp(c->argv[2]->ptr,"cluster-node-timeout")) {
788         if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
789             ll <= 0) goto badfmt;
790         server.cluster_node_timeout = ll;
791     } else {
792         addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s",
793             (char*)c->argv[2]->ptr);
794         return;
795     }
796     addReply(c,shared.ok);
797     return;
798 
799 badfmt: /* Bad format errors */
800     addReplyErrorFormat(c,"Invalid argument '%s' for CONFIG SET '%s'",
801             (char*)o->ptr,
802             (char*)c->argv[2]->ptr);
803 }
804 
805 #define config_get_string_field(_name,_var) do { \
806     if (stringmatch(pattern,_name,0)) { \
807         addReplyBulkCString(c,_name); \
808         addReplyBulkCString(c,_var ? _var : ""); \
809         matches++; \
810     } \
811 } while(0);
812 
813 #define config_get_bool_field(_name,_var) do { \
814     if (stringmatch(pattern,_name,0)) { \
815         addReplyBulkCString(c,_name); \
816         addReplyBulkCString(c,_var ? "yes" : "no"); \
817         matches++; \
818     } \
819 } while(0);
820 
821 #define config_get_numerical_field(_name,_var) do { \
822     if (stringmatch(pattern,_name,0)) { \
823         ll2string(buf,sizeof(buf),_var); \
824         addReplyBulkCString(c,_name); \
825         addReplyBulkCString(c,buf); \
826         matches++; \
827     } \
828 } while(0);
829 
830 void configGetCommand(redisClient *c) {
831     robj *o = c->argv[2];
832     void *replylen = addDeferredMultiBulkLength(c);
833     char *pattern = o->ptr;
834     char buf[128];
835     int matches = 0;
836     redisAssertWithInfo(c,o,o->encoding == REDIS_ENCODING_RAW);
837 
838     /* String values */
839     config_get_string_field("dbfilename",server.rdb_filename);
840     config_get_string_field("requirepass",server.requirepass);
841     config_get_string_field("masterauth",server.masterauth);
842     config_get_string_field("bind",server.bindaddr);
843     config_get_string_field("unixsocket",server.unixsocket);
844     config_get_string_field("logfile",server.logfile);
845     config_get_string_field("pidfile",server.pidfile);
846 
847     /* Numerical values */
848     config_get_numerical_field("maxmemory",server.maxmemory);
849     config_get_numerical_field("maxmemory-samples",server.maxmemory_samples);
850     config_get_numerical_field("timeout",server.maxidletime);
851     config_get_numerical_field("tcp-keepalive",server.tcpkeepalive);
852     config_get_numerical_field("auto-aof-rewrite-percentage",
853             server.aof_rewrite_perc);
854     config_get_numerical_field("auto-aof-rewrite-min-size",
855             server.aof_rewrite_min_size);
856     config_get_numerical_field("hash-max-ziplist-entries",
857             server.hash_max_ziplist_entries);
858     config_get_numerical_field("hash-max-ziplist-value",
859             server.hash_max_ziplist_value);
860     config_get_numerical_field("list-max-ziplist-entries",
861             server.list_max_ziplist_entries);
862     config_get_numerical_field("list-max-ziplist-value",
863             server.list_max_ziplist_value);
864     config_get_numerical_field("set-max-intset-entries",
865             server.set_max_intset_entries);
866     config_get_numerical_field("zset-max-ziplist-entries",
867             server.zset_max_ziplist_entries);
868     config_get_numerical_field("zset-max-ziplist-value",
869             server.zset_max_ziplist_value);
870     config_get_numerical_field("lua-time-limit",server.lua_time_limit);
871     config_get_numerical_field("slowlog-log-slower-than",
872             server.slowlog_log_slower_than);
873     config_get_numerical_field("slowlog-max-len",
874             server.slowlog_max_len);
875     config_get_numerical_field("port",server.port);
876     config_get_numerical_field("databases",server.dbnum);
877     config_get_numerical_field("repl-ping-slave-period",server.repl_ping_slave_period);
878     config_get_numerical_field("repl-timeout",server.repl_timeout);
879     config_get_numerical_field("repl-backlog-size",server.repl_backlog_size);
880     config_get_numerical_field("repl-backlog-ttl",server.repl_backlog_time_limit);
881     config_get_numerical_field("maxclients",server.maxclients);
882     config_get_numerical_field("watchdog-period",server.watchdog_period);
883     config_get_numerical_field("slave-priority",server.slave_priority);
884     config_get_numerical_field("hz",server.hz);
885     config_get_numerical_field("cluster-node-timeout",server.cluster_node_timeout);
886 
887     /* Bool (yes/no) values */
888     config_get_bool_field("no-appendfsync-on-rewrite",
889             server.aof_no_fsync_on_rewrite);
890     config_get_bool_field("slave-serve-stale-data",
891             server.repl_serve_stale_data);
892     config_get_bool_field("slave-read-only",
893             server.repl_slave_ro);
894     config_get_bool_field("stop-writes-on-bgsave-error",
895             server.stop_writes_on_bgsave_err);
896     config_get_bool_field("daemonize", server.daemonize);
897     config_get_bool_field("rdbcompression", server.rdb_compression);
898     config_get_bool_field("rdbchecksum", server.rdb_checksum);
899     config_get_bool_field("activerehashing", server.activerehashing);
900     config_get_bool_field("repl-disable-tcp-nodelay",
901             server.repl_disable_tcp_nodelay);
902 
903     /* Everything we can't handle with macros follows. */
904 
905     if (stringmatch(pattern,"appendonly",0)) {
906         addReplyBulkCString(c,"appendonly");
907         addReplyBulkCString(c,server.aof_state == REDIS_AOF_OFF ? "no" : "yes");
908         matches++;
909     }
910     if (stringmatch(pattern,"dir",0)) {
911         char buf[1024];
912 
913         if (getcwd(buf,sizeof(buf)) == NULL)
914             buf[0] = '\0';
915 
916         addReplyBulkCString(c,"dir");
917         addReplyBulkCString(c,buf);
918         matches++;
919     }
920     if (stringmatch(pattern,"maxmemory-policy",0)) {
921         char *s;
922 
923         switch(server.maxmemory_policy) {
924         case REDIS_MAXMEMORY_VOLATILE_LRU: s = "volatile-lru"; break;
925         case REDIS_MAXMEMORY_VOLATILE_TTL: s = "volatile-ttl"; break;
926         case REDIS_MAXMEMORY_VOLATILE_RANDOM: s = "volatile-random"; break;
927         case REDIS_MAXMEMORY_ALLKEYS_LRU: s = "allkeys-lru"; break;
928         case REDIS_MAXMEMORY_ALLKEYS_RANDOM: s = "allkeys-random"; break;
929         case REDIS_MAXMEMORY_NO_EVICTION: s = "noeviction"; break;
930         default: s = "unknown"; break; /* too harmless to panic */
931         }
932         addReplyBulkCString(c,"maxmemory-policy");
933         addReplyBulkCString(c,s);
934         matches++;
935     }
936     if (stringmatch(pattern,"appendfsync",0)) {
937         char *policy;
938 
939         switch(server.aof_fsync) {
940         case AOF_FSYNC_NO: policy = "no"; break;
941         case AOF_FSYNC_EVERYSEC: policy = "everysec"; break;
942         case AOF_FSYNC_ALWAYS: policy = "always"; break;
943         default: policy = "unknown"; break; /* too harmless to panic */
944         }
945         addReplyBulkCString(c,"appendfsync");
946         addReplyBulkCString(c,policy);
947         matches++;
948     }
949     if (stringmatch(pattern,"save",0)) {
950         sds buf = sdsempty();
951         int j;
952 
953         for (j = 0; j < server.saveparamslen; j++) {
954             buf = sdscatprintf(buf,"%ld %d",
955                     server.saveparams[j].seconds,
956                     server.saveparams[j].changes);
957             if (j != server.saveparamslen-1)
958                 buf = sdscatlen(buf," ",1);
959         }
960         addReplyBulkCString(c,"save");
961         addReplyBulkCString(c,buf);
962         sdsfree(buf);
963         matches++;
964     }
965     if (stringmatch(pattern,"loglevel",0)) {
966         char *s;
967 
968         switch(server.verbosity) {
969         case REDIS_WARNING: s = "warning"; break;
970         case REDIS_VERBOSE: s = "verbose"; break;
971         case REDIS_NOTICE: s = "notice"; break;
972         case REDIS_DEBUG: s = "debug"; break;
973         default: s = "unknown"; break; /* too harmless to panic */
974         }
975         addReplyBulkCString(c,"loglevel");
976         addReplyBulkCString(c,s);
977         matches++;
978     }
979     if (stringmatch(pattern,"client-output-buffer-limit",0)) {
980         sds buf = sdsempty();
981         int j;
982 
983         for (j = 0; j < REDIS_CLIENT_LIMIT_NUM_CLASSES; j++) {
984             buf = sdscatprintf(buf,"%s %llu %llu %ld",
985                     getClientLimitClassName(j),
986                     server.client_obuf_limits[j].hard_limit_bytes,
987                     server.client_obuf_limits[j].soft_limit_bytes,
988                     (long) server.client_obuf_limits[j].soft_limit_seconds);
989             if (j != REDIS_CLIENT_LIMIT_NUM_CLASSES-1)
990                 buf = sdscatlen(buf," ",1);
991         }
992         addReplyBulkCString(c,"client-output-buffer-limit");
993         addReplyBulkCString(c,buf);
994         sdsfree(buf);
995         matches++;
996     }
997     if (stringmatch(pattern,"unixsocketperm",0)) {
998         char buf[32];
999         snprintf(buf,sizeof(buf),"%o",server.unixsocketperm);
1000         addReplyBulkCString(c,"unixsocketperm");
1001         addReplyBulkCString(c,buf);
1002         matches++;
1003     }
1004     if (stringmatch(pattern,"slaveof",0)) {
1005         char buf[256];
1006 
1007         addReplyBulkCString(c,"slaveof");
1008         if (server.masterhost)
1009             snprintf(buf,sizeof(buf),"%s %d",
1010                 server.masterhost, server.masterport);
1011         else
1012             buf[0] = '\0';
1013         addReplyBulkCString(c,buf);
1014         matches++;
1015     }
1016     if (stringmatch(pattern,"notify-keyspace-events",0)) {
1017         robj *flagsobj = createObject(REDIS_STRING,
1018             keyspaceEventsFlagsToString(server.notify_keyspace_events));
1019 
1020         addReplyBulkCString(c,"notify-keyspace-events");
1021         addReplyBulk(c,flagsobj);
1022         decrRefCount(flagsobj);
1023         matches++;
1024     }
1025     setDeferredMultiBulkLength(c,replylen,matches*2);
1026 }
1027 
1028 void configCommand(redisClient *c) {
1029     if (!strcasecmp(c->argv[1]->ptr,"set")) {
1030         if (c->argc != 4) goto badarity;
1031         configSetCommand(c);
1032     } else if (!strcasecmp(c->argv[1]->ptr,"get")) {
1033         if (c->argc != 3) goto badarity;
1034         configGetCommand(c);
1035     } else if (!strcasecmp(c->argv[1]->ptr,"resetstat")) {
1036         if (c->argc != 2) goto badarity;
1037         server.stat_keyspace_hits = 0;
1038         server.stat_keyspace_misses = 0;
1039         server.stat_numcommands = 0;
1040         server.stat_numconnections = 0;
1041         server.stat_expiredkeys = 0;
1042         server.stat_rejected_conn = 0;
1043         server.stat_fork_time = 0;
1044         server.aof_delayed_fsync = 0;
1045         resetCommandTableStats();
1046         addReply(c,shared.ok);
1047     } else {
1048         addReplyError(c,
1049             "CONFIG subcommand must be one of GET, SET, RESETSTAT");
1050     }
1051     return;
1052 
1053 badarity:
1054     addReplyErrorFormat(c,"Wrong number of arguments for CONFIG %s",
1055         (char*) c->argv[1]->ptr);
1056 }
1057