Use buffer traits over specific IDs
[platform/core/system/dlog.git] / src / logctl / logctl.c
1 // C
2 #include <assert.h>
3 #include <ctype.h>
4 #include <stdbool.h>
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8
9 // POSIX
10 #include <getopt.h>
11 #include <unistd.h>
12 #include <sys/file.h>
13 #include <sys/stat.h>
14
15 // DLog
16 #include <buffer_config.h>
17 #include <buffer_traits.h>
18 #include <dynamic_config.h>
19 #include <logconfig.h>
20 #include <loglimiter.h>
21
22 #include "logctl.h"
23
24 int get_is_pipe(struct log_config *conf, bool *out)
25 {
26         const char *const backend = log_config_claim_backend(conf);
27         if (!backend)
28                 return -ENOKEY;
29
30         *out = strcmp(backend, "pipe") == 0;
31         return 0;
32 }
33
34 bool parse_options(int argc, const char **argv, struct parsed_params *params, int *exit_value)
35 {
36         assert(argv);
37         assert(params);
38         assert(params->action == ACTION_NONE);
39         assert(!params->value);
40         assert(!params->tag);
41         assert(exit_value);
42
43         params->tag = "*";
44         params->prio = '\0';
45         params->pid = 0;
46         params->plog_buffers_bitset = 0;
47
48         bool use_global_action = true; // if neither tag, pid nor priority is specified
49
50 #define SET_ACTION(x) do { \
51         if (params->action != x && params->action != ACTION_NONE) \
52                 goto failure; \
53         params->action = x; \
54 } while (false)
55
56         for (;;) {
57
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},
71                         {0}
72                 };
73
74                 int long_index;
75                 int opt = getopt_long(argc, (char **) argv, "chb:gs:t:p:", long_options, &long_index);
76                 if (opt < 0)
77                         break;
78
79                 switch (opt) {
80                 case 0:
81                         SET_ACTION(ACTION_PLOG);
82                         params->plog_enable = true;
83                         break;
84                 case 1:
85                         SET_ACTION(ACTION_PLOG);
86                         params->plog_enable = false;
87                         break;
88                 case 2: {
89                         const int value_num = atoi(optarg);
90                         if (value_num <= 0)
91                                 goto failure;
92                         params->pid = value_num;
93                         use_global_action = false;
94                         break;
95                 }
96                 case 3:
97                         SET_ACTION(ACTION_STDOUT);
98                         params->plog_enable = true;
99                         break;
100                 case 4:
101                         SET_ACTION(ACTION_STDOUT);
102                         params->plog_enable = false;
103                         break;
104                 case 'b': {
105                         const int buf_id = log_id_by_name(optarg);
106                         if (buf_id == LOG_ID_INVALID)
107                                 goto failure;
108
109                         params->plog_buffers_bitset |= 1 << buf_id;
110                         break;
111                 } case 'h':
112                         *exit_value = EXIT_SUCCESS;
113                         goto print_help;
114                 case 'g':
115                         SET_ACTION(ACTION_GET);
116                         break;
117                 case 'c':
118                         SET_ACTION(ACTION_CLEAR);
119                         params->value = NULL;
120                         break;
121                 case 's': {
122                         const int value_num = atoi(optarg);
123                         if (strcmp(optarg, "allow") && strcmp(optarg, "deny") && (value_num <= 0 || value_num >= __LOG_LIMITER_LIMIT_MAX))
124                                 goto failure;
125                         params->value = optarg;
126                         SET_ACTION(ACTION_SET);
127                         break;
128                 } case 't': {
129                         params->tag = optarg;
130                         use_global_action = false;
131                         break;
132                 } case 'p':
133                         params->prio = toupper(*optarg);
134                         if (!strchr("DVIWEFS*", params->prio))
135                                 goto failure;
136                         use_global_action = false;
137                         break;
138
139                 default:
140                         break;
141                 }
142         }
143
144 #undef SET_ACTION
145
146         if (params->pid != 0 && (params->prio != '\0' || strcmp(params->tag, "*") != 0))
147                 goto failure;
148
149         if (params->plog_buffers_bitset != 0 && params->action != ACTION_PLOG && params->action != ACTION_STDOUT)
150                 goto failure;
151
152         if (params->action == ACTION_NONE) {
153                 *exit_value = EXIT_SUCCESS;
154                 goto print_help;
155         }
156
157         switch (params->action) {
158         case ACTION_GET:
159                 if (use_global_action)
160                         params->action = ACTION_DUMP;
161                 break;
162         case ACTION_CLEAR:
163                 if (params->pid != 0)
164                         params->action = ACTION_SET_PID;
165                 else if (!use_global_action)
166                         params->action = ACTION_SET;
167                 break;
168         case ACTION_SET:
169                 if (params->pid != 0)
170                         params->action = ACTION_SET_PID;
171                 break;
172         default:
173                 break;
174         }
175
176         if (params->action == ACTION_SET && params->prio == '\0')
177                 params->prio = '*';
178
179         return true;
180
181 failure:
182         *exit_value = EXIT_FAILURE;
183
184 print_help:
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"
203                 , argv[0]
204                 , __LOG_LIMITER_LIMIT_MAX
205                 , argv[0]
206                 , DYNAMIC_CONFIG_CONF_KEY
207         );
208
209         return false;
210 }
211
212 /**
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.
220  *
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
223  *
224  * @example Consider a file with the contents:
225  *   FOO=BAR
226  *   FOOOO=BAZ
227  *   ABC=XYZ
228  *   ABCABC=QQQ
229  *   ABC=ZYX
230  *
231  * And a set of kv pairs:
232  *   {"FOO", NULL}
233  *   {"ABC=", "YYY"}
234  *   {"QWER=", "ASDF"}
235  *
236  * Thus:
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.
242  *
243  * The resulting file will therefore look like:
244  *  ABCABC=QQQ
245  *  ABC=YYY
246  *  QWER=ASDF
247  *
248  */
249 int copy_except(FILE *in, FILE *out, const struct keys_values *kv)
250 {
251         __attribute__((cleanup(free_ptr))) size_t *const key_len = calloc(kv->n, sizeof *key_len);
252         if (!key_len)
253                 return -ENOMEM;
254
255         for (size_t i = 0; i < kv->n; ++i)
256                 key_len[i] = strlen(kv->keys[i]);
257
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)) {
260                 size_t i = 0;
261                 while (i < kv->n && strncmp(line, kv->keys[i], key_len[i]))
262                         ++i;
263                 if (i == kv->n)
264                         fputs(line, out);
265         }
266
267         for (size_t i = 0; i < kv->n; ++i)
268                 if (kv->values[i])
269                         fprintf(out, "%s%s\n", kv->keys[i], kv->values[i]);
270
271         return 0;
272 }
273
274 int copy_file_with_exceptions(const char *config_path, const struct keys_values *kv)
275 {
276         const int configfd = open(config_path, O_RDONLY | O_CREAT, 0644);
277         if (configfd < 0) {
278                 ERR("configfd open failed: %m");
279                 return EXIT_FAILURE;
280         }
281
282         int r = flock(configfd, LOCK_EX);
283         if (r < 0) {
284                 ERR("flock failed: %m");
285                 close(configfd);
286                 return EXIT_FAILURE;
287         }
288
289         __attribute__((cleanup(close_FILE))) FILE *const configfile = fdopen(configfd, "r");
290         if (!configfile) {
291                 ERR("configfile fdopen failed: %m");
292                 close(configfd);
293                 return EXIT_FAILURE;
294         }
295
296         __attribute__((cleanup(free_ptr))) char *tempname = NULL;
297         r = asprintf(&tempname, "%sXXXXXX", config_path);
298         if (r < 0) {
299                 tempname = NULL;
300                 ERR("asprintf tempname failed: %m\n");
301                 return EXIT_FAILURE;
302         }
303
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);
314         umask(old);
315         if (tempfd < 0) {
316                 ERR("mkstemp failed %m\n");
317                 return EXIT_FAILURE;
318         }
319
320         __attribute__((cleanup(close_FILE))) FILE *const tempfile = fdopen(tempfd, "w");
321         if (!tempfile) {
322                 ERR("tmpfile fdopen failed: %s %m\n", tempname);
323                 goto remove_temp;
324         }
325
326         if (fchmod(tempfd, 0644) < 0) {
327                 ERR("fchmod failed on %s %m\n", tempname);
328                 goto remove_temp;
329         }
330
331         if (copy_except(configfile, tempfile, kv) < 0) {
332                 ERR("copy_except failed\n");
333                 goto remove_temp;
334         }
335
336         if (rename(tempname, config_path) < 0) {
337                 ERR("rename failed: %m\n");
338                 goto remove_temp;
339         }
340
341         return EXIT_SUCCESS;
342
343 remove_temp:
344         unlink(tempname);
345
346         return EXIT_FAILURE;
347 }
348
349 int handle_set(const struct parsed_params *params, const char *config_path, struct log_config *conf)
350 {
351         (void) conf;
352         __attribute__((cleanup(free_ptr))) char *key;
353         if (asprintf(&key, "limiter|%s|%c=", params->tag, params->prio) < 0) {
354                 key = NULL;
355                 ERR("asprintf key failed: %m\n");
356                 return EXIT_FAILURE;
357         }
358         const char *value = params->value;
359
360         const struct keys_values kv = {
361                 .keys = (const char **)&key,
362                 .values = &value,
363                 .n = 1,
364         };
365
366         return copy_file_with_exceptions(config_path, &kv);
367 }
368
369 int handle_set_pid(const struct parsed_params *params, const char *config_path, struct log_config *conf)
370 {
371         (void) conf;
372
373         __attribute__((cleanup(free_ptr))) char *key;
374         if (asprintf(&key, "pidlimit|%d=", params->pid) < 0) {
375                 key = NULL;
376                 ERR("asprintf key failed: %m\n");
377                 return EXIT_FAILURE;
378         }
379         const char *value = params->value;
380
381         const struct keys_values kv = {
382                 .keys = (const char **)&key,
383                 .values = &value,
384                 .n = 1,
385         };
386
387         return copy_file_with_exceptions(config_path, &kv);
388 }
389
390 int handle_clear(const struct parsed_params *params, const char *config_path, struct log_config *conf)
391 {
392         (void) params;
393         (void) 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
396
397         const struct keys_values kv = {
398                 .keys = key,
399                 .values = value,
400                 .n = 2,
401         };
402
403         return copy_file_with_exceptions(config_path, &kv);
404 }
405
406 int handle_plog(const struct parsed_params *params, const char *config_path, struct log_config *conf)
407 {
408         (void) conf;
409         const int buffers_bitset = params->plog_buffers_bitset ?: ((1 << LOG_ID_MAX) - 1);
410
411         int count = 0;
412         for (int i = 0; i < LOG_ID_MAX; ++i)
413                 if (buffers_bitset & (1 << i))
414                         ++count;
415         assert(count <= LOG_ID_MAX);
416
417         const char *key_arr[LOG_ID_MAX];
418         const char *value_arr[LOG_ID_MAX];
419
420         const int BUF_SIZE = LOG_ID_MAX * 20;
421         char buf[BUF_SIZE];
422         int buf_pos = 0;
423         int real_count = 0;
424         for (int i = 0; i < LOG_ID_MAX; ++i) {
425                 if (!(buffers_bitset & (1 << i)))
426                         continue;
427
428                 const int r = snprintf(buf + buf_pos, BUF_SIZE - buf_pos, "enable_%s=", log_name_by_id((log_id_t)i));
429                 if (r < 0) {
430                         ERR("snprintf bufname %d failed: %m\n", i);
431                         return EXIT_FAILURE;
432                 }
433                 assert(r < BUF_SIZE - buf_pos);
434                 key_arr[real_count] = buf + buf_pos;
435                 buf_pos += r + 1; // Also go past NULL
436
437                 value_arr[real_count] = params->plog_enable ? "1" : "0";
438                 ++real_count;
439         }
440         assert(real_count == count);
441
442         const struct keys_values kv = {
443                 .keys = key_arr,
444                 .values = value_arr,
445                 .n = count,
446         };
447
448         return copy_file_with_exceptions(config_path, &kv);
449 }
450
451 int handle_stdout_one_pipe(log_id_t id, struct log_config *conf, bool enabled)
452 {
453         assert(conf);
454
455         char conf_key[MAX_CONF_KEY_LEN];
456         snprintf(conf_key, sizeof(conf_key), "%s_ctl_sock", log_name_by_id(id));
457
458         const char *sock_path = log_config_get(conf, conf_key);
459         if (!sock_path)
460                 return -ENOENT;
461
462         __attribute__((cleanup(close_fd))) int sock_fd = connect_sock(sock_path);
463         if (sock_fd < 0)
464                 return sock_fd;
465
466         char enabled_char = (char)enabled;
467
468         int r = send_dlog_request(sock_fd, DLOG_REQ_GLOBAL_ENABLE_DISABLE_STDOUT, &enabled_char, sizeof(enabled_char));
469         if (r < 0)
470                 return r;
471
472         return 0;
473 }
474
475 int handle_stdout_one_nonpipe(log_id_t id, struct log_config *conf, bool enabled)
476 {
477         assert(conf);
478
479         return -ENOTSUP;
480 }
481
482 int handle_stdout(const struct parsed_params *params, const char *config_path, struct log_config *conf)
483 {
484         assert(params);
485         assert(conf);
486         (void)config_path;
487
488         const int buffers_bitset = params->plog_buffers_bitset ?: ((1 << LOG_ID_MAX) - 1);
489
490         bool is_pipe;
491         int r = get_is_pipe(conf, &is_pipe);
492         if (r < 0)
493                 return r;
494
495         for (int i = 0; i < LOG_ID_MAX; ++i) {
496                 if (!(buffers_bitset & (1 << i)))
497                         continue;
498
499                 r = (is_pipe ? handle_stdout_one_pipe : handle_stdout_one_nonpipe)(i, conf, params->plog_enable);
500                 if (r < 0)
501                         return r;
502         }
503
504         return 0;
505 }
506
507 int get_stdout_one_pipe(log_id_t id, struct log_config *conf, bool *out_enabled)
508 {
509         assert(conf);
510         assert(out_enabled);
511
512         char conf_key[MAX_CONF_KEY_LEN];
513         snprintf(conf_key, sizeof(conf_key), "%s_ctl_sock", log_name_by_id(id));
514
515         const char *sock_path = log_config_get(conf, conf_key);
516         if (!sock_path)
517                 return -ENOENT;
518
519         __attribute__((cleanup(close_fd))) int sock_fd = connect_sock(sock_path);
520         if (sock_fd < 0)
521                 return sock_fd;
522
523         int r = send_dlog_request(sock_fd, DLOG_REQ_GET_STDOUT, NULL, 0);
524         if (r < 0)
525                 return r;
526
527         __attribute__((cleanup(free_ptr))) char *enabled_char = NULL;
528         int recv_size;
529         r = recv_dlog_reply(sock_fd, DLOG_REQ_GET_STDOUT, (void **)&enabled_char, &recv_size);
530         if (r < 0)
531                 return r;
532         if (recv_size != sizeof(char))
533                 return -EINVAL;
534
535         *out_enabled = *enabled_char != '\0';
536         return 0;
537 }
538
539 int get_stdout_one_nonpipe(log_id_t id, struct log_config *conf, bool *out_enabled)
540 {
541         assert(conf);
542
543         return -ENOTSUP;
544 }
545
546 #ifndef UNIT_TEST
547 static void print_limit(int limit, const char *tag, char prio, bool *shadowed, bool dynamic)
548 {
549         const char *const shadow_str = *shadowed ? "[shadowed] " : "";
550         const char *const source_str = dynamic ? " (dynamic)" : " (static)";
551
552         switch (limit) {
553         case -1:
554                 printf("%sNothing set for %s:%c%s\n", shadow_str, tag, prio, source_str);
555                 break;
556         case 0:
557                 printf("%sDenied for %s:%c%s\n", shadow_str, tag, prio, source_str);
558                 break;
559         case __LOG_LIMITER_LIMIT_MAX + 1:
560                 printf("%sUnlimited for %s:%c%s\n", shadow_str, tag, prio, source_str);
561                 break;
562         default:
563                 printf("%s%d logs/minute for %s:%c%s\n", shadow_str, limit, tag, prio, source_str);
564                 break;
565         }
566
567         if (limit != -1)
568                 *shadowed = true;
569 }
570
571 static void get_pid_rule(void *prerule, void *prepid)
572 {
573         struct pid_limit *rule = prerule;
574         assert(rule);
575
576         assert(prepid);
577         pid_t pid = *(pid_t *)prepid;
578
579         if (rule->pid != pid)
580                 return;
581
582         if (rule->limit == 0)
583                 printf("Denied\n");
584         else if (rule->limit == __LOG_LIMITER_LIMIT_MAX + 1)
585                 printf("Unlimited\n");
586         else
587                 printf("%zu logs/min\n", rule->limit);
588 }
589
590 static void get_pid_limits(struct limiter_data *limiter_data, const struct parsed_params *params, const char *config_path, struct log_config *conf)
591 {
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);
594
595         pid_t pid_to_find = params->pid;
596         list_head pidrules = __log_limiter_get_pid_limits(limiter_data);
597         if (pidrules)
598                 list_foreach(pidrules, &pid_to_find, get_pid_rule);
599 }
600
601 static void get_limits(struct limiter_data *limiter_data, const struct parsed_params *params, const char *config_path, struct log_config *conf)
602 {
603         struct limiter_limits lims_static = __log_limiter_get_limits(limiter_data, params->tag, params->prio);
604
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);
608
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);
614                 }
615                 print_limit(lims_dynamic.tag, params->tag, '*', &shadowed, true);
616                 print_limit(lims_static.tag,  params->tag, '*', &shadowed, false);
617         }
618         if (params->prio != '*') {
619                 print_limit(lims_dynamic.prio, "*", params->prio, &shadowed, true);
620                 print_limit(lims_static.prio,  "*", params->prio, &shadowed, false);
621         }
622         print_limit(lims_dynamic.global, "*", '*', &shadowed, true);
623         print_limit(lims_static.global,  "*", '*', &shadowed, false);
624 }
625
626 struct prio_applies_to {
627         char prios[DLOG_PRIO_MAX];
628         size_t count;
629         struct limiter_limits lims[DLOG_PRIO_MAX];
630 };
631
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)
633 {
634         assert(prio_idx < DLOG_PRIO_MAX);
635
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];
639                 else {
640                         print_limit(applies->lims[prio_idx].tag_and_prio, params->tag, prio[prio_idx], shadowed, dynamic);
641                         *shadowed = true;
642                 }
643         } else if (prio[prio_idx] == '*') {
644                 if (applies->count == 0)
645                         *shadowed = true;
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) ? ']' : ',');
650                 }
651                 print_limit(applies->lims[prio_idx].tag, params->tag, '*', shadowed, dynamic);
652                 *shadowed = true;
653         } else if (applies->lims[prio_idx].prio == -1)
654                 applies->prios[applies->count++] = prio[prio_idx];
655         else {
656                 print_limit(applies->lims[prio_idx].prio, "*", prio[prio_idx], shadowed, dynamic);
657                 *shadowed = true;
658         }
659 }
660
661 static void get_prio_limits(struct limiter_data *limiter_data, const struct parsed_params *params, const char *config_path, struct log_config *conf)
662 {
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 };
666
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]);
669
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);
672
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;
676
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);
679         }
680 }
681
682
683 int handle_get(const struct parsed_params *params, const char *config_path, struct log_config *conf)
684 {
685         struct limiter_data *limiter_data = __log_limiter_create(conf);
686         if (!limiter_data) {
687                 ERR("error creating limiter\n");
688                 return EXIT_FAILURE;
689         }
690
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);
695         else
696                 get_limits(limiter_data, params, config_path, conf);
697
698         __log_limiter_destroy(limiter_data);
699         return EXIT_SUCCESS;
700 }
701
702 void dump_pid_rule(void *prerule, void *_garbage)
703 {
704         struct pid_limit *rule = prerule;
705         assert(rule);
706
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);
711         else
712                 printf("%zu logs/min for %d\n", rule->limit, rule->pid);
713 }
714
715 int handle_dump(const struct parsed_params *params, const char *config_path, struct log_config *conf)
716 {
717         (void) params;
718         struct limiter_data *limiter_data = __log_limiter_create(conf);
719         if (!limiter_data) {
720                 ERR("error creating limiter\n");
721                 return EXIT_FAILURE;
722         }
723         char buf[1024];
724
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);
727
728         struct rule *r = NULL;
729         do {
730                 int ret = __log_limiter_dump_rule(limiter_data, &r, buf, sizeof buf);
731                 if (ret < 0) {
732                         ERR("Error dumping rule\n");
733                         __log_limiter_destroy(limiter_data);
734                         return EXIT_FAILURE;
735                 }
736                 printf("%s\n", buf);
737         } while (r);
738
739         list_head pidrules = __log_limiter_get_pid_limits(limiter_data);
740         if (pidrules)
741                 list_foreach(pidrules, NULL, dump_pid_rule);
742
743         bool is_pipe;
744         int ret = get_is_pipe(conf, &is_pipe);
745         if (ret < 0)
746                 return ret;
747
748         printf("\nLogging buffer status:\n");
749         for (int i = 0; i < LOG_ID_MAX; ++i) {
750                 bool stdout_enabled;
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"
755                 );
756                 printf("* %s (stdout): %s\n"
757                         , log_name_by_id((log_id_t)i)
758                         , ret != 0 ? "UNKNOWN" : stdout_enabled ? "ENABLED" : "DISABLED"
759                 );
760         }
761
762         __log_limiter_destroy(limiter_data);
763         return EXIT_SUCCESS;
764 }
765
766 int main(int argc, const char **argv)
767 {
768         int exit_code = EXIT_FAILURE;
769         struct parsed_params params = {0};
770         if (!parse_options(argc, argv, &params, &exit_code))
771                 return exit_code;
772
773         __attribute__((cleanup(log_config_free))) struct log_config conf = {0};
774         const int r = log_config_read(&conf);
775         if (r < 0) {
776                 ERR("error reading config: %d\n", r);
777                 return EXIT_FAILURE;
778         }
779
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);
783                 return EXIT_SUCCESS;
784         }
785         if (extra_config_path[0] != '/') {
786                 printf("Dynamic config: invalid path in (is \"%s\" but has to be absolute)\n", extra_config_path);
787                 return EXIT_FAILURE;
788         }
789
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");
794                 return EXIT_FAILURE;
795         }
796
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,
805         };
806         exit_code = handles[params.action](&params, full_inotify_path, &conf);
807
808         return exit_code;
809 }
810 #endif // !UNIT_TEST