16 #include <buffer_config.h>
17 #include <buffer_traits.h>
18 #include <dynamic_config.h>
19 #include <logconfig.h>
20 #include <loglimiter.h>
24 int get_is_pipe(struct log_config *conf, bool *out)
26 const char *const backend = log_config_claim_backend(conf);
30 *out = strcmp(backend, "pipe") == 0;
34 bool parse_options(int argc, const char **argv, struct parsed_params *params, int *exit_value)
38 assert(params->action == ACTION_NONE);
39 assert(!params->value);
46 params->plog_buffers_bitset = 0;
48 bool use_global_action = true; // if neither tag, pid nor priority is specified
50 #define SET_ACTION(x) do { \
51 if (params->action != x && params->action != ACTION_NONE) \
58 static const struct option long_options[] = {
59 {"get" , no_argument, NULL, 'g'},
60 {"set" , required_argument, NULL, 's'},
61 {"tag" , required_argument, NULL, 't'},
62 {"priority", required_argument, NULL, 'p'},
63 {"help" , no_argument, NULL, 'h'},
64 {"buffer" , required_argument, NULL, 'b'},
65 {"clear" , no_argument, NULL, 'c'},
66 {"enable" , no_argument, NULL, 0},
67 {"disable" , no_argument, NULL, 1},
68 {"pid" , required_argument, NULL, 2},
69 {"enable-stdout", no_argument, NULL, 3},
70 {"disable-stdout", no_argument, NULL, 4},
75 int opt = getopt_long(argc, (char **) argv, "chb:gs:t:p:", long_options, &long_index);
81 SET_ACTION(ACTION_PLOG);
82 params->plog_enable = true;
85 SET_ACTION(ACTION_PLOG);
86 params->plog_enable = false;
89 const int value_num = atoi(optarg);
92 params->pid = value_num;
93 use_global_action = false;
97 SET_ACTION(ACTION_STDOUT);
98 params->plog_enable = true;
101 SET_ACTION(ACTION_STDOUT);
102 params->plog_enable = false;
105 const int buf_id = log_id_by_name(optarg);
106 if (buf_id == LOG_ID_INVALID)
109 params->plog_buffers_bitset |= 1 << buf_id;
112 *exit_value = EXIT_SUCCESS;
115 SET_ACTION(ACTION_GET);
118 SET_ACTION(ACTION_CLEAR);
119 params->value = NULL;
122 const int value_num = atoi(optarg);
123 if (strcmp(optarg, "allow") && strcmp(optarg, "deny") && (value_num <= 0 || value_num >= __LOG_LIMITER_LIMIT_MAX))
125 params->value = optarg;
126 SET_ACTION(ACTION_SET);
129 params->tag = optarg;
130 use_global_action = false;
133 params->prio = toupper(*optarg);
134 if (!strchr("DVIWEFS*", params->prio))
136 use_global_action = false;
146 if (params->pid != 0 && (params->prio != '\0' || strcmp(params->tag, "*") != 0))
149 if (params->plog_buffers_bitset != 0 && params->action != ACTION_PLOG && params->action != ACTION_STDOUT)
152 if (params->action == ACTION_NONE) {
153 *exit_value = EXIT_SUCCESS;
157 switch (params->action) {
159 if (use_global_action)
160 params->action = ACTION_DUMP;
163 if (params->pid != 0)
164 params->action = ACTION_SET_PID;
165 else if (!use_global_action)
166 params->action = ACTION_SET;
169 if (params->pid != 0)
170 params->action = ACTION_SET_PID;
176 if (params->action == ACTION_SET && params->prio == '\0')
182 *exit_value = EXIT_FAILURE;
185 printf("Usage: %s [-t/--tag tag] [-p/--priority priority] [--pid pid] (-g/--get | -s/--set value)\n"
186 "\ttag: defaults to *\n"
187 "\tpriority: F[atal], E[rror], W[arning], I[nfo], V[erbose], D[ebug] S[ilent] * (all, default)\n"
188 "\tpid: integer larger than 0, mutually exclusive with --tag and --priority\n"
189 "\t-g/--get: get current limit for given tag/priority; if neither given, dump the whole filterset\n"
190 "\t-s/--set value: set a new limit to value (\"allow\", \"deny\", or number (0; %d) - allowed logs per minute)\n"
191 "\t-c/--clear: clear the limit for given tag/priority; if neither given, clear the whole filterset\n"
192 "Note: static filters are shadowed by dynamic ones but cannot be overridden at runtime\n\n"
193 " %s [-b/--buffer bufname] [-b ...] (--enable | --disable)\n"
194 "\tbufname: main system radio apps (default: all of them)\n"
195 "\t-b/--buffer bufname: pick given buffer for plog control\n"
196 "\t--enable/--disable: control platform logging for chosen buffers\n"
197 "\t--enable-stdout/--disable-stdout: control stdout redirection for chosen buffers\n\n"
198 "NOTE: to use dlogctl, dynamic dlog config has to be enabled!\n"
199 "Define the %s config entry to a *DIRECTORY* path.\n"
200 "ALSO: filters won't work for the APPS buffer (whence most spam comes)\n"
201 "unless you set `limiter_apply_to_all_buffers` in the config to 1!\n"
202 "The filters are also currently per process; this may change in the future.\n\n"
204 , __LOG_LIMITER_LIMIT_MAX
206 , DYNAMIC_CONFIG_CONF_KEY
213 * @brief Copy file, except modify some lines
214 * @details Copies a file linewise to another, except ensures lines starting with given prefixes have certain follow-up
215 * @param[in] in Copy from this file
216 * @param[in] out Copy into this file
217 * @param[in] kv A set of key-value pairs, where lines which do not match any of the prefixes are copied verbatim, while
218 * lines that do are ignored. For each key where the value is not NULL, a line with the key as the prefix
219 * and the value as the suffix is then appended to the resulting file.
221 * @notes Usually the keys will contain a '=' at the end so as to only replace exact matches in a config file,
222 * and keys without '=' at the end should usually have the value NULL to keep the config format valid
224 * @example Consider a file with the contents:
231 * And a set of kv pairs:
237 * FOO matches both FOO=BAR and FOOOO=BAZ. Since the value is NULL, both these lines are removed.
238 * ABC= matches both ABC=XYZ and ABC=ZYX, these lines are not copied. Instead, ABC=YYY is added
239 * behind any lines that were not matched.
240 * ABCABC is not matched by any kv pair, so it is copied verbatim.
241 * The QWER= prefix is not matched by any line, so QWER=ASDF is appended to the end.
243 * The resulting file will therefore look like:
249 int copy_except(FILE *in, FILE *out, const struct keys_values *kv)
251 __attribute__((cleanup(free_ptr))) size_t *const key_len = calloc(kv->n, sizeof *key_len);
255 for (size_t i = 0; i < kv->n; ++i)
256 key_len[i] = strlen(kv->keys[i]);
258 char line[MAX_CONF_KEY_LEN + MAX_CONF_VAL_LEN + 2]; // +2 for delimiter and terminator: "K=V\0"
259 while (fgets(line, sizeof line, in)) {
261 while (i < kv->n && strncmp(line, kv->keys[i], key_len[i]))
267 for (size_t i = 0; i < kv->n; ++i)
269 fprintf(out, "%s%s\n", kv->keys[i], kv->values[i]);
274 int copy_file_with_exceptions(const char *config_path, const struct keys_values *kv)
276 const int configfd = open(config_path, O_RDONLY | O_CREAT, 0644);
278 ERR("configfd open failed: %m");
282 int r = flock(configfd, LOCK_EX);
284 ERR("flock failed: %m");
289 __attribute__((cleanup(close_FILE))) FILE *const configfile = fdopen(configfd, "r");
291 ERR("configfile fdopen failed: %m");
296 __attribute__((cleanup(free_ptr))) char *tempname = NULL;
297 r = asprintf(&tempname, "%sXXXXXX", config_path);
300 ERR("asprintf tempname failed: %m\n");
304 /* In older libc versions, umask(077) needed to be called
305 * before mkstemp() to prevent a potential race condition
306 * vulnerability. POSIX-2008 requires mkstemp() to create
307 * the file with rights 600, but our static analysis tools
308 * still complain about this and in this case it was safe
309 * to just do this and silence them. In the general case,
310 * doing this is unsafe as umask() is not thread-aware but
311 * logctl is single-threaded anyway. */
312 mode_t old = umask(077);
313 const int tempfd = mkstemp(tempname);
316 ERR("mkstemp failed %m\n");
320 __attribute__((cleanup(close_FILE))) FILE *const tempfile = fdopen(tempfd, "w");
322 ERR("tmpfile fdopen failed: %s %m\n", tempname);
326 if (fchmod(tempfd, 0644) < 0) {
327 ERR("fchmod failed on %s %m\n", tempname);
331 if (copy_except(configfile, tempfile, kv) < 0) {
332 ERR("copy_except failed\n");
336 if (rename(tempname, config_path) < 0) {
337 ERR("rename failed: %m\n");
349 int handle_set(const struct parsed_params *params, const char *config_path, struct log_config *conf)
352 __attribute__((cleanup(free_ptr))) char *key;
353 if (asprintf(&key, "limiter|%s|%c=", params->tag, params->prio) < 0) {
355 ERR("asprintf key failed: %m\n");
358 const char *value = params->value;
360 const struct keys_values kv = {
361 .keys = (const char **)&key,
366 return copy_file_with_exceptions(config_path, &kv);
369 int handle_set_pid(const struct parsed_params *params, const char *config_path, struct log_config *conf)
373 __attribute__((cleanup(free_ptr))) char *key;
374 if (asprintf(&key, "pidlimit|%d=", params->pid) < 0) {
376 ERR("asprintf key failed: %m\n");
379 const char *value = params->value;
381 const struct keys_values kv = {
382 .keys = (const char **)&key,
387 return copy_file_with_exceptions(config_path, &kv);
390 int handle_clear(const struct parsed_params *params, const char *config_path, struct log_config *conf)
394 const char *key[] = { "limiter|", "pidlimit|" }; // note: no '=' at the end, so as to match all rules
395 const char *value[] = { NULL, NULL }; // removal
397 const struct keys_values kv = {
403 return copy_file_with_exceptions(config_path, &kv);
406 int handle_plog(const struct parsed_params *params, const char *config_path, struct log_config *conf)
409 const int buffers_bitset = params->plog_buffers_bitset ?: ((1 << LOG_ID_MAX) - 1);
412 for (int i = 0; i < LOG_ID_MAX; ++i)
413 if (buffers_bitset & (1 << i))
415 assert(count <= LOG_ID_MAX);
417 const char *key_arr[LOG_ID_MAX];
418 const char *value_arr[LOG_ID_MAX];
420 const int BUF_SIZE = LOG_ID_MAX * 20;
424 for (int i = 0; i < LOG_ID_MAX; ++i) {
425 if (!(buffers_bitset & (1 << i)))
428 const int r = snprintf(buf + buf_pos, BUF_SIZE - buf_pos, "enable_%s=", log_name_by_id((log_id_t)i));
430 ERR("snprintf bufname %d failed: %m\n", i);
433 assert(r < BUF_SIZE - buf_pos);
434 key_arr[real_count] = buf + buf_pos;
435 buf_pos += r + 1; // Also go past NULL
437 value_arr[real_count] = params->plog_enable ? "1" : "0";
440 assert(real_count == count);
442 const struct keys_values kv = {
448 return copy_file_with_exceptions(config_path, &kv);
451 int handle_stdout_one_pipe(log_id_t id, struct log_config *conf, bool enabled)
455 char conf_key[MAX_CONF_KEY_LEN];
456 snprintf(conf_key, sizeof(conf_key), "%s_ctl_sock", log_name_by_id(id));
458 const char *sock_path = log_config_get(conf, conf_key);
462 __attribute__((cleanup(close_fd))) int sock_fd = connect_sock(sock_path);
466 char enabled_char = (char)enabled;
468 int r = send_dlog_request(sock_fd, DLOG_REQ_GLOBAL_ENABLE_DISABLE_STDOUT, &enabled_char, sizeof(enabled_char));
475 int handle_stdout_one_nonpipe(log_id_t id, struct log_config *conf, bool enabled)
482 int handle_stdout(const struct parsed_params *params, const char *config_path, struct log_config *conf)
488 const int buffers_bitset = params->plog_buffers_bitset ?: ((1 << LOG_ID_MAX) - 1);
491 int r = get_is_pipe(conf, &is_pipe);
495 for (int i = 0; i < LOG_ID_MAX; ++i) {
496 if (!(buffers_bitset & (1 << i)))
499 r = (is_pipe ? handle_stdout_one_pipe : handle_stdout_one_nonpipe)(i, conf, params->plog_enable);
507 int get_stdout_one_pipe(log_id_t id, struct log_config *conf, bool *out_enabled)
512 char conf_key[MAX_CONF_KEY_LEN];
513 snprintf(conf_key, sizeof(conf_key), "%s_ctl_sock", log_name_by_id(id));
515 const char *sock_path = log_config_get(conf, conf_key);
519 __attribute__((cleanup(close_fd))) int sock_fd = connect_sock(sock_path);
523 int r = send_dlog_request(sock_fd, DLOG_REQ_GET_STDOUT, NULL, 0);
527 __attribute__((cleanup(free_ptr))) char *enabled_char = NULL;
529 r = recv_dlog_reply(sock_fd, DLOG_REQ_GET_STDOUT, (void **)&enabled_char, &recv_size);
532 if (recv_size != sizeof(char))
535 *out_enabled = *enabled_char != '\0';
539 int get_stdout_one_nonpipe(log_id_t id, struct log_config *conf, bool *out_enabled)
547 static void print_limit(int limit, const char *tag, char prio, bool *shadowed, bool dynamic)
549 const char *const shadow_str = *shadowed ? "[shadowed] " : "";
550 const char *const source_str = dynamic ? " (dynamic)" : " (static)";
554 printf("%sNothing set for %s:%c%s\n", shadow_str, tag, prio, source_str);
557 printf("%sDenied for %s:%c%s\n", shadow_str, tag, prio, source_str);
559 case __LOG_LIMITER_LIMIT_MAX + 1:
560 printf("%sUnlimited for %s:%c%s\n", shadow_str, tag, prio, source_str);
563 printf("%s%d logs/minute for %s:%c%s\n", shadow_str, limit, tag, prio, source_str);
571 static void get_pid_rule(void *prerule, void *prepid)
573 struct pid_limit *rule = prerule;
577 pid_t pid = *(pid_t *)prepid;
579 if (rule->pid != pid)
582 if (rule->limit == 0)
584 else if (rule->limit == __LOG_LIMITER_LIMIT_MAX + 1)
585 printf("Unlimited\n");
587 printf("%zu logs/min\n", rule->limit);
590 static void get_pid_limits(struct limiter_data *limiter_data, const struct parsed_params *params, const char *config_path, struct log_config *conf)
592 log_config_read_file(conf, config_path); // not an error on failure - config still valid if missing, static rules apply
593 __log_limiter_update(limiter_data, conf);
595 pid_t pid_to_find = params->pid;
596 list_head pidrules = __log_limiter_get_pid_limits(limiter_data);
598 list_foreach(pidrules, &pid_to_find, get_pid_rule);
601 static void get_limits(struct limiter_data *limiter_data, const struct parsed_params *params, const char *config_path, struct log_config *conf)
603 struct limiter_limits lims_static = __log_limiter_get_limits(limiter_data, params->tag, params->prio);
605 log_config_read_file(conf, config_path); // not an error on failure - config still valid if missing, static rules apply
606 __log_limiter_update(limiter_data, conf);
607 struct limiter_limits lims_dynamic = __log_limiter_get_limits(limiter_data, params->tag, params->prio);
609 bool shadowed = false;
610 if (strcmp(params->tag, "*")) {
611 if (params->prio != '*') {
612 print_limit(lims_dynamic.tag_and_prio, params->tag, params->prio, &shadowed, true);
613 print_limit(lims_static.tag_and_prio, params->tag, params->prio, &shadowed, false);
615 print_limit(lims_dynamic.tag, params->tag, '*', &shadowed, true);
616 print_limit(lims_static.tag, params->tag, '*', &shadowed, false);
618 if (params->prio != '*') {
619 print_limit(lims_dynamic.prio, "*", params->prio, &shadowed, true);
620 print_limit(lims_static.prio, "*", params->prio, &shadowed, false);
622 print_limit(lims_dynamic.global, "*", '*', &shadowed, true);
623 print_limit(lims_static.global, "*", '*', &shadowed, false);
626 struct prio_applies_to {
627 char prios[DLOG_PRIO_MAX];
629 struct limiter_limits lims[DLOG_PRIO_MAX];
632 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)
634 assert(prio_idx < DLOG_PRIO_MAX);
636 if (strcmp(params->tag, "*") && prio[prio_idx] != '*') {
637 if (applies->lims[prio_idx].tag_and_prio == -1)
638 applies->prios[applies->count++] = prio[prio_idx];
640 print_limit(applies->lims[prio_idx].tag_and_prio, params->tag, prio[prio_idx], shadowed, dynamic);
643 } else if (prio[prio_idx] == '*') {
644 if (applies->count == 0)
646 else if (applies->count != NELEMS(applies->prios) - 2) {
647 printf("[applies to ");
648 for (size_t i = 0; i < applies->count; i++)
649 printf("%c%c ", applies->prios[i], (i == applies->count-1) ? ']' : ',');
651 print_limit(applies->lims[prio_idx].tag, params->tag, '*', shadowed, dynamic);
653 } else if (applies->lims[prio_idx].prio == -1)
654 applies->prios[applies->count++] = prio[prio_idx];
656 print_limit(applies->lims[prio_idx].prio, "*", prio[prio_idx], shadowed, dynamic);
661 static void get_prio_limits(struct limiter_data *limiter_data, const struct parsed_params *params, const char *config_path, struct log_config *conf)
663 static const char prio_list[] = {'V', 'D', 'I', 'W', 'E', 'F' , 'S' , '*'};
664 struct prio_applies_to applies_dynamic = { .count = 0 };
665 struct prio_applies_to applies_static = { .count = 0 };
667 for (size_t i = 0; i < NELEMS(prio_list); i++)
668 applies_static.lims[i] = __log_limiter_get_limits(limiter_data, params->tag, prio_list[i]);
670 log_config_read_file(conf, config_path); // not an error on failure - config still valid if missing, static rules apply
671 __log_limiter_update(limiter_data, conf);
673 for (size_t i = 0; i < NELEMS(prio_list); i++) {
674 applies_dynamic.lims[i] = __log_limiter_get_limits(limiter_data, params->tag, prio_list[i]);
675 bool shadowed = false;
677 print_limits_for_prio(params, prio_list, &applies_dynamic, i, &shadowed, true);
678 print_limits_for_prio(params, prio_list, &applies_static, i, &shadowed, false);
683 int handle_get(const struct parsed_params *params, const char *config_path, struct log_config *conf)
685 struct limiter_data *limiter_data = __log_limiter_create(conf);
687 ERR("error creating limiter\n");
691 if (params->pid != 0)
692 get_pid_limits(limiter_data, params, config_path, conf);
693 else if (params->prio == '\0')
694 get_prio_limits(limiter_data, params, config_path, conf);
696 get_limits(limiter_data, params, config_path, conf);
698 __log_limiter_destroy(limiter_data);
702 void dump_pid_rule(void *prerule, void *_garbage)
704 struct pid_limit *rule = prerule;
707 if (rule->limit == 0)
708 printf("Denied for %d\n", rule->pid);
709 else if (rule->limit == __LOG_LIMITER_LIMIT_MAX + 1)
710 printf("Unlimited for %d\n", rule->pid);
712 printf("%zu logs/min for %d\n", rule->limit, rule->pid);
715 int handle_dump(const struct parsed_params *params, const char *config_path, struct log_config *conf)
718 struct limiter_data *limiter_data = __log_limiter_create(conf);
720 ERR("error creating limiter\n");
725 log_config_read_file(conf, config_path); // not an error on failure - config still valid if missing, static rules apply
726 __log_limiter_update(limiter_data, conf);
728 struct rule *r = NULL;
730 int ret = __log_limiter_dump_rule(limiter_data, &r, buf, sizeof buf);
732 ERR("Error dumping rule\n");
733 __log_limiter_destroy(limiter_data);
739 list_head pidrules = __log_limiter_get_pid_limits(limiter_data);
741 list_foreach(pidrules, NULL, dump_pid_rule);
744 int ret = get_is_pipe(conf, &is_pipe);
748 printf("\nLogging buffer status:\n");
749 for (int i = 0; i < LOG_ID_MAX; ++i) {
751 ret = (is_pipe ? get_stdout_one_pipe : get_stdout_one_nonpipe)(i, conf, &stdout_enabled);
752 printf("* %s (regular): %s\n"
753 , log_name_by_id((log_id_t)i)
754 , is_buffer_enabled((log_id_t) i, conf) ? "ENABLED" : "DISABLED"
756 printf("* %s (stdout): %s\n"
757 , log_name_by_id((log_id_t)i)
758 , ret != 0 ? "UNKNOWN" : stdout_enabled ? "ENABLED" : "DISABLED"
762 __log_limiter_destroy(limiter_data);
766 int main(int argc, const char **argv)
768 int exit_code = EXIT_FAILURE;
769 struct parsed_params params = {0};
770 if (!parse_options(argc, argv, ¶ms, &exit_code))
773 __attribute__((cleanup(log_config_free))) struct log_config conf = {0};
774 const int r = log_config_read(&conf);
776 ERR("error reading config: %d\n", r);
780 const char *const extra_config_path = log_config_get(&conf, DYNAMIC_CONFIG_CONF_KEY);
781 if (!extra_config_path) {
782 printf("Dynamic config is disabled (\"%s\" not defined in config)\n", DYNAMIC_CONFIG_CONF_KEY);
785 if (extra_config_path[0] != '/') {
786 printf("Dynamic config: invalid path in (is \"%s\" but has to be absolute)\n", extra_config_path);
790 __attribute__ ((cleanup(free_ptr))) char *full_inotify_path = NULL;
791 if (asprintf(&full_inotify_path, "%s/%s", extra_config_path, DYNAMIC_CONFIG_FILENAME) < 0) {
792 full_inotify_path = NULL;
793 ERR("asprintf: no memory");
797 int (*handles[])(const struct parsed_params *, const char *, struct log_config *) = {
798 [ACTION_GET] = handle_get,
799 [ACTION_SET] = handle_set,
800 [ACTION_SET_PID] = handle_set_pid,
801 [ACTION_DUMP] = handle_dump,
802 [ACTION_PLOG] = handle_plog,
803 [ACTION_CLEAR] = handle_clear,
804 [ACTION_STDOUT] = handle_stdout,
806 exit_code = handles[params.action](¶ms, full_inotify_path, &conf);