16 #include <buffer_config.h>
17 #include <buffer_traits.h>
18 #include <dynamic_config.h>
19 #include <extra_sinks.h>
20 #include <logconfig.h>
21 #include <loglimiter.h>
23 #include "log_compressed_storage.h"
25 #include "fdi_pipe_internal.h"
29 int get_is_pipe(struct log_config *conf, bool *out)
31 const char *const backend = log_config_claim_backend(conf);
35 *out = strcmp(backend, "pipe") == 0;
39 bool parse_options(int argc, const char **argv, struct parsed_params *params, int *exit_value)
43 assert(params->action == ACTION_NONE);
44 assert(!params->value);
51 params->plog_buffers_bitset = 0;
53 bool use_global_action = true; // if neither tag, pid nor priority is specified
55 /* skips a part of the help message */
56 bool show_dynamic_conf_msg = true;
58 #define SET_ACTION(x) do { \
59 if (params->action != x && params->action != ACTION_NONE) \
66 static const struct option long_options[] = {
67 {"get" , no_argument, NULL, 'g'},
68 {"set" , required_argument, NULL, 's'},
69 {"tag" , required_argument, NULL, 't'},
70 {"priority", required_argument, NULL, 'p'},
71 {"help" , no_argument, NULL, 'h'},
72 {"buffer" , required_argument, NULL, 'b'},
73 {"clear" , no_argument, NULL, 'c'},
74 {"enable" , no_argument, NULL, 0},
75 {"disable" , no_argument, NULL, 1},
76 {"pid" , required_argument, NULL, 2},
77 {"enable-stdout", no_argument, NULL, 3},
78 {"disable-stdout", no_argument, NULL, 4},
79 {"compression-resize", required_argument, NULL, 5},
80 {"compression-name", required_argument, NULL, 6},
85 int opt = getopt_long(argc, (char **) argv, "chb:gs:t:p:", long_options, &long_index);
91 SET_ACTION(ACTION_PLOG);
92 params->plog_enable = true;
95 SET_ACTION(ACTION_PLOG);
96 params->plog_enable = false;
99 const int value_num = atoi(optarg);
102 params->pid = value_num;
103 use_global_action = false;
107 SET_ACTION(ACTION_STDOUT);
108 params->plog_enable = true;
111 SET_ACTION(ACTION_STDOUT);
112 params->plog_enable = false;
115 const int capacity = atoi(optarg);
118 params->capacity = capacity;
119 SET_ACTION(ACTION_RESIZE);
120 show_dynamic_conf_msg = false;
123 params->storage_name = optarg;
124 SET_ACTION(ACTION_RESIZE);
125 show_dynamic_conf_msg = false;
128 const int buf_id = sink_id_by_name(optarg);
129 if (buf_id == LOG_ID_INVALID)
132 params->plog_buffers_bitset |= 1 << buf_id;
135 *exit_value = EXIT_SUCCESS;
138 SET_ACTION(ACTION_GET);
141 SET_ACTION(ACTION_CLEAR);
142 params->value = NULL;
145 const int value_num = atoi(optarg);
146 if (strcmp(optarg, "allow") && strcmp(optarg, "deny") && (value_num <= 0 || value_num >= __LOG_LIMITER_LIMIT_MAX))
148 params->value = optarg;
149 SET_ACTION(ACTION_SET);
152 params->tag = optarg;
153 use_global_action = false;
156 params->prio = toupper(*optarg);
157 if (!strchr("DVIWEFS*", params->prio))
159 use_global_action = false;
169 if (params->pid != 0 && (params->prio != '\0' || strcmp(params->tag, "*") != 0))
172 if (params->plog_buffers_bitset != 0 && params->action != ACTION_PLOG && params->action != ACTION_STDOUT)
175 if (params->action == ACTION_NONE) {
176 *exit_value = EXIT_SUCCESS;
180 switch (params->action) {
182 if (use_global_action)
183 params->action = ACTION_DUMP;
186 if (params->pid != 0)
187 params->action = ACTION_SET_PID;
188 else if (!use_global_action)
189 params->action = ACTION_SET;
192 if (params->pid != 0)
193 params->action = ACTION_SET_PID;
199 if (params->action == ACTION_SET && params->prio == '\0')
205 *exit_value = EXIT_FAILURE;
208 printf("Usage: %s [-t/--tag tag] [-p/--priority priority] [--pid pid] (-g/--get | -s/--set value)\n"
209 "\ttag: defaults to *\n"
210 "\tpriority: F[atal], E[rror], W[arning], I[nfo], V[erbose], D[ebug] S[ilent] * (all, default)\n"
211 "\tpid: integer larger than 0, mutually exclusive with --tag and --priority\n"
212 "\t-g/--get: get current limit for given tag/priority; if neither given, dump the whole filterset\n"
213 "\t-s/--set value: set a new limit to value (\"allow\", \"deny\", or number (0; %d) - allowed logs per minute)\n"
214 "\t-c/--clear: clear the limit for given tag/priority; if neither given, clear the whole filterset\n"
215 "Note: static filters are shadowed by dynamic ones but cannot be overridden at runtime\n\n"
216 " %s [-b/--buffer bufname] [-b ...] (--enable | --disable)\n"
217 "\tbufname: main system radio apps (default: all of them)\n"
218 "\t-b/--buffer bufname: pick given buffer for plog control\n"
219 "\t--enable/--disable: control platform logging for chosen buffers\n"
220 "\t--enable-stdout/--disable-stdout: control stdout redirection for chosen buffers\n"
221 "\t--compression-name: resizes given named compression buffer. Must be used with --compression-resize\n"
222 "\t--compression-resize: sets compression buffer to given size, in bytes.\n"
225 , __LOG_LIMITER_LIMIT_MAX
229 if (show_dynamic_conf_msg) {
230 printf("NOTE: to use dlogctl, dynamic dlog config has to be enabled!\n"
231 "Define the %s config entry to a *DIRECTORY* path.\n"
232 "ALSO: filters won't work for the APPS buffer (whence most spam comes)\n"
233 "unless you set `limiter_apply_to_all_buffers` in the config to 1!\n"
234 "The filters are also currently per process; this may change in the future.\n\n"
235 , DYNAMIC_CONFIG_CONF_KEY
243 * @brief Copy file, except modify some lines
244 * @details Copies a file linewise to another, except ensures lines starting with given prefixes have certain follow-up
245 * @param[in] in Copy from this file
246 * @param[in] out Copy into this file
247 * @param[in] kv A set of key-value pairs, where lines which do not match any of the prefixes are copied verbatim, while
248 * lines that do are ignored. For each key where the value is not NULL, a line with the key as the prefix
249 * and the value as the suffix is then appended to the resulting file.
251 * @notes Usually the keys will contain a '=' at the end so as to only replace exact matches in a config file,
252 * and keys without '=' at the end should usually have the value NULL to keep the config format valid
254 * @example Consider a file with the contents:
261 * And a set of kv pairs:
267 * FOO matches both FOO=BAR and FOOOO=BAZ. Since the value is NULL, both these lines are removed.
268 * ABC= matches both ABC=XYZ and ABC=ZYX, these lines are not copied. Instead, ABC=YYY is added
269 * behind any lines that were not matched.
270 * ABCABC is not matched by any kv pair, so it is copied verbatim.
271 * The QWER= prefix is not matched by any line, so QWER=ASDF is appended to the end.
273 * The resulting file will therefore look like:
279 int copy_except(FILE *in, FILE *out, const struct keys_values *kv)
281 __attribute__((cleanup(free_ptr))) size_t *const key_len = calloc(kv->n, sizeof *key_len);
285 for (size_t i = 0; i < kv->n; ++i)
286 key_len[i] = strlen(kv->keys[i]);
288 char line[MAX_CONF_KEY_LEN + MAX_CONF_VAL_LEN + 2]; // +2 for delimiter and terminator: "K=V\0"
289 while (fgets(line, sizeof line, in)) {
291 while (i < kv->n && strncmp(line, kv->keys[i], key_len[i]))
297 for (size_t i = 0; i < kv->n; ++i)
299 fprintf(out, "%s%s\n", kv->keys[i], kv->values[i]);
304 int copy_file_with_exceptions(const char *config_path, const struct keys_values *kv)
306 const int configfd = open(config_path, O_RDONLY | O_CREAT, 0644);
308 ERR("configfd open failed: %m");
312 int r = flock(configfd, LOCK_EX);
314 ERR("flock failed: %m");
319 __attribute__((cleanup(close_FILE))) FILE *const configfile = fdopen(configfd, "r");
321 ERR("configfile fdopen failed: %m");
326 __attribute__((cleanup(free_ptr))) char *tempname = NULL;
327 r = asprintf(&tempname, "%sXXXXXX", config_path);
330 ERR("asprintf tempname failed: %m\n");
334 /* In older libc versions, umask(077) needed to be called
335 * before mkstemp() to prevent a potential race condition
336 * vulnerability. POSIX-2008 requires mkstemp() to create
337 * the file with rights 600, but our static analysis tools
338 * still complain about this and in this case it was safe
339 * to just do this and silence them. In the general case,
340 * doing this is unsafe as umask() is not thread-aware but
341 * logctl is single-threaded anyway. */
342 mode_t old = umask(077);
343 const int tempfd = mkstemp(tempname);
346 ERR("mkstemp failed %m\n");
350 __attribute__((cleanup(close_FILE))) FILE *const tempfile = fdopen(tempfd, "w");
352 ERR("tmpfile fdopen failed: %s %m\n", tempname);
356 if (fchmod(tempfd, 0644) < 0) {
357 ERR("fchmod failed on %s %m\n", tempname);
361 if (copy_except(configfile, tempfile, kv) < 0) {
362 ERR("copy_except failed\n");
366 if (rename(tempname, config_path) < 0) {
367 ERR("rename failed: %m\n");
379 int handle_set(const struct parsed_params *params, const char *config_path, struct log_config *conf)
382 __attribute__((cleanup(free_ptr))) char *key;
383 if (asprintf(&key, "limiter|%s|%c=", params->tag, params->prio) < 0) {
385 ERR("asprintf key failed: %m\n");
388 const char *value = params->value;
390 const struct keys_values kv = {
391 .keys = (const char **)&key,
396 return copy_file_with_exceptions(config_path, &kv);
399 int handle_set_pid(const struct parsed_params *params, const char *config_path, struct log_config *conf)
403 __attribute__((cleanup(free_ptr))) char *key;
404 if (asprintf(&key, "pidlimit|%d=", params->pid) < 0) {
406 ERR("asprintf key failed: %m\n");
409 const char *value = params->value;
411 const struct keys_values kv = {
412 .keys = (const char **)&key,
417 return copy_file_with_exceptions(config_path, &kv);
420 int handle_clear(const struct parsed_params *params, const char *config_path, struct log_config *conf)
424 const char *key[] = { "limiter|", "pidlimit|" }; // note: no '=' at the end, so as to match all rules
425 const char *value[] = { NULL, NULL }; // removal
427 const struct keys_values kv = {
433 return copy_file_with_exceptions(config_path, &kv);
436 int handle_plog(const struct parsed_params *params, const char *config_path, struct log_config *conf)
439 const int buffers_bitset = params->plog_buffers_bitset ?: ((1 << SINKS_MAX) - 1);
442 for (int i = 0; i < SINKS_MAX; ++i)
443 if (buffers_bitset & (1 << i))
445 assert(count <= SINKS_MAX);
447 const char *key_arr[SINKS_MAX];
448 const char *value_arr[SINKS_MAX];
450 const int BUF_SIZE = SINKS_MAX * 20;
454 for (int i = 0; i < SINKS_MAX; ++i) {
455 if (!(buffers_bitset & (1 << i)))
458 const int r = snprintf(buf + buf_pos, BUF_SIZE - buf_pos, "enable_%s=", log_name_by_id((log_id_t)i));
460 ERR("snprintf bufname %d failed: %m\n", i);
463 assert(r < BUF_SIZE - buf_pos);
464 key_arr[real_count] = buf + buf_pos;
465 buf_pos += r + 1; // Also go past NULL
467 value_arr[real_count] = params->plog_enable ? "1" : "0";
470 assert(real_count == count);
472 const struct keys_values kv = {
478 return copy_file_with_exceptions(config_path, &kv);
481 int handle_stdout_one_pipe(log_id_t id, struct log_config *conf, bool enabled)
485 char conf_key[MAX_CONF_KEY_LEN];
486 snprintf(conf_key, sizeof(conf_key), "%s_ctl_sock", log_name_by_id(id));
488 const char *sock_path = log_config_get(conf, conf_key);
492 __attribute__((cleanup(close_fd))) int sock_fd = connect_sock(sock_path);
496 char enabled_char = (char)enabled;
498 int r = send_dlog_request(sock_fd, DLOG_REQ_GLOBAL_ENABLE_DISABLE_STDOUT, &enabled_char, sizeof(enabled_char));
505 int handle_stdout_one_nonpipe(log_id_t id, struct log_config *conf, bool enabled)
512 int handle_stdout(const struct parsed_params *params, const char *config_path, struct log_config *conf)
518 const int buffers_bitset = params->plog_buffers_bitset ?: ((1 << LOG_ID_MAX) - 1);
521 int r = get_is_pipe(conf, &is_pipe);
525 for (int i = 0; i < LOG_ID_MAX; ++i) {
526 if (!(buffers_bitset & (1 << i)))
529 r = (is_pipe ? handle_stdout_one_pipe : handle_stdout_one_nonpipe)(i, conf, params->plog_enable);
537 int handle_resize(const struct parsed_params *params, const char *config_path, struct log_config *conf)
543 const unsigned int capacity = params->capacity;
544 int request = DLOG_REQ_CHANGE_COMPRESSION_SIZE;
546 char conf_key[MAX_CONF_KEY_LEN];
547 snprintf(conf_key, sizeof(conf_key), "main_ctl_sock");
548 strtok(conf_key, "\n");
550 const char *sock_path = log_config_get(conf, conf_key);
552 fprintf(stderr, "Couldn't get sock path\n");
556 __attribute__((cleanup(close_fd))) int sock_fd = connect_sock(sock_path);
558 fprintf(stderr, "Couldn't connect to the sock\n");
562 size_t data_size = sizeof(unsigned int) + strlen(params->storage_name) + 1;
563 if (data_size > MAX_LOGGER_REQUEST_LEN) {
564 fprintf(stderr, "Request data is too big\n");
568 __attribute__((cleanup(free_ptr))) char *data = malloc(data_size);
569 memcpy(data, &capacity, sizeof(unsigned int));
570 memcpy(data + sizeof(unsigned int), params->storage_name, strlen(params->storage_name) + 1);
572 return send_dlog_request(sock_fd, request, (void *)data, data_size);
575 int get_stdout_one_pipe(log_id_t id, struct log_config *conf, bool *out_enabled)
580 char conf_key[MAX_CONF_KEY_LEN];
581 snprintf(conf_key, sizeof(conf_key), "%s_ctl_sock", log_name_by_id(id));
583 const char *sock_path = log_config_get(conf, conf_key);
587 __attribute__((cleanup(close_fd))) int sock_fd = connect_sock(sock_path);
591 int r = send_dlog_request(sock_fd, DLOG_REQ_GET_STDOUT, NULL, 0);
595 __attribute__((cleanup(free_ptr))) char *enabled_char = NULL;
597 r = recv_dlog_reply(sock_fd, DLOG_REQ_GET_STDOUT, (void **)&enabled_char, &recv_size);
600 if (recv_size != sizeof(char))
603 *out_enabled = *enabled_char != '\0';
607 int get_stdout_one_nonpipe(log_id_t id, struct log_config *conf, bool *out_enabled)
615 static void print_limit(int limit, const char *tag, char prio, bool *shadowed, bool dynamic)
617 const char *const shadow_str = *shadowed ? "[shadowed] " : "";
618 const char *const source_str = dynamic ? " (dynamic)" : " (static)";
622 printf("%sNothing set for %s:%c%s\n", shadow_str, tag, prio, source_str);
625 printf("%sDenied for %s:%c%s\n", shadow_str, tag, prio, source_str);
627 case __LOG_LIMITER_LIMIT_MAX + 1:
628 printf("%sUnlimited for %s:%c%s\n", shadow_str, tag, prio, source_str);
631 printf("%s%d logs/minute for %s:%c%s\n", shadow_str, limit, tag, prio, source_str);
639 static void get_pid_rule(void *prerule, void *prepid)
641 struct pid_limit *rule = prerule;
645 pid_t pid = *(pid_t *)prepid;
647 if (rule->pid != pid)
650 if (rule->limit == 0)
652 else if (rule->limit == __LOG_LIMITER_LIMIT_MAX + 1)
653 printf("Unlimited\n");
655 printf("%zu logs/min\n", rule->limit);
658 static void get_pid_limits(struct limiter_data *limiter_data, const struct parsed_params *params, const char *config_path, struct log_config *conf)
660 log_config_read_file(conf, config_path); // not an error on failure - config still valid if missing, static rules apply
661 __log_limiter_update(limiter_data, conf);
663 pid_t pid_to_find = params->pid;
664 list_head pidrules = __log_limiter_get_pid_limits(limiter_data);
666 list_foreach(pidrules, &pid_to_find, get_pid_rule);
669 static void get_limits(struct limiter_data *limiter_data, const struct parsed_params *params, const char *config_path, struct log_config *conf)
671 struct limiter_limits lims_static = __log_limiter_get_limits(limiter_data, params->tag, params->prio);
673 log_config_read_file(conf, config_path); // not an error on failure - config still valid if missing, static rules apply
674 __log_limiter_update(limiter_data, conf);
675 struct limiter_limits lims_dynamic = __log_limiter_get_limits(limiter_data, params->tag, params->prio);
677 bool shadowed = false;
678 if (strcmp(params->tag, "*")) {
679 if (params->prio != '*') {
680 print_limit(lims_dynamic.tag_and_prio, params->tag, params->prio, &shadowed, true);
681 print_limit(lims_static.tag_and_prio, params->tag, params->prio, &shadowed, false);
683 print_limit(lims_dynamic.tag, params->tag, '*', &shadowed, true);
684 print_limit(lims_static.tag, params->tag, '*', &shadowed, false);
686 if (params->prio != '*') {
687 print_limit(lims_dynamic.prio, "*", params->prio, &shadowed, true);
688 print_limit(lims_static.prio, "*", params->prio, &shadowed, false);
690 print_limit(lims_dynamic.global, "*", '*', &shadowed, true);
691 print_limit(lims_static.global, "*", '*', &shadowed, false);
694 struct prio_applies_to {
695 char prios[DLOG_PRIO_MAX];
697 struct limiter_limits lims[DLOG_PRIO_MAX];
700 static void print_limits_for_prio(const struct parsed_params *params, const char prio[], struct prio_applies_to *applies, size_t prio_idx, bool *shadowed, int dynamic)
702 assert(prio_idx < DLOG_PRIO_MAX);
704 if (strcmp(params->tag, "*") && prio[prio_idx] != '*') {
705 if (applies->lims[prio_idx].tag_and_prio == -1)
706 applies->prios[applies->count++] = prio[prio_idx];
708 print_limit(applies->lims[prio_idx].tag_and_prio, params->tag, prio[prio_idx], shadowed, dynamic);
711 } else if (prio[prio_idx] == '*') {
712 if (applies->count == 0)
714 else if (applies->count != NELEMS(applies->prios) - 2) {
715 printf("[applies to ");
716 for (size_t i = 0; i < applies->count; i++)
717 printf("%c%c ", applies->prios[i], (i == applies->count-1) ? ']' : ',');
719 print_limit(applies->lims[prio_idx].tag, params->tag, '*', shadowed, dynamic);
721 } else if (applies->lims[prio_idx].prio == -1)
722 applies->prios[applies->count++] = prio[prio_idx];
724 print_limit(applies->lims[prio_idx].prio, "*", prio[prio_idx], shadowed, dynamic);
729 static void get_prio_limits(struct limiter_data *limiter_data, const struct parsed_params *params, const char *config_path, struct log_config *conf)
731 static const char prio_list[] = {'V', 'D', 'I', 'W', 'E', 'F' , 'S' , '*'};
732 struct prio_applies_to applies_dynamic = { .count = 0 };
733 struct prio_applies_to applies_static = { .count = 0 };
735 for (size_t i = 0; i < NELEMS(prio_list); i++)
736 applies_static.lims[i] = __log_limiter_get_limits(limiter_data, params->tag, prio_list[i]);
738 log_config_read_file(conf, config_path); // not an error on failure - config still valid if missing, static rules apply
739 __log_limiter_update(limiter_data, conf);
741 for (size_t i = 0; i < NELEMS(prio_list); i++) {
742 applies_dynamic.lims[i] = __log_limiter_get_limits(limiter_data, params->tag, prio_list[i]);
743 bool shadowed = false;
745 print_limits_for_prio(params, prio_list, &applies_dynamic, i, &shadowed, true);
746 print_limits_for_prio(params, prio_list, &applies_static, i, &shadowed, false);
751 int handle_get(const struct parsed_params *params, const char *config_path, struct log_config *conf)
753 struct limiter_data *limiter_data = __log_limiter_create(conf);
755 ERR("error creating limiter\n");
759 if (params->pid != 0)
760 get_pid_limits(limiter_data, params, config_path, conf);
761 else if (params->prio == '\0')
762 get_prio_limits(limiter_data, params, config_path, conf);
764 get_limits(limiter_data, params, config_path, conf);
766 __log_limiter_destroy(limiter_data);
770 void dump_pid_rule(void *prerule, void *_garbage)
772 struct pid_limit *rule = prerule;
775 if (rule->limit == 0)
776 printf("Denied for %d\n", rule->pid);
777 else if (rule->limit == __LOG_LIMITER_LIMIT_MAX + 1)
778 printf("Unlimited for %d\n", rule->pid);
780 printf("%zu logs/min for %d\n", rule->limit, rule->pid);
783 int handle_dump(const struct parsed_params *params, const char *config_path, struct log_config *conf)
786 struct limiter_data *limiter_data = __log_limiter_create(conf);
788 ERR("error creating limiter\n");
793 log_config_read_file(conf, config_path); // not an error on failure - config still valid if missing, static rules apply
794 __log_limiter_update(limiter_data, conf);
796 struct rule *r = NULL;
798 int ret = __log_limiter_dump_rule(limiter_data, &r, buf, sizeof buf);
800 ERR("Error dumping rule\n");
801 __log_limiter_destroy(limiter_data);
807 list_head pidrules = __log_limiter_get_pid_limits(limiter_data);
809 list_foreach(pidrules, NULL, dump_pid_rule);
812 int ret = get_is_pipe(conf, &is_pipe);
816 printf("\nLogging buffer status:\n");
817 for (int i = 0; i < SINKS_MAX; ++i) {
818 printf("* %s (regular): %s\n"
819 , log_name_by_id((log_id_t)i)
820 , is_buffer_enabled((log_id_t) i, conf) ? "ENABLED" : "DISABLED"
823 if (is_core_buffer(i)) {
825 ret = (is_pipe ? get_stdout_one_pipe : get_stdout_one_nonpipe)(i, conf, &stdout_enabled);
826 printf("* %s (stdout): %s\n"
827 , log_name_by_id((log_id_t)i)
828 , ret != 0 ? "UNKNOWN" : stdout_enabled ? "ENABLED" : "DISABLED"
833 __log_limiter_destroy(limiter_data);
837 int main(int argc, const char **argv)
839 int exit_code = EXIT_FAILURE;
840 struct parsed_params params = {0};
841 if (!parse_options(argc, argv, ¶ms, &exit_code))
844 __attribute__((cleanup(log_config_free))) struct log_config conf = {0};
845 const int r = log_config_read(&conf);
847 ERR("error reading config: %d\n", r);
851 const char *const extra_config_path = log_config_get(&conf, DYNAMIC_CONFIG_CONF_KEY);
852 if (!extra_config_path) {
853 printf("Dynamic config is disabled (\"%s\" not defined in config)\n", DYNAMIC_CONFIG_CONF_KEY);
856 if (extra_config_path[0] != '/') {
857 printf("Dynamic config: invalid path in (is \"%s\" but has to be absolute)\n", extra_config_path);
861 __attribute__ ((cleanup(free_ptr))) char *full_inotify_path = NULL;
862 if (asprintf(&full_inotify_path, "%s/%s", extra_config_path, DYNAMIC_CONFIG_FILENAME) < 0) {
863 full_inotify_path = NULL;
864 ERR("asprintf: no memory");
868 int (*handles[])(const struct parsed_params *, const char *, struct log_config *) = {
869 [ACTION_GET] = handle_get,
870 [ACTION_SET] = handle_set,
871 [ACTION_SET_PID] = handle_set_pid,
872 [ACTION_DUMP] = handle_dump,
873 [ACTION_PLOG] = handle_plog,
874 [ACTION_CLEAR] = handle_clear,
875 [ACTION_STDOUT] = handle_stdout,
876 [ACTION_RESIZE] = handle_resize,
878 exit_code = handles[params.action](¶ms, full_inotify_path, &conf);