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 | #include "server.h" |
32 | #include "cluster.h" |
33 | |
34 | #include <fcntl.h> |
35 | #include <sys/stat.h> |
36 | #include <glob.h> |
37 | #include <string.h> |
38 | |
39 | /*----------------------------------------------------------------------------- |
40 | * Config file name-value maps. |
41 | *----------------------------------------------------------------------------*/ |
42 | |
43 | typedef struct deprecatedConfig { |
44 | const char *name; |
45 | const int argc_min; |
46 | const int argc_max; |
47 | } deprecatedConfig; |
48 | |
49 | configEnum maxmemory_policy_enum[] = { |
50 | {"volatile-lru" , MAXMEMORY_VOLATILE_LRU}, |
51 | {"volatile-lfu" , MAXMEMORY_VOLATILE_LFU}, |
52 | {"volatile-random" ,MAXMEMORY_VOLATILE_RANDOM}, |
53 | {"volatile-ttl" ,MAXMEMORY_VOLATILE_TTL}, |
54 | {"allkeys-lru" ,MAXMEMORY_ALLKEYS_LRU}, |
55 | {"allkeys-lfu" ,MAXMEMORY_ALLKEYS_LFU}, |
56 | {"allkeys-random" ,MAXMEMORY_ALLKEYS_RANDOM}, |
57 | {"noeviction" ,MAXMEMORY_NO_EVICTION}, |
58 | {NULL, 0} |
59 | }; |
60 | |
61 | configEnum syslog_facility_enum[] = { |
62 | {"user" , LOG_USER}, |
63 | {"local0" , LOG_LOCAL0}, |
64 | {"local1" , LOG_LOCAL1}, |
65 | {"local2" , LOG_LOCAL2}, |
66 | {"local3" , LOG_LOCAL3}, |
67 | {"local4" , LOG_LOCAL4}, |
68 | {"local5" , LOG_LOCAL5}, |
69 | {"local6" , LOG_LOCAL6}, |
70 | {"local7" , LOG_LOCAL7}, |
71 | {NULL, 0} |
72 | }; |
73 | |
74 | configEnum loglevel_enum[] = { |
75 | {"debug" , LL_DEBUG}, |
76 | {"verbose" , LL_VERBOSE}, |
77 | {"notice" , LL_NOTICE}, |
78 | {"warning" , LL_WARNING}, |
79 | {NULL,0} |
80 | }; |
81 | |
82 | configEnum supervised_mode_enum[] = { |
83 | {"upstart" , SUPERVISED_UPSTART}, |
84 | {"systemd" , SUPERVISED_SYSTEMD}, |
85 | {"auto" , SUPERVISED_AUTODETECT}, |
86 | {"no" , SUPERVISED_NONE}, |
87 | {NULL, 0} |
88 | }; |
89 | |
90 | configEnum aof_fsync_enum[] = { |
91 | {"everysec" , AOF_FSYNC_EVERYSEC}, |
92 | {"always" , AOF_FSYNC_ALWAYS}, |
93 | {"no" , AOF_FSYNC_NO}, |
94 | {NULL, 0} |
95 | }; |
96 | |
97 | configEnum shutdown_on_sig_enum[] = { |
98 | {"default" , 0}, |
99 | {"save" , SHUTDOWN_SAVE}, |
100 | {"nosave" , SHUTDOWN_NOSAVE}, |
101 | {"now" , SHUTDOWN_NOW}, |
102 | {"force" , SHUTDOWN_FORCE}, |
103 | {NULL, 0} |
104 | }; |
105 | |
106 | configEnum repl_diskless_load_enum[] = { |
107 | {"disabled" , REPL_DISKLESS_LOAD_DISABLED}, |
108 | {"on-empty-db" , REPL_DISKLESS_LOAD_WHEN_DB_EMPTY}, |
109 | {"swapdb" , REPL_DISKLESS_LOAD_SWAPDB}, |
110 | {NULL, 0} |
111 | }; |
112 | |
113 | configEnum tls_auth_clients_enum[] = { |
114 | {"no" , TLS_CLIENT_AUTH_NO}, |
115 | {"yes" , TLS_CLIENT_AUTH_YES}, |
116 | {"optional" , TLS_CLIENT_AUTH_OPTIONAL}, |
117 | {NULL, 0} |
118 | }; |
119 | |
120 | configEnum oom_score_adj_enum[] = { |
121 | {"no" , OOM_SCORE_ADJ_NO}, |
122 | {"yes" , OOM_SCORE_RELATIVE}, |
123 | {"relative" , OOM_SCORE_RELATIVE}, |
124 | {"absolute" , OOM_SCORE_ADJ_ABSOLUTE}, |
125 | {NULL, 0} |
126 | }; |
127 | |
128 | configEnum acl_pubsub_default_enum[] = { |
129 | {"allchannels" , SELECTOR_FLAG_ALLCHANNELS}, |
130 | {"resetchannels" , 0}, |
131 | {NULL, 0} |
132 | }; |
133 | |
134 | configEnum sanitize_dump_payload_enum[] = { |
135 | {"no" , SANITIZE_DUMP_NO}, |
136 | {"yes" , SANITIZE_DUMP_YES}, |
137 | {"clients" , SANITIZE_DUMP_CLIENTS}, |
138 | {NULL, 0} |
139 | }; |
140 | |
141 | configEnum protected_action_enum[] = { |
142 | {"no" , PROTECTED_ACTION_ALLOWED_NO}, |
143 | {"yes" , PROTECTED_ACTION_ALLOWED_YES}, |
144 | {"local" , PROTECTED_ACTION_ALLOWED_LOCAL}, |
145 | {NULL, 0} |
146 | }; |
147 | |
148 | configEnum cluster_preferred_endpoint_type_enum[] = { |
149 | {"ip" , CLUSTER_ENDPOINT_TYPE_IP}, |
150 | {"hostname" , CLUSTER_ENDPOINT_TYPE_HOSTNAME}, |
151 | {"unknown-endpoint" , CLUSTER_ENDPOINT_TYPE_UNKNOWN_ENDPOINT}, |
152 | {NULL, 0} |
153 | }; |
154 | |
155 | configEnum propagation_error_behavior_enum[] = { |
156 | {"ignore" , PROPAGATION_ERR_BEHAVIOR_IGNORE}, |
157 | {"panic" , PROPAGATION_ERR_BEHAVIOR_PANIC}, |
158 | {"panic-on-replicas" , PROPAGATION_ERR_BEHAVIOR_PANIC_ON_REPLICAS}, |
159 | {NULL, 0} |
160 | }; |
161 | |
162 | /* Output buffer limits presets. */ |
163 | clientBufferLimitsConfig clientBufferLimitsDefaults[CLIENT_TYPE_OBUF_COUNT] = { |
164 | {0, 0, 0}, /* normal */ |
165 | {1024*1024*256, 1024*1024*64, 60}, /* slave */ |
166 | {1024*1024*32, 1024*1024*8, 60} /* pubsub */ |
167 | }; |
168 | |
169 | /* OOM Score defaults */ |
170 | int configOOMScoreAdjValuesDefaults[CONFIG_OOM_COUNT] = { 0, 200, 800 }; |
171 | |
172 | /* Generic config infrastructure function pointers |
173 | * int is_valid_fn(val, err) |
174 | * Return 1 when val is valid, and 0 when invalid. |
175 | * Optionally set err to a static error string. |
176 | */ |
177 | |
178 | /* Configuration values that require no special handling to set, get, load or |
179 | * rewrite. */ |
180 | typedef struct boolConfigData { |
181 | int *config; /* The pointer to the server config this value is stored in */ |
182 | int default_value; /* The default value of the config on rewrite */ |
183 | int (*is_valid_fn)(int val, const char **err); /* Optional function to check validity of new value (generic doc above) */ |
184 | } boolConfigData; |
185 | |
186 | typedef struct stringConfigData { |
187 | char **config; /* Pointer to the server config this value is stored in. */ |
188 | const char *default_value; /* Default value of the config on rewrite. */ |
189 | int (*is_valid_fn)(char* val, const char **err); /* Optional function to check validity of new value (generic doc above) */ |
190 | int convert_empty_to_null; /* Boolean indicating if empty strings should |
191 | be stored as a NULL value. */ |
192 | } stringConfigData; |
193 | |
194 | typedef struct sdsConfigData { |
195 | sds *config; /* Pointer to the server config this value is stored in. */ |
196 | char *default_value; /* Default value of the config on rewrite. */ |
197 | int (*is_valid_fn)(sds val, const char **err); /* Optional function to check validity of new value (generic doc above) */ |
198 | int convert_empty_to_null; /* Boolean indicating if empty SDS strings should |
199 | be stored as a NULL value. */ |
200 | } sdsConfigData; |
201 | |
202 | typedef struct enumConfigData { |
203 | int *config; /* The pointer to the server config this value is stored in */ |
204 | configEnum *enum_value; /* The underlying enum type this data represents */ |
205 | int default_value; /* The default value of the config on rewrite */ |
206 | int (*is_valid_fn)(int val, const char **err); /* Optional function to check validity of new value (generic doc above) */ |
207 | } enumConfigData; |
208 | |
209 | typedef enum numericType { |
210 | NUMERIC_TYPE_INT, |
211 | NUMERIC_TYPE_UINT, |
212 | NUMERIC_TYPE_LONG, |
213 | NUMERIC_TYPE_ULONG, |
214 | NUMERIC_TYPE_LONG_LONG, |
215 | NUMERIC_TYPE_ULONG_LONG, |
216 | NUMERIC_TYPE_SIZE_T, |
217 | NUMERIC_TYPE_SSIZE_T, |
218 | NUMERIC_TYPE_OFF_T, |
219 | NUMERIC_TYPE_TIME_T, |
220 | } numericType; |
221 | |
222 | typedef struct numericConfigData { |
223 | union { |
224 | int *i; |
225 | unsigned int *ui; |
226 | long *l; |
227 | unsigned long *ul; |
228 | long long *ll; |
229 | unsigned long long *ull; |
230 | size_t *st; |
231 | ssize_t *sst; |
232 | off_t *ot; |
233 | time_t *tt; |
234 | } config; /* The pointer to the numeric config this value is stored in */ |
235 | unsigned int flags; |
236 | numericType numeric_type; /* An enum indicating the type of this value */ |
237 | long long lower_bound; /* The lower bound of this numeric value */ |
238 | long long upper_bound; /* The upper bound of this numeric value */ |
239 | long long default_value; /* The default value of the config on rewrite */ |
240 | int (*is_valid_fn)(long long val, const char **err); /* Optional function to check validity of new value (generic doc above) */ |
241 | } numericConfigData; |
242 | |
243 | typedef union typeData { |
244 | boolConfigData yesno; |
245 | stringConfigData string; |
246 | sdsConfigData sds; |
247 | enumConfigData enumd; |
248 | numericConfigData numeric; |
249 | } typeData; |
250 | |
251 | typedef struct standardConfig standardConfig; |
252 | |
253 | typedef int (*apply_fn)(const char **err); |
254 | typedef struct typeInterface { |
255 | /* Called on server start, to init the server with default value */ |
256 | void (*init)(standardConfig *config); |
257 | /* Called on server startup and CONFIG SET, returns 1 on success, |
258 | * 2 meaning no actual change done, 0 on error and can set a verbose err |
259 | * string */ |
260 | int (*set)(standardConfig *config, sds *argv, int argc, const char **err); |
261 | /* Optional: called after `set()` to apply the config change. Used only in |
262 | * the context of CONFIG SET. Returns 1 on success, 0 on failure. |
263 | * Optionally set err to a static error string. */ |
264 | apply_fn apply; |
265 | /* Called on CONFIG GET, returns sds to be used in reply */ |
266 | sds (*get)(standardConfig *config); |
267 | /* Called on CONFIG REWRITE, required to rewrite the config state */ |
268 | void (*rewrite)(standardConfig *config, const char *name, struct rewriteConfigState *state); |
269 | } typeInterface; |
270 | |
271 | struct standardConfig { |
272 | const char *name; /* The user visible name of this config */ |
273 | const char *alias; /* An alias that can also be used for this config */ |
274 | unsigned int flags; /* Flags for this specific config */ |
275 | typeInterface interface; /* The function pointers that define the type interface */ |
276 | typeData data; /* The type specific data exposed used by the interface */ |
277 | configType type; /* The type of config this is. */ |
278 | void *privdata; /* privdata for this config, for module configs this is a ModuleConfig struct */ |
279 | }; |
280 | |
281 | dict *configs = NULL; /* Runtime config values */ |
282 | |
283 | /* Lookup a config by the provided sds string name, or return NULL |
284 | * if the config does not exist */ |
285 | static standardConfig *lookupConfig(sds name) { |
286 | dictEntry *de = dictFind(configs, name); |
287 | return de ? dictGetVal(de) : NULL; |
288 | } |
289 | |
290 | /*----------------------------------------------------------------------------- |
291 | * Enum access functions |
292 | *----------------------------------------------------------------------------*/ |
293 | |
294 | /* Get enum value from name. If there is no match INT_MIN is returned. */ |
295 | int configEnumGetValue(configEnum *ce, sds *argv, int argc, int bitflags) { |
296 | if (argc == 0 || (!bitflags && argc != 1)) return INT_MIN; |
297 | int values = 0; |
298 | for (int i = 0; i < argc; i++) { |
299 | int matched = 0; |
300 | for (configEnum *ceItem = ce; ceItem->name != NULL; ceItem++) { |
301 | if (!strcasecmp(argv[i],ceItem->name)) { |
302 | values |= ceItem->val; |
303 | matched = 1; |
304 | } |
305 | } |
306 | if (!matched) return INT_MIN; |
307 | } |
308 | return values; |
309 | } |
310 | |
311 | /* Get enum name/s from value. If no matches are found "unknown" is returned. */ |
312 | static sds configEnumGetName(configEnum *ce, int values, int bitflags) { |
313 | sds names = NULL; |
314 | int unmatched = values; |
315 | for( ; ce->name != NULL; ce++) { |
316 | if (values == ce->val) { /* Short path for perfect match */ |
317 | sdsfree(names); |
318 | return sdsnew(ce->name); |
319 | } |
320 | |
321 | /* Note: for bitflags, we want them sorted from high to low, so that if there are several / partially |
322 | * overlapping entries, we'll prefer the ones matching more bits. */ |
323 | if (bitflags && ce->val && ce->val == (unmatched & ce->val)) { |
324 | names = names ? sdscatfmt(names, " %s" , ce->name) : sdsnew(ce->name); |
325 | unmatched &= ~ce->val; |
326 | } |
327 | } |
328 | if (!names || unmatched) { |
329 | sdsfree(names); |
330 | return sdsnew("unknown" ); |
331 | } |
332 | return names; |
333 | } |
334 | |
335 | /* Used for INFO generation. */ |
336 | const char *evictPolicyToString(void) { |
337 | for (configEnum *ce = maxmemory_policy_enum; ce->name != NULL; ce++) { |
338 | if (server.maxmemory_policy == ce->val) |
339 | return ce->name; |
340 | } |
341 | serverPanic("unknown eviction policy" ); |
342 | } |
343 | |
344 | /*----------------------------------------------------------------------------- |
345 | * Config file parsing |
346 | *----------------------------------------------------------------------------*/ |
347 | |
348 | int yesnotoi(char *s) { |
349 | if (!strcasecmp(s,"yes" )) return 1; |
350 | else if (!strcasecmp(s,"no" )) return 0; |
351 | else return -1; |
352 | } |
353 | |
354 | void appendServerSaveParams(time_t seconds, int changes) { |
355 | server.saveparams = zrealloc(server.saveparams,sizeof(struct saveparam)*(server.saveparamslen+1)); |
356 | server.saveparams[server.saveparamslen].seconds = seconds; |
357 | server.saveparams[server.saveparamslen].changes = changes; |
358 | server.saveparamslen++; |
359 | } |
360 | |
361 | void resetServerSaveParams(void) { |
362 | zfree(server.saveparams); |
363 | server.saveparams = NULL; |
364 | server.saveparamslen = 0; |
365 | } |
366 | |
367 | void queueLoadModule(sds path, sds *argv, int argc) { |
368 | int i; |
369 | struct moduleLoadQueueEntry *loadmod; |
370 | |
371 | loadmod = zmalloc(sizeof(struct moduleLoadQueueEntry)); |
372 | loadmod->argv = argc ? zmalloc(sizeof(robj*)*argc) : NULL; |
373 | loadmod->path = sdsnew(path); |
374 | loadmod->argc = argc; |
375 | for (i = 0; i < argc; i++) { |
376 | loadmod->argv[i] = createRawStringObject(argv[i],sdslen(argv[i])); |
377 | } |
378 | listAddNodeTail(server.loadmodule_queue,loadmod); |
379 | } |
380 | |
381 | /* Parse an array of `arg_len` sds strings, validate and populate |
382 | * server.client_obuf_limits if valid. |
383 | * Used in CONFIG SET and configuration file parsing. */ |
384 | static int updateClientOutputBufferLimit(sds *args, int arg_len, const char **err) { |
385 | int j; |
386 | int class; |
387 | unsigned long long hard, soft; |
388 | int hard_err, soft_err; |
389 | int soft_seconds; |
390 | char *soft_seconds_eptr; |
391 | clientBufferLimitsConfig values[CLIENT_TYPE_OBUF_COUNT]; |
392 | int classes[CLIENT_TYPE_OBUF_COUNT] = {0}; |
393 | |
394 | /* We need a multiple of 4: <class> <hard> <soft> <soft_seconds> */ |
395 | if (arg_len % 4) { |
396 | if (err) *err = "Wrong number of arguments in " |
397 | "buffer limit configuration." ; |
398 | return 0; |
399 | } |
400 | |
401 | /* Sanity check of single arguments, so that we either refuse the |
402 | * whole configuration string or accept it all, even if a single |
403 | * error in a single client class is present. */ |
404 | for (j = 0; j < arg_len; j += 4) { |
405 | class = getClientTypeByName(args[j]); |
406 | if (class == -1 || class == CLIENT_TYPE_MASTER) { |
407 | if (err) *err = "Invalid client class specified in " |
408 | "buffer limit configuration." ; |
409 | return 0; |
410 | } |
411 | |
412 | hard = memtoull(args[j+1], &hard_err); |
413 | soft = memtoull(args[j+2], &soft_err); |
414 | soft_seconds = strtoll(args[j+3], &soft_seconds_eptr, 10); |
415 | if (hard_err || soft_err || |
416 | soft_seconds < 0 || *soft_seconds_eptr != '\0') |
417 | { |
418 | if (err) *err = "Error in hard, soft or soft_seconds setting in " |
419 | "buffer limit configuration." ; |
420 | return 0; |
421 | } |
422 | |
423 | values[class].hard_limit_bytes = hard; |
424 | values[class].soft_limit_bytes = soft; |
425 | values[class].soft_limit_seconds = soft_seconds; |
426 | classes[class] = 1; |
427 | } |
428 | |
429 | /* Finally set the new config. */ |
430 | for (j = 0; j < CLIENT_TYPE_OBUF_COUNT; j++) { |
431 | if (classes[j]) server.client_obuf_limits[j] = values[j]; |
432 | } |
433 | |
434 | return 1; |
435 | } |
436 | |
437 | /* Note this is here to support detecting we're running a config set from |
438 | * within conf file parsing. This is only needed to support the deprecated |
439 | * abnormal aggregate `save T C` functionality. Remove in the future. */ |
440 | static int reading_config_file; |
441 | |
442 | void loadServerConfigFromString(char *config) { |
443 | deprecatedConfig deprecated_configs[] = { |
444 | {"list-max-ziplist-entries" , 2, 2}, |
445 | {"list-max-ziplist-value" , 2, 2}, |
446 | {"lua-replicate-commands" , 2, 2}, |
447 | {NULL, 0}, |
448 | }; |
449 | char buf[1024]; |
450 | const char *err = NULL; |
451 | int linenum = 0, totlines, i; |
452 | sds *lines; |
453 | |
454 | reading_config_file = 1; |
455 | lines = sdssplitlen(config,strlen(config),"\n" ,1,&totlines); |
456 | |
457 | for (i = 0; i < totlines; i++) { |
458 | sds *argv; |
459 | int argc; |
460 | |
461 | linenum = i+1; |
462 | lines[i] = sdstrim(lines[i]," \t\r\n" ); |
463 | |
464 | /* Skip comments and blank lines */ |
465 | if (lines[i][0] == '#' || lines[i][0] == '\0') continue; |
466 | |
467 | /* Split into arguments */ |
468 | argv = sdssplitargs(lines[i],&argc); |
469 | if (argv == NULL) { |
470 | err = "Unbalanced quotes in configuration line" ; |
471 | goto loaderr; |
472 | } |
473 | |
474 | /* Skip this line if the resulting command vector is empty. */ |
475 | if (argc == 0) { |
476 | sdsfreesplitres(argv,argc); |
477 | continue; |
478 | } |
479 | sdstolower(argv[0]); |
480 | |
481 | /* Iterate the configs that are standard */ |
482 | standardConfig *config = lookupConfig(argv[0]); |
483 | if (config) { |
484 | /* For normal single arg configs enforce we have a single argument. |
485 | * Note that MULTI_ARG_CONFIGs need to validate arg count on their own */ |
486 | if (!(config->flags & MULTI_ARG_CONFIG) && argc != 2) { |
487 | err = "wrong number of arguments" ; |
488 | goto loaderr; |
489 | } |
490 | |
491 | if ((config->flags & MULTI_ARG_CONFIG) && argc == 2 && sdslen(argv[1])) { |
492 | /* For MULTI_ARG_CONFIGs, if we only have one argument, try to split it by spaces. |
493 | * Only if the argument is not empty, otherwise something like --save "" will fail. |
494 | * So that we can support something like --config "arg1 arg2 arg3". */ |
495 | sds *new_argv; |
496 | int new_argc; |
497 | new_argv = sdssplitargs(argv[1], &new_argc); |
498 | if (!config->interface.set(config, new_argv, new_argc, &err)) { |
499 | goto loaderr; |
500 | } |
501 | sdsfreesplitres(new_argv, new_argc); |
502 | } else { |
503 | /* Set config using all arguments that follows */ |
504 | if (!config->interface.set(config, &argv[1], argc-1, &err)) { |
505 | goto loaderr; |
506 | } |
507 | } |
508 | |
509 | sdsfreesplitres(argv,argc); |
510 | continue; |
511 | } else { |
512 | int match = 0; |
513 | for (deprecatedConfig *config = deprecated_configs; config->name != NULL; config++) { |
514 | if (!strcasecmp(argv[0], config->name) && |
515 | config->argc_min <= argc && |
516 | argc <= config->argc_max) |
517 | { |
518 | match = 1; |
519 | break; |
520 | } |
521 | } |
522 | if (match) { |
523 | sdsfreesplitres(argv,argc); |
524 | continue; |
525 | } |
526 | } |
527 | |
528 | /* Execute config directives */ |
529 | if (!strcasecmp(argv[0],"include" ) && argc == 2) { |
530 | loadServerConfig(argv[1], 0, NULL); |
531 | } else if (!strcasecmp(argv[0],"rename-command" ) && argc == 3) { |
532 | struct redisCommand *cmd = lookupCommandBySds(argv[1]); |
533 | int retval; |
534 | |
535 | if (!cmd) { |
536 | err = "No such command in rename-command" ; |
537 | goto loaderr; |
538 | } |
539 | |
540 | /* If the target command name is the empty string we just |
541 | * remove it from the command table. */ |
542 | retval = dictDelete(server.commands, argv[1]); |
543 | serverAssert(retval == DICT_OK); |
544 | |
545 | /* Otherwise we re-add the command under a different name. */ |
546 | if (sdslen(argv[2]) != 0) { |
547 | sds copy = sdsdup(argv[2]); |
548 | |
549 | retval = dictAdd(server.commands, copy, cmd); |
550 | if (retval != DICT_OK) { |
551 | sdsfree(copy); |
552 | err = "Target command name already exists" ; goto loaderr; |
553 | } |
554 | } |
555 | } else if (!strcasecmp(argv[0],"user" ) && argc >= 2) { |
556 | int argc_err; |
557 | if (ACLAppendUserForLoading(argv,argc,&argc_err) == C_ERR) { |
558 | const char *errmsg = ACLSetUserStringError(); |
559 | snprintf(buf,sizeof(buf),"Error in user declaration '%s': %s" , |
560 | argv[argc_err],errmsg); |
561 | err = buf; |
562 | goto loaderr; |
563 | } |
564 | } else if (!strcasecmp(argv[0],"loadmodule" ) && argc >= 2) { |
565 | queueLoadModule(argv[1],&argv[2],argc-2); |
566 | } else if (strchr(argv[0], '.')) { |
567 | if (argc < 2) { |
568 | err = "Module config specified without value" ; |
569 | goto loaderr; |
570 | } |
571 | sds name = sdsdup(argv[0]); |
572 | sds val = sdsdup(argv[1]); |
573 | for (int i = 2; i < argc; i++) |
574 | val = sdscatfmt(val, " %S" , argv[i]); |
575 | if (!dictReplace(server.module_configs_queue, name, val)) sdsfree(name); |
576 | } else if (!strcasecmp(argv[0],"sentinel" )) { |
577 | /* argc == 1 is handled by main() as we need to enter the sentinel |
578 | * mode ASAP. */ |
579 | if (argc != 1) { |
580 | if (!server.sentinel_mode) { |
581 | err = "sentinel directive while not in sentinel mode" ; |
582 | goto loaderr; |
583 | } |
584 | queueSentinelConfig(argv+1,argc-1,linenum,lines[i]); |
585 | } |
586 | } else { |
587 | err = "Bad directive or wrong number of arguments" ; goto loaderr; |
588 | } |
589 | sdsfreesplitres(argv,argc); |
590 | } |
591 | |
592 | if (server.logfile[0] != '\0') { |
593 | FILE *logfp; |
594 | |
595 | /* Test if we are able to open the file. The server will not |
596 | * be able to abort just for this problem later... */ |
597 | logfp = fopen(server.logfile,"a" ); |
598 | if (logfp == NULL) { |
599 | err = sdscatprintf(sdsempty(), |
600 | "Can't open the log file: %s" , strerror(errno)); |
601 | goto loaderr; |
602 | } |
603 | fclose(logfp); |
604 | } |
605 | |
606 | /* Sanity checks. */ |
607 | if (server.cluster_enabled && server.masterhost) { |
608 | err = "replicaof directive not allowed in cluster mode" ; |
609 | goto loaderr; |
610 | } |
611 | |
612 | /* To ensure backward compatibility and work while hz is out of range */ |
613 | if (server.config_hz < CONFIG_MIN_HZ) server.config_hz = CONFIG_MIN_HZ; |
614 | if (server.config_hz > CONFIG_MAX_HZ) server.config_hz = CONFIG_MAX_HZ; |
615 | |
616 | sdsfreesplitres(lines,totlines); |
617 | reading_config_file = 0; |
618 | return; |
619 | |
620 | loaderr: |
621 | fprintf(stderr, "\n*** FATAL CONFIG FILE ERROR (Redis %s) ***\n" , |
622 | REDIS_VERSION); |
623 | if (i < totlines) { |
624 | fprintf(stderr, "Reading the configuration file, at line %d\n" , linenum); |
625 | fprintf(stderr, ">>> '%s'\n" , lines[i]); |
626 | } |
627 | fprintf(stderr, "%s\n" , err); |
628 | exit(1); |
629 | } |
630 | |
631 | /* Load the server configuration from the specified filename. |
632 | * The function appends the additional configuration directives stored |
633 | * in the 'options' string to the config file before loading. |
634 | * |
635 | * Both filename and options can be NULL, in such a case are considered |
636 | * empty. This way loadServerConfig can be used to just load a file or |
637 | * just load a string. */ |
638 | #define CONFIG_READ_LEN 1024 |
639 | void loadServerConfig(char *filename, char config_from_stdin, char *options) { |
640 | sds config = sdsempty(); |
641 | char buf[CONFIG_READ_LEN+1]; |
642 | FILE *fp; |
643 | glob_t globbuf; |
644 | |
645 | /* Load the file content */ |
646 | if (filename) { |
647 | |
648 | /* The logic for handling wildcards has slightly different behavior in cases where |
649 | * there is a failure to locate the included file. |
650 | * Whether or not a wildcard is specified, we should ALWAYS log errors when attempting |
651 | * to open included config files. |
652 | * |
653 | * However, we desire a behavioral difference between instances where a wildcard was |
654 | * specified and those where it hasn't: |
655 | * no wildcards : attempt to open the specified file and fail with a logged error |
656 | * if the file cannot be found and opened. |
657 | * with wildcards : attempt to glob the specified pattern; if no files match the |
658 | * pattern, then gracefully continue on to the next entry in the |
659 | * config file, as if the current entry was never encountered. |
660 | * This will allow for empty conf.d directories to be included. */ |
661 | |
662 | if (strchr(filename, '*') || strchr(filename, '?') || strchr(filename, '[')) { |
663 | /* A wildcard character detected in filename, so let us use glob */ |
664 | if (glob(filename, 0, NULL, &globbuf) == 0) { |
665 | |
666 | for (size_t i = 0; i < globbuf.gl_pathc; i++) { |
667 | if ((fp = fopen(globbuf.gl_pathv[i], "r" )) == NULL) { |
668 | serverLog(LL_WARNING, |
669 | "Fatal error, can't open config file '%s': %s" , |
670 | globbuf.gl_pathv[i], strerror(errno)); |
671 | exit(1); |
672 | } |
673 | while(fgets(buf,CONFIG_READ_LEN+1,fp) != NULL) |
674 | config = sdscat(config,buf); |
675 | fclose(fp); |
676 | } |
677 | |
678 | globfree(&globbuf); |
679 | } |
680 | } else { |
681 | /* No wildcard in filename means we can use the original logic to read and |
682 | * potentially fail traditionally */ |
683 | if ((fp = fopen(filename, "r" )) == NULL) { |
684 | serverLog(LL_WARNING, |
685 | "Fatal error, can't open config file '%s': %s" , |
686 | filename, strerror(errno)); |
687 | exit(1); |
688 | } |
689 | while(fgets(buf,CONFIG_READ_LEN+1,fp) != NULL) |
690 | config = sdscat(config,buf); |
691 | fclose(fp); |
692 | } |
693 | } |
694 | |
695 | /* Append content from stdin */ |
696 | if (config_from_stdin) { |
697 | serverLog(LL_WARNING,"Reading config from stdin" ); |
698 | fp = stdin; |
699 | while(fgets(buf,CONFIG_READ_LEN+1,fp) != NULL) |
700 | config = sdscat(config,buf); |
701 | } |
702 | |
703 | /* Append the additional options */ |
704 | if (options) { |
705 | config = sdscat(config,"\n" ); |
706 | config = sdscat(config,options); |
707 | } |
708 | loadServerConfigFromString(config); |
709 | sdsfree(config); |
710 | } |
711 | |
712 | static int performInterfaceSet(standardConfig *config, sds value, const char **errstr) { |
713 | sds *argv; |
714 | int argc, res; |
715 | |
716 | if (config->flags & MULTI_ARG_CONFIG) { |
717 | argv = sdssplitlen(value, sdslen(value), " " , 1, &argc); |
718 | } else { |
719 | argv = (char**)&value; |
720 | argc = 1; |
721 | } |
722 | |
723 | /* Set the config */ |
724 | res = config->interface.set(config, argv, argc, errstr); |
725 | if (config->flags & MULTI_ARG_CONFIG) sdsfreesplitres(argv, argc); |
726 | return res; |
727 | } |
728 | |
729 | /* Find the config by name and attempt to set it to value. */ |
730 | int performModuleConfigSetFromName(sds name, sds value, const char **err) { |
731 | standardConfig *config = lookupConfig(name); |
732 | if (!config || !(config->flags & MODULE_CONFIG)) { |
733 | *err = "Config name not found" ; |
734 | return 0; |
735 | } |
736 | return performInterfaceSet(config, value, err); |
737 | } |
738 | |
739 | /* Find config by name and attempt to set it to its default value. */ |
740 | int performModuleConfigSetDefaultFromName(sds name, const char **err) { |
741 | standardConfig *config = lookupConfig(name); |
742 | serverAssert(config); |
743 | if (!(config->flags & MODULE_CONFIG)) { |
744 | *err = "Config name not found" ; |
745 | return 0; |
746 | } |
747 | switch (config->type) { |
748 | case BOOL_CONFIG: |
749 | return setModuleBoolConfig(config->privdata, config->data.yesno.default_value, err); |
750 | case SDS_CONFIG: |
751 | return setModuleStringConfig(config->privdata, config->data.sds.default_value, err); |
752 | case NUMERIC_CONFIG: |
753 | return setModuleNumericConfig(config->privdata, config->data.numeric.default_value, err); |
754 | case ENUM_CONFIG: |
755 | return setModuleEnumConfig(config->privdata, config->data.enumd.default_value, err); |
756 | default: |
757 | serverPanic("Config type of module config is not allowed." ); |
758 | } |
759 | return 0; |
760 | } |
761 | |
762 | static void restoreBackupConfig(standardConfig **set_configs, sds *old_values, int count, apply_fn *apply_fns, list *module_configs) { |
763 | int i; |
764 | const char *errstr = "unknown error" ; |
765 | /* Set all backup values */ |
766 | for (i = 0; i < count; i++) { |
767 | if (!performInterfaceSet(set_configs[i], old_values[i], &errstr)) |
768 | serverLog(LL_WARNING, "Failed restoring failed CONFIG SET command. Error setting %s to '%s': %s" , |
769 | set_configs[i]->name, old_values[i], errstr); |
770 | } |
771 | /* Apply backup */ |
772 | if (apply_fns) { |
773 | for (i = 0; i < count && apply_fns[i] != NULL; i++) { |
774 | if (!apply_fns[i](&errstr)) |
775 | serverLog(LL_WARNING, "Failed applying restored failed CONFIG SET command: %s" , errstr); |
776 | } |
777 | } |
778 | if (module_configs) { |
779 | if (!moduleConfigApplyConfig(module_configs, &errstr, NULL)) |
780 | serverLog(LL_WARNING, "Failed applying restored failed CONFIG SET command: %s" , errstr); |
781 | } |
782 | } |
783 | |
784 | /*----------------------------------------------------------------------------- |
785 | * CONFIG SET implementation |
786 | *----------------------------------------------------------------------------*/ |
787 | |
788 | void configSetCommand(client *c) { |
789 | const char *errstr = NULL; |
790 | const char *invalid_arg_name = NULL; |
791 | const char *err_arg_name = NULL; |
792 | standardConfig **set_configs; /* TODO: make this a dict for better performance */ |
793 | list *module_configs_apply; |
794 | const char **config_names; |
795 | sds *new_values; |
796 | sds *old_values = NULL; |
797 | apply_fn *apply_fns; /* TODO: make this a set for better performance */ |
798 | int config_count, i, j; |
799 | int invalid_args = 0, deny_loading_error = 0; |
800 | int *config_map_fns; |
801 | |
802 | /* Make sure we have an even number of arguments: conf-val pairs */ |
803 | if (c->argc & 1) { |
804 | addReplyErrorObject(c, shared.syntaxerr); |
805 | return; |
806 | } |
807 | config_count = (c->argc - 2) / 2; |
808 | |
809 | module_configs_apply = listCreate(); |
810 | set_configs = zcalloc(sizeof(standardConfig*)*config_count); |
811 | config_names = zcalloc(sizeof(char*)*config_count); |
812 | new_values = zmalloc(sizeof(sds*)*config_count); |
813 | old_values = zcalloc(sizeof(sds*)*config_count); |
814 | apply_fns = zcalloc(sizeof(apply_fn)*config_count); |
815 | config_map_fns = zmalloc(sizeof(int)*config_count); |
816 | |
817 | /* Find all relevant configs */ |
818 | for (i = 0; i < config_count; i++) { |
819 | standardConfig *config = lookupConfig(c->argv[2+i*2]->ptr); |
820 | /* Fail if we couldn't find this config */ |
821 | if (!config) { |
822 | if (!invalid_args) { |
823 | invalid_arg_name = c->argv[2+i*2]->ptr; |
824 | invalid_args = 1; |
825 | } |
826 | continue; |
827 | } |
828 | |
829 | /* Note: it's important we run over ALL passed configs and check if we need to call `redactClientCommandArgument()`. |
830 | * This is in order to avoid anyone using this command for a log/slowlog/monitor/etc. displaying sensitive info. |
831 | * So even if we encounter an error we still continue running over the remaining arguments. */ |
832 | if (config->flags & SENSITIVE_CONFIG) { |
833 | redactClientCommandArgument(c,2+i*2+1); |
834 | } |
835 | |
836 | /* We continue to make sure we redact all the configs */ |
837 | if (invalid_args) continue; |
838 | |
839 | if (config->flags & IMMUTABLE_CONFIG || |
840 | (config->flags & PROTECTED_CONFIG && !allowProtectedAction(server.enable_protected_configs, c))) |
841 | { |
842 | /* Note: we don't abort the loop since we still want to handle redacting sensitive configs (above) */ |
843 | errstr = (config->flags & IMMUTABLE_CONFIG) ? "can't set immutable config" : "can't set protected config" ; |
844 | err_arg_name = c->argv[2+i*2]->ptr; |
845 | invalid_args = 1; |
846 | continue; |
847 | } |
848 | |
849 | if (server.loading && config->flags & DENY_LOADING_CONFIG) { |
850 | /* Note: we don't abort the loop since we still want to handle redacting sensitive configs (above) */ |
851 | deny_loading_error = 1; |
852 | invalid_args = 1; |
853 | continue; |
854 | } |
855 | |
856 | /* If this config appears twice then fail */ |
857 | for (j = 0; j < i; j++) { |
858 | if (set_configs[j] == config) { |
859 | /* Note: we don't abort the loop since we still want to handle redacting sensitive configs (above) */ |
860 | errstr = "duplicate parameter" ; |
861 | err_arg_name = c->argv[2+i*2]->ptr; |
862 | invalid_args = 1; |
863 | break; |
864 | } |
865 | } |
866 | set_configs[i] = config; |
867 | config_names[i] = config->name; |
868 | new_values[i] = c->argv[2+i*2+1]->ptr; |
869 | } |
870 | |
871 | if (invalid_args) goto err; |
872 | |
873 | /* Backup old values before setting new ones */ |
874 | for (i = 0; i < config_count; i++) |
875 | old_values[i] = set_configs[i]->interface.get(set_configs[i]); |
876 | |
877 | /* Set all new values (don't apply yet) */ |
878 | for (i = 0; i < config_count; i++) { |
879 | int res = performInterfaceSet(set_configs[i], new_values[i], &errstr); |
880 | if (!res) { |
881 | restoreBackupConfig(set_configs, old_values, i+1, NULL, NULL); |
882 | err_arg_name = set_configs[i]->name; |
883 | goto err; |
884 | } else if (res == 1) { |
885 | /* A new value was set, if this config has an apply function then store it for execution later */ |
886 | if (set_configs[i]->flags & MODULE_CONFIG) { |
887 | addModuleConfigApply(module_configs_apply, set_configs[i]->privdata); |
888 | } else if (set_configs[i]->interface.apply) { |
889 | /* Check if this apply function is already stored */ |
890 | int exists = 0; |
891 | for (j = 0; apply_fns[j] != NULL && j <= i; j++) { |
892 | if (apply_fns[j] == set_configs[i]->interface.apply) { |
893 | exists = 1; |
894 | break; |
895 | } |
896 | } |
897 | /* Apply function not stored, store it */ |
898 | if (!exists) { |
899 | apply_fns[j] = set_configs[i]->interface.apply; |
900 | config_map_fns[j] = i; |
901 | } |
902 | } |
903 | } |
904 | } |
905 | |
906 | /* Apply all configs after being set */ |
907 | for (i = 0; i < config_count && apply_fns[i] != NULL; i++) { |
908 | if (!apply_fns[i](&errstr)) { |
909 | serverLog(LL_WARNING, "Failed applying new configuration. Possibly related to new %s setting. Restoring previous settings." , set_configs[config_map_fns[i]]->name); |
910 | restoreBackupConfig(set_configs, old_values, config_count, apply_fns, NULL); |
911 | err_arg_name = set_configs[config_map_fns[i]]->name; |
912 | goto err; |
913 | } |
914 | } |
915 | /* Apply all module configs that were set. */ |
916 | if (!moduleConfigApplyConfig(module_configs_apply, &errstr, &err_arg_name)) { |
917 | serverLogRaw(LL_WARNING, "Failed applying new module configuration. Restoring previous settings." ); |
918 | restoreBackupConfig(set_configs, old_values, config_count, apply_fns, module_configs_apply); |
919 | goto err; |
920 | } |
921 | |
922 | RedisModuleConfigChangeV1 cc = {.num_changes = config_count, .config_names = config_names}; |
923 | moduleFireServerEvent(REDISMODULE_EVENT_CONFIG, REDISMODULE_SUBEVENT_CONFIG_CHANGE, &cc); |
924 | addReply(c,shared.ok); |
925 | goto end; |
926 | |
927 | err: |
928 | if (deny_loading_error) { |
929 | /* We give the loading error precedence because it may be handled by clients differently, unlike a plain -ERR. */ |
930 | addReplyErrorObject(c,shared.loadingerr); |
931 | } else if (invalid_arg_name) { |
932 | addReplyErrorFormat(c,"Unknown option or number of arguments for CONFIG SET - '%s'" , invalid_arg_name); |
933 | } else if (errstr) { |
934 | addReplyErrorFormat(c,"CONFIG SET failed (possibly related to argument '%s') - %s" , err_arg_name, errstr); |
935 | } else { |
936 | addReplyErrorFormat(c,"CONFIG SET failed (possibly related to argument '%s')" , err_arg_name); |
937 | } |
938 | end: |
939 | zfree(set_configs); |
940 | zfree(config_names); |
941 | zfree(new_values); |
942 | for (i = 0; i < config_count; i++) |
943 | sdsfree(old_values[i]); |
944 | zfree(old_values); |
945 | zfree(apply_fns); |
946 | zfree(config_map_fns); |
947 | listRelease(module_configs_apply); |
948 | } |
949 | |
950 | /*----------------------------------------------------------------------------- |
951 | * CONFIG GET implementation |
952 | *----------------------------------------------------------------------------*/ |
953 | |
954 | void configGetCommand(client *c) { |
955 | int i; |
956 | dictEntry *de; |
957 | dictIterator *di; |
958 | /* Create a dictionary to store the matched configs */ |
959 | dict *matches = dictCreate(&externalStringType); |
960 | for (i = 0; i < c->argc - 2; i++) { |
961 | robj *o = c->argv[2+i]; |
962 | sds name = o->ptr; |
963 | |
964 | /* If the string doesn't contain glob patterns, just directly |
965 | * look up the key in the dictionary. */ |
966 | if (!strpbrk(name, "[*?" )) { |
967 | if (dictFind(matches, name)) continue; |
968 | standardConfig *config = lookupConfig(name); |
969 | |
970 | if (config) { |
971 | dictAdd(matches, name, config); |
972 | } |
973 | continue; |
974 | } |
975 | |
976 | /* Otherwise, do a match against all items in the dictionary. */ |
977 | di = dictGetIterator(configs); |
978 | |
979 | while ((de = dictNext(di)) != NULL) { |
980 | standardConfig *config = dictGetVal(de); |
981 | /* Note that hidden configs require an exact match (not a pattern) */ |
982 | if (config->flags & HIDDEN_CONFIG) continue; |
983 | if (dictFind(matches, config->name)) continue; |
984 | if (stringmatch(name, de->key, 1)) { |
985 | dictAdd(matches, de->key, config); |
986 | } |
987 | } |
988 | dictReleaseIterator(di); |
989 | } |
990 | |
991 | di = dictGetIterator(matches); |
992 | addReplyMapLen(c, dictSize(matches)); |
993 | while ((de = dictNext(di)) != NULL) { |
994 | standardConfig *config = (standardConfig *) dictGetVal(de); |
995 | addReplyBulkCString(c, de->key); |
996 | addReplyBulkSds(c, config->interface.get(config)); |
997 | } |
998 | dictReleaseIterator(di); |
999 | dictRelease(matches); |
1000 | } |
1001 | |
1002 | /*----------------------------------------------------------------------------- |
1003 | * CONFIG REWRITE implementation |
1004 | *----------------------------------------------------------------------------*/ |
1005 | |
1006 | #define REDIS_CONFIG_REWRITE_SIGNATURE "# Generated by CONFIG REWRITE" |
1007 | |
1008 | /* We use the following dictionary type to store where a configuration |
1009 | * option is mentioned in the old configuration file, so it's |
1010 | * like "maxmemory" -> list of line numbers (first line is zero). */ |
1011 | void dictListDestructor(dict *d, void *val); |
1012 | |
1013 | /* Sentinel config rewriting is implemented inside sentinel.c by |
1014 | * rewriteConfigSentinelOption(). */ |
1015 | void rewriteConfigSentinelOption(struct rewriteConfigState *state); |
1016 | |
1017 | dictType optionToLineDictType = { |
1018 | dictSdsCaseHash, /* hash function */ |
1019 | NULL, /* key dup */ |
1020 | NULL, /* val dup */ |
1021 | dictSdsKeyCaseCompare, /* key compare */ |
1022 | dictSdsDestructor, /* key destructor */ |
1023 | dictListDestructor, /* val destructor */ |
1024 | NULL /* allow to expand */ |
1025 | }; |
1026 | |
1027 | dictType optionSetDictType = { |
1028 | dictSdsCaseHash, /* hash function */ |
1029 | NULL, /* key dup */ |
1030 | NULL, /* val dup */ |
1031 | dictSdsKeyCaseCompare, /* key compare */ |
1032 | dictSdsDestructor, /* key destructor */ |
1033 | NULL, /* val destructor */ |
1034 | NULL /* allow to expand */ |
1035 | }; |
1036 | |
1037 | /* The config rewrite state. */ |
1038 | struct rewriteConfigState { |
1039 | dict *option_to_line; /* Option -> list of config file lines map */ |
1040 | dict *rewritten; /* Dictionary of already processed options */ |
1041 | int numlines; /* Number of lines in current config */ |
1042 | sds *lines; /* Current lines as an array of sds strings */ |
1043 | int needs_signature; /* True if we need to append the rewrite |
1044 | signature. */ |
1045 | int force_write; /* True if we want all keywords to be force |
1046 | written. Currently only used for testing |
1047 | and debug information. */ |
1048 | }; |
1049 | |
1050 | /* Free the configuration rewrite state. */ |
1051 | void rewriteConfigReleaseState(struct rewriteConfigState *state) { |
1052 | sdsfreesplitres(state->lines,state->numlines); |
1053 | dictRelease(state->option_to_line); |
1054 | dictRelease(state->rewritten); |
1055 | zfree(state); |
1056 | } |
1057 | |
1058 | /* Create the configuration rewrite state */ |
1059 | struct rewriteConfigState *rewriteConfigCreateState() { |
1060 | struct rewriteConfigState *state = zmalloc(sizeof(*state)); |
1061 | state->option_to_line = dictCreate(&optionToLineDictType); |
1062 | state->rewritten = dictCreate(&optionSetDictType); |
1063 | state->numlines = 0; |
1064 | state->lines = NULL; |
1065 | state->needs_signature = 1; |
1066 | state->force_write = 0; |
1067 | return state; |
1068 | } |
1069 | |
1070 | /* Append the new line to the current configuration state. */ |
1071 | void rewriteConfigAppendLine(struct rewriteConfigState *state, sds line) { |
1072 | state->lines = zrealloc(state->lines, sizeof(char*) * (state->numlines+1)); |
1073 | state->lines[state->numlines++] = line; |
1074 | } |
1075 | |
1076 | /* Populate the option -> list of line numbers map. */ |
1077 | void rewriteConfigAddLineNumberToOption(struct rewriteConfigState *state, sds option, int linenum) { |
1078 | list *l = dictFetchValue(state->option_to_line,option); |
1079 | |
1080 | if (l == NULL) { |
1081 | l = listCreate(); |
1082 | dictAdd(state->option_to_line,sdsdup(option),l); |
1083 | } |
1084 | listAddNodeTail(l,(void*)(long)linenum); |
1085 | } |
1086 | |
1087 | /* Add the specified option to the set of processed options. |
1088 | * This is useful as only unused lines of processed options will be blanked |
1089 | * in the config file, while options the rewrite process does not understand |
1090 | * remain untouched. */ |
1091 | void rewriteConfigMarkAsProcessed(struct rewriteConfigState *state, const char *option) { |
1092 | sds opt = sdsnew(option); |
1093 | |
1094 | if (dictAdd(state->rewritten,opt,NULL) != DICT_OK) sdsfree(opt); |
1095 | } |
1096 | |
1097 | /* Read the old file, split it into lines to populate a newly created |
1098 | * config rewrite state, and return it to the caller. |
1099 | * |
1100 | * If it is impossible to read the old file, NULL is returned. |
1101 | * If the old file does not exist at all, an empty state is returned. */ |
1102 | struct rewriteConfigState *rewriteConfigReadOldFile(char *path) { |
1103 | FILE *fp = fopen(path,"r" ); |
1104 | if (fp == NULL && errno != ENOENT) return NULL; |
1105 | |
1106 | struct redis_stat sb; |
1107 | if (fp && redis_fstat(fileno(fp),&sb) == -1) return NULL; |
1108 | |
1109 | int linenum = -1; |
1110 | struct rewriteConfigState *state = rewriteConfigCreateState(); |
1111 | |
1112 | if (fp == NULL || sb.st_size == 0) return state; |
1113 | |
1114 | /* Load the file content */ |
1115 | sds config = sdsnewlen(SDS_NOINIT,sb.st_size); |
1116 | if (fread(config,1,sb.st_size,fp) == 0) { |
1117 | sdsfree(config); |
1118 | rewriteConfigReleaseState(state); |
1119 | fclose(fp); |
1120 | return NULL; |
1121 | } |
1122 | |
1123 | int i, totlines; |
1124 | sds *lines = sdssplitlen(config,sdslen(config),"\n" ,1,&totlines); |
1125 | |
1126 | /* Read the old content line by line, populate the state. */ |
1127 | for (i = 0; i < totlines; i++) { |
1128 | int argc; |
1129 | sds *argv; |
1130 | sds line = sdstrim(lines[i],"\r\n\t " ); |
1131 | lines[i] = NULL; |
1132 | |
1133 | linenum++; /* Zero based, so we init at -1 */ |
1134 | |
1135 | /* Handle comments and empty lines. */ |
1136 | if (line[0] == '#' || line[0] == '\0') { |
1137 | if (state->needs_signature && !strcmp(line,REDIS_CONFIG_REWRITE_SIGNATURE)) |
1138 | state->needs_signature = 0; |
1139 | rewriteConfigAppendLine(state,line); |
1140 | continue; |
1141 | } |
1142 | |
1143 | /* Not a comment, split into arguments. */ |
1144 | argv = sdssplitargs(line,&argc); |
1145 | |
1146 | if (argv == NULL || |
1147 | (!lookupConfig(argv[0]) && |
1148 | /* The following is a list of config features that are only supported in |
1149 | * config file parsing and are not recognized by lookupConfig */ |
1150 | strcasecmp(argv[0],"include" ) && |
1151 | strcasecmp(argv[0],"rename-command" ) && |
1152 | strcasecmp(argv[0],"user" ) && |
1153 | strcasecmp(argv[0],"loadmodule" ) && |
1154 | strcasecmp(argv[0],"sentinel" ))) |
1155 | { |
1156 | /* The line is either unparsable for some reason, for |
1157 | * instance it may have unbalanced quotes, may contain a |
1158 | * config that doesn't exist anymore, for instance a module that got |
1159 | * unloaded. Load it as a comment. */ |
1160 | sds aux = sdsnew("# ??? " ); |
1161 | aux = sdscatsds(aux,line); |
1162 | if (argv) sdsfreesplitres(argv, argc); |
1163 | sdsfree(line); |
1164 | rewriteConfigAppendLine(state,aux); |
1165 | continue; |
1166 | } |
1167 | |
1168 | sdstolower(argv[0]); /* We only want lowercase config directives. */ |
1169 | |
1170 | /* Now we populate the state according to the content of this line. |
1171 | * Append the line and populate the option -> line numbers map. */ |
1172 | rewriteConfigAppendLine(state,line); |
1173 | |
1174 | /* If this is a alias config, replace it with the original name. */ |
1175 | standardConfig *s_conf = lookupConfig(argv[0]); |
1176 | if (s_conf && s_conf->flags & ALIAS_CONFIG) { |
1177 | sdsfree(argv[0]); |
1178 | argv[0] = sdsnew(s_conf->alias); |
1179 | } |
1180 | |
1181 | /* If this is sentinel config, we use sentinel "sentinel <config>" as option |
1182 | to avoid messing up the sequence. */ |
1183 | if (server.sentinel_mode && argc > 1 && !strcasecmp(argv[0],"sentinel" )) { |
1184 | sds sentinelOption = sdsempty(); |
1185 | sentinelOption = sdscatfmt(sentinelOption,"%S %S" ,argv[0],argv[1]); |
1186 | rewriteConfigAddLineNumberToOption(state,sentinelOption,linenum); |
1187 | sdsfree(sentinelOption); |
1188 | } else { |
1189 | rewriteConfigAddLineNumberToOption(state,argv[0],linenum); |
1190 | } |
1191 | sdsfreesplitres(argv,argc); |
1192 | } |
1193 | fclose(fp); |
1194 | sdsfreesplitres(lines,totlines); |
1195 | sdsfree(config); |
1196 | return state; |
1197 | } |
1198 | |
1199 | /* Rewrite the specified configuration option with the new "line". |
1200 | * It progressively uses lines of the file that were already used for the same |
1201 | * configuration option in the old version of the file, removing that line from |
1202 | * the map of options -> line numbers. |
1203 | * |
1204 | * If there are lines associated with a given configuration option and |
1205 | * "force" is non-zero, the line is appended to the configuration file. |
1206 | * Usually "force" is true when an option has not its default value, so it |
1207 | * must be rewritten even if not present previously. |
1208 | * |
1209 | * The first time a line is appended into a configuration file, a comment |
1210 | * is added to show that starting from that point the config file was generated |
1211 | * by CONFIG REWRITE. |
1212 | * |
1213 | * "line" is either used, or freed, so the caller does not need to free it |
1214 | * in any way. */ |
1215 | void rewriteConfigRewriteLine(struct rewriteConfigState *state, const char *option, sds line, int force) { |
1216 | sds o = sdsnew(option); |
1217 | list *l = dictFetchValue(state->option_to_line,o); |
1218 | |
1219 | rewriteConfigMarkAsProcessed(state,option); |
1220 | |
1221 | if (!l && !force && !state->force_write) { |
1222 | /* Option not used previously, and we are not forced to use it. */ |
1223 | sdsfree(line); |
1224 | sdsfree(o); |
1225 | return; |
1226 | } |
1227 | |
1228 | if (l) { |
1229 | listNode *ln = listFirst(l); |
1230 | int linenum = (long) ln->value; |
1231 | |
1232 | /* There are still lines in the old configuration file we can reuse |
1233 | * for this option. Replace the line with the new one. */ |
1234 | listDelNode(l,ln); |
1235 | if (listLength(l) == 0) dictDelete(state->option_to_line,o); |
1236 | sdsfree(state->lines[linenum]); |
1237 | state->lines[linenum] = line; |
1238 | } else { |
1239 | /* Append a new line. */ |
1240 | if (state->needs_signature) { |
1241 | rewriteConfigAppendLine(state, |
1242 | sdsnew(REDIS_CONFIG_REWRITE_SIGNATURE)); |
1243 | state->needs_signature = 0; |
1244 | } |
1245 | rewriteConfigAppendLine(state,line); |
1246 | } |
1247 | sdsfree(o); |
1248 | } |
1249 | |
1250 | /* Write the long long 'bytes' value as a string in a way that is parsable |
1251 | * inside redis.conf. If possible uses the GB, MB, KB notation. */ |
1252 | int rewriteConfigFormatMemory(char *buf, size_t len, long long bytes) { |
1253 | int gb = 1024*1024*1024; |
1254 | int mb = 1024*1024; |
1255 | int kb = 1024; |
1256 | |
1257 | if (bytes && (bytes % gb) == 0) { |
1258 | return snprintf(buf,len,"%lldgb" ,bytes/gb); |
1259 | } else if (bytes && (bytes % mb) == 0) { |
1260 | return snprintf(buf,len,"%lldmb" ,bytes/mb); |
1261 | } else if (bytes && (bytes % kb) == 0) { |
1262 | return snprintf(buf,len,"%lldkb" ,bytes/kb); |
1263 | } else { |
1264 | return snprintf(buf,len,"%lld" ,bytes); |
1265 | } |
1266 | } |
1267 | |
1268 | /* Rewrite a simple "option-name <bytes>" configuration option. */ |
1269 | void rewriteConfigBytesOption(struct rewriteConfigState *state, const char *option, long long value, long long defvalue) { |
1270 | char buf[64]; |
1271 | int force = value != defvalue; |
1272 | sds line; |
1273 | |
1274 | rewriteConfigFormatMemory(buf,sizeof(buf),value); |
1275 | line = sdscatprintf(sdsempty(),"%s %s" ,option,buf); |
1276 | rewriteConfigRewriteLine(state,option,line,force); |
1277 | } |
1278 | |
1279 | /* Rewrite a simple "option-name n%" configuration option. */ |
1280 | void rewriteConfigPercentOption(struct rewriteConfigState *state, const char *option, long long value, long long defvalue) { |
1281 | int force = value != defvalue; |
1282 | sds line = sdscatprintf(sdsempty(),"%s %lld%%" ,option,value); |
1283 | |
1284 | rewriteConfigRewriteLine(state,option,line,force); |
1285 | } |
1286 | |
1287 | /* Rewrite a yes/no option. */ |
1288 | void rewriteConfigYesNoOption(struct rewriteConfigState *state, const char *option, int value, int defvalue) { |
1289 | int force = value != defvalue; |
1290 | sds line = sdscatprintf(sdsempty(),"%s %s" ,option, |
1291 | value ? "yes" : "no" ); |
1292 | |
1293 | rewriteConfigRewriteLine(state,option,line,force); |
1294 | } |
1295 | |
1296 | /* Rewrite a string option. */ |
1297 | void rewriteConfigStringOption(struct rewriteConfigState *state, const char *option, char *value, const char *defvalue) { |
1298 | int force = 1; |
1299 | sds line; |
1300 | |
1301 | /* String options set to NULL need to be not present at all in the |
1302 | * configuration file to be set to NULL again at the next reboot. */ |
1303 | if (value == NULL) { |
1304 | rewriteConfigMarkAsProcessed(state,option); |
1305 | return; |
1306 | } |
1307 | |
1308 | /* Set force to zero if the value is set to its default. */ |
1309 | if (defvalue && strcmp(value,defvalue) == 0) force = 0; |
1310 | |
1311 | line = sdsnew(option); |
1312 | line = sdscatlen(line, " " , 1); |
1313 | line = sdscatrepr(line, value, strlen(value)); |
1314 | |
1315 | rewriteConfigRewriteLine(state,option,line,force); |
1316 | } |
1317 | |
1318 | /* Rewrite a SDS string option. */ |
1319 | void rewriteConfigSdsOption(struct rewriteConfigState *state, const char *option, sds value, const char *defvalue) { |
1320 | int force = 1; |
1321 | sds line; |
1322 | |
1323 | /* If there is no value set, we don't want the SDS option |
1324 | * to be present in the configuration at all. */ |
1325 | if (value == NULL) { |
1326 | rewriteConfigMarkAsProcessed(state, option); |
1327 | return; |
1328 | } |
1329 | |
1330 | /* Set force to zero if the value is set to its default. */ |
1331 | if (defvalue && strcmp(value, defvalue) == 0) force = 0; |
1332 | |
1333 | line = sdsnew(option); |
1334 | line = sdscatlen(line, " " , 1); |
1335 | line = sdscatrepr(line, value, sdslen(value)); |
1336 | |
1337 | rewriteConfigRewriteLine(state, option, line, force); |
1338 | } |
1339 | |
1340 | /* Rewrite a numerical (long long range) option. */ |
1341 | void rewriteConfigNumericalOption(struct rewriteConfigState *state, const char *option, long long value, long long defvalue) { |
1342 | int force = value != defvalue; |
1343 | sds line = sdscatprintf(sdsempty(),"%s %lld" ,option,value); |
1344 | |
1345 | rewriteConfigRewriteLine(state,option,line,force); |
1346 | } |
1347 | |
1348 | /* Rewrite an octal option. */ |
1349 | void rewriteConfigOctalOption(struct rewriteConfigState *state, const char *option, long long value, long long defvalue) { |
1350 | int force = value != defvalue; |
1351 | sds line = sdscatprintf(sdsempty(),"%s %llo" ,option,value); |
1352 | |
1353 | rewriteConfigRewriteLine(state,option,line,force); |
1354 | } |
1355 | |
1356 | /* Rewrite an enumeration option. It takes as usually state and option name, |
1357 | * and in addition the enumeration array and the default value for the |
1358 | * option. */ |
1359 | void rewriteConfigEnumOption(struct rewriteConfigState *state, const char *option, int value, standardConfig *config) { |
1360 | int multiarg = config->flags & MULTI_ARG_CONFIG; |
1361 | sds names = configEnumGetName(config->data.enumd.enum_value,value,multiarg); |
1362 | sds line = sdscatfmt(sdsempty(),"%s %s" ,option,names); |
1363 | sdsfree(names); |
1364 | int force = value != config->data.enumd.default_value; |
1365 | |
1366 | rewriteConfigRewriteLine(state,option,line,force); |
1367 | } |
1368 | |
1369 | /* Rewrite the save option. */ |
1370 | void rewriteConfigSaveOption(standardConfig *config, const char *name, struct rewriteConfigState *state) { |
1371 | UNUSED(config); |
1372 | int j; |
1373 | sds line; |
1374 | |
1375 | /* In Sentinel mode we don't need to rewrite the save parameters */ |
1376 | if (server.sentinel_mode) { |
1377 | rewriteConfigMarkAsProcessed(state,name); |
1378 | return; |
1379 | } |
1380 | |
1381 | /* Rewrite save parameters, or an empty 'save ""' line to avoid the |
1382 | * defaults from being used. |
1383 | */ |
1384 | if (!server.saveparamslen) { |
1385 | rewriteConfigRewriteLine(state,name,sdsnew("save \"\"" ),1); |
1386 | } else { |
1387 | for (j = 0; j < server.saveparamslen; j++) { |
1388 | line = sdscatprintf(sdsempty(),"save %ld %d" , |
1389 | (long) server.saveparams[j].seconds, server.saveparams[j].changes); |
1390 | rewriteConfigRewriteLine(state,name,line,1); |
1391 | } |
1392 | } |
1393 | |
1394 | /* Mark "save" as processed in case server.saveparamslen is zero. */ |
1395 | rewriteConfigMarkAsProcessed(state,name); |
1396 | } |
1397 | |
1398 | /* Rewrite the user option. */ |
1399 | void rewriteConfigUserOption(struct rewriteConfigState *state) { |
1400 | /* If there is a user file defined we just mark this configuration |
1401 | * directive as processed, so that all the lines containing users |
1402 | * inside the config file gets discarded. */ |
1403 | if (server.acl_filename[0] != '\0') { |
1404 | rewriteConfigMarkAsProcessed(state,"user" ); |
1405 | return; |
1406 | } |
1407 | |
1408 | /* Otherwise scan the list of users and rewrite every line. Note that |
1409 | * in case the list here is empty, the effect will just be to comment |
1410 | * all the users directive inside the config file. */ |
1411 | raxIterator ri; |
1412 | raxStart(&ri,Users); |
1413 | raxSeek(&ri,"^" ,NULL,0); |
1414 | while(raxNext(&ri)) { |
1415 | user *u = ri.data; |
1416 | sds line = sdsnew("user " ); |
1417 | line = sdscatsds(line,u->name); |
1418 | line = sdscatlen(line," " ,1); |
1419 | sds descr = ACLDescribeUser(u); |
1420 | line = sdscatsds(line,descr); |
1421 | sdsfree(descr); |
1422 | rewriteConfigRewriteLine(state,"user" ,line,1); |
1423 | } |
1424 | raxStop(&ri); |
1425 | |
1426 | /* Mark "user" as processed in case there are no defined users. */ |
1427 | rewriteConfigMarkAsProcessed(state,"user" ); |
1428 | } |
1429 | |
1430 | /* Rewrite the dir option, always using absolute paths.*/ |
1431 | void rewriteConfigDirOption(standardConfig *config, const char *name, struct rewriteConfigState *state) { |
1432 | UNUSED(config); |
1433 | char cwd[1024]; |
1434 | |
1435 | if (getcwd(cwd,sizeof(cwd)) == NULL) { |
1436 | rewriteConfigMarkAsProcessed(state,name); |
1437 | return; /* no rewrite on error. */ |
1438 | } |
1439 | rewriteConfigStringOption(state,name,cwd,NULL); |
1440 | } |
1441 | |
1442 | /* Rewrite the slaveof option. */ |
1443 | void rewriteConfigReplicaOfOption(standardConfig *config, const char *name, struct rewriteConfigState *state) { |
1444 | UNUSED(config); |
1445 | sds line; |
1446 | |
1447 | /* If this is a master, we want all the slaveof config options |
1448 | * in the file to be removed. Note that if this is a cluster instance |
1449 | * we don't want a slaveof directive inside redis.conf. */ |
1450 | if (server.cluster_enabled || server.masterhost == NULL) { |
1451 | rewriteConfigMarkAsProcessed(state, name); |
1452 | return; |
1453 | } |
1454 | line = sdscatprintf(sdsempty(),"%s %s %d" , name, |
1455 | server.masterhost, server.masterport); |
1456 | rewriteConfigRewriteLine(state,name,line,1); |
1457 | } |
1458 | |
1459 | /* Rewrite the notify-keyspace-events option. */ |
1460 | void rewriteConfigNotifyKeyspaceEventsOption(standardConfig *config, const char *name, struct rewriteConfigState *state) { |
1461 | UNUSED(config); |
1462 | int force = server.notify_keyspace_events != 0; |
1463 | sds line, flags; |
1464 | |
1465 | flags = keyspaceEventsFlagsToString(server.notify_keyspace_events); |
1466 | line = sdsnew(name); |
1467 | line = sdscatlen(line, " " , 1); |
1468 | line = sdscatrepr(line, flags, sdslen(flags)); |
1469 | sdsfree(flags); |
1470 | rewriteConfigRewriteLine(state,name,line,force); |
1471 | } |
1472 | |
1473 | /* Rewrite the client-output-buffer-limit option. */ |
1474 | void rewriteConfigClientOutputBufferLimitOption(standardConfig *config, const char *name, struct rewriteConfigState *state) { |
1475 | UNUSED(config); |
1476 | int j; |
1477 | for (j = 0; j < CLIENT_TYPE_OBUF_COUNT; j++) { |
1478 | int force = (server.client_obuf_limits[j].hard_limit_bytes != |
1479 | clientBufferLimitsDefaults[j].hard_limit_bytes) || |
1480 | (server.client_obuf_limits[j].soft_limit_bytes != |
1481 | clientBufferLimitsDefaults[j].soft_limit_bytes) || |
1482 | (server.client_obuf_limits[j].soft_limit_seconds != |
1483 | clientBufferLimitsDefaults[j].soft_limit_seconds); |
1484 | sds line; |
1485 | char hard[64], soft[64]; |
1486 | |
1487 | rewriteConfigFormatMemory(hard,sizeof(hard), |
1488 | server.client_obuf_limits[j].hard_limit_bytes); |
1489 | rewriteConfigFormatMemory(soft,sizeof(soft), |
1490 | server.client_obuf_limits[j].soft_limit_bytes); |
1491 | |
1492 | char *typename = getClientTypeName(j); |
1493 | if (!strcmp(typename,"slave" )) typename = "replica" ; |
1494 | line = sdscatprintf(sdsempty(),"%s %s %s %s %ld" , |
1495 | name, typename, hard, soft, |
1496 | (long) server.client_obuf_limits[j].soft_limit_seconds); |
1497 | rewriteConfigRewriteLine(state,name,line,force); |
1498 | } |
1499 | } |
1500 | |
1501 | /* Rewrite the oom-score-adj-values option. */ |
1502 | void rewriteConfigOOMScoreAdjValuesOption(standardConfig *config, const char *name, struct rewriteConfigState *state) { |
1503 | UNUSED(config); |
1504 | int force = 0; |
1505 | int j; |
1506 | sds line; |
1507 | |
1508 | line = sdsnew(name); |
1509 | line = sdscatlen(line, " " , 1); |
1510 | for (j = 0; j < CONFIG_OOM_COUNT; j++) { |
1511 | if (server.oom_score_adj_values[j] != configOOMScoreAdjValuesDefaults[j]) |
1512 | force = 1; |
1513 | |
1514 | line = sdscatprintf(line, "%d" , server.oom_score_adj_values[j]); |
1515 | if (j+1 != CONFIG_OOM_COUNT) |
1516 | line = sdscatlen(line, " " , 1); |
1517 | } |
1518 | rewriteConfigRewriteLine(state,name,line,force); |
1519 | } |
1520 | |
1521 | /* Rewrite the bind option. */ |
1522 | void rewriteConfigBindOption(standardConfig *config, const char *name, struct rewriteConfigState *state) { |
1523 | UNUSED(config); |
1524 | int force = 1; |
1525 | sds line, addresses; |
1526 | int is_default = 0; |
1527 | |
1528 | /* Compare server.bindaddr with CONFIG_DEFAULT_BINDADDR */ |
1529 | if (server.bindaddr_count == CONFIG_DEFAULT_BINDADDR_COUNT) { |
1530 | is_default = 1; |
1531 | char *default_bindaddr[CONFIG_DEFAULT_BINDADDR_COUNT] = CONFIG_DEFAULT_BINDADDR; |
1532 | for (int j = 0; j < CONFIG_DEFAULT_BINDADDR_COUNT; j++) { |
1533 | if (strcmp(server.bindaddr[j], default_bindaddr[j]) != 0) { |
1534 | is_default = 0; |
1535 | break; |
1536 | } |
1537 | } |
1538 | } |
1539 | |
1540 | if (is_default) { |
1541 | rewriteConfigMarkAsProcessed(state,name); |
1542 | return; |
1543 | } |
1544 | |
1545 | /* Rewrite as bind <addr1> <addr2> ... <addrN> */ |
1546 | if (server.bindaddr_count > 0) |
1547 | addresses = sdsjoin(server.bindaddr,server.bindaddr_count," " ); |
1548 | else |
1549 | addresses = sdsnew("\"\"" ); |
1550 | line = sdsnew(name); |
1551 | line = sdscatlen(line, " " , 1); |
1552 | line = sdscatsds(line, addresses); |
1553 | sdsfree(addresses); |
1554 | |
1555 | rewriteConfigRewriteLine(state,name,line,force); |
1556 | } |
1557 | |
1558 | /* Rewrite the loadmodule option. */ |
1559 | void rewriteConfigLoadmoduleOption(struct rewriteConfigState *state) { |
1560 | sds line; |
1561 | |
1562 | dictIterator *di = dictGetIterator(modules); |
1563 | dictEntry *de; |
1564 | while ((de = dictNext(di)) != NULL) { |
1565 | struct RedisModule *module = dictGetVal(de); |
1566 | line = sdsnew("loadmodule " ); |
1567 | line = sdscatsds(line, module->loadmod->path); |
1568 | for (int i = 0; i < module->loadmod->argc; i++) { |
1569 | line = sdscatlen(line, " " , 1); |
1570 | line = sdscatsds(line, module->loadmod->argv[i]->ptr); |
1571 | } |
1572 | rewriteConfigRewriteLine(state,"loadmodule" ,line,1); |
1573 | } |
1574 | dictReleaseIterator(di); |
1575 | /* Mark "loadmodule" as processed in case modules is empty. */ |
1576 | rewriteConfigMarkAsProcessed(state,"loadmodule" ); |
1577 | } |
1578 | |
1579 | /* Glue together the configuration lines in the current configuration |
1580 | * rewrite state into a single string, stripping multiple empty lines. */ |
1581 | sds rewriteConfigGetContentFromState(struct rewriteConfigState *state) { |
1582 | sds content = sdsempty(); |
1583 | int j, was_empty = 0; |
1584 | |
1585 | for (j = 0; j < state->numlines; j++) { |
1586 | /* Every cluster of empty lines is turned into a single empty line. */ |
1587 | if (sdslen(state->lines[j]) == 0) { |
1588 | if (was_empty) continue; |
1589 | was_empty = 1; |
1590 | } else { |
1591 | was_empty = 0; |
1592 | } |
1593 | content = sdscatsds(content,state->lines[j]); |
1594 | content = sdscatlen(content,"\n" ,1); |
1595 | } |
1596 | return content; |
1597 | } |
1598 | |
1599 | /* At the end of the rewrite process the state contains the remaining |
1600 | * map between "option name" => "lines in the original config file". |
1601 | * Lines used by the rewrite process were removed by the function |
1602 | * rewriteConfigRewriteLine(), all the other lines are "orphaned" and |
1603 | * should be replaced by empty lines. |
1604 | * |
1605 | * This function does just this, iterating all the option names and |
1606 | * blanking all the lines still associated. */ |
1607 | void rewriteConfigRemoveOrphaned(struct rewriteConfigState *state) { |
1608 | dictIterator *di = dictGetIterator(state->option_to_line); |
1609 | dictEntry *de; |
1610 | |
1611 | while((de = dictNext(di)) != NULL) { |
1612 | list *l = dictGetVal(de); |
1613 | sds option = dictGetKey(de); |
1614 | |
1615 | /* Don't blank lines about options the rewrite process |
1616 | * don't understand. */ |
1617 | if (dictFind(state->rewritten,option) == NULL) { |
1618 | serverLog(LL_DEBUG,"Not rewritten option: %s" , option); |
1619 | continue; |
1620 | } |
1621 | |
1622 | while(listLength(l)) { |
1623 | listNode *ln = listFirst(l); |
1624 | int linenum = (long) ln->value; |
1625 | |
1626 | sdsfree(state->lines[linenum]); |
1627 | state->lines[linenum] = sdsempty(); |
1628 | listDelNode(l,ln); |
1629 | } |
1630 | } |
1631 | dictReleaseIterator(di); |
1632 | } |
1633 | |
1634 | /* This function returns a string representation of all the config options |
1635 | * marked with DEBUG_CONFIG, which can be used to help with debugging. */ |
1636 | sds getConfigDebugInfo() { |
1637 | struct rewriteConfigState *state = rewriteConfigCreateState(); |
1638 | state->force_write = 1; /* Force the output */ |
1639 | state->needs_signature = 0; /* Omit the rewrite signature */ |
1640 | |
1641 | /* Iterate the configs and "rewrite" the ones that have |
1642 | * the debug flag. */ |
1643 | dictIterator *di = dictGetIterator(configs); |
1644 | dictEntry *de; |
1645 | while ((de = dictNext(di)) != NULL) { |
1646 | standardConfig *config = dictGetVal(de); |
1647 | if (!(config->flags & DEBUG_CONFIG)) continue; |
1648 | config->interface.rewrite(config, config->name, state); |
1649 | } |
1650 | dictReleaseIterator(di); |
1651 | sds info = rewriteConfigGetContentFromState(state); |
1652 | rewriteConfigReleaseState(state); |
1653 | return info; |
1654 | } |
1655 | |
1656 | /* This function replaces the old configuration file with the new content |
1657 | * in an atomic manner. |
1658 | * |
1659 | * The function returns 0 on success, otherwise -1 is returned and errno |
1660 | * is set accordingly. */ |
1661 | int rewriteConfigOverwriteFile(char *configfile, sds content) { |
1662 | int fd = -1; |
1663 | int retval = -1; |
1664 | char tmp_conffile[PATH_MAX]; |
1665 | const char *tmp_suffix = ".XXXXXX" ; |
1666 | size_t offset = 0; |
1667 | ssize_t written_bytes = 0; |
1668 | int old_errno; |
1669 | |
1670 | int tmp_path_len = snprintf(tmp_conffile, sizeof(tmp_conffile), "%s%s" , configfile, tmp_suffix); |
1671 | if (tmp_path_len <= 0 || (unsigned int)tmp_path_len >= sizeof(tmp_conffile)) { |
1672 | serverLog(LL_WARNING, "Config file full path is too long" ); |
1673 | errno = ENAMETOOLONG; |
1674 | return retval; |
1675 | } |
1676 | |
1677 | #ifdef _GNU_SOURCE |
1678 | fd = mkostemp(tmp_conffile, O_CLOEXEC); |
1679 | #else |
1680 | /* There's a theoretical chance here to leak the FD if a module thread forks & execv in the middle */ |
1681 | fd = mkstemp(tmp_conffile); |
1682 | #endif |
1683 | |
1684 | if (fd == -1) { |
1685 | serverLog(LL_WARNING, "Could not create tmp config file (%s)" , strerror(errno)); |
1686 | return retval; |
1687 | } |
1688 | |
1689 | while (offset < sdslen(content)) { |
1690 | written_bytes = write(fd, content + offset, sdslen(content) - offset); |
1691 | if (written_bytes <= 0) { |
1692 | if (errno == EINTR) continue; /* FD is blocking, no other retryable errors */ |
1693 | serverLog(LL_WARNING, "Failed after writing (%zd) bytes to tmp config file (%s)" , offset, strerror(errno)); |
1694 | goto cleanup; |
1695 | } |
1696 | offset+=written_bytes; |
1697 | } |
1698 | |
1699 | if (fsync(fd)) |
1700 | serverLog(LL_WARNING, "Could not sync tmp config file to disk (%s)" , strerror(errno)); |
1701 | else if (fchmod(fd, 0644 & ~server.umask) == -1) |
1702 | serverLog(LL_WARNING, "Could not chmod config file (%s)" , strerror(errno)); |
1703 | else if (rename(tmp_conffile, configfile) == -1) |
1704 | serverLog(LL_WARNING, "Could not rename tmp config file (%s)" , strerror(errno)); |
1705 | else if (fsyncFileDir(configfile) == -1) |
1706 | serverLog(LL_WARNING, "Could not sync config file dir (%s)" , strerror(errno)); |
1707 | else { |
1708 | retval = 0; |
1709 | serverLog(LL_DEBUG, "Rewritten config file (%s) successfully" , configfile); |
1710 | } |
1711 | |
1712 | cleanup: |
1713 | old_errno = errno; |
1714 | close(fd); |
1715 | if (retval) unlink(tmp_conffile); |
1716 | errno = old_errno; |
1717 | return retval; |
1718 | } |
1719 | |
1720 | /* Rewrite the configuration file at "path". |
1721 | * If the configuration file already exists, we try at best to retain comments |
1722 | * and overall structure. |
1723 | * |
1724 | * Configuration parameters that are at their default value, unless already |
1725 | * explicitly included in the old configuration file, are not rewritten. |
1726 | * The force_write flag overrides this behavior and forces everything to be |
1727 | * written. This is currently only used for testing purposes. |
1728 | * |
1729 | * On error -1 is returned and errno is set accordingly, otherwise 0. */ |
1730 | int rewriteConfig(char *path, int force_write) { |
1731 | struct rewriteConfigState *state; |
1732 | sds newcontent; |
1733 | int retval; |
1734 | |
1735 | /* Step 1: read the old config into our rewrite state. */ |
1736 | if ((state = rewriteConfigReadOldFile(path)) == NULL) return -1; |
1737 | if (force_write) state->force_write = 1; |
1738 | |
1739 | /* Step 2: rewrite every single option, replacing or appending it inside |
1740 | * the rewrite state. */ |
1741 | |
1742 | /* Iterate the configs that are standard */ |
1743 | dictIterator *di = dictGetIterator(configs); |
1744 | dictEntry *de; |
1745 | while ((de = dictNext(di)) != NULL) { |
1746 | standardConfig *config = dictGetVal(de); |
1747 | /* Only rewrite the primary names */ |
1748 | if (config->flags & ALIAS_CONFIG) continue; |
1749 | if (config->interface.rewrite) config->interface.rewrite(config, de->key, state); |
1750 | } |
1751 | dictReleaseIterator(di); |
1752 | |
1753 | rewriteConfigUserOption(state); |
1754 | rewriteConfigLoadmoduleOption(state); |
1755 | |
1756 | /* Rewrite Sentinel config if in Sentinel mode. */ |
1757 | if (server.sentinel_mode) rewriteConfigSentinelOption(state); |
1758 | |
1759 | /* Step 3: remove all the orphaned lines in the old file, that is, lines |
1760 | * that were used by a config option and are no longer used, like in case |
1761 | * of multiple "save" options or duplicated options. */ |
1762 | rewriteConfigRemoveOrphaned(state); |
1763 | |
1764 | /* Step 4: generate a new configuration file from the modified state |
1765 | * and write it into the original file. */ |
1766 | newcontent = rewriteConfigGetContentFromState(state); |
1767 | retval = rewriteConfigOverwriteFile(server.configfile,newcontent); |
1768 | |
1769 | sdsfree(newcontent); |
1770 | rewriteConfigReleaseState(state); |
1771 | return retval; |
1772 | } |
1773 | |
1774 | /*----------------------------------------------------------------------------- |
1775 | * Configs that fit one of the major types and require no special handling |
1776 | *----------------------------------------------------------------------------*/ |
1777 | #define LOADBUF_SIZE 256 |
1778 | static char loadbuf[LOADBUF_SIZE]; |
1779 | |
1780 | #define embedCommonConfig(config_name, config_alias, config_flags) \ |
1781 | .name = (config_name), \ |
1782 | .alias = (config_alias), \ |
1783 | .flags = (config_flags), |
1784 | |
1785 | #define embedConfigInterface(initfn, setfn, getfn, rewritefn, applyfn) .interface = { \ |
1786 | .init = (initfn), \ |
1787 | .set = (setfn), \ |
1788 | .get = (getfn), \ |
1789 | .rewrite = (rewritefn), \ |
1790 | .apply = (applyfn) \ |
1791 | }, |
1792 | |
1793 | /* What follows is the generic config types that are supported. To add a new |
1794 | * config with one of these types, add it to the standardConfig table with |
1795 | * the creation macro for each type. |
1796 | * |
1797 | * Each type contains the following: |
1798 | * * A function defining how to load this type on startup. |
1799 | * * A function defining how to update this type on CONFIG SET. |
1800 | * * A function defining how to serialize this type on CONFIG SET. |
1801 | * * A function defining how to rewrite this type on CONFIG REWRITE. |
1802 | * * A Macro defining how to create this type. |
1803 | */ |
1804 | |
1805 | /* Bool Configs */ |
1806 | static void boolConfigInit(standardConfig *config) { |
1807 | *config->data.yesno.config = config->data.yesno.default_value; |
1808 | } |
1809 | |
1810 | static int boolConfigSet(standardConfig *config, sds *argv, int argc, const char **err) { |
1811 | UNUSED(argc); |
1812 | int yn = yesnotoi(argv[0]); |
1813 | if (yn == -1) { |
1814 | *err = "argument must be 'yes' or 'no'" ; |
1815 | return 0; |
1816 | } |
1817 | if (config->data.yesno.is_valid_fn && !config->data.yesno.is_valid_fn(yn, err)) |
1818 | return 0; |
1819 | int prev = config->flags & MODULE_CONFIG ? getModuleBoolConfig(config->privdata) : *(config->data.yesno.config); |
1820 | if (prev != yn) { |
1821 | if (config->flags & MODULE_CONFIG) { |
1822 | return setModuleBoolConfig(config->privdata, yn, err); |
1823 | } |
1824 | *(config->data.yesno.config) = yn; |
1825 | return 1; |
1826 | } |
1827 | return (config->flags & VOLATILE_CONFIG) ? 1 : 2; |
1828 | } |
1829 | |
1830 | static sds boolConfigGet(standardConfig *config) { |
1831 | if (config->flags & MODULE_CONFIG) { |
1832 | return sdsnew(getModuleBoolConfig(config->privdata) ? "yes" : "no" ); |
1833 | } |
1834 | return sdsnew(*config->data.yesno.config ? "yes" : "no" ); |
1835 | } |
1836 | |
1837 | static void boolConfigRewrite(standardConfig *config, const char *name, struct rewriteConfigState *state) { |
1838 | int val = config->flags & MODULE_CONFIG ? getModuleBoolConfig(config->privdata) : *(config->data.yesno.config); |
1839 | rewriteConfigYesNoOption(state, name, val, config->data.yesno.default_value); |
1840 | } |
1841 | |
1842 | #define createBoolConfig(name, alias, flags, config_addr, default, is_valid, apply) { \ |
1843 | embedCommonConfig(name, alias, flags) \ |
1844 | embedConfigInterface(boolConfigInit, boolConfigSet, boolConfigGet, boolConfigRewrite, apply) \ |
1845 | .type = BOOL_CONFIG, \ |
1846 | .data.yesno = { \ |
1847 | .config = &(config_addr), \ |
1848 | .default_value = (default), \ |
1849 | .is_valid_fn = (is_valid), \ |
1850 | } \ |
1851 | } |
1852 | |
1853 | /* String Configs */ |
1854 | static void stringConfigInit(standardConfig *config) { |
1855 | *config->data.string.config = (config->data.string.convert_empty_to_null && !config->data.string.default_value) ? NULL : zstrdup(config->data.string.default_value); |
1856 | } |
1857 | |
1858 | static int stringConfigSet(standardConfig *config, sds *argv, int argc, const char **err) { |
1859 | UNUSED(argc); |
1860 | if (config->data.string.is_valid_fn && !config->data.string.is_valid_fn(argv[0], err)) |
1861 | return 0; |
1862 | char *prev = *config->data.string.config; |
1863 | char *new = (config->data.string.convert_empty_to_null && !argv[0][0]) ? NULL : argv[0]; |
1864 | if (new != prev && (new == NULL || prev == NULL || strcmp(prev, new))) { |
1865 | *config->data.string.config = new != NULL ? zstrdup(new) : NULL; |
1866 | zfree(prev); |
1867 | return 1; |
1868 | } |
1869 | return (config->flags & VOLATILE_CONFIG) ? 1 : 2; |
1870 | } |
1871 | |
1872 | static sds stringConfigGet(standardConfig *config) { |
1873 | return sdsnew(*config->data.string.config ? *config->data.string.config : "" ); |
1874 | } |
1875 | |
1876 | static void stringConfigRewrite(standardConfig *config, const char *name, struct rewriteConfigState *state) { |
1877 | rewriteConfigStringOption(state, name,*(config->data.string.config), config->data.string.default_value); |
1878 | } |
1879 | |
1880 | /* SDS Configs */ |
1881 | static void sdsConfigInit(standardConfig *config) { |
1882 | *config->data.sds.config = (config->data.sds.convert_empty_to_null && !config->data.sds.default_value) ? NULL : sdsnew(config->data.sds.default_value); |
1883 | } |
1884 | |
1885 | static int sdsConfigSet(standardConfig *config, sds *argv, int argc, const char **err) { |
1886 | UNUSED(argc); |
1887 | if (config->data.sds.is_valid_fn && !config->data.sds.is_valid_fn(argv[0], err)) |
1888 | return 0; |
1889 | |
1890 | sds prev = config->flags & MODULE_CONFIG ? getModuleStringConfig(config->privdata) : *config->data.sds.config; |
1891 | sds new = (config->data.string.convert_empty_to_null && (sdslen(argv[0]) == 0)) ? NULL : argv[0]; |
1892 | |
1893 | /* if prev and new configuration are not equal, set the new one */ |
1894 | if (new != prev && (new == NULL || prev == NULL || sdscmp(prev, new))) { |
1895 | /* If MODULE_CONFIG flag is set, then free temporary prev getModuleStringConfig returned. |
1896 | * Otherwise, free the actual previous config value Redis held (Same action, different reasons) */ |
1897 | sdsfree(prev); |
1898 | |
1899 | if (config->flags & MODULE_CONFIG) { |
1900 | return setModuleStringConfig(config->privdata, new, err); |
1901 | } |
1902 | *config->data.sds.config = new != NULL ? sdsdup(new) : NULL; |
1903 | return 1; |
1904 | } |
1905 | if (config->flags & MODULE_CONFIG && prev) sdsfree(prev); |
1906 | return (config->flags & VOLATILE_CONFIG) ? 1 : 2; |
1907 | } |
1908 | |
1909 | static sds sdsConfigGet(standardConfig *config) { |
1910 | sds val = config->flags & MODULE_CONFIG ? getModuleStringConfig(config->privdata) : *config->data.sds.config; |
1911 | if (val) { |
1912 | if (config->flags & MODULE_CONFIG) return val; |
1913 | return sdsdup(val); |
1914 | } else { |
1915 | return sdsnew("" ); |
1916 | } |
1917 | } |
1918 | |
1919 | static void sdsConfigRewrite(standardConfig *config, const char *name, struct rewriteConfigState *state) { |
1920 | sds val = config->flags & MODULE_CONFIG ? getModuleStringConfig(config->privdata) : *config->data.sds.config; |
1921 | rewriteConfigSdsOption(state, name, val, config->data.sds.default_value); |
1922 | if ((val) && (config->flags & MODULE_CONFIG)) sdsfree(val); |
1923 | } |
1924 | |
1925 | |
1926 | #define ALLOW_EMPTY_STRING 0 |
1927 | #define EMPTY_STRING_IS_NULL 1 |
1928 | |
1929 | #define createStringConfig(name, alias, flags, empty_to_null, config_addr, default, is_valid, apply) { \ |
1930 | embedCommonConfig(name, alias, flags) \ |
1931 | embedConfigInterface(stringConfigInit, stringConfigSet, stringConfigGet, stringConfigRewrite, apply) \ |
1932 | .type = STRING_CONFIG, \ |
1933 | .data.string = { \ |
1934 | .config = &(config_addr), \ |
1935 | .default_value = (default), \ |
1936 | .is_valid_fn = (is_valid), \ |
1937 | .convert_empty_to_null = (empty_to_null), \ |
1938 | } \ |
1939 | } |
1940 | |
1941 | #define createSDSConfig(name, alias, flags, empty_to_null, config_addr, default, is_valid, apply) { \ |
1942 | embedCommonConfig(name, alias, flags) \ |
1943 | embedConfigInterface(sdsConfigInit, sdsConfigSet, sdsConfigGet, sdsConfigRewrite, apply) \ |
1944 | .type = SDS_CONFIG, \ |
1945 | .data.sds = { \ |
1946 | .config = &(config_addr), \ |
1947 | .default_value = (default), \ |
1948 | .is_valid_fn = (is_valid), \ |
1949 | .convert_empty_to_null = (empty_to_null), \ |
1950 | } \ |
1951 | } |
1952 | |
1953 | /* Enum configs */ |
1954 | static void enumConfigInit(standardConfig *config) { |
1955 | *config->data.enumd.config = config->data.enumd.default_value; |
1956 | } |
1957 | |
1958 | static int enumConfigSet(standardConfig *config, sds *argv, int argc, const char **err) { |
1959 | int enumval; |
1960 | int bitflags = !!(config->flags & MULTI_ARG_CONFIG); |
1961 | enumval = configEnumGetValue(config->data.enumd.enum_value, argv, argc, bitflags); |
1962 | |
1963 | if (enumval == INT_MIN) { |
1964 | sds enumerr = sdsnew("argument(s) must be one of the following: " ); |
1965 | configEnum *enumNode = config->data.enumd.enum_value; |
1966 | while(enumNode->name != NULL) { |
1967 | enumerr = sdscatlen(enumerr, enumNode->name, |
1968 | strlen(enumNode->name)); |
1969 | enumerr = sdscatlen(enumerr, ", " , 2); |
1970 | enumNode++; |
1971 | } |
1972 | sdsrange(enumerr,0,-3); /* Remove final ", ". */ |
1973 | |
1974 | strncpy(loadbuf, enumerr, LOADBUF_SIZE); |
1975 | loadbuf[LOADBUF_SIZE - 1] = '\0'; |
1976 | |
1977 | sdsfree(enumerr); |
1978 | *err = loadbuf; |
1979 | return 0; |
1980 | } |
1981 | if (config->data.enumd.is_valid_fn && !config->data.enumd.is_valid_fn(enumval, err)) |
1982 | return 0; |
1983 | int prev = config->flags & MODULE_CONFIG ? getModuleEnumConfig(config->privdata) : *(config->data.enumd.config); |
1984 | if (prev != enumval) { |
1985 | if (config->flags & MODULE_CONFIG) |
1986 | return setModuleEnumConfig(config->privdata, enumval, err); |
1987 | *(config->data.enumd.config) = enumval; |
1988 | return 1; |
1989 | } |
1990 | return (config->flags & VOLATILE_CONFIG) ? 1 : 2; |
1991 | } |
1992 | |
1993 | static sds enumConfigGet(standardConfig *config) { |
1994 | int val = config->flags & MODULE_CONFIG ? getModuleEnumConfig(config->privdata) : *(config->data.enumd.config); |
1995 | int bitflags = !!(config->flags & MULTI_ARG_CONFIG); |
1996 | return configEnumGetName(config->data.enumd.enum_value,val,bitflags); |
1997 | } |
1998 | |
1999 | static void enumConfigRewrite(standardConfig *config, const char *name, struct rewriteConfigState *state) { |
2000 | int val = config->flags & MODULE_CONFIG ? getModuleEnumConfig(config->privdata) : *(config->data.enumd.config); |
2001 | rewriteConfigEnumOption(state, name, val, config); |
2002 | } |
2003 | |
2004 | #define createEnumConfig(name, alias, flags, enum, config_addr, default, is_valid, apply) { \ |
2005 | embedCommonConfig(name, alias, flags) \ |
2006 | embedConfigInterface(enumConfigInit, enumConfigSet, enumConfigGet, enumConfigRewrite, apply) \ |
2007 | .type = ENUM_CONFIG, \ |
2008 | .data.enumd = { \ |
2009 | .config = &(config_addr), \ |
2010 | .default_value = (default), \ |
2011 | .is_valid_fn = (is_valid), \ |
2012 | .enum_value = (enum), \ |
2013 | } \ |
2014 | } |
2015 | |
2016 | /* Gets a 'long long val' and sets it into the union, using a macro to get |
2017 | * compile time type check. */ |
2018 | int setNumericType(standardConfig *config, long long val, const char **err) { |
2019 | if (config->data.numeric.numeric_type == NUMERIC_TYPE_INT) { |
2020 | *(config->data.numeric.config.i) = (int) val; |
2021 | } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_UINT) { |
2022 | *(config->data.numeric.config.ui) = (unsigned int) val; |
2023 | } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_LONG) { |
2024 | *(config->data.numeric.config.l) = (long) val; |
2025 | } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_ULONG) { |
2026 | *(config->data.numeric.config.ul) = (unsigned long) val; |
2027 | } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_LONG_LONG) { |
2028 | if (config->flags & MODULE_CONFIG) |
2029 | return setModuleNumericConfig(config->privdata, val, err); |
2030 | else *(config->data.numeric.config.ll) = (long long) val; |
2031 | } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_ULONG_LONG) { |
2032 | *(config->data.numeric.config.ull) = (unsigned long long) val; |
2033 | } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_SIZE_T) { |
2034 | *(config->data.numeric.config.st) = (size_t) val; |
2035 | } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_SSIZE_T) { |
2036 | *(config->data.numeric.config.sst) = (ssize_t) val; |
2037 | } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_OFF_T) { |
2038 | *(config->data.numeric.config.ot) = (off_t) val; |
2039 | } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_TIME_T) { |
2040 | *(config->data.numeric.config.tt) = (time_t) val; |
2041 | } |
2042 | return 1; |
2043 | } |
2044 | |
2045 | /* Gets a 'long long val' and sets it with the value from the union, using a |
2046 | * macro to get compile time type check. */ |
2047 | #define GET_NUMERIC_TYPE(val) \ |
2048 | if (config->data.numeric.numeric_type == NUMERIC_TYPE_INT) { \ |
2049 | val = *(config->data.numeric.config.i); \ |
2050 | } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_UINT) { \ |
2051 | val = *(config->data.numeric.config.ui); \ |
2052 | } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_LONG) { \ |
2053 | val = *(config->data.numeric.config.l); \ |
2054 | } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_ULONG) { \ |
2055 | val = *(config->data.numeric.config.ul); \ |
2056 | } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_LONG_LONG) { \ |
2057 | if (config->flags & MODULE_CONFIG) val = getModuleNumericConfig(config->privdata); \ |
2058 | else val = *(config->data.numeric.config.ll); \ |
2059 | } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_ULONG_LONG) { \ |
2060 | val = *(config->data.numeric.config.ull); \ |
2061 | } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_SIZE_T) { \ |
2062 | val = *(config->data.numeric.config.st); \ |
2063 | } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_SSIZE_T) { \ |
2064 | val = *(config->data.numeric.config.sst); \ |
2065 | } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_OFF_T) { \ |
2066 | val = *(config->data.numeric.config.ot); \ |
2067 | } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_TIME_T) { \ |
2068 | val = *(config->data.numeric.config.tt); \ |
2069 | } |
2070 | |
2071 | /* Numeric configs */ |
2072 | static void numericConfigInit(standardConfig *config) { |
2073 | setNumericType(config, config->data.numeric.default_value, NULL); |
2074 | } |
2075 | |
2076 | static int numericBoundaryCheck(standardConfig *config, long long ll, const char **err) { |
2077 | if (config->data.numeric.numeric_type == NUMERIC_TYPE_ULONG_LONG || |
2078 | config->data.numeric.numeric_type == NUMERIC_TYPE_UINT || |
2079 | config->data.numeric.numeric_type == NUMERIC_TYPE_SIZE_T) { |
2080 | /* Boundary check for unsigned types */ |
2081 | unsigned long long ull = ll; |
2082 | unsigned long long upper_bound = config->data.numeric.upper_bound; |
2083 | unsigned long long lower_bound = config->data.numeric.lower_bound; |
2084 | if (ull > upper_bound || ull < lower_bound) { |
2085 | if (config->data.numeric.flags & OCTAL_CONFIG) { |
2086 | snprintf(loadbuf, LOADBUF_SIZE, |
2087 | "argument must be between %llo and %llo inclusive" , |
2088 | lower_bound, |
2089 | upper_bound); |
2090 | } else { |
2091 | snprintf(loadbuf, LOADBUF_SIZE, |
2092 | "argument must be between %llu and %llu inclusive" , |
2093 | lower_bound, |
2094 | upper_bound); |
2095 | } |
2096 | *err = loadbuf; |
2097 | return 0; |
2098 | } |
2099 | } else { |
2100 | /* Boundary check for percentages */ |
2101 | if (config->data.numeric.flags & PERCENT_CONFIG && ll < 0) { |
2102 | if (ll < config->data.numeric.lower_bound) { |
2103 | snprintf(loadbuf, LOADBUF_SIZE, |
2104 | "percentage argument must be less or equal to %lld" , |
2105 | -config->data.numeric.lower_bound); |
2106 | *err = loadbuf; |
2107 | return 0; |
2108 | } |
2109 | } |
2110 | /* Boundary check for signed types */ |
2111 | else if (ll > config->data.numeric.upper_bound || ll < config->data.numeric.lower_bound) { |
2112 | snprintf(loadbuf, LOADBUF_SIZE, |
2113 | "argument must be between %lld and %lld inclusive" , |
2114 | config->data.numeric.lower_bound, |
2115 | config->data.numeric.upper_bound); |
2116 | *err = loadbuf; |
2117 | return 0; |
2118 | } |
2119 | } |
2120 | return 1; |
2121 | } |
2122 | |
2123 | static int numericParseString(standardConfig *config, sds value, const char **err, long long *res) { |
2124 | /* First try to parse as memory */ |
2125 | if (config->data.numeric.flags & MEMORY_CONFIG) { |
2126 | int memerr; |
2127 | *res = memtoull(value, &memerr); |
2128 | if (!memerr) |
2129 | return 1; |
2130 | } |
2131 | |
2132 | /* Attempt to parse as percent */ |
2133 | if (config->data.numeric.flags & PERCENT_CONFIG && |
2134 | sdslen(value) > 1 && value[sdslen(value)-1] == '%' && |
2135 | string2ll(value, sdslen(value)-1, res) && |
2136 | *res >= 0) { |
2137 | /* We store percentage as negative value */ |
2138 | *res = -*res; |
2139 | return 1; |
2140 | } |
2141 | |
2142 | /* Attempt to parse as an octal number */ |
2143 | if (config->data.numeric.flags & OCTAL_CONFIG) { |
2144 | char *endptr; |
2145 | errno = 0; |
2146 | *res = strtoll(value, &endptr, 8); |
2147 | if (errno == 0 && *endptr == '\0') |
2148 | return 1; /* No overflow or invalid characters */ |
2149 | } |
2150 | |
2151 | /* Attempt a simple number (no special flags set) */ |
2152 | if (!config->data.numeric.flags && string2ll(value, sdslen(value), res)) |
2153 | return 1; |
2154 | |
2155 | /* Select appropriate error string */ |
2156 | if (config->data.numeric.flags & MEMORY_CONFIG && |
2157 | config->data.numeric.flags & PERCENT_CONFIG) |
2158 | *err = "argument must be a memory or percent value" ; |
2159 | else if (config->data.numeric.flags & MEMORY_CONFIG) |
2160 | *err = "argument must be a memory value" ; |
2161 | else if (config->data.numeric.flags & OCTAL_CONFIG) |
2162 | *err = "argument couldn't be parsed as an octal number" ; |
2163 | else |
2164 | *err = "argument couldn't be parsed into an integer" ; |
2165 | return 0; |
2166 | } |
2167 | |
2168 | static int numericConfigSet(standardConfig *config, sds *argv, int argc, const char **err) { |
2169 | UNUSED(argc); |
2170 | long long ll, prev = 0; |
2171 | |
2172 | if (!numericParseString(config, argv[0], err, &ll)) |
2173 | return 0; |
2174 | |
2175 | if (!numericBoundaryCheck(config, ll, err)) |
2176 | return 0; |
2177 | |
2178 | if (config->data.numeric.is_valid_fn && !config->data.numeric.is_valid_fn(ll, err)) |
2179 | return 0; |
2180 | |
2181 | GET_NUMERIC_TYPE(prev) |
2182 | if (prev != ll) { |
2183 | return setNumericType(config, ll, err); |
2184 | } |
2185 | |
2186 | return (config->flags & VOLATILE_CONFIG) ? 1 : 2; |
2187 | } |
2188 | |
2189 | static sds numericConfigGet(standardConfig *config) { |
2190 | char buf[128]; |
2191 | |
2192 | long long value = 0; |
2193 | GET_NUMERIC_TYPE(value) |
2194 | |
2195 | if (config->data.numeric.flags & PERCENT_CONFIG && value < 0) { |
2196 | int len = ll2string(buf, sizeof(buf), -value); |
2197 | buf[len] = '%'; |
2198 | buf[len+1] = '\0'; |
2199 | } |
2200 | else if (config->data.numeric.flags & MEMORY_CONFIG) { |
2201 | ull2string(buf, sizeof(buf), value); |
2202 | } else if (config->data.numeric.flags & OCTAL_CONFIG) { |
2203 | snprintf(buf, sizeof(buf), "%llo" , value); |
2204 | } else { |
2205 | ll2string(buf, sizeof(buf), value); |
2206 | } |
2207 | return sdsnew(buf); |
2208 | } |
2209 | |
2210 | static void numericConfigRewrite(standardConfig *config, const char *name, struct rewriteConfigState *state) { |
2211 | long long value = 0; |
2212 | |
2213 | GET_NUMERIC_TYPE(value) |
2214 | |
2215 | if (config->data.numeric.flags & PERCENT_CONFIG && value < 0) { |
2216 | rewriteConfigPercentOption(state, name, -value, config->data.numeric.default_value); |
2217 | } else if (config->data.numeric.flags & MEMORY_CONFIG) { |
2218 | rewriteConfigBytesOption(state, name, value, config->data.numeric.default_value); |
2219 | } else if (config->data.numeric.flags & OCTAL_CONFIG) { |
2220 | rewriteConfigOctalOption(state, name, value, config->data.numeric.default_value); |
2221 | } else { |
2222 | rewriteConfigNumericalOption(state, name, value, config->data.numeric.default_value); |
2223 | } |
2224 | } |
2225 | |
2226 | #define embedCommonNumericalConfig(name, alias, _flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) { \ |
2227 | embedCommonConfig(name, alias, _flags) \ |
2228 | embedConfigInterface(numericConfigInit, numericConfigSet, numericConfigGet, numericConfigRewrite, apply) \ |
2229 | .type = NUMERIC_CONFIG, \ |
2230 | .data.numeric = { \ |
2231 | .lower_bound = (lower), \ |
2232 | .upper_bound = (upper), \ |
2233 | .default_value = (default), \ |
2234 | .is_valid_fn = (is_valid), \ |
2235 | .flags = (num_conf_flags), |
2236 | |
2237 | #define createIntConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ |
2238 | embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ |
2239 | .numeric_type = NUMERIC_TYPE_INT, \ |
2240 | .config.i = &(config_addr) \ |
2241 | } \ |
2242 | } |
2243 | |
2244 | #define createUIntConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ |
2245 | embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ |
2246 | .numeric_type = NUMERIC_TYPE_UINT, \ |
2247 | .config.ui = &(config_addr) \ |
2248 | } \ |
2249 | } |
2250 | |
2251 | #define createLongConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ |
2252 | embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ |
2253 | .numeric_type = NUMERIC_TYPE_LONG, \ |
2254 | .config.l = &(config_addr) \ |
2255 | } \ |
2256 | } |
2257 | |
2258 | #define createULongConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ |
2259 | embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ |
2260 | .numeric_type = NUMERIC_TYPE_ULONG, \ |
2261 | .config.ul = &(config_addr) \ |
2262 | } \ |
2263 | } |
2264 | |
2265 | #define createLongLongConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ |
2266 | embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ |
2267 | .numeric_type = NUMERIC_TYPE_LONG_LONG, \ |
2268 | .config.ll = &(config_addr) \ |
2269 | } \ |
2270 | } |
2271 | |
2272 | #define createULongLongConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ |
2273 | embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ |
2274 | .numeric_type = NUMERIC_TYPE_ULONG_LONG, \ |
2275 | .config.ull = &(config_addr) \ |
2276 | } \ |
2277 | } |
2278 | |
2279 | #define createSizeTConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ |
2280 | embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ |
2281 | .numeric_type = NUMERIC_TYPE_SIZE_T, \ |
2282 | .config.st = &(config_addr) \ |
2283 | } \ |
2284 | } |
2285 | |
2286 | #define createSSizeTConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ |
2287 | embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ |
2288 | .numeric_type = NUMERIC_TYPE_SSIZE_T, \ |
2289 | .config.sst = &(config_addr) \ |
2290 | } \ |
2291 | } |
2292 | |
2293 | #define createTimeTConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ |
2294 | embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ |
2295 | .numeric_type = NUMERIC_TYPE_TIME_T, \ |
2296 | .config.tt = &(config_addr) \ |
2297 | } \ |
2298 | } |
2299 | |
2300 | #define createOffTConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ |
2301 | embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ |
2302 | .numeric_type = NUMERIC_TYPE_OFF_T, \ |
2303 | .config.ot = &(config_addr) \ |
2304 | } \ |
2305 | } |
2306 | |
2307 | #define createSpecialConfig(name, alias, modifiable, setfn, getfn, rewritefn, applyfn) { \ |
2308 | .type = SPECIAL_CONFIG, \ |
2309 | embedCommonConfig(name, alias, modifiable) \ |
2310 | embedConfigInterface(NULL, setfn, getfn, rewritefn, applyfn) \ |
2311 | } |
2312 | |
2313 | static int isValidActiveDefrag(int val, const char **err) { |
2314 | #ifndef HAVE_DEFRAG |
2315 | if (val) { |
2316 | *err = "Active defragmentation cannot be enabled: it " |
2317 | "requires a Redis server compiled with a modified Jemalloc " |
2318 | "like the one shipped by default with the Redis source " |
2319 | "distribution" ; |
2320 | return 0; |
2321 | } |
2322 | #else |
2323 | UNUSED(val); |
2324 | UNUSED(err); |
2325 | #endif |
2326 | return 1; |
2327 | } |
2328 | |
2329 | static int isValidDBfilename(char *val, const char **err) { |
2330 | if (!pathIsBaseName(val)) { |
2331 | *err = "dbfilename can't be a path, just a filename" ; |
2332 | return 0; |
2333 | } |
2334 | return 1; |
2335 | } |
2336 | |
2337 | static int isValidAOFfilename(char *val, const char **err) { |
2338 | if (!strcmp(val, "" )) { |
2339 | *err = "appendfilename can't be empty" ; |
2340 | return 0; |
2341 | } |
2342 | if (!pathIsBaseName(val)) { |
2343 | *err = "appendfilename can't be a path, just a filename" ; |
2344 | return 0; |
2345 | } |
2346 | return 1; |
2347 | } |
2348 | |
2349 | static int isValidAOFdirname(char *val, const char **err) { |
2350 | if (!strcmp(val, "" )) { |
2351 | *err = "appenddirname can't be empty" ; |
2352 | return 0; |
2353 | } |
2354 | if (!pathIsBaseName(val)) { |
2355 | *err = "appenddirname can't be a path, just a dirname" ; |
2356 | return 0; |
2357 | } |
2358 | return 1; |
2359 | } |
2360 | |
2361 | static int isValidShutdownOnSigFlags(int val, const char **err) { |
2362 | /* Individual arguments are validated by createEnumConfig logic. |
2363 | * We just need to ensure valid combinations here. */ |
2364 | if (val & SHUTDOWN_NOSAVE && val & SHUTDOWN_SAVE) { |
2365 | *err = "shutdown options SAVE and NOSAVE can't be used simultaneously" ; |
2366 | return 0; |
2367 | } |
2368 | return 1; |
2369 | } |
2370 | |
2371 | static int isValidAnnouncedHostname(char *val, const char **err) { |
2372 | if (strlen(val) >= NET_HOST_STR_LEN) { |
2373 | *err = "Hostnames must be less than " |
2374 | STRINGIFY(NET_HOST_STR_LEN) " characters" ; |
2375 | return 0; |
2376 | } |
2377 | |
2378 | int i = 0; |
2379 | char c; |
2380 | while ((c = val[i])) { |
2381 | /* We just validate the character set to make sure that everything |
2382 | * is parsed and handled correctly. */ |
2383 | if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') |
2384 | || (c >= '0' && c <= '9') || (c == '-') || (c == '.'))) |
2385 | { |
2386 | *err = "Hostnames may only contain alphanumeric characters, " |
2387 | "hyphens or dots" ; |
2388 | return 0; |
2389 | } |
2390 | c = val[i++]; |
2391 | } |
2392 | return 1; |
2393 | } |
2394 | |
2395 | /* Validate specified string is a valid proc-title-template */ |
2396 | static int isValidProcTitleTemplate(char *val, const char **err) { |
2397 | if (!validateProcTitleTemplate(val)) { |
2398 | *err = "template format is invalid or contains unknown variables" ; |
2399 | return 0; |
2400 | } |
2401 | return 1; |
2402 | } |
2403 | |
2404 | static int updateProcTitleTemplate(const char **err) { |
2405 | if (redisSetProcTitle(NULL) == C_ERR) { |
2406 | *err = "failed to set process title" ; |
2407 | return 0; |
2408 | } |
2409 | return 1; |
2410 | } |
2411 | |
2412 | static int updateHZ(const char **err) { |
2413 | UNUSED(err); |
2414 | /* Hz is more a hint from the user, so we accept values out of range |
2415 | * but cap them to reasonable values. */ |
2416 | if (server.config_hz < CONFIG_MIN_HZ) server.config_hz = CONFIG_MIN_HZ; |
2417 | if (server.config_hz > CONFIG_MAX_HZ) server.config_hz = CONFIG_MAX_HZ; |
2418 | server.hz = server.config_hz; |
2419 | return 1; |
2420 | } |
2421 | |
2422 | static int updatePort(const char **err) { |
2423 | if (changeListenPort(server.port, &server.ipfd, acceptTcpHandler) == C_ERR) { |
2424 | *err = "Unable to listen on this port. Check server logs." ; |
2425 | return 0; |
2426 | } |
2427 | |
2428 | return 1; |
2429 | } |
2430 | |
2431 | static int updateJemallocBgThread(const char **err) { |
2432 | UNUSED(err); |
2433 | set_jemalloc_bg_thread(server.jemalloc_bg_thread); |
2434 | return 1; |
2435 | } |
2436 | |
2437 | static int updateReplBacklogSize(const char **err) { |
2438 | UNUSED(err); |
2439 | resizeReplicationBacklog(); |
2440 | return 1; |
2441 | } |
2442 | |
2443 | static int updateMaxmemory(const char **err) { |
2444 | UNUSED(err); |
2445 | if (server.maxmemory) { |
2446 | size_t used = zmalloc_used_memory()-freeMemoryGetNotCountedMemory(); |
2447 | if (server.maxmemory < used) { |
2448 | serverLog(LL_WARNING,"WARNING: the new maxmemory value set via CONFIG SET (%llu) is smaller than the current memory usage (%zu). This will result in key eviction and/or the inability to accept new write commands depending on the maxmemory-policy." , server.maxmemory, used); |
2449 | } |
2450 | startEvictionTimeProc(); |
2451 | } |
2452 | return 1; |
2453 | } |
2454 | |
2455 | static int updateGoodSlaves(const char **err) { |
2456 | UNUSED(err); |
2457 | refreshGoodSlavesCount(); |
2458 | return 1; |
2459 | } |
2460 | |
2461 | static int updateWatchdogPeriod(const char **err) { |
2462 | UNUSED(err); |
2463 | applyWatchdogPeriod(); |
2464 | return 1; |
2465 | } |
2466 | |
2467 | static int updateAppendonly(const char **err) { |
2468 | if (!server.aof_enabled && server.aof_state != AOF_OFF) { |
2469 | stopAppendOnly(); |
2470 | } else if (server.aof_enabled && server.aof_state == AOF_OFF) { |
2471 | if (startAppendOnly() == C_ERR) { |
2472 | *err = "Unable to turn on AOF. Check server logs." ; |
2473 | return 0; |
2474 | } |
2475 | } |
2476 | return 1; |
2477 | } |
2478 | |
2479 | static int updateAofAutoGCEnabled(const char **err) { |
2480 | UNUSED(err); |
2481 | if (!server.aof_disable_auto_gc) { |
2482 | aofDelHistoryFiles(); |
2483 | } |
2484 | |
2485 | return 1; |
2486 | } |
2487 | |
2488 | static int updateSighandlerEnabled(const char **err) { |
2489 | UNUSED(err); |
2490 | if (server.crashlog_enabled) |
2491 | setupSignalHandlers(); |
2492 | else |
2493 | removeSignalHandlers(); |
2494 | return 1; |
2495 | } |
2496 | |
2497 | static int updateMaxclients(const char **err) { |
2498 | unsigned int new_maxclients = server.maxclients; |
2499 | adjustOpenFilesLimit(); |
2500 | if (server.maxclients != new_maxclients) { |
2501 | static char msg[128]; |
2502 | sprintf(msg, "The operating system is not able to handle the specified number of clients, try with %d" , server.maxclients); |
2503 | *err = msg; |
2504 | return 0; |
2505 | } |
2506 | if ((unsigned int) aeGetSetSize(server.el) < |
2507 | server.maxclients + CONFIG_FDSET_INCR) |
2508 | { |
2509 | if (aeResizeSetSize(server.el, |
2510 | server.maxclients + CONFIG_FDSET_INCR) == AE_ERR) |
2511 | { |
2512 | *err = "The event loop API used by Redis is not able to handle the specified number of clients" ; |
2513 | return 0; |
2514 | } |
2515 | } |
2516 | return 1; |
2517 | } |
2518 | |
2519 | static int updateOOMScoreAdj(const char **err) { |
2520 | if (setOOMScoreAdj(-1) == C_ERR) { |
2521 | *err = "Failed to set current oom_score_adj. Check server logs." ; |
2522 | return 0; |
2523 | } |
2524 | |
2525 | return 1; |
2526 | } |
2527 | |
2528 | int updateRequirePass(const char **err) { |
2529 | UNUSED(err); |
2530 | /* The old "requirepass" directive just translates to setting |
2531 | * a password to the default user. The only thing we do |
2532 | * additionally is to remember the cleartext password in this |
2533 | * case, for backward compatibility with Redis <= 5. */ |
2534 | ACLUpdateDefaultUserPassword(server.requirepass); |
2535 | return 1; |
2536 | } |
2537 | |
2538 | static int applyBind(const char **err) { |
2539 | if (changeBindAddr() == C_ERR) { |
2540 | *err = "Failed to bind to specified addresses." ; |
2541 | return 0; |
2542 | } |
2543 | |
2544 | return 1; |
2545 | } |
2546 | |
2547 | int updateClusterFlags(const char **err) { |
2548 | UNUSED(err); |
2549 | clusterUpdateMyselfFlags(); |
2550 | return 1; |
2551 | } |
2552 | |
2553 | static int updateClusterIp(const char **err) { |
2554 | UNUSED(err); |
2555 | clusterUpdateMyselfIp(); |
2556 | return 1; |
2557 | } |
2558 | |
2559 | int updateClusterHostname(const char **err) { |
2560 | UNUSED(err); |
2561 | clusterUpdateMyselfHostname(); |
2562 | return 1; |
2563 | } |
2564 | |
2565 | #ifdef USE_OPENSSL |
2566 | static int applyTlsCfg(const char **err) { |
2567 | UNUSED(err); |
2568 | |
2569 | /* If TLS is enabled, try to configure OpenSSL. */ |
2570 | if ((server.tls_port || server.tls_replication || server.tls_cluster) |
2571 | && tlsConfigure(&server.tls_ctx_config) == C_ERR) { |
2572 | *err = "Unable to update TLS configuration. Check server logs." ; |
2573 | return 0; |
2574 | } |
2575 | return 1; |
2576 | } |
2577 | |
2578 | static int applyTLSPort(const char **err) { |
2579 | /* Configure TLS in case it wasn't enabled */ |
2580 | if (!isTlsConfigured() && tlsConfigure(&server.tls_ctx_config) == C_ERR) { |
2581 | *err = "Unable to update TLS configuration. Check server logs." ; |
2582 | return 0; |
2583 | } |
2584 | |
2585 | if (changeListenPort(server.tls_port, &server.tlsfd, acceptTLSHandler) == C_ERR) { |
2586 | *err = "Unable to listen on this port. Check server logs." ; |
2587 | return 0; |
2588 | } |
2589 | |
2590 | return 1; |
2591 | } |
2592 | |
2593 | #endif /* USE_OPENSSL */ |
2594 | |
2595 | static int setConfigDirOption(standardConfig *config, sds *argv, int argc, const char **err) { |
2596 | UNUSED(config); |
2597 | if (argc != 1) { |
2598 | *err = "wrong number of arguments" ; |
2599 | return 0; |
2600 | } |
2601 | if (chdir(argv[0]) == -1) { |
2602 | *err = strerror(errno); |
2603 | return 0; |
2604 | } |
2605 | return 1; |
2606 | } |
2607 | |
2608 | static sds getConfigDirOption(standardConfig *config) { |
2609 | UNUSED(config); |
2610 | char buf[1024]; |
2611 | |
2612 | if (getcwd(buf,sizeof(buf)) == NULL) |
2613 | buf[0] = '\0'; |
2614 | |
2615 | return sdsnew(buf); |
2616 | } |
2617 | |
2618 | static int setConfigSaveOption(standardConfig *config, sds *argv, int argc, const char **err) { |
2619 | UNUSED(config); |
2620 | int j; |
2621 | |
2622 | /* Special case: treat single arg "" as zero args indicating empty save configuration */ |
2623 | if (argc == 1 && !strcasecmp(argv[0],"" )) { |
2624 | resetServerSaveParams(); |
2625 | argc = 0; |
2626 | } |
2627 | |
2628 | /* Perform sanity check before setting the new config: |
2629 | * - Even number of args |
2630 | * - Seconds >= 1, changes >= 0 */ |
2631 | if (argc & 1) { |
2632 | *err = "Invalid save parameters" ; |
2633 | return 0; |
2634 | } |
2635 | for (j = 0; j < argc; j++) { |
2636 | char *eptr; |
2637 | long val; |
2638 | |
2639 | val = strtoll(argv[j], &eptr, 10); |
2640 | if (eptr[0] != '\0' || |
2641 | ((j & 1) == 0 && val < 1) || |
2642 | ((j & 1) == 1 && val < 0)) { |
2643 | *err = "Invalid save parameters" ; |
2644 | return 0; |
2645 | } |
2646 | } |
2647 | /* Finally set the new config */ |
2648 | if (!reading_config_file) { |
2649 | resetServerSaveParams(); |
2650 | } else { |
2651 | /* We don't reset save params before loading, because if they're not part |
2652 | * of the file the defaults should be used. |
2653 | */ |
2654 | static int save_loaded = 0; |
2655 | if (!save_loaded) { |
2656 | save_loaded = 1; |
2657 | resetServerSaveParams(); |
2658 | } |
2659 | } |
2660 | |
2661 | for (j = 0; j < argc; j += 2) { |
2662 | time_t seconds; |
2663 | int changes; |
2664 | |
2665 | seconds = strtoll(argv[j],NULL,10); |
2666 | changes = strtoll(argv[j+1],NULL,10); |
2667 | appendServerSaveParams(seconds, changes); |
2668 | } |
2669 | |
2670 | return 1; |
2671 | } |
2672 | |
2673 | static sds getConfigSaveOption(standardConfig *config) { |
2674 | UNUSED(config); |
2675 | sds buf = sdsempty(); |
2676 | int j; |
2677 | |
2678 | for (j = 0; j < server.saveparamslen; j++) { |
2679 | buf = sdscatprintf(buf,"%jd %d" , |
2680 | (intmax_t)server.saveparams[j].seconds, |
2681 | server.saveparams[j].changes); |
2682 | if (j != server.saveparamslen-1) |
2683 | buf = sdscatlen(buf," " ,1); |
2684 | } |
2685 | |
2686 | return buf; |
2687 | } |
2688 | |
2689 | static int setConfigClientOutputBufferLimitOption(standardConfig *config, sds *argv, int argc, const char **err) { |
2690 | UNUSED(config); |
2691 | return updateClientOutputBufferLimit(argv, argc, err); |
2692 | } |
2693 | |
2694 | static sds getConfigClientOutputBufferLimitOption(standardConfig *config) { |
2695 | UNUSED(config); |
2696 | sds buf = sdsempty(); |
2697 | int j; |
2698 | for (j = 0; j < CLIENT_TYPE_OBUF_COUNT; j++) { |
2699 | buf = sdscatprintf(buf,"%s %llu %llu %ld" , |
2700 | getClientTypeName(j), |
2701 | server.client_obuf_limits[j].hard_limit_bytes, |
2702 | server.client_obuf_limits[j].soft_limit_bytes, |
2703 | (long) server.client_obuf_limits[j].soft_limit_seconds); |
2704 | if (j != CLIENT_TYPE_OBUF_COUNT-1) |
2705 | buf = sdscatlen(buf," " ,1); |
2706 | } |
2707 | return buf; |
2708 | } |
2709 | |
2710 | /* Parse an array of CONFIG_OOM_COUNT sds strings, validate and populate |
2711 | * server.oom_score_adj_values if valid. |
2712 | */ |
2713 | static int setConfigOOMScoreAdjValuesOption(standardConfig *config, sds *argv, int argc, const char **err) { |
2714 | int i; |
2715 | int values[CONFIG_OOM_COUNT]; |
2716 | int change = 0; |
2717 | UNUSED(config); |
2718 | |
2719 | if (argc != CONFIG_OOM_COUNT) { |
2720 | *err = "wrong number of arguments" ; |
2721 | return 0; |
2722 | } |
2723 | |
2724 | for (i = 0; i < CONFIG_OOM_COUNT; i++) { |
2725 | char *eptr; |
2726 | long long val = strtoll(argv[i], &eptr, 10); |
2727 | |
2728 | if (*eptr != '\0' || val < -2000 || val > 2000) { |
2729 | if (err) *err = "Invalid oom-score-adj-values, elements must be between -2000 and 2000." ; |
2730 | return 0; |
2731 | } |
2732 | |
2733 | values[i] = val; |
2734 | } |
2735 | |
2736 | /* Verify that the values make sense. If they don't omit a warning but |
2737 | * keep the configuration, which may still be valid for privileged processes. |
2738 | */ |
2739 | |
2740 | if (values[CONFIG_OOM_REPLICA] < values[CONFIG_OOM_MASTER] || |
2741 | values[CONFIG_OOM_BGCHILD] < values[CONFIG_OOM_REPLICA]) |
2742 | { |
2743 | serverLog(LL_WARNING, |
2744 | "The oom-score-adj-values configuration may not work for non-privileged processes! " |
2745 | "Please consult the documentation." ); |
2746 | } |
2747 | |
2748 | for (i = 0; i < CONFIG_OOM_COUNT; i++) { |
2749 | if (server.oom_score_adj_values[i] != values[i]) { |
2750 | server.oom_score_adj_values[i] = values[i]; |
2751 | change = 1; |
2752 | } |
2753 | } |
2754 | |
2755 | return change ? 1 : 2; |
2756 | } |
2757 | |
2758 | static sds getConfigOOMScoreAdjValuesOption(standardConfig *config) { |
2759 | UNUSED(config); |
2760 | sds buf = sdsempty(); |
2761 | int j; |
2762 | |
2763 | for (j = 0; j < CONFIG_OOM_COUNT; j++) { |
2764 | buf = sdscatprintf(buf,"%d" , server.oom_score_adj_values[j]); |
2765 | if (j != CONFIG_OOM_COUNT-1) |
2766 | buf = sdscatlen(buf," " ,1); |
2767 | } |
2768 | |
2769 | return buf; |
2770 | } |
2771 | |
2772 | static int setConfigNotifyKeyspaceEventsOption(standardConfig *config, sds *argv, int argc, const char **err) { |
2773 | UNUSED(config); |
2774 | if (argc != 1) { |
2775 | *err = "wrong number of arguments" ; |
2776 | return 0; |
2777 | } |
2778 | int flags = keyspaceEventsStringToFlags(argv[0]); |
2779 | if (flags == -1) { |
2780 | *err = "Invalid event class character. Use 'Ag$lshzxeKEtmdn'." ; |
2781 | return 0; |
2782 | } |
2783 | server.notify_keyspace_events = flags; |
2784 | return 1; |
2785 | } |
2786 | |
2787 | static sds getConfigNotifyKeyspaceEventsOption(standardConfig *config) { |
2788 | UNUSED(config); |
2789 | return keyspaceEventsFlagsToString(server.notify_keyspace_events); |
2790 | } |
2791 | |
2792 | static int setConfigBindOption(standardConfig *config, sds* argv, int argc, const char **err) { |
2793 | UNUSED(config); |
2794 | int j; |
2795 | |
2796 | if (argc > CONFIG_BINDADDR_MAX) { |
2797 | *err = "Too many bind addresses specified." ; |
2798 | return 0; |
2799 | } |
2800 | |
2801 | /* A single empty argument is treated as a zero bindaddr count */ |
2802 | if (argc == 1 && sdslen(argv[0]) == 0) argc = 0; |
2803 | |
2804 | /* Free old bind addresses */ |
2805 | for (j = 0; j < server.bindaddr_count; j++) { |
2806 | zfree(server.bindaddr[j]); |
2807 | } |
2808 | for (j = 0; j < argc; j++) |
2809 | server.bindaddr[j] = zstrdup(argv[j]); |
2810 | server.bindaddr_count = argc; |
2811 | |
2812 | return 1; |
2813 | } |
2814 | |
2815 | static int setConfigReplicaOfOption(standardConfig *config, sds* argv, int argc, const char **err) { |
2816 | UNUSED(config); |
2817 | |
2818 | if (argc != 2) { |
2819 | *err = "wrong number of arguments" ; |
2820 | return 0; |
2821 | } |
2822 | |
2823 | sdsfree(server.masterhost); |
2824 | server.masterhost = NULL; |
2825 | if (!strcasecmp(argv[0], "no" ) && !strcasecmp(argv[1], "one" )) { |
2826 | return 1; |
2827 | } |
2828 | char *ptr; |
2829 | server.masterport = strtol(argv[1], &ptr, 10); |
2830 | if (server.masterport < 0 || server.masterport > 65535 || *ptr != '\0') { |
2831 | *err = "Invalid master port" ; |
2832 | return 0; |
2833 | } |
2834 | server.masterhost = sdsnew(argv[0]); |
2835 | server.repl_state = REPL_STATE_CONNECT; |
2836 | return 1; |
2837 | } |
2838 | |
2839 | static sds getConfigBindOption(standardConfig *config) { |
2840 | UNUSED(config); |
2841 | return sdsjoin(server.bindaddr,server.bindaddr_count," " ); |
2842 | } |
2843 | |
2844 | static sds getConfigReplicaOfOption(standardConfig *config) { |
2845 | UNUSED(config); |
2846 | char buf[256]; |
2847 | if (server.masterhost) |
2848 | snprintf(buf,sizeof(buf),"%s %d" , |
2849 | server.masterhost, server.masterport); |
2850 | else |
2851 | buf[0] = '\0'; |
2852 | return sdsnew(buf); |
2853 | } |
2854 | |
2855 | int allowProtectedAction(int config, client *c) { |
2856 | return (config == PROTECTED_ACTION_ALLOWED_YES) || |
2857 | (config == PROTECTED_ACTION_ALLOWED_LOCAL && islocalClient(c)); |
2858 | } |
2859 | |
2860 | |
2861 | static int setConfigLatencyTrackingInfoPercentilesOutputOption(standardConfig *config, sds *argv, int argc, const char **err) { |
2862 | UNUSED(config); |
2863 | zfree(server.latency_tracking_info_percentiles); |
2864 | server.latency_tracking_info_percentiles = NULL; |
2865 | server.latency_tracking_info_percentiles_len = argc; |
2866 | |
2867 | /* Special case: treat single arg "" as zero args indicating empty percentile configuration */ |
2868 | if (argc == 1 && sdslen(argv[0]) == 0) |
2869 | server.latency_tracking_info_percentiles_len = 0; |
2870 | else |
2871 | server.latency_tracking_info_percentiles = zmalloc(sizeof(double)*argc); |
2872 | |
2873 | for (int j = 0; j < server.latency_tracking_info_percentiles_len; j++) { |
2874 | double percentile; |
2875 | if (!string2d(argv[j], sdslen(argv[j]), &percentile)) { |
2876 | *err = "Invalid latency-tracking-info-percentiles parameters" ; |
2877 | goto configerr; |
2878 | } |
2879 | if (percentile > 100.0 || percentile < 0.0) { |
2880 | *err = "latency-tracking-info-percentiles parameters should sit between [0.0,100.0]" ; |
2881 | goto configerr; |
2882 | } |
2883 | server.latency_tracking_info_percentiles[j] = percentile; |
2884 | } |
2885 | |
2886 | return 1; |
2887 | configerr: |
2888 | zfree(server.latency_tracking_info_percentiles); |
2889 | server.latency_tracking_info_percentiles = NULL; |
2890 | server.latency_tracking_info_percentiles_len = 0; |
2891 | return 0; |
2892 | } |
2893 | |
2894 | static sds getConfigLatencyTrackingInfoPercentilesOutputOption(standardConfig *config) { |
2895 | UNUSED(config); |
2896 | sds buf = sdsempty(); |
2897 | for (int j = 0; j < server.latency_tracking_info_percentiles_len; j++) { |
2898 | char fbuf[128]; |
2899 | size_t len = sprintf(fbuf, "%f" , server.latency_tracking_info_percentiles[j]); |
2900 | len = trimDoubleString(fbuf, len); |
2901 | buf = sdscatlen(buf, fbuf, len); |
2902 | if (j != server.latency_tracking_info_percentiles_len-1) |
2903 | buf = sdscatlen(buf," " ,1); |
2904 | } |
2905 | return buf; |
2906 | } |
2907 | |
2908 | /* Rewrite the latency-tracking-info-percentiles option. */ |
2909 | void rewriteConfigLatencyTrackingInfoPercentilesOutputOption(standardConfig *config, const char *name, struct rewriteConfigState *state) { |
2910 | UNUSED(config); |
2911 | sds line = sdsnew(name); |
2912 | /* Rewrite latency-tracking-info-percentiles parameters, |
2913 | * or an empty 'latency-tracking-info-percentiles ""' line to avoid the |
2914 | * defaults from being used. |
2915 | */ |
2916 | if (!server.latency_tracking_info_percentiles_len) { |
2917 | line = sdscat(line," \"\"" ); |
2918 | } else { |
2919 | for (int j = 0; j < server.latency_tracking_info_percentiles_len; j++) { |
2920 | char fbuf[128]; |
2921 | size_t len = sprintf(fbuf, " %f" , server.latency_tracking_info_percentiles[j]); |
2922 | len = trimDoubleString(fbuf, len); |
2923 | line = sdscatlen(line, fbuf, len); |
2924 | } |
2925 | } |
2926 | rewriteConfigRewriteLine(state,name,line,1); |
2927 | } |
2928 | |
2929 | standardConfig static_configs[] = { |
2930 | /* Bool configs */ |
2931 | createBoolConfig("rdbchecksum" , NULL, IMMUTABLE_CONFIG, server.rdb_checksum, 1, NULL, NULL), |
2932 | createBoolConfig("daemonize" , NULL, IMMUTABLE_CONFIG, server.daemonize, 0, NULL, NULL), |
2933 | createBoolConfig("io-threads-do-reads" , NULL, DEBUG_CONFIG | IMMUTABLE_CONFIG, server.io_threads_do_reads, 0,NULL, NULL), /* Read + parse from threads? */ |
2934 | createBoolConfig("always-show-logo" , NULL, IMMUTABLE_CONFIG, server.always_show_logo, 0, NULL, NULL), |
2935 | createBoolConfig("protected-mode" , NULL, MODIFIABLE_CONFIG, server.protected_mode, 1, NULL, NULL), |
2936 | createBoolConfig("rdbcompression" , NULL, MODIFIABLE_CONFIG, server.rdb_compression, 1, NULL, NULL), |
2937 | createBoolConfig("rdb-del-sync-files" , NULL, MODIFIABLE_CONFIG, server.rdb_del_sync_files, 0, NULL, NULL), |
2938 | createBoolConfig("activerehashing" , NULL, MODIFIABLE_CONFIG, server.activerehashing, 1, NULL, NULL), |
2939 | createBoolConfig("stop-writes-on-bgsave-error" , NULL, MODIFIABLE_CONFIG, server.stop_writes_on_bgsave_err, 1, NULL, NULL), |
2940 | createBoolConfig("set-proc-title" , NULL, IMMUTABLE_CONFIG, server.set_proc_title, 1, NULL, NULL), /* Should setproctitle be used? */ |
2941 | createBoolConfig("dynamic-hz" , NULL, MODIFIABLE_CONFIG, server.dynamic_hz, 1, NULL, NULL), /* Adapt hz to # of clients.*/ |
2942 | createBoolConfig("lazyfree-lazy-eviction" , NULL, DEBUG_CONFIG | MODIFIABLE_CONFIG, server.lazyfree_lazy_eviction, 0, NULL, NULL), |
2943 | createBoolConfig("lazyfree-lazy-expire" , NULL, DEBUG_CONFIG | MODIFIABLE_CONFIG, server.lazyfree_lazy_expire, 0, NULL, NULL), |
2944 | createBoolConfig("lazyfree-lazy-server-del" , NULL, DEBUG_CONFIG | MODIFIABLE_CONFIG, server.lazyfree_lazy_server_del, 0, NULL, NULL), |
2945 | createBoolConfig("lazyfree-lazy-user-del" , NULL, DEBUG_CONFIG | MODIFIABLE_CONFIG, server.lazyfree_lazy_user_del , 0, NULL, NULL), |
2946 | createBoolConfig("lazyfree-lazy-user-flush" , NULL, DEBUG_CONFIG | MODIFIABLE_CONFIG, server.lazyfree_lazy_user_flush , 0, NULL, NULL), |
2947 | createBoolConfig("repl-disable-tcp-nodelay" , NULL, MODIFIABLE_CONFIG, server.repl_disable_tcp_nodelay, 0, NULL, NULL), |
2948 | createBoolConfig("repl-diskless-sync" , NULL, DEBUG_CONFIG | MODIFIABLE_CONFIG, server.repl_diskless_sync, 1, NULL, NULL), |
2949 | createBoolConfig("aof-rewrite-incremental-fsync" , NULL, MODIFIABLE_CONFIG, server.aof_rewrite_incremental_fsync, 1, NULL, NULL), |
2950 | createBoolConfig("no-appendfsync-on-rewrite" , NULL, MODIFIABLE_CONFIG, server.aof_no_fsync_on_rewrite, 0, NULL, NULL), |
2951 | createBoolConfig("cluster-require-full-coverage" , NULL, MODIFIABLE_CONFIG, server.cluster_require_full_coverage, 1, NULL, NULL), |
2952 | createBoolConfig("rdb-save-incremental-fsync" , NULL, MODIFIABLE_CONFIG, server.rdb_save_incremental_fsync, 1, NULL, NULL), |
2953 | createBoolConfig("aof-load-truncated" , NULL, MODIFIABLE_CONFIG, server.aof_load_truncated, 1, NULL, NULL), |
2954 | createBoolConfig("aof-use-rdb-preamble" , NULL, MODIFIABLE_CONFIG, server.aof_use_rdb_preamble, 1, NULL, NULL), |
2955 | createBoolConfig("aof-timestamp-enabled" , NULL, MODIFIABLE_CONFIG, server.aof_timestamp_enabled, 0, NULL, NULL), |
2956 | createBoolConfig("cluster-replica-no-failover" , "cluster-slave-no-failover" , MODIFIABLE_CONFIG, server.cluster_slave_no_failover, 0, NULL, updateClusterFlags), /* Failover by default. */ |
2957 | createBoolConfig("replica-lazy-flush" , "slave-lazy-flush" , MODIFIABLE_CONFIG, server.repl_slave_lazy_flush, 0, NULL, NULL), |
2958 | createBoolConfig("replica-serve-stale-data" , "slave-serve-stale-data" , MODIFIABLE_CONFIG, server.repl_serve_stale_data, 1, NULL, NULL), |
2959 | createBoolConfig("replica-read-only" , "slave-read-only" , DEBUG_CONFIG | MODIFIABLE_CONFIG, server.repl_slave_ro, 1, NULL, NULL), |
2960 | createBoolConfig("replica-ignore-maxmemory" , "slave-ignore-maxmemory" , MODIFIABLE_CONFIG, server.repl_slave_ignore_maxmemory, 1, NULL, NULL), |
2961 | createBoolConfig("jemalloc-bg-thread" , NULL, MODIFIABLE_CONFIG, server.jemalloc_bg_thread, 1, NULL, updateJemallocBgThread), |
2962 | createBoolConfig("activedefrag" , NULL, DEBUG_CONFIG | MODIFIABLE_CONFIG, server.active_defrag_enabled, 0, isValidActiveDefrag, NULL), |
2963 | createBoolConfig("syslog-enabled" , NULL, IMMUTABLE_CONFIG, server.syslog_enabled, 0, NULL, NULL), |
2964 | createBoolConfig("cluster-enabled" , NULL, IMMUTABLE_CONFIG, server.cluster_enabled, 0, NULL, NULL), |
2965 | createBoolConfig("appendonly" , NULL, MODIFIABLE_CONFIG | DENY_LOADING_CONFIG, server.aof_enabled, 0, NULL, updateAppendonly), |
2966 | createBoolConfig("cluster-allow-reads-when-down" , NULL, MODIFIABLE_CONFIG, server.cluster_allow_reads_when_down, 0, NULL, NULL), |
2967 | createBoolConfig("cluster-allow-pubsubshard-when-down" , NULL, MODIFIABLE_CONFIG, server.cluster_allow_pubsubshard_when_down, 1, NULL, NULL), |
2968 | createBoolConfig("crash-log-enabled" , NULL, MODIFIABLE_CONFIG, server.crashlog_enabled, 1, NULL, updateSighandlerEnabled), |
2969 | createBoolConfig("crash-memcheck-enabled" , NULL, MODIFIABLE_CONFIG, server.memcheck_enabled, 1, NULL, NULL), |
2970 | createBoolConfig("use-exit-on-panic" , NULL, MODIFIABLE_CONFIG | HIDDEN_CONFIG, server.use_exit_on_panic, 0, NULL, NULL), |
2971 | createBoolConfig("disable-thp" , NULL, IMMUTABLE_CONFIG, server.disable_thp, 1, NULL, NULL), |
2972 | createBoolConfig("cluster-allow-replica-migration" , NULL, MODIFIABLE_CONFIG, server.cluster_allow_replica_migration, 1, NULL, NULL), |
2973 | createBoolConfig("replica-announced" , NULL, MODIFIABLE_CONFIG, server.replica_announced, 1, NULL, NULL), |
2974 | createBoolConfig("latency-tracking" , NULL, MODIFIABLE_CONFIG, server.latency_tracking_enabled, 1, NULL, NULL), |
2975 | createBoolConfig("aof-disable-auto-gc" , NULL, MODIFIABLE_CONFIG, server.aof_disable_auto_gc, 0, NULL, updateAofAutoGCEnabled), |
2976 | createBoolConfig("replica-ignore-disk-write-errors" , NULL, MODIFIABLE_CONFIG, server.repl_ignore_disk_write_error, 0, NULL, NULL), |
2977 | |
2978 | /* String Configs */ |
2979 | createStringConfig("aclfile" , NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.acl_filename, "" , NULL, NULL), |
2980 | createStringConfig("unixsocket" , NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.unixsocket, NULL, NULL, NULL), |
2981 | createStringConfig("pidfile" , NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.pidfile, NULL, NULL, NULL), |
2982 | createStringConfig("replica-announce-ip" , "slave-announce-ip" , MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.slave_announce_ip, NULL, NULL, NULL), |
2983 | createStringConfig("masteruser" , NULL, MODIFIABLE_CONFIG | SENSITIVE_CONFIG, EMPTY_STRING_IS_NULL, server.masteruser, NULL, NULL, NULL), |
2984 | createStringConfig("cluster-announce-ip" , NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.cluster_announce_ip, NULL, NULL, updateClusterIp), |
2985 | createStringConfig("cluster-config-file" , NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.cluster_configfile, "nodes.conf" , NULL, NULL), |
2986 | createStringConfig("cluster-announce-hostname" , NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.cluster_announce_hostname, NULL, isValidAnnouncedHostname, updateClusterHostname), |
2987 | createStringConfig("syslog-ident" , NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.syslog_ident, "redis" , NULL, NULL), |
2988 | createStringConfig("dbfilename" , NULL, MODIFIABLE_CONFIG | PROTECTED_CONFIG, ALLOW_EMPTY_STRING, server.rdb_filename, "dump.rdb" , isValidDBfilename, NULL), |
2989 | createStringConfig("appendfilename" , NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.aof_filename, "appendonly.aof" , isValidAOFfilename, NULL), |
2990 | createStringConfig("appenddirname" , NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.aof_dirname, "appendonlydir" , isValidAOFdirname, NULL), |
2991 | createStringConfig("server_cpulist" , NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.server_cpulist, NULL, NULL, NULL), |
2992 | createStringConfig("bio_cpulist" , NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.bio_cpulist, NULL, NULL, NULL), |
2993 | createStringConfig("aof_rewrite_cpulist" , NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.aof_rewrite_cpulist, NULL, NULL, NULL), |
2994 | createStringConfig("bgsave_cpulist" , NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.bgsave_cpulist, NULL, NULL, NULL), |
2995 | createStringConfig("ignore-warnings" , NULL, MODIFIABLE_CONFIG, ALLOW_EMPTY_STRING, server.ignore_warnings, "" , NULL, NULL), |
2996 | createStringConfig("proc-title-template" , NULL, MODIFIABLE_CONFIG, ALLOW_EMPTY_STRING, server.proc_title_template, CONFIG_DEFAULT_PROC_TITLE_TEMPLATE, isValidProcTitleTemplate, updateProcTitleTemplate), |
2997 | createStringConfig("bind-source-addr" , NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.bind_source_addr, NULL, NULL, NULL), |
2998 | createStringConfig("logfile" , NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.logfile, "" , NULL, NULL), |
2999 | |
3000 | /* SDS Configs */ |
3001 | createSDSConfig("masterauth" , NULL, MODIFIABLE_CONFIG | SENSITIVE_CONFIG, EMPTY_STRING_IS_NULL, server.masterauth, NULL, NULL, NULL), |
3002 | createSDSConfig("requirepass" , NULL, MODIFIABLE_CONFIG | SENSITIVE_CONFIG, EMPTY_STRING_IS_NULL, server.requirepass, NULL, NULL, updateRequirePass), |
3003 | |
3004 | /* Enum Configs */ |
3005 | createEnumConfig("supervised" , NULL, IMMUTABLE_CONFIG, supervised_mode_enum, server.supervised_mode, SUPERVISED_NONE, NULL, NULL), |
3006 | createEnumConfig("syslog-facility" , NULL, IMMUTABLE_CONFIG, syslog_facility_enum, server.syslog_facility, LOG_LOCAL0, NULL, NULL), |
3007 | createEnumConfig("repl-diskless-load" , NULL, DEBUG_CONFIG | MODIFIABLE_CONFIG | DENY_LOADING_CONFIG, repl_diskless_load_enum, server.repl_diskless_load, REPL_DISKLESS_LOAD_DISABLED, NULL, NULL), |
3008 | createEnumConfig("loglevel" , NULL, MODIFIABLE_CONFIG, loglevel_enum, server.verbosity, LL_NOTICE, NULL, NULL), |
3009 | createEnumConfig("maxmemory-policy" , NULL, MODIFIABLE_CONFIG, maxmemory_policy_enum, server.maxmemory_policy, MAXMEMORY_NO_EVICTION, NULL, NULL), |
3010 | createEnumConfig("appendfsync" , NULL, MODIFIABLE_CONFIG, aof_fsync_enum, server.aof_fsync, AOF_FSYNC_EVERYSEC, NULL, NULL), |
3011 | createEnumConfig("oom-score-adj" , NULL, MODIFIABLE_CONFIG, oom_score_adj_enum, server.oom_score_adj, OOM_SCORE_ADJ_NO, NULL, updateOOMScoreAdj), |
3012 | createEnumConfig("acl-pubsub-default" , NULL, MODIFIABLE_CONFIG, acl_pubsub_default_enum, server.acl_pubsub_default, 0, NULL, NULL), |
3013 | createEnumConfig("sanitize-dump-payload" , NULL, DEBUG_CONFIG | MODIFIABLE_CONFIG, sanitize_dump_payload_enum, server.sanitize_dump_payload, SANITIZE_DUMP_NO, NULL, NULL), |
3014 | createEnumConfig("enable-protected-configs" , NULL, IMMUTABLE_CONFIG, protected_action_enum, server.enable_protected_configs, PROTECTED_ACTION_ALLOWED_NO, NULL, NULL), |
3015 | createEnumConfig("enable-debug-command" , NULL, IMMUTABLE_CONFIG, protected_action_enum, server.enable_debug_cmd, PROTECTED_ACTION_ALLOWED_NO, NULL, NULL), |
3016 | createEnumConfig("enable-module-command" , NULL, IMMUTABLE_CONFIG, protected_action_enum, server.enable_module_cmd, PROTECTED_ACTION_ALLOWED_NO, NULL, NULL), |
3017 | createEnumConfig("cluster-preferred-endpoint-type" , NULL, MODIFIABLE_CONFIG, cluster_preferred_endpoint_type_enum, server.cluster_preferred_endpoint_type, CLUSTER_ENDPOINT_TYPE_IP, NULL, NULL), |
3018 | createEnumConfig("propagation-error-behavior" , NULL, MODIFIABLE_CONFIG, propagation_error_behavior_enum, server.propagation_error_behavior, PROPAGATION_ERR_BEHAVIOR_IGNORE, NULL, NULL), |
3019 | createEnumConfig("shutdown-on-sigint" , NULL, MODIFIABLE_CONFIG | MULTI_ARG_CONFIG, shutdown_on_sig_enum, server.shutdown_on_sigint, 0, isValidShutdownOnSigFlags, NULL), |
3020 | createEnumConfig("shutdown-on-sigterm" , NULL, MODIFIABLE_CONFIG | MULTI_ARG_CONFIG, shutdown_on_sig_enum, server.shutdown_on_sigterm, 0, isValidShutdownOnSigFlags, NULL), |
3021 | |
3022 | /* Integer configs */ |
3023 | createIntConfig("databases" , NULL, IMMUTABLE_CONFIG, 1, INT_MAX, server.dbnum, 16, INTEGER_CONFIG, NULL, NULL), |
3024 | createIntConfig("port" , NULL, MODIFIABLE_CONFIG, 0, 65535, server.port, 6379, INTEGER_CONFIG, NULL, updatePort), /* TCP port. */ |
3025 | createIntConfig("io-threads" , NULL, DEBUG_CONFIG | IMMUTABLE_CONFIG, 1, 128, server.io_threads_num, 1, INTEGER_CONFIG, NULL, NULL), /* Single threaded by default */ |
3026 | createIntConfig("auto-aof-rewrite-percentage" , NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.aof_rewrite_perc, 100, INTEGER_CONFIG, NULL, NULL), |
3027 | createIntConfig("cluster-replica-validity-factor" , "cluster-slave-validity-factor" , MODIFIABLE_CONFIG, 0, INT_MAX, server.cluster_slave_validity_factor, 10, INTEGER_CONFIG, NULL, NULL), /* Slave max data age factor. */ |
3028 | createIntConfig("list-max-listpack-size" , "list-max-ziplist-size" , MODIFIABLE_CONFIG, INT_MIN, INT_MAX, server.list_max_listpack_size, -2, INTEGER_CONFIG, NULL, NULL), |
3029 | createIntConfig("tcp-keepalive" , NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.tcpkeepalive, 300, INTEGER_CONFIG, NULL, NULL), |
3030 | createIntConfig("cluster-migration-barrier" , NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.cluster_migration_barrier, 1, INTEGER_CONFIG, NULL, NULL), |
3031 | createIntConfig("active-defrag-cycle-min" , NULL, MODIFIABLE_CONFIG, 1, 99, server.active_defrag_cycle_min, 1, INTEGER_CONFIG, NULL, NULL), /* Default: 1% CPU min (at lower threshold) */ |
3032 | createIntConfig("active-defrag-cycle-max" , NULL, MODIFIABLE_CONFIG, 1, 99, server.active_defrag_cycle_max, 25, INTEGER_CONFIG, NULL, NULL), /* Default: 25% CPU max (at upper threshold) */ |
3033 | createIntConfig("active-defrag-threshold-lower" , NULL, MODIFIABLE_CONFIG, 0, 1000, server.active_defrag_threshold_lower, 10, INTEGER_CONFIG, NULL, NULL), /* Default: don't defrag when fragmentation is below 10% */ |
3034 | createIntConfig("active-defrag-threshold-upper" , NULL, MODIFIABLE_CONFIG, 0, 1000, server.active_defrag_threshold_upper, 100, INTEGER_CONFIG, NULL, NULL), /* Default: maximum defrag force at 100% fragmentation */ |
3035 | createIntConfig("lfu-log-factor" , NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.lfu_log_factor, 10, INTEGER_CONFIG, NULL, NULL), |
3036 | createIntConfig("lfu-decay-time" , NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.lfu_decay_time, 1, INTEGER_CONFIG, NULL, NULL), |
3037 | createIntConfig("replica-priority" , "slave-priority" , MODIFIABLE_CONFIG, 0, INT_MAX, server.slave_priority, 100, INTEGER_CONFIG, NULL, NULL), |
3038 | createIntConfig("repl-diskless-sync-delay" , NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.repl_diskless_sync_delay, 5, INTEGER_CONFIG, NULL, NULL), |
3039 | createIntConfig("maxmemory-samples" , NULL, MODIFIABLE_CONFIG, 1, INT_MAX, server.maxmemory_samples, 5, INTEGER_CONFIG, NULL, NULL), |
3040 | createIntConfig("maxmemory-eviction-tenacity" , NULL, MODIFIABLE_CONFIG, 0, 100, server.maxmemory_eviction_tenacity, 10, INTEGER_CONFIG, NULL, NULL), |
3041 | createIntConfig("timeout" , NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.maxidletime, 0, INTEGER_CONFIG, NULL, NULL), /* Default client timeout: infinite */ |
3042 | createIntConfig("replica-announce-port" , "slave-announce-port" , MODIFIABLE_CONFIG, 0, 65535, server.slave_announce_port, 0, INTEGER_CONFIG, NULL, NULL), |
3043 | createIntConfig("tcp-backlog" , NULL, IMMUTABLE_CONFIG, 0, INT_MAX, server.tcp_backlog, 511, INTEGER_CONFIG, NULL, NULL), /* TCP listen backlog. */ |
3044 | createIntConfig("cluster-port" , NULL, IMMUTABLE_CONFIG, 0, 65535, server.cluster_port, 0, INTEGER_CONFIG, NULL, NULL), |
3045 | createIntConfig("cluster-announce-bus-port" , NULL, MODIFIABLE_CONFIG, 0, 65535, server.cluster_announce_bus_port, 0, INTEGER_CONFIG, NULL, NULL), /* Default: Use +10000 offset. */ |
3046 | createIntConfig("cluster-announce-port" , NULL, MODIFIABLE_CONFIG, 0, 65535, server.cluster_announce_port, 0, INTEGER_CONFIG, NULL, NULL), /* Use server.port */ |
3047 | createIntConfig("cluster-announce-tls-port" , NULL, MODIFIABLE_CONFIG, 0, 65535, server.cluster_announce_tls_port, 0, INTEGER_CONFIG, NULL, NULL), /* Use server.tls_port */ |
3048 | createIntConfig("repl-timeout" , NULL, MODIFIABLE_CONFIG, 1, INT_MAX, server.repl_timeout, 60, INTEGER_CONFIG, NULL, NULL), |
3049 | createIntConfig("repl-ping-replica-period" , "repl-ping-slave-period" , MODIFIABLE_CONFIG, 1, INT_MAX, server.repl_ping_slave_period, 10, INTEGER_CONFIG, NULL, NULL), |
3050 | createIntConfig("list-compress-depth" , NULL, DEBUG_CONFIG | MODIFIABLE_CONFIG, 0, INT_MAX, server.list_compress_depth, 0, INTEGER_CONFIG, NULL, NULL), |
3051 | createIntConfig("rdb-key-save-delay" , NULL, MODIFIABLE_CONFIG | HIDDEN_CONFIG, INT_MIN, INT_MAX, server.rdb_key_save_delay, 0, INTEGER_CONFIG, NULL, NULL), |
3052 | createIntConfig("key-load-delay" , NULL, MODIFIABLE_CONFIG | HIDDEN_CONFIG, INT_MIN, INT_MAX, server.key_load_delay, 0, INTEGER_CONFIG, NULL, NULL), |
3053 | createIntConfig("active-expire-effort" , NULL, MODIFIABLE_CONFIG, 1, 10, server.active_expire_effort, 1, INTEGER_CONFIG, NULL, NULL), /* From 1 to 10. */ |
3054 | createIntConfig("hz" , NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.config_hz, CONFIG_DEFAULT_HZ, INTEGER_CONFIG, NULL, updateHZ), |
3055 | createIntConfig("min-replicas-to-write" , "min-slaves-to-write" , MODIFIABLE_CONFIG, 0, INT_MAX, server.repl_min_slaves_to_write, 0, INTEGER_CONFIG, NULL, updateGoodSlaves), |
3056 | createIntConfig("min-replicas-max-lag" , "min-slaves-max-lag" , MODIFIABLE_CONFIG, 0, INT_MAX, server.repl_min_slaves_max_lag, 10, INTEGER_CONFIG, NULL, updateGoodSlaves), |
3057 | createIntConfig("watchdog-period" , NULL, MODIFIABLE_CONFIG | HIDDEN_CONFIG, 0, INT_MAX, server.watchdog_period, 0, INTEGER_CONFIG, NULL, updateWatchdogPeriod), |
3058 | createIntConfig("shutdown-timeout" , NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.shutdown_timeout, 10, INTEGER_CONFIG, NULL, NULL), |
3059 | createIntConfig("repl-diskless-sync-max-replicas" , NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.repl_diskless_sync_max_replicas, 0, INTEGER_CONFIG, NULL, NULL), |
3060 | |
3061 | /* Unsigned int configs */ |
3062 | createUIntConfig("maxclients" , NULL, MODIFIABLE_CONFIG, 1, UINT_MAX, server.maxclients, 10000, INTEGER_CONFIG, NULL, updateMaxclients), |
3063 | createUIntConfig("unixsocketperm" , NULL, IMMUTABLE_CONFIG, 0, 0777, server.unixsocketperm, 0, OCTAL_CONFIG, NULL, NULL), |
3064 | createUIntConfig("socket-mark-id" , NULL, IMMUTABLE_CONFIG, 0, UINT_MAX, server.socket_mark_id, 0, INTEGER_CONFIG, NULL, NULL), |
3065 | |
3066 | /* Unsigned Long configs */ |
3067 | createULongConfig("active-defrag-max-scan-fields" , NULL, MODIFIABLE_CONFIG, 1, LONG_MAX, server.active_defrag_max_scan_fields, 1000, INTEGER_CONFIG, NULL, NULL), /* Default: keys with more than 1000 fields will be processed separately */ |
3068 | createULongConfig("slowlog-max-len" , NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.slowlog_max_len, 128, INTEGER_CONFIG, NULL, NULL), |
3069 | createULongConfig("acllog-max-len" , NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.acllog_max_len, 128, INTEGER_CONFIG, NULL, NULL), |
3070 | |
3071 | /* Long Long configs */ |
3072 | createLongLongConfig("busy-reply-threshold" , "lua-time-limit" , MODIFIABLE_CONFIG, 0, LONG_MAX, server.busy_reply_threshold, 5000, INTEGER_CONFIG, NULL, NULL),/* milliseconds */ |
3073 | createLongLongConfig("cluster-node-timeout" , NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, server.cluster_node_timeout, 15000, INTEGER_CONFIG, NULL, NULL), |
3074 | createLongLongConfig("slowlog-log-slower-than" , NULL, MODIFIABLE_CONFIG, -1, LLONG_MAX, server.slowlog_log_slower_than, 10000, INTEGER_CONFIG, NULL, NULL), |
3075 | createLongLongConfig("latency-monitor-threshold" , NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, server.latency_monitor_threshold, 0, INTEGER_CONFIG, NULL, NULL), |
3076 | createLongLongConfig("proto-max-bulk-len" , NULL, DEBUG_CONFIG | MODIFIABLE_CONFIG, 1024*1024, LONG_MAX, server.proto_max_bulk_len, 512ll*1024*1024, MEMORY_CONFIG, NULL, NULL), /* Bulk request max size */ |
3077 | createLongLongConfig("stream-node-max-entries" , NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, server.stream_node_max_entries, 100, INTEGER_CONFIG, NULL, NULL), |
3078 | createLongLongConfig("repl-backlog-size" , NULL, MODIFIABLE_CONFIG, 1, LLONG_MAX, server.repl_backlog_size, 1024*1024, MEMORY_CONFIG, NULL, updateReplBacklogSize), /* Default: 1mb */ |
3079 | |
3080 | /* Unsigned Long Long configs */ |
3081 | createULongLongConfig("maxmemory" , NULL, MODIFIABLE_CONFIG, 0, ULLONG_MAX, server.maxmemory, 0, MEMORY_CONFIG, NULL, updateMaxmemory), |
3082 | createULongLongConfig("cluster-link-sendbuf-limit" , NULL, MODIFIABLE_CONFIG, 0, ULLONG_MAX, server.cluster_link_sendbuf_limit_bytes, 0, MEMORY_CONFIG, NULL, NULL), |
3083 | |
3084 | /* Size_t configs */ |
3085 | createSizeTConfig("hash-max-listpack-entries" , "hash-max-ziplist-entries" , MODIFIABLE_CONFIG, 0, LONG_MAX, server.hash_max_listpack_entries, 512, INTEGER_CONFIG, NULL, NULL), |
3086 | createSizeTConfig("set-max-intset-entries" , NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.set_max_intset_entries, 512, INTEGER_CONFIG, NULL, NULL), |
3087 | createSizeTConfig("zset-max-listpack-entries" , "zset-max-ziplist-entries" , MODIFIABLE_CONFIG, 0, LONG_MAX, server.zset_max_listpack_entries, 128, INTEGER_CONFIG, NULL, NULL), |
3088 | createSizeTConfig("active-defrag-ignore-bytes" , NULL, MODIFIABLE_CONFIG, 1, LLONG_MAX, server.active_defrag_ignore_bytes, 100<<20, MEMORY_CONFIG, NULL, NULL), /* Default: don't defrag if frag overhead is below 100mb */ |
3089 | createSizeTConfig("hash-max-listpack-value" , "hash-max-ziplist-value" , MODIFIABLE_CONFIG, 0, LONG_MAX, server.hash_max_listpack_value, 64, MEMORY_CONFIG, NULL, NULL), |
3090 | createSizeTConfig("stream-node-max-bytes" , NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.stream_node_max_bytes, 4096, MEMORY_CONFIG, NULL, NULL), |
3091 | createSizeTConfig("zset-max-listpack-value" , "zset-max-ziplist-value" , MODIFIABLE_CONFIG, 0, LONG_MAX, server.zset_max_listpack_value, 64, MEMORY_CONFIG, NULL, NULL), |
3092 | createSizeTConfig("hll-sparse-max-bytes" , NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.hll_sparse_max_bytes, 3000, MEMORY_CONFIG, NULL, NULL), |
3093 | createSizeTConfig("tracking-table-max-keys" , NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.tracking_table_max_keys, 1000000, INTEGER_CONFIG, NULL, NULL), /* Default: 1 million keys max. */ |
3094 | createSizeTConfig("client-query-buffer-limit" , NULL, DEBUG_CONFIG | MODIFIABLE_CONFIG, 1024*1024, LONG_MAX, server.client_max_querybuf_len, 1024*1024*1024, MEMORY_CONFIG, NULL, NULL), /* Default: 1GB max query buffer. */ |
3095 | createSSizeTConfig("maxmemory-clients" , NULL, MODIFIABLE_CONFIG, -100, SSIZE_MAX, server.maxmemory_clients, 0, MEMORY_CONFIG | PERCENT_CONFIG, NULL, NULL), |
3096 | |
3097 | /* Other configs */ |
3098 | createTimeTConfig("repl-backlog-ttl" , NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.repl_backlog_time_limit, 60*60, INTEGER_CONFIG, NULL, NULL), /* Default: 1 hour */ |
3099 | createOffTConfig("auto-aof-rewrite-min-size" , NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, server.aof_rewrite_min_size, 64*1024*1024, MEMORY_CONFIG, NULL, NULL), |
3100 | createOffTConfig("loading-process-events-interval-bytes" , NULL, MODIFIABLE_CONFIG | HIDDEN_CONFIG, 1024, INT_MAX, server.loading_process_events_interval_bytes, 1024*1024*2, INTEGER_CONFIG, NULL, NULL), |
3101 | |
3102 | #ifdef USE_OPENSSL |
3103 | createIntConfig("tls-port" , NULL, MODIFIABLE_CONFIG, 0, 65535, server.tls_port, 0, INTEGER_CONFIG, NULL, applyTLSPort), /* TCP port. */ |
3104 | createIntConfig("tls-session-cache-size" , NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.tls_ctx_config.session_cache_size, 20*1024, INTEGER_CONFIG, NULL, applyTlsCfg), |
3105 | createIntConfig("tls-session-cache-timeout" , NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.tls_ctx_config.session_cache_timeout, 300, INTEGER_CONFIG, NULL, applyTlsCfg), |
3106 | createBoolConfig("tls-cluster" , NULL, MODIFIABLE_CONFIG, server.tls_cluster, 0, NULL, applyTlsCfg), |
3107 | createBoolConfig("tls-replication" , NULL, MODIFIABLE_CONFIG, server.tls_replication, 0, NULL, applyTlsCfg), |
3108 | createEnumConfig("tls-auth-clients" , NULL, MODIFIABLE_CONFIG, tls_auth_clients_enum, server.tls_auth_clients, TLS_CLIENT_AUTH_YES, NULL, NULL), |
3109 | createBoolConfig("tls-prefer-server-ciphers" , NULL, MODIFIABLE_CONFIG, server.tls_ctx_config.prefer_server_ciphers, 0, NULL, applyTlsCfg), |
3110 | createBoolConfig("tls-session-caching" , NULL, MODIFIABLE_CONFIG, server.tls_ctx_config.session_caching, 1, NULL, applyTlsCfg), |
3111 | createStringConfig("tls-cert-file" , NULL, VOLATILE_CONFIG | MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.cert_file, NULL, NULL, applyTlsCfg), |
3112 | createStringConfig("tls-key-file" , NULL, VOLATILE_CONFIG | MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.key_file, NULL, NULL, applyTlsCfg), |
3113 | createStringConfig("tls-key-file-pass" , NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.key_file_pass, NULL, NULL, applyTlsCfg), |
3114 | createStringConfig("tls-client-cert-file" , NULL, VOLATILE_CONFIG | MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.client_cert_file, NULL, NULL, applyTlsCfg), |
3115 | createStringConfig("tls-client-key-file" , NULL, VOLATILE_CONFIG | MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.client_key_file, NULL, NULL, applyTlsCfg), |
3116 | createStringConfig("tls-client-key-file-pass" , NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.client_key_file_pass, NULL, NULL, applyTlsCfg), |
3117 | createStringConfig("tls-dh-params-file" , NULL, VOLATILE_CONFIG | MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.dh_params_file, NULL, NULL, applyTlsCfg), |
3118 | createStringConfig("tls-ca-cert-file" , NULL, VOLATILE_CONFIG | MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ca_cert_file, NULL, NULL, applyTlsCfg), |
3119 | createStringConfig("tls-ca-cert-dir" , NULL, VOLATILE_CONFIG | MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ca_cert_dir, NULL, NULL, applyTlsCfg), |
3120 | createStringConfig("tls-protocols" , NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.protocols, NULL, NULL, applyTlsCfg), |
3121 | createStringConfig("tls-ciphers" , NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ciphers, NULL, NULL, applyTlsCfg), |
3122 | createStringConfig("tls-ciphersuites" , NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ciphersuites, NULL, NULL, applyTlsCfg), |
3123 | #endif |
3124 | |
3125 | /* Special configs */ |
3126 | createSpecialConfig("dir" , NULL, MODIFIABLE_CONFIG | PROTECTED_CONFIG | DENY_LOADING_CONFIG, setConfigDirOption, getConfigDirOption, rewriteConfigDirOption, NULL), |
3127 | createSpecialConfig("save" , NULL, MODIFIABLE_CONFIG | MULTI_ARG_CONFIG, setConfigSaveOption, getConfigSaveOption, rewriteConfigSaveOption, NULL), |
3128 | createSpecialConfig("client-output-buffer-limit" , NULL, MODIFIABLE_CONFIG | MULTI_ARG_CONFIG, setConfigClientOutputBufferLimitOption, getConfigClientOutputBufferLimitOption, rewriteConfigClientOutputBufferLimitOption, NULL), |
3129 | createSpecialConfig("oom-score-adj-values" , NULL, MODIFIABLE_CONFIG | MULTI_ARG_CONFIG, setConfigOOMScoreAdjValuesOption, getConfigOOMScoreAdjValuesOption, rewriteConfigOOMScoreAdjValuesOption, updateOOMScoreAdj), |
3130 | createSpecialConfig("notify-keyspace-events" , NULL, MODIFIABLE_CONFIG, setConfigNotifyKeyspaceEventsOption, getConfigNotifyKeyspaceEventsOption, rewriteConfigNotifyKeyspaceEventsOption, NULL), |
3131 | createSpecialConfig("bind" , NULL, MODIFIABLE_CONFIG | MULTI_ARG_CONFIG, setConfigBindOption, getConfigBindOption, rewriteConfigBindOption, applyBind), |
3132 | createSpecialConfig("replicaof" , "slaveof" , IMMUTABLE_CONFIG | MULTI_ARG_CONFIG, setConfigReplicaOfOption, getConfigReplicaOfOption, rewriteConfigReplicaOfOption, NULL), |
3133 | createSpecialConfig("latency-tracking-info-percentiles" , NULL, MODIFIABLE_CONFIG | MULTI_ARG_CONFIG, setConfigLatencyTrackingInfoPercentilesOutputOption, getConfigLatencyTrackingInfoPercentilesOutputOption, rewriteConfigLatencyTrackingInfoPercentilesOutputOption, NULL), |
3134 | |
3135 | /* NULL Terminator, this is dropped when we convert to the runtime array. */ |
3136 | {NULL} |
3137 | }; |
3138 | |
3139 | /* Create a new config by copying the passed in config. Returns 1 on success |
3140 | * or 0 when their was already a config with the same name.. */ |
3141 | int registerConfigValue(const char *name, const standardConfig *config, int alias) { |
3142 | standardConfig *new = zmalloc(sizeof(standardConfig)); |
3143 | memcpy(new, config, sizeof(standardConfig)); |
3144 | if (alias) { |
3145 | new->flags |= ALIAS_CONFIG; |
3146 | new->name = config->alias; |
3147 | new->alias = config->name; |
3148 | } |
3149 | |
3150 | return dictAdd(configs, sdsnew(name), new) == DICT_OK; |
3151 | } |
3152 | |
3153 | /* Initialize configs to their default values and create and populate the |
3154 | * runtime configuration dictionary. */ |
3155 | void initConfigValues() { |
3156 | configs = dictCreate(&sdsHashDictType); |
3157 | dictExpand(configs, sizeof(static_configs) / sizeof(standardConfig)); |
3158 | for (standardConfig *config = static_configs; config->name != NULL; config++) { |
3159 | if (config->interface.init) config->interface.init(config); |
3160 | /* Add the primary config to the dictionary. */ |
3161 | int ret = registerConfigValue(config->name, config, 0); |
3162 | serverAssert(ret); |
3163 | |
3164 | /* Aliases are the same as their primary counter parts, but they |
3165 | * also have a flag indicating they are the alias. */ |
3166 | if (config->alias) { |
3167 | int ret = registerConfigValue(config->alias, config, ALIAS_CONFIG); |
3168 | serverAssert(ret); |
3169 | } |
3170 | } |
3171 | } |
3172 | |
3173 | /* Remove a config by name from the configs dict. */ |
3174 | void removeConfig(sds name) { |
3175 | standardConfig *config = lookupConfig(name); |
3176 | if (!config) return; |
3177 | if (config->flags & MODULE_CONFIG) { |
3178 | sdsfree((sds) config->name); |
3179 | if (config->type == ENUM_CONFIG) { |
3180 | configEnum *enumNode = config->data.enumd.enum_value; |
3181 | while(enumNode->name != NULL) { |
3182 | zfree(enumNode->name); |
3183 | enumNode++; |
3184 | } |
3185 | zfree(config->data.enumd.enum_value); |
3186 | } else if (config->type == SDS_CONFIG) { |
3187 | if (config->data.sds.default_value) sdsfree((sds)config->data.sds.default_value); |
3188 | } |
3189 | } |
3190 | dictDelete(configs, name); |
3191 | } |
3192 | |
3193 | /*----------------------------------------------------------------------------- |
3194 | * Module Config |
3195 | *----------------------------------------------------------------------------*/ |
3196 | |
3197 | /* Create a bool/string/enum/numeric standardConfig for a module config in the configs dictionary */ |
3198 | void addModuleBoolConfig(const char *module_name, const char *name, int flags, void *privdata, int default_val) { |
3199 | sds config_name = sdscatfmt(sdsempty(), "%s.%s" , module_name, name); |
3200 | int config_dummy_address; |
3201 | standardConfig module_config = createBoolConfig(config_name, NULL, flags | MODULE_CONFIG, config_dummy_address, default_val, NULL, NULL); |
3202 | module_config.data.yesno.config = NULL; |
3203 | module_config.privdata = privdata; |
3204 | registerConfigValue(config_name, &module_config, 0); |
3205 | } |
3206 | |
3207 | void addModuleStringConfig(const char *module_name, const char *name, int flags, void *privdata, sds default_val) { |
3208 | sds config_name = sdscatfmt(sdsempty(), "%s.%s" , module_name, name); |
3209 | sds config_dummy_address; |
3210 | standardConfig module_config = createSDSConfig(config_name, NULL, flags | MODULE_CONFIG, 0, config_dummy_address, default_val, NULL, NULL); |
3211 | module_config.data.sds.config = NULL; |
3212 | module_config.privdata = privdata; |
3213 | registerConfigValue(config_name, &module_config, 0); |
3214 | } |
3215 | |
3216 | void addModuleEnumConfig(const char *module_name, const char *name, int flags, void *privdata, int default_val, configEnum *enum_vals) { |
3217 | sds config_name = sdscatfmt(sdsempty(), "%s.%s" , module_name, name); |
3218 | int config_dummy_address; |
3219 | standardConfig module_config = createEnumConfig(config_name, NULL, flags | MODULE_CONFIG, enum_vals, config_dummy_address, default_val, NULL, NULL); |
3220 | module_config.data.enumd.config = NULL; |
3221 | module_config.privdata = privdata; |
3222 | registerConfigValue(config_name, &module_config, 0); |
3223 | } |
3224 | |
3225 | void addModuleNumericConfig(const char *module_name, const char *name, int flags, void *privdata, long long default_val, int conf_flags, long long lower, long long upper) { |
3226 | sds config_name = sdscatfmt(sdsempty(), "%s.%s" , module_name, name); |
3227 | long long config_dummy_address; |
3228 | standardConfig module_config = createLongLongConfig(config_name, NULL, flags | MODULE_CONFIG, lower, upper, config_dummy_address, default_val, conf_flags, NULL, NULL); |
3229 | module_config.data.numeric.config.ll = NULL; |
3230 | module_config.privdata = privdata; |
3231 | registerConfigValue(config_name, &module_config, 0); |
3232 | } |
3233 | |
3234 | /*----------------------------------------------------------------------------- |
3235 | * CONFIG HELP |
3236 | *----------------------------------------------------------------------------*/ |
3237 | |
3238 | void configHelpCommand(client *c) { |
3239 | const char *help[] = { |
3240 | "GET <pattern>" , |
3241 | " Return parameters matching the glob-like <pattern> and their values." , |
3242 | "SET <directive> <value>" , |
3243 | " Set the configuration <directive> to <value>." , |
3244 | "RESETSTAT" , |
3245 | " Reset statistics reported by the INFO command." , |
3246 | "REWRITE" , |
3247 | " Rewrite the configuration file." , |
3248 | NULL |
3249 | }; |
3250 | |
3251 | addReplyHelp(c, help); |
3252 | } |
3253 | |
3254 | /*----------------------------------------------------------------------------- |
3255 | * CONFIG RESETSTAT |
3256 | *----------------------------------------------------------------------------*/ |
3257 | |
3258 | void configResetStatCommand(client *c) { |
3259 | resetServerStats(); |
3260 | resetCommandTableStats(server.commands); |
3261 | resetErrorTableStats(); |
3262 | addReply(c,shared.ok); |
3263 | } |
3264 | |
3265 | /*----------------------------------------------------------------------------- |
3266 | * CONFIG REWRITE |
3267 | *----------------------------------------------------------------------------*/ |
3268 | |
3269 | void configRewriteCommand(client *c) { |
3270 | if (server.configfile == NULL) { |
3271 | addReplyError(c,"The server is running without a config file" ); |
3272 | return; |
3273 | } |
3274 | if (rewriteConfig(server.configfile, 0) == -1) { |
3275 | /* save errno in case of being tainted. */ |
3276 | int err = errno; |
3277 | serverLog(LL_WARNING,"CONFIG REWRITE failed: %s" , strerror(err)); |
3278 | addReplyErrorFormat(c,"Rewriting config file: %s" , strerror(err)); |
3279 | } else { |
3280 | serverLog(LL_WARNING,"CONFIG REWRITE executed with success." ); |
3281 | addReply(c,shared.ok); |
3282 | } |
3283 | } |
3284 | |