dlog_logger: add log compressed storage resize to logctl options
[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 <extra_sinks.h>
20 #include <logconfig.h>
21 #include <loglimiter.h>
22
23 #include "log_compressed_storage.h"
24 #include "fdi_pipe.h"
25 #include "fdi_pipe_internal.h"
26 #include "logctl.h"
27 #include "logpipe.h"
28
29 int get_is_pipe(struct log_config *conf, bool *out)
30 {
31         const char *const backend = log_config_claim_backend(conf);
32         if (!backend)
33                 return -ENOKEY;
34
35         *out = strcmp(backend, "pipe") == 0;
36         return 0;
37 }
38
39 bool parse_options(int argc, const char **argv, struct parsed_params *params, int *exit_value)
40 {
41         assert(argv);
42         assert(params);
43         assert(params->action == ACTION_NONE);
44         assert(!params->value);
45         assert(!params->tag);
46         assert(exit_value);
47
48         params->tag = "*";
49         params->prio = '\0';
50         params->pid = 0;
51         params->plog_buffers_bitset = 0;
52
53         bool use_global_action = true; // if neither tag, pid nor priority is specified
54
55         /* skips a part of the help message */
56         bool show_dynamic_conf_msg = true;
57
58 #define SET_ACTION(x) do { \
59         if (params->action != x && params->action != ACTION_NONE) \
60                 goto failure; \
61         params->action = x; \
62 } while (false)
63
64         for (;;) {
65
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},
81                         {0}
82                 };
83
84                 int long_index;
85                 int opt = getopt_long(argc, (char **) argv, "chb:gs:t:p:", long_options, &long_index);
86                 if (opt < 0)
87                         break;
88
89                 switch (opt) {
90                 case 0:
91                         SET_ACTION(ACTION_PLOG);
92                         params->plog_enable = true;
93                         break;
94                 case 1:
95                         SET_ACTION(ACTION_PLOG);
96                         params->plog_enable = false;
97                         break;
98                 case 2: {
99                         const int value_num = atoi(optarg);
100                         if (value_num <= 0)
101                                 goto failure;
102                         params->pid = value_num;
103                         use_global_action = false;
104                         break;
105                 }
106                 case 3:
107                         SET_ACTION(ACTION_STDOUT);
108                         params->plog_enable = true;
109                         break;
110                 case 4:
111                         SET_ACTION(ACTION_STDOUT);
112                         params->plog_enable = false;
113                         break;
114                 case 5: {
115                         const int capacity = atoi(optarg);
116                         if (capacity <= 0)
117                                 goto failure;
118                         params->capacity = capacity;
119                         SET_ACTION(ACTION_RESIZE);
120                         show_dynamic_conf_msg = false;
121                         break;
122                 } case 6: {
123                         params->storage_name = optarg;
124                         SET_ACTION(ACTION_RESIZE);
125                         show_dynamic_conf_msg = false;
126                         break;
127                 } case 'b': {
128                         const int buf_id = sink_id_by_name(optarg);
129                         if (buf_id == LOG_ID_INVALID)
130                                 goto failure;
131
132                         params->plog_buffers_bitset |= 1 << buf_id;
133                         break;
134                 } case 'h':
135                         *exit_value = EXIT_SUCCESS;
136                         goto print_help;
137                 case 'g':
138                         SET_ACTION(ACTION_GET);
139                         break;
140                 case 'c':
141                         SET_ACTION(ACTION_CLEAR);
142                         params->value = NULL;
143                         break;
144                 case 's': {
145                         const int value_num = atoi(optarg);
146                         if (strcmp(optarg, "allow") && strcmp(optarg, "deny") && (value_num <= 0 || value_num >= __LOG_LIMITER_LIMIT_MAX))
147                                 goto failure;
148                         params->value = optarg;
149                         SET_ACTION(ACTION_SET);
150                         break;
151                 } case 't': {
152                         params->tag = optarg;
153                         use_global_action = false;
154                         break;
155                 } case 'p':
156                         params->prio = toupper(*optarg);
157                         if (!strchr("DVIWEFS*", params->prio))
158                                 goto failure;
159                         use_global_action = false;
160                         break;
161
162                 default:
163                         break;
164                 }
165         }
166
167 #undef SET_ACTION
168
169         if (params->pid != 0 && (params->prio != '\0' || strcmp(params->tag, "*") != 0))
170                 goto failure;
171
172         if (params->plog_buffers_bitset != 0 && params->action != ACTION_PLOG && params->action != ACTION_STDOUT)
173                 goto failure;
174
175         if (params->action == ACTION_NONE) {
176                 *exit_value = EXIT_SUCCESS;
177                 goto print_help;
178         }
179
180         switch (params->action) {
181         case ACTION_GET:
182                 if (use_global_action)
183                         params->action = ACTION_DUMP;
184                 break;
185         case ACTION_CLEAR:
186                 if (params->pid != 0)
187                         params->action = ACTION_SET_PID;
188                 else if (!use_global_action)
189                         params->action = ACTION_SET;
190                 break;
191         case ACTION_SET:
192                 if (params->pid != 0)
193                         params->action = ACTION_SET_PID;
194                 break;
195         default:
196                 break;
197         }
198
199         if (params->action == ACTION_SET && params->prio == '\0')
200                 params->prio = '*';
201
202         return true;
203
204 failure:
205         *exit_value = EXIT_FAILURE;
206
207 print_help:
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"
223                 "\n"
224                 , argv[0]
225                 , __LOG_LIMITER_LIMIT_MAX
226                 , argv[0]
227         );
228
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
236                 );
237         }
238
239         return false;
240 }
241
242 /**
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.
250  *
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
253  *
254  * @example Consider a file with the contents:
255  *   FOO=BAR
256  *   FOOOO=BAZ
257  *   ABC=XYZ
258  *   ABCABC=QQQ
259  *   ABC=ZYX
260  *
261  * And a set of kv pairs:
262  *   {"FOO", NULL}
263  *   {"ABC=", "YYY"}
264  *   {"QWER=", "ASDF"}
265  *
266  * Thus:
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.
272  *
273  * The resulting file will therefore look like:
274  *  ABCABC=QQQ
275  *  ABC=YYY
276  *  QWER=ASDF
277  *
278  */
279 int copy_except(FILE *in, FILE *out, const struct keys_values *kv)
280 {
281         __attribute__((cleanup(free_ptr))) size_t *const key_len = calloc(kv->n, sizeof *key_len);
282         if (!key_len)
283                 return -ENOMEM;
284
285         for (size_t i = 0; i < kv->n; ++i)
286                 key_len[i] = strlen(kv->keys[i]);
287
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)) {
290                 size_t i = 0;
291                 while (i < kv->n && strncmp(line, kv->keys[i], key_len[i]))
292                         ++i;
293                 if (i == kv->n)
294                         fputs(line, out);
295         }
296
297         for (size_t i = 0; i < kv->n; ++i)
298                 if (kv->values[i])
299                         fprintf(out, "%s%s\n", kv->keys[i], kv->values[i]);
300
301         return 0;
302 }
303
304 int copy_file_with_exceptions(const char *config_path, const struct keys_values *kv)
305 {
306         const int configfd = open(config_path, O_RDONLY | O_CREAT, 0644);
307         if (configfd < 0) {
308                 ERR("configfd open failed: %m");
309                 return EXIT_FAILURE;
310         }
311
312         int r = flock(configfd, LOCK_EX);
313         if (r < 0) {
314                 ERR("flock failed: %m");
315                 close(configfd);
316                 return EXIT_FAILURE;
317         }
318
319         __attribute__((cleanup(close_FILE))) FILE *const configfile = fdopen(configfd, "r");
320         if (!configfile) {
321                 ERR("configfile fdopen failed: %m");
322                 close(configfd);
323                 return EXIT_FAILURE;
324         }
325
326         __attribute__((cleanup(free_ptr))) char *tempname = NULL;
327         r = asprintf(&tempname, "%sXXXXXX", config_path);
328         if (r < 0) {
329                 tempname = NULL;
330                 ERR("asprintf tempname failed: %m\n");
331                 return EXIT_FAILURE;
332         }
333
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);
344         umask(old);
345         if (tempfd < 0) {
346                 ERR("mkstemp failed %m\n");
347                 return EXIT_FAILURE;
348         }
349
350         __attribute__((cleanup(close_FILE))) FILE *const tempfile = fdopen(tempfd, "w");
351         if (!tempfile) {
352                 ERR("tmpfile fdopen failed: %s %m\n", tempname);
353                 goto remove_temp;
354         }
355
356         if (fchmod(tempfd, 0644) < 0) {
357                 ERR("fchmod failed on %s %m\n", tempname);
358                 goto remove_temp;
359         }
360
361         if (copy_except(configfile, tempfile, kv) < 0) {
362                 ERR("copy_except failed\n");
363                 goto remove_temp;
364         }
365
366         if (rename(tempname, config_path) < 0) {
367                 ERR("rename failed: %m\n");
368                 goto remove_temp;
369         }
370
371         return EXIT_SUCCESS;
372
373 remove_temp:
374         unlink(tempname);
375
376         return EXIT_FAILURE;
377 }
378
379 int handle_set(const struct parsed_params *params, const char *config_path, struct log_config *conf)
380 {
381         (void) conf;
382         __attribute__((cleanup(free_ptr))) char *key;
383         if (asprintf(&key, "limiter|%s|%c=", params->tag, params->prio) < 0) {
384                 key = NULL;
385                 ERR("asprintf key failed: %m\n");
386                 return EXIT_FAILURE;
387         }
388         const char *value = params->value;
389
390         const struct keys_values kv = {
391                 .keys = (const char **)&key,
392                 .values = &value,
393                 .n = 1,
394         };
395
396         return copy_file_with_exceptions(config_path, &kv);
397 }
398
399 int handle_set_pid(const struct parsed_params *params, const char *config_path, struct log_config *conf)
400 {
401         (void) conf;
402
403         __attribute__((cleanup(free_ptr))) char *key;
404         if (asprintf(&key, "pidlimit|%d=", params->pid) < 0) {
405                 key = NULL;
406                 ERR("asprintf key failed: %m\n");
407                 return EXIT_FAILURE;
408         }
409         const char *value = params->value;
410
411         const struct keys_values kv = {
412                 .keys = (const char **)&key,
413                 .values = &value,
414                 .n = 1,
415         };
416
417         return copy_file_with_exceptions(config_path, &kv);
418 }
419
420 int handle_clear(const struct parsed_params *params, const char *config_path, struct log_config *conf)
421 {
422         (void) params;
423         (void) 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
426
427         const struct keys_values kv = {
428                 .keys = key,
429                 .values = value,
430                 .n = 2,
431         };
432
433         return copy_file_with_exceptions(config_path, &kv);
434 }
435
436 int handle_plog(const struct parsed_params *params, const char *config_path, struct log_config *conf)
437 {
438         (void) conf;
439         const int buffers_bitset = params->plog_buffers_bitset ?: ((1 << SINKS_MAX) - 1);
440
441         int count = 0;
442         for (int i = 0; i < SINKS_MAX; ++i)
443                 if (buffers_bitset & (1 << i))
444                         ++count;
445         assert(count <= SINKS_MAX);
446
447         const char *key_arr[SINKS_MAX];
448         const char *value_arr[SINKS_MAX];
449
450         const int BUF_SIZE = SINKS_MAX * 20;
451         char buf[BUF_SIZE];
452         int buf_pos = 0;
453         int real_count = 0;
454         for (int i = 0; i < SINKS_MAX; ++i) {
455                 if (!(buffers_bitset & (1 << i)))
456                         continue;
457
458                 const int r = snprintf(buf + buf_pos, BUF_SIZE - buf_pos, "enable_%s=", log_name_by_id((log_id_t)i));
459                 if (r < 0) {
460                         ERR("snprintf bufname %d failed: %m\n", i);
461                         return EXIT_FAILURE;
462                 }
463                 assert(r < BUF_SIZE - buf_pos);
464                 key_arr[real_count] = buf + buf_pos;
465                 buf_pos += r + 1; // Also go past NULL
466
467                 value_arr[real_count] = params->plog_enable ? "1" : "0";
468                 ++real_count;
469         }
470         assert(real_count == count);
471
472         const struct keys_values kv = {
473                 .keys = key_arr,
474                 .values = value_arr,
475                 .n = count,
476         };
477
478         return copy_file_with_exceptions(config_path, &kv);
479 }
480
481 int handle_stdout_one_pipe(log_id_t id, struct log_config *conf, bool enabled)
482 {
483         assert(conf);
484
485         char conf_key[MAX_CONF_KEY_LEN];
486         snprintf(conf_key, sizeof(conf_key), "%s_ctl_sock", log_name_by_id(id));
487
488         const char *sock_path = log_config_get(conf, conf_key);
489         if (!sock_path)
490                 return -ENOENT;
491
492         __attribute__((cleanup(close_fd))) int sock_fd = connect_sock(sock_path);
493         if (sock_fd < 0)
494                 return sock_fd;
495
496         char enabled_char = (char)enabled;
497
498         int r = send_dlog_request(sock_fd, DLOG_REQ_GLOBAL_ENABLE_DISABLE_STDOUT, &enabled_char, sizeof(enabled_char));
499         if (r < 0)
500                 return r;
501
502         return 0;
503 }
504
505 int handle_stdout_one_nonpipe(log_id_t id, struct log_config *conf, bool enabled)
506 {
507         assert(conf);
508
509         return -ENOTSUP;
510 }
511
512 int handle_stdout(const struct parsed_params *params, const char *config_path, struct log_config *conf)
513 {
514         assert(params);
515         assert(conf);
516         (void)config_path;
517
518         const int buffers_bitset = params->plog_buffers_bitset ?: ((1 << LOG_ID_MAX) - 1);
519
520         bool is_pipe;
521         int r = get_is_pipe(conf, &is_pipe);
522         if (r < 0)
523                 return r;
524
525         for (int i = 0; i < LOG_ID_MAX; ++i) {
526                 if (!(buffers_bitset & (1 << i)))
527                         continue;
528
529                 r = (is_pipe ? handle_stdout_one_pipe : handle_stdout_one_nonpipe)(i, conf, params->plog_enable);
530                 if (r < 0)
531                         return r;
532         }
533
534         return 0;
535 }
536
537 int handle_resize(const struct parsed_params *params, const char *config_path, struct log_config *conf)
538 {
539         assert(params);
540         assert(conf);
541         (void)config_path;
542
543         const unsigned int capacity = params->capacity;
544         int request = DLOG_REQ_CHANGE_COMPRESSION_SIZE;
545
546         char conf_key[MAX_CONF_KEY_LEN];
547         snprintf(conf_key, sizeof(conf_key), "main_ctl_sock");
548         strtok(conf_key, "\n");
549
550         const char *sock_path = log_config_get(conf, conf_key);
551         if (!sock_path) {
552                 fprintf(stderr, "Couldn't get sock path\n");
553                 return -ENOENT;
554         }
555
556         __attribute__((cleanup(close_fd))) int sock_fd = connect_sock(sock_path);
557         if (sock_fd < 0) {
558                 fprintf(stderr, "Couldn't connect to the sock\n");
559                 return sock_fd;
560         }
561
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");
565                 return -ENOMEM;
566         }
567
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);
571
572         return send_dlog_request(sock_fd, request, (void *)data, data_size);
573 }
574
575 int get_stdout_one_pipe(log_id_t id, struct log_config *conf, bool *out_enabled)
576 {
577         assert(conf);
578         assert(out_enabled);
579
580         char conf_key[MAX_CONF_KEY_LEN];
581         snprintf(conf_key, sizeof(conf_key), "%s_ctl_sock", log_name_by_id(id));
582
583         const char *sock_path = log_config_get(conf, conf_key);
584         if (!sock_path)
585                 return -ENOENT;
586
587         __attribute__((cleanup(close_fd))) int sock_fd = connect_sock(sock_path);
588         if (sock_fd < 0)
589                 return sock_fd;
590
591         int r = send_dlog_request(sock_fd, DLOG_REQ_GET_STDOUT, NULL, 0);
592         if (r < 0)
593                 return r;
594
595         __attribute__((cleanup(free_ptr))) char *enabled_char = NULL;
596         int recv_size;
597         r = recv_dlog_reply(sock_fd, DLOG_REQ_GET_STDOUT, (void **)&enabled_char, &recv_size);
598         if (r < 0)
599                 return r;
600         if (recv_size != sizeof(char))
601                 return -EINVAL;
602
603         *out_enabled = *enabled_char != '\0';
604         return 0;
605 }
606
607 int get_stdout_one_nonpipe(log_id_t id, struct log_config *conf, bool *out_enabled)
608 {
609         assert(conf);
610
611         return -ENOTSUP;
612 }
613
614 #ifndef UNIT_TEST
615 static void print_limit(int limit, const char *tag, char prio, bool *shadowed, bool dynamic)
616 {
617         const char *const shadow_str = *shadowed ? "[shadowed] " : "";
618         const char *const source_str = dynamic ? " (dynamic)" : " (static)";
619
620         switch (limit) {
621         case -1:
622                 printf("%sNothing set for %s:%c%s\n", shadow_str, tag, prio, source_str);
623                 break;
624         case 0:
625                 printf("%sDenied for %s:%c%s\n", shadow_str, tag, prio, source_str);
626                 break;
627         case __LOG_LIMITER_LIMIT_MAX + 1:
628                 printf("%sUnlimited for %s:%c%s\n", shadow_str, tag, prio, source_str);
629                 break;
630         default:
631                 printf("%s%d logs/minute for %s:%c%s\n", shadow_str, limit, tag, prio, source_str);
632                 break;
633         }
634
635         if (limit != -1)
636                 *shadowed = true;
637 }
638
639 static void get_pid_rule(void *prerule, void *prepid)
640 {
641         struct pid_limit *rule = prerule;
642         assert(rule);
643
644         assert(prepid);
645         pid_t pid = *(pid_t *)prepid;
646
647         if (rule->pid != pid)
648                 return;
649
650         if (rule->limit == 0)
651                 printf("Denied\n");
652         else if (rule->limit == __LOG_LIMITER_LIMIT_MAX + 1)
653                 printf("Unlimited\n");
654         else
655                 printf("%zu logs/min\n", rule->limit);
656 }
657
658 static void get_pid_limits(struct limiter_data *limiter_data, const struct parsed_params *params, const char *config_path, struct log_config *conf)
659 {
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);
662
663         pid_t pid_to_find = params->pid;
664         list_head pidrules = __log_limiter_get_pid_limits(limiter_data);
665         if (pidrules)
666                 list_foreach(pidrules, &pid_to_find, get_pid_rule);
667 }
668
669 static void get_limits(struct limiter_data *limiter_data, const struct parsed_params *params, const char *config_path, struct log_config *conf)
670 {
671         struct limiter_limits lims_static = __log_limiter_get_limits(limiter_data, params->tag, params->prio);
672
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);
676
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);
682                 }
683                 print_limit(lims_dynamic.tag, params->tag, '*', &shadowed, true);
684                 print_limit(lims_static.tag,  params->tag, '*', &shadowed, false);
685         }
686         if (params->prio != '*') {
687                 print_limit(lims_dynamic.prio, "*", params->prio, &shadowed, true);
688                 print_limit(lims_static.prio,  "*", params->prio, &shadowed, false);
689         }
690         print_limit(lims_dynamic.global, "*", '*', &shadowed, true);
691         print_limit(lims_static.global,  "*", '*', &shadowed, false);
692 }
693
694 struct prio_applies_to {
695         char prios[DLOG_PRIO_MAX];
696         size_t count;
697         struct limiter_limits lims[DLOG_PRIO_MAX];
698 };
699
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)
701 {
702         assert(prio_idx < DLOG_PRIO_MAX);
703
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];
707                 else {
708                         print_limit(applies->lims[prio_idx].tag_and_prio, params->tag, prio[prio_idx], shadowed, dynamic);
709                         *shadowed = true;
710                 }
711         } else if (prio[prio_idx] == '*') {
712                 if (applies->count == 0)
713                         *shadowed = true;
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) ? ']' : ',');
718                 }
719                 print_limit(applies->lims[prio_idx].tag, params->tag, '*', shadowed, dynamic);
720                 *shadowed = true;
721         } else if (applies->lims[prio_idx].prio == -1)
722                 applies->prios[applies->count++] = prio[prio_idx];
723         else {
724                 print_limit(applies->lims[prio_idx].prio, "*", prio[prio_idx], shadowed, dynamic);
725                 *shadowed = true;
726         }
727 }
728
729 static void get_prio_limits(struct limiter_data *limiter_data, const struct parsed_params *params, const char *config_path, struct log_config *conf)
730 {
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 };
734
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]);
737
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);
740
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;
744
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);
747         }
748 }
749
750
751 int handle_get(const struct parsed_params *params, const char *config_path, struct log_config *conf)
752 {
753         struct limiter_data *limiter_data = __log_limiter_create(conf);
754         if (!limiter_data) {
755                 ERR("error creating limiter\n");
756                 return EXIT_FAILURE;
757         }
758
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);
763         else
764                 get_limits(limiter_data, params, config_path, conf);
765
766         __log_limiter_destroy(limiter_data);
767         return EXIT_SUCCESS;
768 }
769
770 void dump_pid_rule(void *prerule, void *_garbage)
771 {
772         struct pid_limit *rule = prerule;
773         assert(rule);
774
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);
779         else
780                 printf("%zu logs/min for %d\n", rule->limit, rule->pid);
781 }
782
783 int handle_dump(const struct parsed_params *params, const char *config_path, struct log_config *conf)
784 {
785         (void) params;
786         struct limiter_data *limiter_data = __log_limiter_create(conf);
787         if (!limiter_data) {
788                 ERR("error creating limiter\n");
789                 return EXIT_FAILURE;
790         }
791         char buf[1024];
792
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);
795
796         struct rule *r = NULL;
797         do {
798                 int ret = __log_limiter_dump_rule(limiter_data, &r, buf, sizeof buf);
799                 if (ret < 0) {
800                         ERR("Error dumping rule\n");
801                         __log_limiter_destroy(limiter_data);
802                         return EXIT_FAILURE;
803                 }
804                 printf("%s\n", buf);
805         } while (r);
806
807         list_head pidrules = __log_limiter_get_pid_limits(limiter_data);
808         if (pidrules)
809                 list_foreach(pidrules, NULL, dump_pid_rule);
810
811         bool is_pipe;
812         int ret = get_is_pipe(conf, &is_pipe);
813         if (ret < 0)
814                 return ret;
815
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"
821                 );
822
823                 if (is_core_buffer(i)) {
824                         bool stdout_enabled;
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"
829                         );
830                 }
831         }
832
833         __log_limiter_destroy(limiter_data);
834         return EXIT_SUCCESS;
835 }
836
837 int main(int argc, const char **argv)
838 {
839         int exit_code = EXIT_FAILURE;
840         struct parsed_params params = {0};
841         if (!parse_options(argc, argv, &params, &exit_code))
842                 return exit_code;
843
844         __attribute__((cleanup(log_config_free))) struct log_config conf = {0};
845         const int r = log_config_read(&conf);
846         if (r < 0) {
847                 ERR("error reading config: %d\n", r);
848                 return EXIT_FAILURE;
849         }
850
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);
854                 return EXIT_SUCCESS;
855         }
856         if (extra_config_path[0] != '/') {
857                 printf("Dynamic config: invalid path in (is \"%s\" but has to be absolute)\n", extra_config_path);
858                 return EXIT_FAILURE;
859         }
860
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");
865                 return EXIT_FAILURE;
866         }
867
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,
877         };
878         exit_code = handles[params.action](&params, full_inotify_path, &conf);
879
880         return exit_code;
881 }
882 #endif // !UNIT_TEST